Skip to content

Conversation

@asterite
Copy link
Collaborator

@asterite asterite commented Jan 30, 2026

Description

Problem

Resolves #11409

Summary

This is a follow-up to #6105

#6105 is good but because we don't unify types, some type variables end up unbound. That's why #11409 was failing.

The solution here is:

  • To unify the types
  • We store the bindings that resulted from that unification. We associate those to the macro expression (an ExprId)
  • If we bump into that macro expression again, we first undo those bindings

This allows a variable to change types across macro loop iterations.

For this I also had to relax the checking of unbound generics when a function context is popped, only in comptime code. The reason is that in the new test here, in foo.len() we end up with foo without a known N, but it's fine because it's that N that we want to infer later on. In this case, if N ends up being used when interpreting the block and it's not bound, we'll get an error anyway.

Additional Context

User Documentation

Check one:

  • No user documentation needed.
  • Changes in docs/ included in this PR.
  • [For Experimental Features] Changes in docs/ to be submitted in a separate PR.

PR Checklist

  • I have tested the changes locally.
  • I have formatted the changes with Prettier and/or cargo fmt on default settings.

@asterite asterite requested a review from a team January 30, 2026 19:50
Copy link
Contributor

@github-actions github-actions bot left a comment

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: 630f649 Previous: fa8934d Ratio
test_report_zkpassport_noir-ecdsa_ 3 s 1 s 3

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

CC: @TomAFrench

Comment on lines +103 to 108
if self.in_comptime_context() {
return;
}

let var = RequiredTypeVariable { type_variable_id, typ, kind, location };
self.get_function_context_mut().required_type_variables.push(var);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if self.in_comptime_context() {
return;
}
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);
}

/// 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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this open us up to regular unbound type variables though? E.g. the case this was meant to originally catch may no longer be caught in comptime code?

Comment on lines +1167 to +1192
// 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();
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,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you factor out all or some of this code? It makes elaborate_call longer and more difficult to understand by getting very involved in a corner case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generic not found after unquote, inside comptime block

3 participants