Skip to content

Commit

Permalink
feat(traits): allow multiple traits to share the same associated func…
Browse files Browse the repository at this point in the history
…tion name and to be implemented for the same type (#3126)
  • Loading branch information
nickysn authored Oct 12, 2023
1 parent 692aa0d commit 004f8dd
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 34 deletions.
10 changes: 6 additions & 4 deletions compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,17 +499,18 @@ fn add_method_to_struct_namespace(
struct_type: &Shared<StructType>,
func_id: FuncId,
name_ident: &Ident,
trait_id: TraitId,
) -> Result<(), DefCollectorErrorKind> {
let struct_type = struct_type.borrow();
let type_module = struct_type.id.local_module_id();
let module = &mut current_def_map.modules[type_module.0];
module.declare_function(name_ident.clone(), func_id).map_err(|(first_def, second_def)| {
DefCollectorErrorKind::Duplicate {
module.declare_trait_function(name_ident.clone(), func_id, trait_id).map_err(
|(first_def, second_def)| DefCollectorErrorKind::Duplicate {
typ: DuplicateType::TraitImplementation,
first_def,
second_def,
}
})
},
)
}

fn collect_trait_impl(
Expand Down Expand Up @@ -550,6 +551,7 @@ fn collect_trait_impl(
struct_type,
*func_id,
ast.name_ident(),
trait_id,
) {
Ok(()) => {}
Err(err) => {
Expand Down
106 changes: 88 additions & 18 deletions compiler/noirc_frontend/src/hir/def_map/item_scope.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use super::{namespace::PerNs, ModuleDefId, ModuleId};
use crate::{node_interner::FuncId, Ident};
use crate::{
node_interner::{FuncId, TraitId},
Ident,
};
use std::collections::{hash_map::Entry, HashMap};

#[derive(Debug, PartialEq, Eq, Copy, Clone)]
Expand All @@ -9,8 +12,8 @@ pub enum Visibility {

#[derive(Default, Debug, PartialEq, Eq)]
pub struct ItemScope {
types: HashMap<Ident, (ModuleDefId, Visibility)>,
values: HashMap<Ident, (ModuleDefId, Visibility)>,
types: HashMap<Ident, HashMap<Option<TraitId>, (ModuleDefId, Visibility)>>,
values: HashMap<Ident, HashMap<Option<TraitId>, (ModuleDefId, Visibility)>>,

defs: Vec<ModuleDefId>,
}
Expand All @@ -20,8 +23,9 @@ impl ItemScope {
&mut self,
name: Ident,
mod_def: ModuleDefId,
trait_id: Option<TraitId>,
) -> Result<(), (Ident, Ident)> {
self.add_item_to_namespace(name, mod_def)?;
self.add_item_to_namespace(name, mod_def, trait_id)?;
self.defs.push(mod_def);
Ok(())
}
Expand All @@ -33,16 +37,26 @@ impl ItemScope {
&mut self,
name: Ident,
mod_def: ModuleDefId,
trait_id: Option<TraitId>,
) -> Result<(), (Ident, Ident)> {
let add_item = |map: &mut HashMap<Ident, (ModuleDefId, Visibility)>| {
if let Entry::Occupied(o) = map.entry(name.clone()) {
let old_ident = o.key();
Err((old_ident.clone(), name))
} else {
map.insert(name, (mod_def, Visibility::Public));
Ok(())
}
};
let add_item =
|map: &mut HashMap<Ident, HashMap<Option<TraitId>, (ModuleDefId, Visibility)>>| {
if let Entry::Occupied(mut o) = map.entry(name.clone()) {
let trait_hashmap = o.get_mut();
if let Entry::Occupied(_) = trait_hashmap.entry(trait_id) {
let old_ident = o.key();
Err((old_ident.clone(), name))
} else {
trait_hashmap.insert(trait_id, (mod_def, Visibility::Public));
Ok(())
}
} else {
let mut trait_hashmap = HashMap::new();
trait_hashmap.insert(trait_id, (mod_def, Visibility::Public));
map.insert(name, trait_hashmap);
Ok(())
}
};

match mod_def {
ModuleDefId::ModuleId(_) => add_item(&mut self.types),
Expand All @@ -55,34 +69,90 @@ impl ItemScope {
}

pub fn find_module_with_name(&self, mod_name: &Ident) -> Option<&ModuleId> {
let (module_def, _) = self.types.get(mod_name)?;
let (module_def, _) = self.types.get(mod_name)?.get(&None)?;
match module_def {
ModuleDefId::ModuleId(id) => Some(id),
_ => None,
}
}

pub fn find_func_with_name(&self, func_name: &Ident) -> Option<FuncId> {
let (module_def, _) = self.values.get(func_name)?;
let trait_hashmap = self.values.get(func_name)?;
// methods introduced without trait take priority and hide methods with the same name that come from a trait
let a = trait_hashmap.get(&None);
match a {
Some((module_def, _)) => match module_def {
ModuleDefId::FunctionId(id) => Some(*id),
_ => None,
},
None => {
if trait_hashmap.len() == 1 {
let (module_def, _) = trait_hashmap.get(trait_hashmap.keys().last()?)?;
match module_def {
ModuleDefId::FunctionId(id) => Some(*id),
_ => None,
}
} else {
// ambiguous name (multiple traits, containing the same function name)
None
}
}
}
}

pub fn find_func_with_name_and_trait_id(
&self,
func_name: &Ident,
trait_id: &Option<TraitId>,
) -> Option<FuncId> {
let (module_def, _) = self.values.get(func_name)?.get(trait_id)?;
match module_def {
ModuleDefId::FunctionId(id) => Some(*id),
_ => None,
}
}

pub fn find_name(&self, name: &Ident) -> PerNs {
PerNs { types: self.types.get(name).cloned(), values: self.values.get(name).cloned() }
// Names, not associated with traits are searched first. If not found, we search for name, coming from a trait.
// If we find only one name from trait, we return it. If there are multiple traits, providing the same name, we return None.
let find_name_in =
|a: &HashMap<Ident, HashMap<Option<TraitId>, (ModuleDefId, Visibility)>>| {
if let Some(t) = a.get(name) {
if let Some(tt) = t.get(&None) {
Some(*tt)
} else if t.len() == 1 {
t.values().last().cloned()
} else {
None
}
} else {
None
}
};

PerNs { types: find_name_in(&self.types), values: find_name_in(&self.values) }
}

pub fn find_name_for_trait_id(&self, name: &Ident, trait_id: &Option<TraitId>) -> PerNs {
PerNs {
types: if let Some(t) = self.types.get(name) { t.get(trait_id).cloned() } else { None },
values: if let Some(v) = self.values.get(name) {
v.get(trait_id).cloned()
} else {
None
},
}
}

pub fn definitions(&self) -> Vec<ModuleDefId> {
self.defs.clone()
}

pub fn types(&self) -> &HashMap<Ident, (ModuleDefId, Visibility)> {
pub fn types(&self) -> &HashMap<Ident, HashMap<Option<TraitId>, (ModuleDefId, Visibility)>> {
&self.types
}

pub fn values(&self) -> &HashMap<Ident, (ModuleDefId, Visibility)> {
pub fn values(&self) -> &HashMap<Ident, HashMap<Option<TraitId>, (ModuleDefId, Visibility)>> {
&self.values
}

Expand Down
38 changes: 26 additions & 12 deletions compiler/noirc_frontend/src/hir/def_map/module_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,30 @@ impl ModuleData {
}
}

fn declare(&mut self, name: Ident, item_id: ModuleDefId) -> Result<(), (Ident, Ident)> {
self.scope.add_definition(name.clone(), item_id)?;
fn declare(
&mut self,
name: Ident,
item_id: ModuleDefId,
trait_id: Option<TraitId>,
) -> Result<(), (Ident, Ident)> {
self.scope.add_definition(name.clone(), item_id, trait_id)?;

// definitions is a subset of self.scope so it is expected if self.scope.define_func_def
// returns without error, so will self.definitions.define_func_def.
self.definitions.add_definition(name, item_id)
self.definitions.add_definition(name, item_id, trait_id)
}

pub fn declare_function(&mut self, name: Ident, id: FuncId) -> Result<(), (Ident, Ident)> {
self.declare(name, id.into())
self.declare(name, id.into(), None)
}

pub fn declare_trait_function(
&mut self,
name: Ident,
id: FuncId,
trait_id: TraitId,
) -> Result<(), (Ident, Ident)> {
self.declare(name, id.into(), Some(trait_id))
}

pub fn remove_function(&mut self, name: &Ident) {
Expand All @@ -59,52 +73,52 @@ impl ModuleData {
}

pub fn declare_global(&mut self, name: Ident, id: StmtId) -> Result<(), (Ident, Ident)> {
self.declare(name, id.into())
self.declare(name, id.into(), None)
}

pub fn declare_struct(&mut self, name: Ident, id: StructId) -> Result<(), (Ident, Ident)> {
self.declare(name, ModuleDefId::TypeId(id))
self.declare(name, ModuleDefId::TypeId(id), None)
}

pub fn declare_type_alias(
&mut self,
name: Ident,
id: TypeAliasId,
) -> Result<(), (Ident, Ident)> {
self.declare(name, id.into())
self.declare(name, id.into(), None)
}

pub fn declare_trait(&mut self, name: Ident, id: TraitId) -> Result<(), (Ident, Ident)> {
self.declare(name, ModuleDefId::TraitId(id))
self.declare(name, ModuleDefId::TraitId(id), None)
}

pub fn declare_child_module(
&mut self,
name: Ident,
child_id: ModuleId,
) -> Result<(), (Ident, Ident)> {
self.declare(name, child_id.into())
self.declare(name, child_id.into(), None)
}

pub fn find_func_with_name(&self, name: &Ident) -> Option<FuncId> {
self.scope.find_func_with_name(name)
}

pub fn import(&mut self, name: Ident, id: ModuleDefId) -> Result<(), (Ident, Ident)> {
self.scope.add_item_to_namespace(name, id)
self.scope.add_item_to_namespace(name, id, None)
}

pub fn find_name(&self, name: &Ident) -> PerNs {
self.scope.find_name(name)
}

pub fn type_definitions(&self) -> impl Iterator<Item = ModuleDefId> + '_ {
self.definitions.types().values().map(|(id, _)| *id)
self.definitions.types().values().flat_map(|a| a.values().map(|(id, _)| *id))
}

/// Return an iterator over all definitions defined within this module,
/// excluding any type definitions.
pub fn value_definitions(&self) -> impl Iterator<Item = ModuleDefId> + '_ {
self.definitions.values().values().map(|(id, _)| *id)
self.definitions.values().values().flat_map(|a| a.values().map(|(id, _)| *id))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "trait_associated_member_names_clashes"
type = "bin"
authors = [""]
compiler_version = "0.16.0"

[dependencies]
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
trait Trait1 {
fn tralala() -> Field;
}

trait Trait2 {
fn tralala() -> Field;
}

struct Struct1 {
}

impl Struct1 {
fn tralala() -> Field {
123456
}
}

impl Trait1 for Struct1 {
fn tralala() -> Field {
111111
}
}

impl Trait2 for Struct1 {
fn tralala() -> Field {
222222
}
}

fn main() {
// the struct impl takes priority over trait methods
assert(Struct1::tralala() == 123456);
// TODO: uncomment these, once we support the <Type as Trait>:: syntax
//assert(<Struct1 as Trait1>::tralala() == 111111);
//assert(<Struct1 as Trait2>::tralala() == 222222);
}

0 comments on commit 004f8dd

Please sign in to comment.