diff --git a/compiler/noirc_frontend/src/elaborator/function_context.rs b/compiler/noirc_frontend/src/elaborator/function_context.rs index 4d442ec4b9a..f7be0bf2a0c 100644 --- a/compiler/noirc_frontend/src/elaborator/function_context.rs +++ b/compiler/noirc_frontend/src/elaborator/function_context.rs @@ -87,6 +87,12 @@ impl Elaborator<'_> { /// Push a type variable (its ID and type) as a required type variable: it must be /// bound after type-checking the current function. + /// + /// The type variable is only pushed if the elaborator is not in a comptime context. + /// The reason is that in a comptime context the type of a variable might change + /// across a loop's iterations, so a type can temporarily remain as `Type<_>` where + /// `_` is bound by the interpreter evaluating an expression's type being unified with + /// that type. pub(super) fn push_required_type_variable( &mut self, type_variable_id: TypeVariableId, @@ -94,8 +100,10 @@ impl Elaborator<'_> { kind: BindableTypeVariableKind, location: Location, ) { - let var = RequiredTypeVariable { type_variable_id, typ, kind, location }; - self.get_function_context_mut().required_type_variables.push(var); + if !self.in_comptime_context() { + let var = RequiredTypeVariable { type_variable_id, typ, kind, location }; + self.get_function_context_mut().required_type_variables.push(var); + } } /// Push a trait constraint into the current FunctionContext to be solved if needed diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 933c8442c5d..17400f7c3f3 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -41,7 +41,6 @@ use iter_extended::{try_vecmap, vecmap}; use noirc_errors::Location; use rustc_hash::FxHashMap as HashMap; -use crate::TypeVariable; use crate::ast::{BinaryOpKind, FunctionKind, IntegerBitSize, UnaryOp}; use crate::elaborator::{ElaborateReason, Elaborator, ElaboratorOptions}; use crate::hir::Context; @@ -75,6 +74,7 @@ use crate::{ }, node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, StmtId}, }; +use crate::{TypeVariable, UnificationError}; use super::errors::{IResult, InterpreterError}; use super::value::{Closure, Value, unwrap_rc}; @@ -1156,14 +1156,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { }); result = self.evaluate(expr)?; - // Macro calls are typed as type variables during type checking. - // Now that we know the type we need to further unify it in case there - // are inconsistencies or the type needs to be known. - // We don't commit any type bindings made this way in case the type of - // the macro result changes across loop iterations. - let expected_type = self.elaborator.interner.id_type(id); - let actual_type = result.get_type(); - self.unify_without_binding(&actual_type, &expected_type, location); + self.unify_macro_call_result_with_expected_type(id, location, &result); } Ok(result) } @@ -1175,17 +1168,45 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } } - /// This function is used by the interpreter for some comptime code - /// which can change types e.g. on each iteration of a for loop. - fn unify_without_binding(&mut self, actual: &Type, expected: &Type, location: Location) { + /// Macro calls are typed as type variables during type checking. + /// Once we know their type we need to further unify it in case there + /// are inconsistencies or the type needs to be known. + fn unify_macro_call_result_with_expected_type( + &mut self, + id: ExprId, + location: Location, + result: &Value, + ) { + let expected_type = self.elaborator.interner.id_type(id); + let actual_type = result.get_type(); + + // Undo any bindings (if any) from the last time we unified this expression's + // type against the actual type + if let Some(bindings) = self.elaborator.interner.macro_call_expression_bindings.remove(&id) + { + for (var, kind, _typ) in bindings.values() { + var.unbind(var.id(), kind.clone()); + } + } + let mut bindings = TypeBindings::default(); - if actual.try_unify(expected, &mut bindings).is_err() { - let error = TypeCheckError::TypeMismatch { - expected_typ: expected.to_string(), - expr_typ: actual.to_string(), - expr_location: location, - }; - self.elaborator.push_err(error); + match actual_type.try_unify(&expected_type, &mut bindings) { + Ok(()) => { + // Store the bindings so we can undo them next time + self.elaborator + .interner + .macro_call_expression_bindings + .insert(id, bindings.clone()); + Type::apply_type_bindings(bindings); + } + Err(UnificationError) => { + let error = TypeCheckError::TypeMismatch { + expected_typ: expected_type.to_string(), + expr_typ: actual_type.to_string(), + expr_location: location, + }; + self.elaborator.push_err(error); + } } } diff --git a/compiler/noirc_frontend/src/node_interner/mod.rs b/compiler/noirc_frontend/src/node_interner/mod.rs index 602f2d554f2..ad564c6100f 100644 --- a/compiler/noirc_frontend/src/node_interner/mod.rs +++ b/compiler/noirc_frontend/src/node_interner/mod.rs @@ -304,6 +304,12 @@ pub struct NodeInterner { /// Tracks statements that encountered errors during elaboration. /// Used by the interpreter to skip evaluation of errored statements. pub(crate) stmts_with_errors: HashSet, + + /// Associates type bindings that resulted from unifying the type of a macro call expression + /// with the expected type at the callsite. + /// Since a single macro call expression might end up having different types across loop + /// iterations, before unifying its type we undo bindings from the last time we unified it. + pub(crate) macro_call_expression_bindings: HashMap, } /// A trait implementation is either a normal implementation that is present in the source @@ -500,6 +506,7 @@ impl Default for NodeInterner { primitive_docs: HashMap::default(), exprs_with_errors: HashSet::default(), stmts_with_errors: HashSet::default(), + macro_call_expression_bindings: HashMap::default(), } } } diff --git a/compiler/noirc_frontend/src/tests/metaprogramming.rs b/compiler/noirc_frontend/src/tests/metaprogramming.rs index 0e623244f22..f425fc9d816 100644 --- a/compiler/noirc_frontend/src/tests/metaprogramming.rs +++ b/compiler/noirc_frontend/src/tests/metaprogramming.rs @@ -1465,3 +1465,28 @@ fn escape_nested_unquote() { "#; assert_no_errors(src); } + +#[test] +fn unifies_macro_call_type_with_variable_type_in_comptime_block() { + let src = r#" + comptime fn unquote(code: Quoted) -> Quoted { + code + } + + struct Foo {} + + impl Foo { + fn len(_self: Self) -> u32 { + N + } + } + + fn main() -> pub u32 { + comptime { + let foo: Foo<_> = unquote!(quote { Foo::<10> {} }); + foo.len() + } + } + "#; + assert_no_errors(src); +} diff --git a/tooling/nargo_cli/tests/snapshots/compile_failure/comptime_invalid_struct_constructor/execute__tests__stderr.snap b/tooling/nargo_cli/tests/snapshots/compile_failure/comptime_invalid_struct_constructor/execute__tests__stderr.snap index 122038bcaba..b9445247bfc 100644 --- a/tooling/nargo_cli/tests/snapshots/compile_failure/comptime_invalid_struct_constructor/execute__tests__stderr.snap +++ b/tooling/nargo_cli/tests/snapshots/compile_failure/comptime_invalid_struct_constructor/execute__tests__stderr.snap @@ -30,11 +30,4 @@ error: Type Foo has no member named c │ - │ -error: Type annotation needed - ┌─ src/main.nr:11:9 - │ -11 │ println(x.c); - │ ------- Could not determine the type of the generic argument `T` declared on the function `println` - │ - -Aborting due to 5 previous errors +Aborting due to 4 previous errors diff --git a/tooling/nargo_cli/tests/snapshots/compile_failure/macro_result_type/execute__tests__stderr.snap b/tooling/nargo_cli/tests/snapshots/compile_failure/macro_result_type/execute__tests__stderr.snap index dfd29533d98..589cd72af62 100644 --- a/tooling/nargo_cli/tests/snapshots/compile_failure/macro_result_type/execute__tests__stderr.snap +++ b/tooling/nargo_cli/tests/snapshots/compile_failure/macro_result_type/execute__tests__stderr.snap @@ -9,11 +9,4 @@ error: trait `std::meta::ctstring::AsCtString` which provides `as_ctstring` is i │ --------------------- │ -error: Type annotation needed - ┌─ src/main.nr:6:22 - │ -6 │ let result = half(string); - │ ---- Could not determine the value of the generic argument `N` declared on the function `half` - │ - -Aborting due to 2 previous errors +Aborting due to 1 previous error