Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions compiler/noirc_frontend/src/elaborator/function_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,23 @@ 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,
typ: Type,
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
Expand Down
59 changes: 40 additions & 19 deletions compiler/noirc_frontend/src/hir/comptime/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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};
Expand Down Expand Up @@ -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)
}
Expand All @@ -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);
}
}
}

Expand Down
7 changes: 7 additions & 0 deletions compiler/noirc_frontend/src/node_interner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<StmtId>,

/// 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<ExprId, TypeBindings>,
}

/// A trait implementation is either a normal implementation that is present in the source
Expand Down Expand Up @@ -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(),
}
}
}
Expand Down
25 changes: 25 additions & 0 deletions compiler/noirc_frontend/src/tests/metaprogramming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<let N: u32> {}

impl<let N: u32> Foo<N> {
fn len(_self: Self) -> u32 {
N
}
}

fn main() -> pub u32 {
comptime {
let foo: Foo<_> = unquote!(quote { Foo::<10> {} });
foo.len()
}
}
"#;
assert_no_errors(src);
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading