Skip to content

Commit

Permalink
feat: Allow resolved types in constructors (#7223)
Browse files Browse the repository at this point in the history
  • Loading branch information
jfecher authored Jan 29, 2025
1 parent 4d37fb0 commit 6d319af
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 34 deletions.
83 changes: 55 additions & 28 deletions compiler/noirc_frontend/src/elaborator/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use crate::{
ArrayLiteral, BlockExpression, CallExpression, CastExpression, ConstructorExpression,
Expression, ExpressionKind, Ident, IfExpression, IndexExpression, InfixExpression,
ItemVisibility, Lambda, Literal, MemberAccessExpression, MethodCallExpression, Path,
PrefixExpression, StatementKind, UnaryOp, UnresolvedTypeData, UnresolvedTypeExpression,
PathSegment, PrefixExpression, StatementKind, UnaryOp, UnresolvedTypeData,
UnresolvedTypeExpression,
},
hir::{
comptime::{self, InterpreterError},
Expand Down Expand Up @@ -603,6 +604,12 @@ impl<'context> Elaborator<'context> {
if let UnresolvedTypeData::Interned(id) = typ {
typ = self.interner.get_unresolved_type_data(id).clone();
}
if let UnresolvedTypeData::Resolved(id) = typ {
// If this type is already resolved we can skip the rest of this function
// which just resolves the type, and go straight to resolving the fields.
let resolved = self.interner.get_quoted_type(id).clone();
return self.elaborate_constructor_with_type(resolved, constructor.fields, span, None);
}
let UnresolvedTypeData::Named(mut path, generics, _) = typ else {
self.push_err(ResolverError::NonStructUsedInConstructor { typ: typ.to_string(), span });
return (HirExpression::Error, Type::Error);
Expand All @@ -614,58 +621,78 @@ impl<'context> Elaborator<'context> {
}

let last_segment = path.last_segment();
let is_self_type = last_segment.ident.is_self_type_name();

let (r#type, struct_generics) = if let Some(struct_id) = constructor.struct_type {
let typ = if let Some(struct_id) = constructor.struct_type {
let typ = self.interner.get_type(struct_id);
let generics = typ.borrow().instantiate(self.interner);
(typ, generics)
Type::DataType(typ, generics)
} else {
match self.lookup_type_or_error(path) {
Some(Type::DataType(r#type, struct_generics)) if r#type.borrow().is_struct() => {
(r#type, struct_generics)
}
Some(typ) => {
self.push_err(ResolverError::NonStructUsedInConstructor {
typ: typ.to_string(),
span,
});
return (HirExpression::Error, Type::Error);
}
Some(typ) => typ,
None => return (HirExpression::Error, Type::Error),
}
};

self.mark_struct_as_constructed(r#type.clone());
self.elaborate_constructor_with_type(typ, constructor.fields, span, Some(last_segment))
}

let turbofish_span = last_segment.turbofish_span();
fn elaborate_constructor_with_type(
&mut self,
typ: Type,
fields: Vec<(Ident, Expression)>,
span: Span,
last_segment: Option<PathSegment>,
) -> (HirExpression, Type) {
let typ = typ.follow_bindings_shallow();
let (r#type, generics) = match typ.as_ref() {
Type::DataType(r#type, struct_generics) if r#type.borrow().is_struct() => {
(r#type, struct_generics)
}
typ => {
self.push_err(ResolverError::NonStructUsedInConstructor {
typ: typ.to_string(),
span,
});
return (HirExpression::Error, Type::Error);
}
};
self.mark_struct_as_constructed(r#type.clone());

let struct_generics = self.resolve_struct_turbofish_generics(
&r#type.borrow(),
struct_generics,
last_segment.generics,
turbofish_span,
);
// `last_segment` is optional if this constructor was resolved from a quoted type
let mut generics = generics.clone();
let mut is_self_type = false;
let mut constructor_type_span = span;

if let Some(last_segment) = last_segment {
let turbofish_span = last_segment.turbofish_span();
is_self_type = last_segment.ident.is_self_type_name();
constructor_type_span = last_segment.ident.span();

generics = self.resolve_struct_turbofish_generics(
&r#type.borrow(),
generics,
last_segment.generics,
turbofish_span,
);
}

let struct_type = r#type.clone();
let generics = struct_generics.clone();

let fields = constructor.fields;
let field_types = r#type
.borrow()
.get_fields_with_visibility(&struct_generics)
.get_fields_with_visibility(&generics)
.expect("This type should already be validated to be a struct");

let fields =
self.resolve_constructor_expr_fields(struct_type.clone(), field_types, fields, span);
let expr = HirExpression::Constructor(HirConstructorExpression {
fields,
r#type,
struct_generics,
r#type: struct_type.clone(),
struct_generics: generics.clone(),
});

let struct_id = struct_type.borrow().id;
let reference_location = Location::new(last_segment.ident.span(), self.file);
let reference_location = Location::new(constructor_type_span, self.file);
self.interner.add_type_reference(struct_id, reference_location, is_self_type);

(expr, Type::DataType(struct_type, generics))
Expand Down
3 changes: 1 addition & 2 deletions compiler/noirc_frontend/src/monomorphization/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,13 +777,12 @@ impl<'interner> Monomorphizer<'interner> {
}

/// For an enum like:
/// ```
/// enum Foo {
/// A(i32, u32),
/// B(Field),
/// C
/// }
/// ```
///
/// this will translate the call `Foo::A(1, 2)` into `(0, (1, 2), (0,), ())` where
/// the first field `0` is the tag value, the second is `A`, third is `B`, and fourth is `C`.
/// Each variant that isn't the desired variant has zeroed values filled in for its data.
Expand Down
8 changes: 5 additions & 3 deletions compiler/noirc_frontend/src/parser/parser/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,13 @@ impl<'a> Parser<'a> {
}

// A constructor where the type is an interned unresolved type data is valid
if matches!(self.token.token(), Token::InternedUnresolvedTypeData(..))
&& self.next_is(Token::LeftBrace)
if matches!(
self.token.token(),
Token::InternedUnresolvedTypeData(..) | Token::QuotedType(..)
) && self.next_is(Token::LeftBrace)
{
let span = self.current_token_span;
let typ = self.parse_interned_type().unwrap();
let typ = self.parse_interned_type().or_else(|| self.parse_resolved_type()).unwrap();
self.eat_or_error(Token::LeftBrace);
let typ = UnresolvedType { typ, span };
return Some(self.parse_constructor(typ));
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/parser/parser/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ impl<'a> Parser<'a> {
Some(UnresolvedTypeData::AsTraitPath(Box::new(as_trait_path)))
}

fn parse_resolved_type(&mut self) -> Option<UnresolvedTypeData> {
pub(super) fn parse_resolved_type(&mut self) -> Option<UnresolvedTypeData> {
if let Some(token) = self.eat_kind(TokenKind::QuotedType) {
match token.into_token() {
Token::QuotedType(id) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "resolved_type_in_constructor"
type = "bin"
authors = [""]

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
fn main() {
let _ = my_macro!();
}

comptime fn my_macro() -> Quoted {
let typ = quote { Foo }.as_type();
quote [$typ {}]
}

struct Foo {}

2 comments on commit 6d319af

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Test Suite Duration'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: 6d319af Previous: 4d37fb0 Ratio
noir-lang_noir_string_search_ 1 s 0 s +∞
AztecProtocol_aztec-packages_noir-projects_noir-protocol-circuits_crates_parity-lib 3 s 2 s 1.50

This comment was automatically generated by workflow using github-action-benchmark.

CC: @TomAFrench

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Test Suite Duration'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: 6d319af Previous: 4d37fb0 Ratio
noir-lang_eddsa_ 2 s 1 s 2
AztecProtocol_aztec-packages_noir-projects_noir-protocol-circuits_crates_parity-lib 3 s 2 s 1.50

This comment was automatically generated by workflow using github-action-benchmark.

CC: @TomAFrench

Please sign in to comment.