Skip to content

Commit 5300ec3

Browse files
asteriteTomAFrench
andauthored
fix: require generic trait impls to be in scope to call them (#6913)
Co-authored-by: Tom French <[email protected]>
1 parent 56c931a commit 5300ec3

File tree

3 files changed

+126
-69
lines changed

3 files changed

+126
-69
lines changed

compiler/noirc_frontend/src/elaborator/types.rs

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,7 +1361,7 @@ impl<'context> Elaborator<'context> {
13611361
span: Span,
13621362
has_self_arg: bool,
13631363
) -> Option<HirMethodReference> {
1364-
// First search in the struct methods
1364+
// First search in the type methods. If there is one, that's the one.
13651365
if let Some(method_id) =
13661366
self.interner.lookup_direct_method(object_type, method_name, has_self_arg)
13671367
{
@@ -1372,43 +1372,55 @@ impl<'context> Elaborator<'context> {
13721372
let trait_methods =
13731373
self.interner.lookup_trait_methods(object_type, method_name, has_self_arg);
13741374

1375-
if trait_methods.is_empty() {
1376-
// If we couldn't find any trait methods, search in
1377-
// impls for all types `T`, e.g. `impl<T> Foo for T`
1378-
if let Some(func_id) =
1379-
self.interner.lookup_generic_method(object_type, method_name, has_self_arg)
1380-
{
1381-
return Some(HirMethodReference::FuncId(func_id));
1382-
}
1375+
// If there's at least one matching trait method we need to see if only one is in scope.
1376+
if !trait_methods.is_empty() {
1377+
return self.return_trait_method_in_scope(&trait_methods, method_name, span);
1378+
}
13831379

1384-
if let Type::Struct(struct_type, _) = object_type {
1385-
let has_field_with_function_type =
1386-
struct_type.borrow().get_fields_as_written().into_iter().any(|field| {
1387-
field.name.0.contents == method_name && field.typ.is_function()
1388-
});
1389-
if has_field_with_function_type {
1390-
self.push_err(TypeCheckError::CannotInvokeStructFieldFunctionType {
1391-
method_name: method_name.to_string(),
1392-
object_type: object_type.clone(),
1393-
span,
1394-
});
1395-
} else {
1396-
self.push_err(TypeCheckError::UnresolvedMethodCall {
1397-
method_name: method_name.to_string(),
1398-
object_type: object_type.clone(),
1399-
span,
1400-
});
1401-
}
1402-
return None;
1380+
// If we couldn't find any trait methods, search in
1381+
// impls for all types `T`, e.g. `impl<T> Foo for T`
1382+
let generic_methods =
1383+
self.interner.lookup_generic_methods(object_type, method_name, has_self_arg);
1384+
if !generic_methods.is_empty() {
1385+
return self.return_trait_method_in_scope(&generic_methods, method_name, span);
1386+
}
1387+
1388+
if let Type::Struct(struct_type, _) = object_type {
1389+
let has_field_with_function_type = struct_type
1390+
.borrow()
1391+
.get_fields_as_written()
1392+
.into_iter()
1393+
.any(|field| field.name.0.contents == method_name && field.typ.is_function());
1394+
if has_field_with_function_type {
1395+
self.push_err(TypeCheckError::CannotInvokeStructFieldFunctionType {
1396+
method_name: method_name.to_string(),
1397+
object_type: object_type.clone(),
1398+
span,
1399+
});
14031400
} else {
1404-
// It could be that this type is a composite type that is bound to a trait,
1405-
// for example `x: (T, U) ... where (T, U): SomeTrait`
1406-
// (so this case is a generalization of the NamedGeneric case)
1407-
return self.lookup_method_in_trait_constraints(object_type, method_name, span);
1401+
self.push_err(TypeCheckError::UnresolvedMethodCall {
1402+
method_name: method_name.to_string(),
1403+
object_type: object_type.clone(),
1404+
span,
1405+
});
14081406
}
1407+
None
1408+
} else {
1409+
// It could be that this type is a composite type that is bound to a trait,
1410+
// for example `x: (T, U) ... where (T, U): SomeTrait`
1411+
// (so this case is a generalization of the NamedGeneric case)
1412+
self.lookup_method_in_trait_constraints(object_type, method_name, span)
14091413
}
1414+
}
14101415

1411-
// We found some trait methods... but is only one of them currently in scope?
1416+
/// Given a list of functions and the trait they belong to, returns the one function
1417+
/// that is in scope.
1418+
fn return_trait_method_in_scope(
1419+
&mut self,
1420+
trait_methods: &[(FuncId, TraitId)],
1421+
method_name: &str,
1422+
span: Span,
1423+
) -> Option<HirMethodReference> {
14121424
let module_id = self.module_id();
14131425
let module_data = self.get_module(module_id);
14141426

@@ -1490,7 +1502,7 @@ impl<'context> Elaborator<'context> {
14901502
fn trait_hir_method_reference(
14911503
&self,
14921504
trait_id: TraitId,
1493-
trait_methods: Vec<(FuncId, TraitId)>,
1505+
trait_methods: &[(FuncId, TraitId)],
14941506
method_name: &str,
14951507
span: Span,
14961508
) -> HirMethodReference {

compiler/noirc_frontend/src/node_interner.rs

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,7 +1746,7 @@ impl NodeInterner {
17461746
Ok(())
17471747
}
17481748

1749-
/// Looks up a method that's directly defined in the given struct.
1749+
/// Looks up a method that's directly defined in the given type.
17501750
pub fn lookup_direct_method(
17511751
&self,
17521752
typ: &Type,
@@ -1761,7 +1761,7 @@ impl NodeInterner {
17611761
.and_then(|methods| methods.find_direct_method(typ, has_self_arg, self))
17621762
}
17631763

1764-
/// Looks up a methods that apply to the given struct but are defined in traits.
1764+
/// Looks up a methods that apply to the given type but are defined in traits.
17651765
pub fn lookup_trait_methods(
17661766
&self,
17671767
typ: &Type,
@@ -1780,43 +1780,18 @@ impl NodeInterner {
17801780
}
17811781
}
17821782

1783-
/// Select the 1 matching method with an object type matching `typ`
1784-
fn find_matching_method(
1785-
&self,
1786-
typ: &Type,
1787-
methods: Option<&Methods>,
1788-
method_name: &str,
1789-
has_self_arg: bool,
1790-
) -> Option<FuncId> {
1791-
if let Some(method) = methods.and_then(|m| m.find_matching_method(typ, has_self_arg, self))
1792-
{
1793-
Some(method)
1794-
} else {
1795-
self.lookup_generic_method(typ, method_name, has_self_arg)
1796-
}
1797-
}
1798-
1799-
/// Looks up a method at impls for all types `T`, e.g. `impl<T> Foo for T`
1800-
pub fn lookup_generic_method(
1783+
/// Looks up methods at impls for all types `T`, e.g. `impl<T> Foo for T`
1784+
pub fn lookup_generic_methods(
18011785
&self,
18021786
typ: &Type,
18031787
method_name: &str,
18041788
has_self_arg: bool,
1805-
) -> Option<FuncId> {
1806-
let global_methods = self.methods.get(&TypeMethodKey::Generic)?.get(method_name)?;
1807-
global_methods.find_matching_method(typ, has_self_arg, self)
1808-
}
1809-
1810-
/// Looks up a given method name on the given primitive type.
1811-
pub fn lookup_primitive_method(
1812-
&self,
1813-
typ: &Type,
1814-
method_name: &str,
1815-
has_self_arg: bool,
1816-
) -> Option<FuncId> {
1817-
let key = get_type_method_key(typ)?;
1818-
let methods = self.methods.get(&key)?.get(method_name)?;
1819-
self.find_matching_method(typ, Some(methods), method_name, has_self_arg)
1789+
) -> Vec<(FuncId, TraitId)> {
1790+
self.methods
1791+
.get(&TypeMethodKey::Generic)
1792+
.and_then(|h| h.get(method_name))
1793+
.map(|methods| methods.find_trait_methods(typ, has_self_arg, self))
1794+
.unwrap_or_default()
18201795
}
18211796

18221797
/// Returns what the next trait impl id is expected to be.

compiler/noirc_frontend/src/tests/traits.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,43 @@ fn errors_if_multiple_trait_methods_are_in_scope_for_function_call() {
879879
assert_eq!(traits, vec!["private_mod::Foo", "private_mod::Foo2"]);
880880
}
881881

882+
#[test]
883+
fn warns_if_trait_is_not_in_scope_for_method_call_and_there_is_only_one_trait_method() {
884+
let src = r#"
885+
fn main() {
886+
let bar = Bar { x: 42 };
887+
let _ = bar.foo();
888+
}
889+
890+
pub struct Bar {
891+
x: i32,
892+
}
893+
894+
mod private_mod {
895+
pub trait Foo {
896+
fn foo(self) -> i32;
897+
}
898+
899+
impl Foo for super::Bar {
900+
fn foo(self) -> i32 {
901+
self.x
902+
}
903+
}
904+
}
905+
"#;
906+
let errors = get_program_errors(src);
907+
assert_eq!(errors.len(), 1);
908+
909+
let CompilationError::ResolverError(ResolverError::PathResolutionError(
910+
PathResolutionError::TraitMethodNotInScope { ident, trait_name },
911+
)) = &errors[0].0
912+
else {
913+
panic!("Expected a 'trait method not in scope' error");
914+
};
915+
assert_eq!(ident.to_string(), "foo");
916+
assert_eq!(trait_name, "private_mod::Foo");
917+
}
918+
882919
#[test]
883920
fn calls_trait_method_if_it_is_in_scope() {
884921
let src = r#"
@@ -1166,3 +1203,36 @@ fn warns_if_trait_is_not_in_scope_for_primitive_method_call_and_there_is_only_on
11661203
assert_eq!(ident.to_string(), "foo");
11671204
assert_eq!(trait_name, "private_mod::Foo");
11681205
}
1206+
1207+
#[test]
1208+
fn warns_if_trait_is_not_in_scope_for_generic_function_call_and_there_is_only_one_trait_method() {
1209+
let src = r#"
1210+
fn main() {
1211+
let x: i32 = 1;
1212+
let _ = x.foo();
1213+
}
1214+
1215+
mod private_mod {
1216+
pub trait Foo<T> {
1217+
fn foo(self) -> i32;
1218+
}
1219+
1220+
impl<T> Foo<T> for T {
1221+
fn foo(self) -> i32 {
1222+
42
1223+
}
1224+
}
1225+
}
1226+
"#;
1227+
let errors = get_program_errors(src);
1228+
assert_eq!(errors.len(), 1);
1229+
1230+
let CompilationError::ResolverError(ResolverError::PathResolutionError(
1231+
PathResolutionError::TraitMethodNotInScope { ident, trait_name },
1232+
)) = &errors[0].0
1233+
else {
1234+
panic!("Expected a 'trait method not in scope' error");
1235+
};
1236+
assert_eq!(ident.to_string(), "foo");
1237+
assert_eq!(trait_name, "private_mod::Foo");
1238+
}

0 commit comments

Comments
 (0)