Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: issue duplicate error on impl function without self #7490

Merged
merged 8 commits into from
Feb 24, 2025
3 changes: 2 additions & 1 deletion compiler/noirc_frontend/src/elaborator/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,8 @@ impl<'context> Elaborator<'context> {

let method_name_location = method_call.method_name.location();
let method_name = method_call.method_name.0.contents.as_str();
match self.lookup_method(&object_type, method_name, location, true) {
let check_self_param = true;
match self.lookup_method(&object_type, method_name, location, check_self_param) {
Some(method_ref) => {
// Automatically add `&mut` if the method expects a mutable reference and
// the object is not already one.
Expand Down
5 changes: 4 additions & 1 deletion compiler/noirc_frontend/src/elaborator/patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -885,8 +885,11 @@ impl<'context> Elaborator<'context> {
pub(super) fn elaborate_type_path(&mut self, path: TypePath) -> (ExprId, Type) {
let location = path.item.location();
let typ = self.resolve_type(path.typ);
let check_self_param = false;

let Some(method) = self.lookup_method(&typ, &path.item.0.contents, location, false) else {
let Some(method) =
self.lookup_method(&typ, &path.item.0.contents, location, check_self_param)
else {
let error = Expression::new(ExpressionKind::Error, location);
return self.elaborate_expression(error);
};
Expand Down
21 changes: 12 additions & 9 deletions compiler/noirc_frontend/src/elaborator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1478,7 +1478,7 @@ impl<'context> Elaborator<'context> {
object_type: &Type,
method_name: &str,
location: Location,
has_self_arg: bool,
check_self_param: bool,
) -> Option<HirMethodReference> {
let span = location.span;
let file = location.file;
Expand All @@ -1502,7 +1502,7 @@ impl<'context> Elaborator<'context> {
// Mutable references to another type should resolve to methods of their element type.
// This may be a data type or a primitive type.
Type::MutableReference(element) => {
self.lookup_method(&element, method_name, location, has_self_arg)
self.lookup_method(&element, method_name, location, check_self_param)
}

// If we fail to resolve the object to a data type, we have no way of type
Expand All @@ -1515,9 +1515,12 @@ impl<'context> Elaborator<'context> {
None
}

other => {
self.lookup_type_or_primitive_method(&other, method_name, location, has_self_arg)
}
other => self.lookup_type_or_primitive_method(
&other,
method_name,
location,
check_self_param,
),
}
}

Expand All @@ -1526,21 +1529,21 @@ impl<'context> Elaborator<'context> {
object_type: &Type,
method_name: &str,
location: Location,
has_self_arg: bool,
check_self_param: bool,
) -> Option<HirMethodReference> {
let span = location.span;
let file = location.file;

// First search in the type methods. If there is one, that's the one.
if let Some(method_id) =
self.interner.lookup_direct_method(object_type, method_name, has_self_arg)
self.interner.lookup_direct_method(object_type, method_name, check_self_param)
{
return Some(HirMethodReference::FuncId(method_id));
}

// Next lookup all matching trait methods.
let trait_methods =
self.interner.lookup_trait_methods(object_type, method_name, has_self_arg);
self.interner.lookup_trait_methods(object_type, method_name, check_self_param);

// If there's at least one matching trait method we need to see if only one is in scope.
if !trait_methods.is_empty() {
Expand All @@ -1550,7 +1553,7 @@ impl<'context> Elaborator<'context> {
// If we couldn't find any trait methods, search in
// impls for all types `T`, e.g. `impl<T> Foo for T`
let generic_methods =
self.interner.lookup_generic_methods(object_type, method_name, has_self_arg);
self.interner.lookup_generic_methods(object_type, method_name, check_self_param);
if !generic_methods.is_empty() {
return self.return_trait_method_in_scope(&generic_methods, method_name, location);
}
Expand Down
3 changes: 2 additions & 1 deletion compiler/noirc_frontend/src/hir/comptime/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@
}
} else {
let name = self.elaborator.interner.function_name(&function);
unreachable!("Non-builtin, lowlevel or oracle builtin fn '{name}'")

Check warning on line 257 in compiler/noirc_frontend/src/hir/comptime/interpreter.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (lowlevel)
}
}

Expand Down Expand Up @@ -1065,7 +1065,7 @@
}

/// Generate matches for bit shifting, which in Noir only accepts `u8` for RHS.
macro_rules! match_bitshift {

Check warning on line 1068 in compiler/noirc_frontend/src/hir/comptime/interpreter.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (bitshift)
(($lhs_value:ident as $lhs:ident $op:literal $rhs_value:ident as $rhs:ident) => $expr:expr) => {
match_values! {
($lhs_value as $lhs $op $rhs_value as $rhs) {
Expand Down Expand Up @@ -1135,10 +1135,10 @@
BinaryOpKind::Xor => match_bitwise! {
(lhs_value as lhs "^" rhs_value as rhs) => lhs ^ rhs
},
BinaryOpKind::ShiftRight => match_bitshift! {

Check warning on line 1138 in compiler/noirc_frontend/src/hir/comptime/interpreter.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (bitshift)
(lhs_value as lhs ">>" rhs_value as rhs) => lhs.checked_shr(rhs.into())
},
BinaryOpKind::ShiftLeft => match_bitshift! {

Check warning on line 1141 in compiler/noirc_frontend/src/hir/comptime/interpreter.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (bitshift)
(lhs_value as lhs "<<" rhs_value as rhs) => lhs.checked_shl(rhs.into())
},
BinaryOpKind::Modulo => match_integer! {
Expand Down Expand Up @@ -1406,10 +1406,11 @@

let typ = object.get_type().follow_bindings();
let method_name = &call.method.0.contents;
let check_self_param = true;

let method = self
.elaborator
.lookup_method(&typ, method_name, location, true)
.lookup_method(&typ, method_name, location, check_self_param)
.and_then(|method| method.func_id(self.elaborator.interner));

if let Some(method) = method {
Expand Down
72 changes: 38 additions & 34 deletions compiler/noirc_frontend/src/node_interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,12 @@
interned_statement_kinds: noirc_arena::Arena<StatementKind>,

// Interned `UnresolvedTypeData`s during comptime code.
interned_unresolved_type_datas: noirc_arena::Arena<UnresolvedTypeData>,

Check warning on line 219 in compiler/noirc_frontend/src/node_interner.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (datas)

// Interned `Pattern`s during comptime code.
interned_patterns: noirc_arena::Arena<Pattern>,

/// Determins whether to run in LSP mode. In LSP mode references are tracked.

Check warning on line 224 in compiler/noirc_frontend/src/node_interner.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (Determins)
pub(crate) lsp_mode: bool,

/// Store the location of the references in the graph.
Expand Down Expand Up @@ -359,16 +359,19 @@
/// as long as these specialized impls do not overlap. E.g. `impl Struct<u32>` and `impl Struct<u64>`
#[derive(Default, Debug, Clone)]
pub struct Methods {
pub direct: Vec<FuncId>,
pub direct: Vec<ImplMethod>,
pub trait_impl_methods: Vec<TraitImplMethod>,
}

#[derive(Debug, Clone)]
pub struct ImplMethod {
pub typ: Type,
pub method: FuncId,
}

#[derive(Debug, Clone)]
pub struct TraitImplMethod {
// This type is only stored for primitive types to be able to
// select the correct static methods between multiple options keyed
// under TypeMethodKey::FieldOrInt
pub typ: Option<Type>,
pub typ: Type,
pub method: FuncId,
pub trait_id: TraitId,
}
Expand Down Expand Up @@ -694,7 +697,7 @@
quoted_types: Default::default(),
interned_expression_kinds: Default::default(),
interned_statement_kinds: Default::default(),
interned_unresolved_type_datas: Default::default(),

Check warning on line 700 in compiler/noirc_frontend/src/node_interner.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (datas)
interned_patterns: Default::default(),
lsp_mode: false,
location_indices: LocationIndices::default(),
Expand Down Expand Up @@ -1402,16 +1405,17 @@
});

if trait_id.is_none() && matches!(self_type, Type::DataType(..)) {
if let Some(existing) = self.lookup_direct_method(self_type, &method_name, true)
let check_self_param = false;
if let Some(existing) =
self.lookup_direct_method(self_type, &method_name, check_self_param)
{
return Some(existing);
}
}

// Only remember the actual type if it's FieldOrInt,
// so later we can disambiguate on calls like `u32::call`.
let typ =
if key == TypeMethodKey::FieldOrInt { Some(self_type.clone()) } else { None };
let typ = self_type.clone();
self.methods
.entry(key)
.or_default()
Expand Down Expand Up @@ -1778,18 +1782,20 @@
}

/// Looks up a method that's directly defined in the given type.
/// If `check_self_param` is `true`, only a method that has a `self` parameter with a type
/// that unifies with `typ` will be returned.
pub fn lookup_direct_method(
&self,
typ: &Type,
method_name: &str,
has_self_arg: bool,
check_self_param: bool,
) -> Option<FuncId> {
let key = get_type_method_key(typ)?;

self.methods
.get(&key)
.and_then(|h| h.get(method_name))
.and_then(|methods| methods.find_direct_method(typ, has_self_arg, self))
.and_then(|methods| methods.find_direct_method(typ, check_self_param, self))
}

/// Looks up a methods that apply to the given type but are defined in traits.
Expand Down Expand Up @@ -2164,11 +2170,11 @@
&mut self,
typ: UnresolvedTypeData,
) -> InternedUnresolvedTypeData {
InternedUnresolvedTypeData(self.interned_unresolved_type_datas.insert(typ))

Check warning on line 2173 in compiler/noirc_frontend/src/node_interner.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (datas)
}

pub fn get_unresolved_type_data(&self, id: InternedUnresolvedTypeData) -> &UnresolvedTypeData {
&self.interned_unresolved_type_datas[id.0]

Check warning on line 2177 in compiler/noirc_frontend/src/node_interner.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (datas)
}

/// Returns the type of an operator (which is always a function), along with its return type.
Expand Down Expand Up @@ -2311,20 +2317,21 @@
}

impl Methods {
fn add_method(&mut self, method: FuncId, typ: Option<Type>, trait_id: Option<TraitId>) {
fn add_method(&mut self, method: FuncId, typ: Type, trait_id: Option<TraitId>) {
if let Some(trait_id) = trait_id {
let trait_impl_method = TraitImplMethod { typ, method, trait_id };
self.trait_impl_methods.push(trait_impl_method);
} else {
self.direct.push(method);
let impl_method = ImplMethod { typ, method };
self.direct.push(impl_method);
}
}

/// Iterate through each method, starting with the direct methods
pub fn iter(&self) -> impl Iterator<Item = (FuncId, Option<&Type>, Option<TraitId>)> {
pub fn iter(&self) -> impl Iterator<Item = (FuncId, &Type, Option<TraitId>)> {
let trait_impl_methods =
self.trait_impl_methods.iter().map(|m| (m.method, m.typ.as_ref(), Some(m.trait_id)));
let direct = self.direct.iter().copied().map(|func_id| (func_id, None, None));
self.trait_impl_methods.iter().map(|m| (m.method, &m.typ, Some(m.trait_id)));
let direct = self.direct.iter().map(|method| (method.method, &method.typ, None));
direct.chain(trait_impl_methods)
}

Expand All @@ -2346,12 +2353,12 @@
pub fn find_direct_method(
&self,
typ: &Type,
has_self_param: bool,
check_self_param: bool,
interner: &NodeInterner,
) -> Option<FuncId> {
for method in &self.direct {
if Self::method_matches(typ, has_self_param, *method, None, interner) {
return Some(*method);
if Self::method_matches(typ, check_self_param, method.method, &method.typ, interner) {
return Some(method.method);
}
}

Expand All @@ -2368,7 +2375,7 @@

for trait_impl_method in &self.trait_impl_methods {
let method = trait_impl_method.method;
let method_type = trait_impl_method.typ.as_ref();
let method_type = &trait_impl_method.typ;
let trait_id = trait_impl_method.trait_id;

if Self::method_matches(typ, has_self_param, method, method_type, interner) {
Expand All @@ -2381,14 +2388,14 @@

fn method_matches(
typ: &Type,
has_self_param: bool,
check_self_param: bool,
method: FuncId,
method_type: Option<&Type>,
method_type: &Type,
interner: &NodeInterner,
) -> bool {
match interner.function_meta(&method).typ.instantiate(interner).0 {
Type::Function(args, _, _, _) => {
if has_self_param {
if check_self_param {
if let Some(object) = args.first() {
if object.unify(typ).is_ok() {
return true;
Expand All @@ -2402,21 +2409,18 @@
}
}
} else {
// If we recorded the concrete type this trait impl method belongs to,
// and it matches typ, it's an exact match and we return that.
if let Some(method_type) = method_type {
// We still need to make sure the method is for the given type
// (this might be false if for example a method for `Struct<i32>` was added but
// now we are looking for a method in `Struct<i64>`)
if method_type.unify(typ).is_ok() {
return true;
}

// Handle auto-dereferencing `&mut T` into `T`
if let Type::MutableReference(method_type) = method_type {
if method_type.unify(typ).is_ok() {
return true;
}

// Handle auto-dereferencing `&mut T` into `T`
if let Type::MutableReference(method_type) = method_type {
if method_type.unify(typ).is_ok() {
return true;
}
}
} else {
return true;
}
}
}
Expand Down
21 changes: 21 additions & 0 deletions compiler/noirc_frontend/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4494,3 +4494,24 @@ fn errors_on_repeated_match_variables_in_pattern() {
CompilationError::ResolverError(ResolverError::VariableAlreadyDefinedInPattern { .. })
));
}

#[test]
fn check_impl_duplicate_method_without_self() {
let src = "
pub struct Foo {}

impl Foo {
fn foo() {}
fn foo() {}
}

fn main() {}
";
let errors = get_program_errors(src);
assert_eq!(errors.len(), 1);

assert!(matches!(
errors[0].0,
CompilationError::ResolverError(ResolverError::DuplicateDefinition { .. })
));
}
Loading