Skip to content

Commit d251309

Browse files
jfecherTomAFrench
authored andcommitted
fix: Impl search no longer selects an impl if multiple are applicable (#4662)
# Description ## Problem\* Resolves #4653 ## Summary\* If the object type is an unbound type variable, impl search currently just chooses the first available matching impl instead of erroring that type annotations are needed or similar. This can lead to confusing situations where the type chosen is chosen just because it was the first impl in the stdlib to be defined. This PR prevents that. ## Additional Context This PR depends on #4648 and should be merged after. ~~The `hashmap` test is currently failing because the `H: Hasher` constraint on its `Eq` implementation ~~is actually unsolvable since `H` isn't mentioned in the hashmap type at all~~. It is solvable since it is meantioned in `B`, although solving it so far has lead to errors during monomorphization. Previously it was fine since H was unbound and the first/only impl was just always chosen. Now I'll need to change or remove H to fix it.~~ This PR is now ready to review. I've been debugging a bug with monomorphizing some HashMap code for a little while but it turns out the bug is also present in master. So I'm considering it a separate issue: #4663 ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings.
1 parent c1f279f commit d251309

6 files changed

Lines changed: 113 additions & 46 deletions

File tree

compiler/noirc_frontend/src/hir/type_check/expr.rs

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ impl<'interner> TypeChecker<'interner> {
126126
}
127127
}
128128
HirLiteral::Bool(_) => Type::Bool,
129-
HirLiteral::Integer(_, _) => Type::polymorphic_integer_or_field(self.interner),
129+
HirLiteral::Integer(_, _) => self.polymorphic_integer_or_field(),
130130
HirLiteral::Str(string) => {
131131
let len = Type::Constant(string.len() as u64);
132132
Type::String(Box::new(len))
@@ -418,23 +418,28 @@ impl<'interner> TypeChecker<'interner> {
418418
self.interner.select_impl_for_expression(function_ident_id, impl_kind);
419419
}
420420
Err(erroring_constraints) => {
421-
// Don't show any errors where try_get_trait returns None.
422-
// This can happen if a trait is used that was never declared.
423-
let constraints = erroring_constraints
424-
.into_iter()
425-
.map(|constraint| {
426-
let r#trait = self.interner.try_get_trait(constraint.trait_id)?;
427-
let mut name = r#trait.name.to_string();
428-
if !constraint.trait_generics.is_empty() {
429-
let generics = vecmap(&constraint.trait_generics, ToString::to_string);
430-
name += &format!("<{}>", generics.join(", "));
431-
}
432-
Some((constraint.typ, name))
433-
})
434-
.collect::<Option<Vec<_>>>();
421+
if erroring_constraints.is_empty() {
422+
self.errors.push(TypeCheckError::TypeAnnotationsNeeded { span });
423+
} else {
424+
// Don't show any errors where try_get_trait returns None.
425+
// This can happen if a trait is used that was never declared.
426+
let constraints = erroring_constraints
427+
.into_iter()
428+
.map(|constraint| {
429+
let r#trait = self.interner.try_get_trait(constraint.trait_id)?;
430+
let mut name = r#trait.name.to_string();
431+
if !constraint.trait_generics.is_empty() {
432+
let generics =
433+
vecmap(&constraint.trait_generics, ToString::to_string);
434+
name += &format!("<{}>", generics.join(", "));
435+
}
436+
Some((constraint.typ, name))
437+
})
438+
.collect::<Option<Vec<_>>>();
435439

436-
if let Some(constraints) = constraints {
437-
self.errors.push(TypeCheckError::NoMatchingImplFound { constraints, span });
440+
if let Some(constraints) = constraints {
441+
self.errors.push(TypeCheckError::NoMatchingImplFound { constraints, span });
442+
}
438443
}
439444
}
440445
}
@@ -560,15 +565,13 @@ impl<'interner> TypeChecker<'interner> {
560565
let index_type = self.check_expression(&index_expr.index);
561566
let span = self.interner.expr_span(&index_expr.index);
562567

563-
index_type.unify(
564-
&Type::polymorphic_integer_or_field(self.interner),
565-
&mut self.errors,
566-
|| TypeCheckError::TypeMismatch {
568+
index_type.unify(&self.polymorphic_integer_or_field(), &mut self.errors, || {
569+
TypeCheckError::TypeMismatch {
567570
expected_typ: "an integer".to_owned(),
568571
expr_typ: index_type.to_string(),
569572
expr_span: span,
570-
},
571-
);
573+
}
574+
});
572575

573576
// When writing `a[i]`, if `a : &mut ...` then automatically dereference `a` as many
574577
// times as needed to get the underlying array.
@@ -1218,7 +1221,7 @@ impl<'interner> TypeChecker<'interner> {
12181221
self.errors
12191222
.push(TypeCheckError::InvalidUnaryOp { kind: rhs_type.to_string(), span });
12201223
}
1221-
let expected = Type::polymorphic_integer_or_field(self.interner);
1224+
let expected = self.polymorphic_integer_or_field();
12221225
rhs_type.unify(&expected, &mut self.errors, || TypeCheckError::InvalidUnaryOp {
12231226
kind: rhs_type.to_string(),
12241227
span,

compiler/noirc_frontend/src/hir/type_check/mod.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ pub struct TypeChecker<'interner> {
3737
/// on each variable, but it is only until function calls when the types
3838
/// needed for the trait constraint may become known.
3939
trait_constraints: Vec<(TraitConstraint, ExprId)>,
40+
41+
/// All type variables created in the current function.
42+
/// This map is used to default any integer type variables at the end of
43+
/// a function (before checking trait constraints) if a type wasn't already chosen.
44+
type_variables: Vec<Type>,
4045
}
4146

4247
/// Type checks a function and assigns the
@@ -127,6 +132,16 @@ pub fn type_check_func(interner: &mut NodeInterner, func_id: FuncId) -> Vec<Type
127132
}
128133
}
129134

135+
// Default any type variables that still need defaulting.
136+
// This is done before trait impl search since leaving them bindable can lead to errors
137+
// when multiple impls are available. Instead we default first to choose the Field or u64 impl.
138+
for typ in &type_checker.type_variables {
139+
if let Type::TypeVariable(variable, kind) = typ.follow_bindings() {
140+
let msg = "TypeChecker should only track defaultable type vars";
141+
variable.bind(kind.default_type().expect(msg));
142+
}
143+
}
144+
130145
// Verify any remaining trait constraints arising from the function body
131146
for (constraint, expr_id) in std::mem::take(&mut type_checker.trait_constraints) {
132147
let span = type_checker.interner.expr_span(&expr_id);
@@ -333,7 +348,13 @@ fn check_function_type_matches_expected_type(
333348

334349
impl<'interner> TypeChecker<'interner> {
335350
fn new(interner: &'interner mut NodeInterner) -> Self {
336-
Self { interner, errors: Vec::new(), trait_constraints: Vec::new(), current_function: None }
351+
Self {
352+
interner,
353+
errors: Vec::new(),
354+
trait_constraints: Vec::new(),
355+
type_variables: Vec::new(),
356+
current_function: None,
357+
}
337358
}
338359

339360
fn check_function_body(&mut self, body: &ExprId) -> Type {
@@ -348,6 +369,7 @@ impl<'interner> TypeChecker<'interner> {
348369
interner,
349370
errors: Vec::new(),
350371
trait_constraints: Vec::new(),
372+
type_variables: Vec::new(),
351373
current_function: None,
352374
};
353375
let statement = this.interner.get_global(id).let_statement;
@@ -381,6 +403,22 @@ impl<'interner> TypeChecker<'interner> {
381403
make_error,
382404
);
383405
}
406+
407+
/// Return a fresh integer or field type variable and log it
408+
/// in self.type_variables to default it later.
409+
fn polymorphic_integer_or_field(&mut self) -> Type {
410+
let typ = Type::polymorphic_integer_or_field(self.interner);
411+
self.type_variables.push(typ.clone());
412+
typ
413+
}
414+
415+
/// Return a fresh integer type variable and log it
416+
/// in self.type_variables to default it later.
417+
fn polymorphic_integer(&mut self) -> Type {
418+
let typ = Type::polymorphic_integer(self.interner);
419+
self.type_variables.push(typ.clone());
420+
typ
421+
}
384422
}
385423

386424
// XXX: These tests are all manual currently.

compiler/noirc_frontend/src/hir/type_check/stmt.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ impl<'interner> TypeChecker<'interner> {
7171
expr_span: range_span,
7272
});
7373

74-
let expected_type = Type::polymorphic_integer(self.interner);
74+
let expected_type = self.polymorphic_integer();
7575

7676
self.unify(&start_range_type, &expected_type, || TypeCheckError::TypeCannotBeUsed {
7777
typ: start_range_type.clone(),
@@ -233,15 +233,13 @@ impl<'interner> TypeChecker<'interner> {
233233
let expr_span = self.interner.expr_span(index);
234234
let location = *location;
235235

236-
index_type.unify(
237-
&Type::polymorphic_integer_or_field(self.interner),
238-
&mut self.errors,
239-
|| TypeCheckError::TypeMismatch {
236+
index_type.unify(&self.polymorphic_integer_or_field(), &mut self.errors, || {
237+
TypeCheckError::TypeMismatch {
240238
expected_typ: "an integer".to_owned(),
241239
expr_typ: index_type.to_string(),
242240
expr_span,
243-
},
244-
);
241+
}
242+
});
245243

246244
let (mut lvalue_type, mut lvalue, mut mutable) =
247245
self.check_lvalue(array, assign_span);

compiler/noirc_frontend/src/monomorphization/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,12 @@ impl<'interner> Monomorphizer<'interner> {
10031003
Err(constraints) => {
10041004
let failed_constraints = vecmap(constraints, |constraint| {
10051005
let id = constraint.trait_id;
1006-
let name = self.interner.get_trait(id).name.to_string();
1006+
let mut name = self.interner.get_trait(id).name.to_string();
1007+
if !constraint.trait_generics.is_empty() {
1008+
let types =
1009+
vecmap(&constraint.trait_generics, |t| format!("{t:?}"));
1010+
name += &format!("<{}>", types.join(", "));
1011+
}
10071012
format!(" {}: {name}", constraint.typ)
10081013
})
10091014
.join("\n");

compiler/noirc_frontend/src/node_interner.rs

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,7 @@ impl NodeInterner {
10791079
/// constraint, but when where clauses are involved, the failing constraint may be several
10801080
/// constraints deep. In this case, all of the constraints are returned, starting with the
10811081
/// failing one.
1082+
/// If this list of failing constraints is empty, this means type annotations are required.
10821083
pub fn lookup_trait_implementation(
10831084
&self,
10841085
object_type: &Type,
@@ -1121,6 +1122,10 @@ impl NodeInterner {
11211122
}
11221123

11231124
/// Similar to `lookup_trait_implementation` but does not apply any type bindings on success.
1125+
/// On error returns either:
1126+
/// - 1+ failing trait constraints, including the original.
1127+
/// Each constraint after the first represents a `where` clause that was followed.
1128+
/// - 0 trait constraints indicating type annotations are needed to choose an impl.
11241129
pub fn try_lookup_trait_implementation(
11251130
&self,
11261131
object_type: &Type,
@@ -1138,6 +1143,11 @@ impl NodeInterner {
11381143
Ok((impl_kind, bindings))
11391144
}
11401145

1146+
/// Returns the trait implementation if found.
1147+
/// On error returns either:
1148+
/// - 1+ failing trait constraints, including the original.
1149+
/// Each constraint after the first represents a `where` clause that was followed.
1150+
/// - 0 trait constraints indicating type annotations are needed to choose an impl.
11411151
fn lookup_trait_implementation_helper(
11421152
&self,
11431153
object_type: &Type,
@@ -1156,15 +1166,22 @@ impl NodeInterner {
11561166

11571167
let object_type = object_type.substitute(type_bindings);
11581168

1169+
// If the object type isn't known, just return an error saying type annotations are needed.
1170+
if object_type.is_bindable() {
1171+
return Err(Vec::new());
1172+
}
1173+
11591174
let impls =
11601175
self.trait_implementation_map.get(&trait_id).ok_or_else(|| vec![make_constraint()])?;
11611176

1177+
let mut matching_impls = Vec::new();
1178+
11621179
for (existing_object_type2, impl_kind) in impls {
11631180
// Bug: We're instantiating only the object type's generics here, not all of the trait's generics like we need to
11641181
let (existing_object_type, instantiation_bindings) =
11651182
existing_object_type2.instantiate(self);
11661183

1167-
let mut fresh_bindings = TypeBindings::new();
1184+
let mut fresh_bindings = type_bindings.clone();
11681185

11691186
let mut check_trait_generics = |impl_generics: &[Type]| {
11701187
trait_generics.iter().zip(impl_generics).all(|(trait_generic, impl_generic2)| {
@@ -1189,16 +1206,13 @@ impl NodeInterner {
11891206
}
11901207

11911208
if object_type.try_unify(&existing_object_type, &mut fresh_bindings).is_ok() {
1192-
// The unification was successful so we can append fresh_bindings to our bindings list
1193-
type_bindings.extend(fresh_bindings);
1194-
11951209
if let TraitImplKind::Normal(impl_id) = impl_kind {
11961210
let trait_impl = self.get_trait_implementation(*impl_id);
11971211
let trait_impl = trait_impl.borrow();
11981212

11991213
if let Err(mut errors) = self.validate_where_clause(
12001214
&trait_impl.where_clause,
1201-
type_bindings,
1215+
&mut fresh_bindings,
12021216
&instantiation_bindings,
12031217
recursion_limit,
12041218
) {
@@ -1207,11 +1221,20 @@ impl NodeInterner {
12071221
}
12081222
}
12091223

1210-
return Ok(impl_kind.clone());
1224+
matching_impls.push((impl_kind.clone(), fresh_bindings));
12111225
}
12121226
}
12131227

1214-
Err(vec![make_constraint()])
1228+
if matching_impls.len() == 1 {
1229+
let (impl_, fresh_bindings) = matching_impls.pop().unwrap();
1230+
*type_bindings = fresh_bindings;
1231+
Ok(impl_)
1232+
} else if matching_impls.is_empty() {
1233+
Err(vec![make_constraint()])
1234+
} else {
1235+
// multiple matching impls, type annotations needed
1236+
Err(vec![])
1237+
}
12151238
}
12161239

12171240
/// Verifies that each constraint in the given where clause is valid.
@@ -1227,12 +1250,11 @@ impl NodeInterner {
12271250
// Instantiation bindings are generally safe to force substitute into the same type.
12281251
// This is needed here to undo any bindings done to trait methods by monomorphization.
12291252
// Otherwise, an impl for (A, B) could get narrowed to only an impl for e.g. (u8, u16).
1230-
let constraint_type = constraint.typ.force_substitute(instantiation_bindings);
1231-
let constraint_type = constraint_type.substitute(type_bindings);
1253+
let constraint_type =
1254+
constraint.typ.force_substitute(instantiation_bindings).substitute(type_bindings);
12321255

12331256
let trait_generics = vecmap(&constraint.trait_generics, |generic| {
1234-
let generic = generic.force_substitute(instantiation_bindings);
1235-
generic.substitute(type_bindings)
1257+
generic.force_substitute(instantiation_bindings).substitute(type_bindings)
12361258
});
12371259

12381260
self.lookup_trait_implementation_helper(
@@ -1241,10 +1263,11 @@ impl NodeInterner {
12411263
&trait_generics,
12421264
// Use a fresh set of type bindings here since the constraint_type originates from
12431265
// our impl list, which we don't want to bind to.
1244-
&mut TypeBindings::new(),
1266+
type_bindings,
12451267
recursion_limit - 1,
12461268
)?;
12471269
}
1270+
12481271
Ok(())
12491272
}
12501273

noir_stdlib/src/hash/poseidon2.nr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::hash::Hasher;
22
use crate::default::Default;
33

4-
global RATE = 3;
4+
global RATE: u32 = 3;
55

66
struct Poseidon2 {
77
cache: [Field;3],

0 commit comments

Comments
 (0)