diff --git a/crates/analyzer/src/context.rs b/crates/analyzer/src/context.rs index fa6e3c5ef7..2fe0517d36 100644 --- a/crates/analyzer/src/context.rs +++ b/crates/analyzer/src/context.rs @@ -1,5 +1,7 @@ -use crate::namespace::items::{Class, ContractId, DiagnosticSink, EventId, FunctionId, Item}; -use crate::namespace::types::{SelfDecl, Struct, Type}; +use crate::namespace::items::{ + Class, ContractId, DiagnosticSink, EventId, FunctionId, FunctionSigId, Item, TraitId, +}; +use crate::namespace::types::{Generic, SelfDecl, Struct, Type}; use crate::AnalyzerDb; use crate::{ builtins::{ContractTypeMethod, GlobalFunction, Intrinsic, ValueMethod}, @@ -361,7 +363,12 @@ impl Location { pub fn assign_location(typ: &Type) -> Self { match typ { Type::Base(_) | Type::Contract(_) => Location::Value, - Type::Array(_) | Type::Tuple(_) | Type::String(_) | Type::Struct(_) => Location::Memory, + // For now assume that generics can only ever refer to structs + Type::Array(_) + | Type::Tuple(_) + | Type::String(_) + | Type::Struct(_) + | Type::Generic(_) => Location::Memory, other => panic!("Type {other} can not be assigned, returned or passed"), } } @@ -458,11 +465,21 @@ pub enum CallType { class: Class, function: FunctionId, }, + // some_struct_or_contract.foo() ValueMethod { - is_self: bool, class: Class, method: FunctionId, }, + // some_trait.foo() + // The reason this can not use `ValueMethod` is mainly because the trait might not have a function implementation + // and even if it had it might not be the one that ends up getting executed. An `impl` block will decide that. + TraitValueMethod { + trait_id: TraitId, + method: FunctionSigId, + // Traits can not directly be used as types but can act as bounds for generics. This is the generic type + // that the method is called on. + generic_type: Generic, + }, External { contract: ContractId, function: FunctionId, @@ -479,6 +496,7 @@ impl CallType { | BuiltinValueMethod { .. } | TypeConstructor(_) | Intrinsic(_) + | TraitValueMethod { .. } | BuiltinAssociatedFunction { .. } => None, AssociatedFunction { function: id, .. } | ValueMethod { method: id, .. } @@ -497,6 +515,7 @@ impl CallType { | CallType::ValueMethod { method: id, .. } | CallType::External { function: id, .. } | CallType::Pure(id) => id.name(db), + CallType::TraitValueMethod { method: id, .. } => id.name(db), CallType::TypeConstructor(typ) => typ.name(), } } @@ -508,7 +527,7 @@ impl CallType { // check that this is the `Context` struct defined in `std` // this should be deleted once associated functions are supported and we can // define unsafe constructors in Fe - struct_.name == "Context" && struct_.id.module(db).ingot(db).name(db) == "std" + struct_.name == "Context" && struct_.id.module(db).is_in_std(db) } else { self.function().map(|id| id.is_unsafe(db)).unwrap_or(false) } diff --git a/crates/analyzer/src/db.rs b/crates/analyzer/src/db.rs index bbcc476a90..71df437858 100644 --- a/crates/analyzer/src/db.rs +++ b/crates/analyzer/src/db.rs @@ -1,10 +1,10 @@ use crate::context::{Analysis, Constant, FunctionBody}; use crate::errors::{ConstEvalError, TypeError}; use crate::namespace::items::{ - self, ContractFieldId, ContractId, DepGraphWrapper, EventId, FunctionId, IngotId, Item, - ModuleConstantId, ModuleId, StructFieldId, StructId, TypeAliasId, + self, ContractFieldId, ContractId, DepGraphWrapper, EventId, FunctionId, FunctionSigId, ImplId, + IngotId, Item, ModuleConstantId, ModuleId, StructFieldId, StructId, TraitId, TypeAliasId, }; -use crate::namespace::types; +use crate::namespace::types::{self, Type}; use fe_common::db::{SourceDb, SourceDbStorage, Upcast, UpcastMut}; use fe_common::{SourceFileId, Span}; use fe_parser::ast; @@ -24,6 +24,10 @@ pub trait AnalyzerDb: SourceDb + Upcast + UpcastMut #[salsa::interned] fn intern_struct(&self, data: Rc) -> StructId; #[salsa::interned] + fn intern_trait(&self, data: Rc) -> TraitId; + #[salsa::interned] + fn intern_impl(&self, data: Rc) -> ImplId; + #[salsa::interned] fn intern_struct_field(&self, data: Rc) -> StructFieldId; #[salsa::interned] fn intern_type_alias(&self, data: Rc) -> TypeAliasId; @@ -32,6 +36,8 @@ pub trait AnalyzerDb: SourceDb + Upcast + UpcastMut #[salsa::interned] fn intern_contract_field(&self, data: Rc) -> ContractFieldId; #[salsa::interned] + fn intern_function_sig(&self, data: Rc) -> FunctionSigId; + #[salsa::interned] fn intern_function(&self, data: Rc) -> FunctionId; #[salsa::interned] fn intern_event(&self, data: Rc) -> EventId; @@ -60,8 +66,12 @@ pub trait AnalyzerDb: SourceDb + Upcast + UpcastMut fn module_is_incomplete(&self, module: ModuleId) -> bool; #[salsa::invoke(queries::module::module_all_items)] fn module_all_items(&self, module: ModuleId) -> Rc<[Item]>; + #[salsa::invoke(queries::module::module_all_impls)] + fn module_all_impls(&self, module: ModuleId) -> Rc<[ImplId]>; #[salsa::invoke(queries::module::module_item_map)] fn module_item_map(&self, module: ModuleId) -> Analysis>>; + #[salsa::invoke(queries::module::module_impl_map)] + fn module_impl_map(&self, module: ModuleId) -> Analysis>>; #[salsa::invoke(queries::module::module_contracts)] fn module_contracts(&self, module: ModuleId) -> Rc<[ContractId]>; #[salsa::invoke(queries::module::module_structs)] @@ -130,7 +140,7 @@ pub trait AnalyzerDb: SourceDb + Upcast + UpcastMut // Function #[salsa::invoke(queries::functions::function_signature)] - fn function_signature(&self, id: FunctionId) -> Analysis>; + fn function_signature(&self, id: FunctionSigId) -> Analysis>; #[salsa::invoke(queries::functions::function_body)] fn function_body(&self, id: FunctionId) -> Analysis>; #[salsa::cycle(queries::functions::function_dependency_graph_cycle)] @@ -154,6 +164,20 @@ pub trait AnalyzerDb: SourceDb + Upcast + UpcastMut #[salsa::invoke(queries::structs::struct_dependency_graph)] fn struct_dependency_graph(&self, id: StructId) -> Analysis; + // Trait + #[salsa::invoke(queries::traits::trait_type)] + fn trait_type(&self, id: TraitId) -> Rc; + #[salsa::invoke(queries::traits::trait_all_functions)] + fn trait_all_functions(&self, id: TraitId) -> Rc<[FunctionSigId]>; + #[salsa::invoke(queries::traits::trait_function_map)] + fn trait_function_map(&self, id: TraitId) -> Analysis>>; + + // Impl + #[salsa::invoke(queries::impls::impl_all_functions)] + fn impl_all_functions(&self, id: ImplId) -> Rc<[FunctionId]>; + #[salsa::invoke(queries::impls::impl_function_map)] + fn impl_function_map(&self, id: ImplId) -> Analysis>>; + // Event #[salsa::invoke(queries::events::event_type)] fn event_type(&self, event: EventId) -> Analysis>; diff --git a/crates/analyzer/src/db/queries.rs b/crates/analyzer/src/db/queries.rs index f6c5db8ad6..3a90513735 100644 --- a/crates/analyzer/src/db/queries.rs +++ b/crates/analyzer/src/db/queries.rs @@ -1,7 +1,9 @@ pub mod contracts; pub mod events; pub mod functions; +pub mod impls; pub mod ingots; pub mod module; pub mod structs; +pub mod traits; pub mod types; diff --git a/crates/analyzer/src/db/queries/contracts.rs b/crates/analyzer/src/db/queries/contracts.rs index 43ad43ce26..e97b086a51 100644 --- a/crates/analyzer/src/db/queries/contracts.rs +++ b/crates/analyzer/src/db/queries/contracts.rs @@ -22,13 +22,9 @@ pub fn contract_all_functions(db: &dyn AnalyzerDb, contract: ContractId) -> Rc<[ body.iter() .filter_map(|stmt| match stmt { ast::ContractStmt::Event(_) => None, - ast::ContractStmt::Function(node) => { - Some(db.intern_function(Rc::new(items::Function { - ast: node.clone(), - module, - parent: Some(items::Class::Contract(contract)), - }))) - } + ast::ContractStmt::Function(node) => Some(db.intern_function(Rc::new( + items::Function::new(db, node, Some(items::Class::Contract(contract)), module), + ))), }) .collect() } @@ -53,7 +49,7 @@ pub fn contract_function_map( def_name, &NamedThing::Item(Item::Event(event)), Some(event.name_span(db)), - def.kind.name.span, + def.kind.sig.kind.name.span, ); continue; } @@ -64,7 +60,7 @@ pub fn contract_function_map( def_name, &named_item, named_item.name_span(db), - def.kind.name.span, + def.kind.sig.kind.name.span, ); continue; } @@ -334,6 +330,14 @@ pub fn contract_field_type( let node = &field.data(db).ast; + if let Ok(Type::Trait(ref val)) = typ { + scope.error( + "traits can not be used as contract fields", + node.span, + &format!("trait `{}` can not appear here", val.name), + ); + } + if node.kind.is_pub { scope.not_yet_implemented("contract `pub` fields", node.span); } diff --git a/crates/analyzer/src/db/queries/functions.rs b/crates/analyzer/src/db/queries/functions.rs index 055dd56e44..2aab4dc6b9 100644 --- a/crates/analyzer/src/db/queries/functions.rs +++ b/crates/analyzer/src/db/queries/functions.rs @@ -1,15 +1,18 @@ use crate::context::{AnalyzerContext, CallType, FunctionBody}; use crate::db::{Analysis, AnalyzerDb}; use crate::errors::TypeError; -use crate::namespace::items::{DepGraph, DepGraphWrapper, DepLocality, FunctionId, Item, TypeDef}; +use crate::namespace::items::{ + Class, DepGraph, DepGraphWrapper, DepLocality, FunctionId, FunctionSigId, Item, TypeDef, +}; use crate::namespace::scopes::{BlockScope, BlockScopeType, FunctionScope, ItemScope}; -use crate::namespace::types::{self, Contract, CtxDecl, SelfDecl, Struct, Type}; +use crate::namespace::types::{self, Contract, CtxDecl, Generic, SelfDecl, Struct, Type}; use crate::traversal::functions::traverse_statements; use crate::traversal::types::type_desc; use fe_common::diagnostics::Label; -use fe_parser::ast; +use fe_parser::ast::{self, GenericParameter}; use fe_parser::node::Node; use if_chain::if_chain; +use smol_str::SmolStr; use std::collections::HashMap; use std::rc::Rc; @@ -17,10 +20,9 @@ use std::rc::Rc; /// errors. Does not inspect the function body. pub fn function_signature( db: &dyn AnalyzerDb, - function: FunctionId, + function: FunctionSigId, ) -> Analysis> { - let node = &function.data(db).ast; - let def = &node.kind; + let def = &function.data(db).ast; let mut scope = ItemScope::new(db, function.module(db)); let fn_parent = function.class(db); @@ -30,7 +32,55 @@ pub fn function_signature( let mut names = HashMap::new(); let mut labels = HashMap::new(); + function.data(db).ast.kind.generic_params.kind.iter().fold( + HashMap::>::new(), + |mut accum, param| { + if let Some(previous) = accum.get(¶m.name()) { + scope.duplicate_name_error( + "duplicate generic parameter", + ¶m.name(), + previous.span, + param.name_node().span, + ); + } else { + accum.insert(param.name(), param.name_node()); + }; + + accum + }, + ); + + if !matches!(fn_parent, Some(Class::Struct(_))) && function.is_generic(db) { + scope.fancy_error( + "generic function parameters aren't yet supported outside of struct functions", + vec![Label::primary( + function.data(db).ast.kind.generic_params.span, + "This can not appear here", + )], + vec!["Hint: Struct functions can have generic parameters".into()], + ); + } + + if function.is_generic(db) { + for param in function.data(db).ast.kind.generic_params.kind.iter() { + if let GenericParameter::Unbounded(val) = param { + scope.fancy_error( + "unbounded generic parameters aren't yet supported", + vec![Label::primary( + val.span, + format!("`{}` needs to be bound by some trait", val.kind), + )], + vec![format!( + "Hint: Change `{}` to `{}: SomeTrait`", + val.kind, val.kind + )], + ); + } + } + } + let params = def + .kind .args .iter() .enumerate() @@ -55,12 +105,12 @@ pub fn function_signature( None } ast::FunctionArg::Regular(reg) => { - let typ = type_desc(&mut scope, ®.typ).and_then(|typ| match typ { + let typ = resolve_function_param_type(db, function, &mut scope, ®.typ).and_then(|typ| match typ { typ if typ.has_fixed_size() => Ok(typ), _ => Err(TypeError::new(scope.error( "function parameter types must have fixed size", reg.typ.span, - "`Map` type can't be used as a function parameter", + &format!("`{}` type can't be used as a function parameter", typ.name()), ))), }); @@ -101,9 +151,9 @@ pub fn function_signature( if label.kind != "_"; if let Some(dup_idx) = labels.get(&label.kind); then { - let dup_arg: &Node = &def.args[*dup_idx]; + let dup_arg: &Node = &def.kind.args[*dup_idx]; scope.fancy_error( - &format!("duplicate parameter labels in function `{}`", def.name.kind), + &format!("duplicate parameter labels in function `{}`", def.kind.name.kind), vec![ Label::primary(dup_arg.span, "the label `{}` was first used here"), Label::primary(label.span, "label `{}` used again here"), @@ -125,9 +175,9 @@ pub fn function_signature( ); None } else if let Some(dup_idx) = names.get(®.name.kind) { - let dup_arg: &Node = &def.args[*dup_idx]; + let dup_arg: &Node = &def.kind.args[*dup_idx]; scope.duplicate_name_error( - &format!("duplicate parameter names in function `{}`", def.name.kind), + &format!("duplicate parameter names in function `{}`", function.name(db)), ®.name.kind, dup_arg.span, arg.span, @@ -146,10 +196,11 @@ pub fn function_signature( .collect(); let return_type = def + .kind .return_type .as_ref() .map(|type_node| { - let fn_name = &def.name.kind; + let fn_name = &function.name(db); if fn_name == "__init__" || fn_name == "__call__" { // `__init__` and `__call__` must not return any type other than `()`. if type_node.kind != ast::TypeDesc::Unit { @@ -192,6 +243,40 @@ pub fn function_signature( } } +pub fn resolve_function_param_type( + db: &dyn AnalyzerDb, + function: FunctionSigId, + context: &mut dyn AnalyzerContext, + desc: &Node, +) -> Result { + // First check if the param type is a local generic of the function. This won't hold when in the future + // generics can appear on the contract, struct or module level but it could be good enough for now. + if let ast::TypeDesc::Base { base } = &desc.kind { + if let Some(val) = function.generic_param(db, base) { + let bounds = match val { + ast::GenericParameter::Unbounded(_) => vec![], + ast::GenericParameter::Bounded { bound, .. } => match type_desc(context, &bound)? { + Type::Trait(trait_ty) => vec![trait_ty.id], + other => { + context.error( + &format!("expected trait, found type `{}`", other), + bound.span, + "not a trait", + ); + vec![] + } + }, + }; + + return Ok(Type::Generic(Generic { + name: base.clone(), + bounds, + })); + } + } + type_desc(context, desc) +} + /// Gather context information for a function body and check for type errors. pub fn function_body(db: &dyn AnalyzerDb, function: FunctionId) -> Analysis> { let def = &function.data(db).ast.kind; @@ -207,11 +292,11 @@ pub fn function_body(db: &dyn AnalyzerDb, function: FunctionId) -> Analysis D // A function that takes `self` depends on the type of `self`, so that any // relevant struct getters/setters are included when compiling. if let Some(class) = function.class(db) { - directs.push((root, class.as_item(), DepLocality::Local)); + directs.push((root, class.as_item(db), DepLocality::Local)); } let body = function.body(db); @@ -312,9 +397,16 @@ pub fn function_dependency_graph(db: &dyn AnalyzerDb, function: FunctionId) -> D // Including the "class" type here is probably redundant; the type will // also be part of the fn sig, or some type decl, or some create/create2 call, // or... - directs.push((root, class.as_item(), DepLocality::Local)); + directs.push((root, class.as_item(db), DepLocality::Local)); directs.push((root, Item::Function(*method), DepLocality::Local)); } + CallType::TraitValueMethod { trait_id, .. } => { + directs.push(( + root, + Item::Type(TypeDef::Trait(*trait_id)), + DepLocality::Local, + )); + } CallType::External { contract, function } => { directs.push((root, Item::Function(*function), DepLocality::External)); // Probably redundant: diff --git a/crates/analyzer/src/db/queries/impls.rs b/crates/analyzer/src/db/queries/impls.rs new file mode 100644 index 0000000000..ac67c8e589 --- /dev/null +++ b/crates/analyzer/src/db/queries/impls.rs @@ -0,0 +1,54 @@ +use indexmap::map::Entry; +use indexmap::IndexMap; +use smol_str::SmolStr; + +use crate::context::{Analysis, AnalyzerContext}; +use crate::namespace::items::{Class, Function, FunctionId, ImplId}; +use crate::namespace::scopes::ItemScope; +use crate::AnalyzerDb; +use std::rc::Rc; + +pub fn impl_all_functions(db: &dyn AnalyzerDb, impl_: ImplId) -> Rc<[FunctionId]> { + let impl_data = impl_.data(db); + impl_data + .ast + .kind + .functions + .iter() + .map(|node| { + db.intern_function(Rc::new(Function::new( + db, + node, + Some(Class::Impl(impl_)), + impl_data.module, + ))) + }) + .collect() +} + +pub fn impl_function_map( + db: &dyn AnalyzerDb, + impl_: ImplId, +) -> Analysis>> { + let scope = ItemScope::new(db, impl_.module(db)); + let mut map = IndexMap::::new(); + + for func in db.impl_all_functions(impl_).iter() { + let def_name = func.name(db); + + match map.entry(def_name) { + Entry::Occupied(entry) => { + scope.duplicate_name_error( + "duplicate function names in `impl` block", + entry.key(), + entry.get().name_span(db), + func.name_span(db), + ); + } + Entry::Vacant(entry) => { + entry.insert(*func); + } + } + } + Analysis::new(Rc::new(map), scope.diagnostics.take().into()) +} diff --git a/crates/analyzer/src/db/queries/module.rs b/crates/analyzer/src/db/queries/module.rs index dea539886f..380c0af16b 100644 --- a/crates/analyzer/src/db/queries/module.rs +++ b/crates/analyzer/src/db/queries/module.rs @@ -2,8 +2,8 @@ use crate::context::{Analysis, AnalyzerContext, Constant}; use crate::db::AnalyzerDb; use crate::errors::{self, ConstEvalError, TypeError}; use crate::namespace::items::{ - Contract, ContractId, Event, Function, Item, ModuleConstant, ModuleConstantId, ModuleId, - ModuleSource, Struct, StructId, TypeAlias, TypeDef, + Contract, ContractId, Event, Function, Impl, ImplId, Item, ModuleConstant, ModuleConstantId, + ModuleId, ModuleSource, Struct, StructId, Trait, TraitId, TypeAlias, TypeDef, }; use crate::namespace::scopes::ItemScope; use crate::namespace::types::{self, Type}; @@ -59,7 +59,6 @@ pub fn module_is_incomplete(db: &dyn AnalyzerDb, module: ModuleId) -> bool { pub fn module_all_items(db: &dyn AnalyzerDb, module: ModuleId) -> Rc<[Item]> { let body = &module.ast(db).body; - body.iter() .filter_map(|stmt| match stmt { ast::ModuleStmt::TypeAlias(node) => Some(Item::Type(TypeDef::Alias( @@ -87,15 +86,16 @@ pub fn module_all_items(db: &dyn AnalyzerDb, module: ModuleId) -> Rc<[Item]> { module, }), ))), - ast::ModuleStmt::Function(node) => { - Some(Item::Function(db.intern_function(Rc::new(Function { + ast::ModuleStmt::Function(node) => Some(Item::Function( + db.intern_function(Rc::new(Function::new(db, node, None, module))), + )), + ast::ModuleStmt::Trait(node) => Some(Item::Type(TypeDef::Trait(db.intern_trait( + Rc::new(Trait { ast: node.clone(), module, - parent: None, - })))) - } - ast::ModuleStmt::Pragma(_) => None, - ast::ModuleStmt::Use(_) => None, + }), + )))), + ast::ModuleStmt::Pragma(_) | ast::ModuleStmt::Use(_) | ast::ModuleStmt::Impl(_) => None, ast::ModuleStmt::Event(node) => Some(Item::Event(db.intern_event(Rc::new(Event { ast: node.clone(), module, @@ -106,6 +106,35 @@ pub fn module_all_items(db: &dyn AnalyzerDb, module: ModuleId) -> Rc<[Item]> { .collect() } +pub fn module_all_impls(db: &dyn AnalyzerDb, module: ModuleId) -> Rc<[ImplId]> { + let body = &module.ast(db).body; + body.iter() + .filter_map(|stmt| match stmt { + ast::ModuleStmt::Impl(impl_node) => { + let treit = module + .items(db) + .get(&impl_node.kind.impl_trait.kind) + .cloned(); + + let mut scope = ItemScope::new(db, module); + let receiver_type = type_desc(&mut scope, &impl_node.kind.receiver).unwrap(); + + if let Some(Item::Type(TypeDef::Trait(val))) = treit { + Some(db.intern_impl(Rc::new(Impl { + trait_id: val, + receiver: receiver_type, + ast: impl_node.clone(), + module, + }))) + } else { + None + } + } + _ => None, + }) + .collect() +} + pub fn module_item_map( db: &dyn AnalyzerDb, module: ModuleId, @@ -204,6 +233,37 @@ pub fn module_item_map( ) } +pub fn module_impl_map( + db: &dyn AnalyzerDb, + module: ModuleId, +) -> Analysis>> { + let scope = ItemScope::new(db, module); + let mut map = IndexMap::<(TraitId, Type), ImplId>::new(); + + for impl_ in db.module_all_impls(module).iter() { + let key = &(impl_.trait_id(db), impl_.receiver(db)); + + match map.entry(key.clone()) { + Entry::Occupied(entry) => { + scope.duplicate_name_error( + &format!( + "duplicate `impl` blocks for trait `{}` for type `{}`", + key.0.name(db), + key.1.name() + ), + "", + entry.get().ast(db).span, + impl_.ast(db).span, + ); + } + Entry::Vacant(entry) => { + entry.insert(*impl_); + } + } + } + Analysis::new(Rc::new(map), scope.diagnostics.take().into()) +} + pub fn module_contracts(db: &dyn AnalyzerDb, module: ModuleId) -> Rc<[ContractId]> { module .all_items(db) diff --git a/crates/analyzer/src/db/queries/structs.rs b/crates/analyzer/src/db/queries/structs.rs index 0f764d6b69..1b68306c65 100644 --- a/crates/analyzer/src/db/queries/structs.rs +++ b/crates/analyzer/src/db/queries/structs.rs @@ -3,8 +3,8 @@ use crate::context::AnalyzerContext; use crate::db::Analysis; use crate::errors::TypeError; use crate::namespace::items::{ - self, DepGraph, DepGraphWrapper, DepLocality, Function, FunctionId, Item, StructField, - StructFieldId, StructId, TypeDef, + self, DepGraph, DepGraphWrapper, DepLocality, FunctionId, Item, StructField, StructFieldId, + StructId, TypeDef, }; use crate::namespace::scopes::ItemScope; use crate::namespace::types::{self, Contract, Struct, Type}; @@ -121,11 +121,12 @@ pub fn struct_all_functions(db: &dyn AnalyzerDb, struct_: StructId) -> Rc<[Funct .functions .iter() .map(|node| { - db.intern_function(Rc::new(Function { - ast: node.clone(), - module: struct_data.module, - parent: Some(items::Class::Struct(struct_)), - })) + db.intern_function(Rc::new(items::Function::new( + db, + node, + Some(items::Class::Struct(struct_)), + struct_data.module, + ))) }) .collect() } @@ -150,7 +151,7 @@ pub fn struct_function_map( def_name, &named_item, named_item.name_span(db), - def.kind.name.span, + func.name_span(db), ); continue; } @@ -161,7 +162,7 @@ pub fn struct_function_map( "function name `{}` conflicts with built-in function", def_name ), - def.kind.name.span, + func.name_span(db), &format!("`{}` is a built-in function", def_name), ); continue; diff --git a/crates/analyzer/src/db/queries/traits.rs b/crates/analyzer/src/db/queries/traits.rs new file mode 100644 index 0000000000..285dd4c3b2 --- /dev/null +++ b/crates/analyzer/src/db/queries/traits.rs @@ -0,0 +1,61 @@ +use indexmap::map::Entry; +use indexmap::IndexMap; +use smol_str::SmolStr; + +use crate::context::{Analysis, AnalyzerContext}; +use crate::namespace::items::{FunctionSig, FunctionSigId, TraitId}; +use crate::namespace::scopes::ItemScope; +use crate::namespace::types; +use crate::AnalyzerDb; +use std::rc::Rc; + +pub fn trait_type(db: &dyn AnalyzerDb, trait_: TraitId) -> Rc { + Rc::new(types::Trait { + name: trait_.name(db), + id: trait_, + }) +} + +pub fn trait_all_functions(db: &dyn AnalyzerDb, trait_: TraitId) -> Rc<[FunctionSigId]> { + let trait_data = trait_.data(db); + trait_data + .ast + .kind + .functions + .iter() + .map(|node| { + db.intern_function_sig(Rc::new(FunctionSig { + ast: node.clone(), + module: trait_.module(db), + parent: Some(trait_.as_class()), + })) + }) + .collect() +} + +pub fn trait_function_map( + db: &dyn AnalyzerDb, + trait_: TraitId, +) -> Analysis>> { + let scope = ItemScope::new(db, trait_.module(db)); + let mut map = IndexMap::::new(); + + for func in db.trait_all_functions(trait_).iter() { + let def_name = func.name(db); + + match map.entry(def_name) { + Entry::Occupied(entry) => { + scope.duplicate_name_error( + &format!("duplicate function names in `trait {}`", trait_.name(db)), + entry.key(), + entry.get().name_span(db), + func.name_span(db), + ); + } + Entry::Vacant(entry) => { + entry.insert(*func); + } + } + } + Analysis::new(Rc::new(map), scope.diagnostics.take().into()) +} diff --git a/crates/analyzer/src/namespace/items.rs b/crates/analyzer/src/namespace/items.rs index 8cb18eb6d1..b5421f4faa 100644 --- a/crates/analyzer/src/namespace/items.rs +++ b/crates/analyzer/src/namespace/items.rs @@ -1,4 +1,5 @@ use crate::context; +use fe_common::diagnostics::Label; use crate::context::{Analysis, Constant}; use crate::errors::{self, IncompleteItem, TypeError}; @@ -9,14 +10,18 @@ use crate::{builtins, errors::ConstEvalError}; use fe_common::diagnostics::Diagnostic; use fe_common::files::{common_prefix, Utf8Path}; use fe_common::{impl_intern_key, FileKind, SourceFileId}; +use fe_parser::ast::GenericParameter; use fe_parser::node::{Node, Span}; use fe_parser::{ast, node::NodeId}; use indexmap::{indexmap, IndexMap}; use smol_str::SmolStr; use std::ops::Deref; use std::rc::Rc; + use strum::IntoEnumIterator; +use super::types::{Type, TypeDowncast}; + /// A named item. This does not include things inside of /// a function body. #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Copy)] @@ -469,11 +474,24 @@ impl ModuleId { db.module_is_incomplete(*self) } + pub fn is_in_std(&self, db: &dyn AnalyzerDb) -> bool { + self.ingot(db).name(db) == "std" + } + /// Includes duplicate names pub fn all_items(&self, db: &dyn AnalyzerDb) -> Rc<[Item]> { db.module_all_items(*self) } + /// Includes duplicate names + pub fn all_impls(&self, db: &dyn AnalyzerDb) -> Rc<[ImplId]> { + db.module_all_impls(*self) + } + + pub fn impls(&self, db: &dyn AnalyzerDb) -> Rc> { + db.module_impl_map(*self).value + } + /// Returns a map of the named items in the module pub fn items(&self, db: &dyn AnalyzerDb) -> Rc> { db.module_item_map(*self).value @@ -665,10 +683,17 @@ impl ModuleId { // duplicate item name errors sink.push_all(db.module_item_map(*self).diagnostics.iter()); + // duplicate impl errors + sink.push_all(db.module_impl_map(*self).diagnostics.iter()); + // errors for each item self.all_items(db) .iter() .for_each(|id| id.sink_diagnostics(db, sink)); + + self.all_impls(db) + .iter() + .for_each(|val| val.sink_diagnostics(db, sink)); } #[doc(hidden)] @@ -747,6 +772,7 @@ impl ModuleConstantId { pub enum TypeDef { Alias(TypeAliasId), Struct(StructId), + Trait(TraitId), Contract(ContractId), Primitive(types::Base), } @@ -778,6 +804,7 @@ impl TypeDef { match self { TypeDef::Alias(id) => id.name(db), TypeDef::Struct(id) => id.name(db), + TypeDef::Trait(id) => id.name(db), TypeDef::Contract(id) => id.name(db), TypeDef::Primitive(typ) => typ.name(), } @@ -787,6 +814,7 @@ impl TypeDef { match self { TypeDef::Alias(id) => Some(id.name_span(db)), TypeDef::Struct(id) => Some(id.name_span(db)), + TypeDef::Trait(id) => Some(id.name_span(db)), TypeDef::Contract(id) => Some(id.name_span(db)), TypeDef::Primitive(_) => None, } @@ -800,6 +828,10 @@ impl TypeDef { name: id.name(db), field_count: id.fields(db).len(), // for the EvmSized trait })), + TypeDef::Trait(id) => Ok(types::Type::Trait(types::Trait { + id: *id, + name: id.name(db), + })), TypeDef::Contract(id) => Ok(types::Type::Contract(types::Contract { id: *id, name: id.name(db), @@ -812,6 +844,7 @@ impl TypeDef { match self { Self::Alias(id) => id.is_public(db), Self::Struct(id) => id.is_public(db), + Self::Trait(id) => id.is_public(db), Self::Contract(id) => id.is_public(db), Self::Primitive(_) => true, } @@ -821,6 +854,7 @@ impl TypeDef { match self { TypeDef::Alias(id) => Some(id.parent(db)), TypeDef::Struct(id) => Some(id.parent(db)), + TypeDef::Trait(id) => Some(id.parent(db)), TypeDef::Contract(id) => Some(id.parent(db)), TypeDef::Primitive(_) => None, } @@ -830,6 +864,7 @@ impl TypeDef { match self { TypeDef::Alias(id) => id.sink_diagnostics(db, sink), TypeDef::Struct(id) => id.sink_diagnostics(db, sink), + TypeDef::Trait(id) => id.sink_diagnostics(db, sink), TypeDef::Contract(id) => id.sink_diagnostics(db, sink), TypeDef::Primitive(_) => {} } @@ -1045,33 +1080,30 @@ impl ContractFieldId { } #[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub struct Function { - pub ast: Node, - pub module: ModuleId, +pub struct FunctionSig { + pub ast: Node, pub parent: Option, + pub module: ModuleId, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] -pub struct FunctionId(pub(crate) u32); -impl_intern_key!(FunctionId); -impl FunctionId { - pub fn data(&self, db: &dyn AnalyzerDb) -> Rc { - db.lookup_intern_function(*self) - } - pub fn span(&self, db: &dyn AnalyzerDb) -> Span { - self.data(db).ast.span - } - pub fn name(&self, db: &dyn AnalyzerDb) -> SmolStr { - self.data(db).ast.name().into() - } - pub fn name_span(&self, db: &dyn AnalyzerDb) -> Span { - self.data(db).ast.kind.name.span +pub struct FunctionSigId(pub(crate) u32); +impl_intern_key!(FunctionSigId); + +impl FunctionSigId { + pub fn data(&self, db: &dyn AnalyzerDb) -> Rc { + db.lookup_intern_function_sig(*self) } // This should probably be scrapped in favor of `parent(` pub fn class(&self, db: &dyn AnalyzerDb) -> Option { self.data(db).parent } + + pub fn takes_self(&self, db: &dyn AnalyzerDb) -> bool { + self.signature(db).self_decl.is_some() + } + pub fn self_typ(&self, db: &dyn AnalyzerDb) -> Option { match self.parent(db) { Item::Type(TypeDef::Contract(cid)) => { @@ -1083,20 +1115,6 @@ impl FunctionId { _ => None, } } - pub fn parent(&self, db: &dyn AnalyzerDb) -> Item { - let data = self.data(db); - data.parent - .map(|class| class.as_item()) - .unwrap_or(Item::Module(data.module)) - } - - pub fn module(&self, db: &dyn AnalyzerDb) -> ModuleId { - self.data(db).module - } - - pub fn takes_self(&self, db: &dyn AnalyzerDb) -> bool { - self.signature(db).self_decl.is_some() - } pub fn self_span(&self, db: &dyn AnalyzerDb) -> Option { if self.takes_self(db) { self.data(db) @@ -1109,9 +1127,21 @@ impl FunctionId { None } } + pub fn signature(&self, db: &dyn AnalyzerDb) -> Rc { + db.function_signature(*self).value + } pub fn is_public(&self, db: &dyn AnalyzerDb) -> bool { - self.pub_span(db).is_some() + self.is_trait_fn(db) || self.is_impl_fn(db) || self.pub_span(db).is_some() + } + pub fn name(&self, db: &dyn AnalyzerDb) -> SmolStr { + self.data(db).ast.kind.name.kind.clone() + } + pub fn name_span(&self, db: &dyn AnalyzerDb) -> Span { + self.data(db).ast.kind.name.span + } + pub fn unsafe_span(&self, db: &dyn AnalyzerDb) -> Option { + self.data(db).ast.kind.unsafe_ } pub fn is_constructor(&self, db: &dyn AnalyzerDb) -> bool { self.name(db) == "__init__" @@ -1119,14 +1149,134 @@ impl FunctionId { pub fn pub_span(&self, db: &dyn AnalyzerDb) -> Option { self.data(db).ast.kind.pub_ } + + pub fn parent(&self, db: &dyn AnalyzerDb) -> Item { + let data = self.data(db); + data.parent + .map(|class| class.as_item(db)) + .unwrap_or(Item::Module(data.module)) + } + + pub fn is_trait_fn(&self, db: &dyn AnalyzerDb) -> bool { + matches!(self.parent(db), Item::Type(TypeDef::Trait(_))) + } + + pub fn is_impl_fn(&self, db: &dyn AnalyzerDb) -> bool { + matches!(self.class(db), Some(Class::Impl(_))) + } + + pub fn is_generic(&self, db: &dyn AnalyzerDb) -> bool { + !self.data(db).ast.kind.generic_params.kind.is_empty() + } + + pub fn generic_params(&self, db: &dyn AnalyzerDb) -> Vec { + self.data(db).ast.kind.generic_params.kind.clone() + } + + pub fn generic_param(&self, db: &dyn AnalyzerDb, param_name: &str) -> Option { + self.generic_params(db) + .iter() + .find(|param| match param { + GenericParameter::Unbounded(name) if name.kind == param_name => true, + GenericParameter::Bounded { name, .. } if name.kind == param_name => true, + _ => false, + }) + .cloned() + } + + pub fn module(&self, db: &dyn AnalyzerDb) -> ModuleId { + self.data(db).module + } + + pub fn is_contract_func(self, db: &dyn AnalyzerDb) -> bool { + matches! {self.parent(db), Item::Type(TypeDef::Contract(_))} + } + + pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) { + sink.push_all(db.function_signature(*self).diagnostics.iter()); + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct Function { + pub ast: Node, + pub sig: FunctionSigId, +} + +impl Function { + pub fn new( + db: &dyn AnalyzerDb, + ast: &Node, + parent: Option, + module: ModuleId, + ) -> Self { + let sig = db.intern_function_sig(Rc::new(FunctionSig { + ast: ast.kind.sig.clone(), + parent, + module, + })); + Function { + sig, + ast: ast.clone(), + } + } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] +pub struct FunctionId(pub(crate) u32); +impl_intern_key!(FunctionId); +impl FunctionId { + pub fn data(&self, db: &dyn AnalyzerDb) -> Rc { + db.lookup_intern_function(*self) + } + pub fn span(&self, db: &dyn AnalyzerDb) -> Span { + self.data(db).ast.span + } + pub fn name(&self, db: &dyn AnalyzerDb) -> SmolStr { + self.sig(db).name(db) + } + pub fn name_span(&self, db: &dyn AnalyzerDb) -> Span { + self.sig(db).name_span(db) + } + pub fn module(&self, db: &dyn AnalyzerDb) -> ModuleId { + self.sig(db).module(db) + } + // This should probably be scrapped in favor of `parent(` + pub fn class(&self, db: &dyn AnalyzerDb) -> Option { + self.sig(db).class(db) + } + pub fn self_typ(&self, db: &dyn AnalyzerDb) -> Option { + self.sig(db).self_typ(db) + } + pub fn parent(&self, db: &dyn AnalyzerDb) -> Item { + self.sig(db).parent(db) + } + pub fn takes_self(&self, db: &dyn AnalyzerDb) -> bool { + self.sig(db).takes_self(db) + } + pub fn self_span(&self, db: &dyn AnalyzerDb) -> Option { + self.sig(db).self_span(db) + } + pub fn is_generic(&self, db: &dyn AnalyzerDb) -> bool { + self.sig(db).is_generic(db) + } + pub fn is_public(&self, db: &dyn AnalyzerDb) -> bool { + self.sig(db).is_public(db) + } + pub fn is_constructor(&self, db: &dyn AnalyzerDb) -> bool { + self.sig(db).is_constructor(db) + } pub fn is_unsafe(&self, db: &dyn AnalyzerDb) -> bool { self.unsafe_span(db).is_some() } pub fn unsafe_span(&self, db: &dyn AnalyzerDb) -> Option { - self.data(db).ast.kind.unsafe_ + self.sig(db).unsafe_span(db) } pub fn signature(&self, db: &dyn AnalyzerDb) -> Rc { - db.function_signature(*self).value + db.function_signature(self.data(db).sig).value + } + pub fn sig(&self, db: &dyn AnalyzerDb) -> FunctionSigId { + self.data(db).sig } pub fn body(&self, db: &dyn AnalyzerDb) -> Rc { db.function_body(*self).value @@ -1135,12 +1285,11 @@ impl FunctionId { db.function_dependency_graph(*self).0 } pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) { - sink.push_all(db.function_signature(*self).diagnostics.iter()); + sink.push_all(db.function_signature(self.data(db).sig).diagnostics.iter()); sink.push_all(db.function_body(*self).diagnostics.iter()); } - pub fn is_contract_func(self, db: &dyn AnalyzerDb) -> bool { - matches! {self.parent(db), Item::Type(TypeDef::Contract(_))} + self.sig(db).is_contract_func(db) } } @@ -1149,16 +1298,29 @@ impl FunctionId { pub enum Class { Contract(ContractId), Struct(StructId), + Trait(TraitId), + Impl(ImplId), } impl Class { pub fn function(&self, db: &dyn AnalyzerDb, name: &str) -> Option { match self { Class::Contract(id) => id.function(db, name), Class::Struct(id) => id.function(db, name), + Class::Impl(id) => id.function(db, name), + // This would possibly change when in the future trait methods can provide a default implementation + Class::Trait(_) => None, } } - pub fn self_function(&self, db: &dyn AnalyzerDb, name: &str) -> Option { - let fun = self.function(db, name)?; + pub fn function_sig(&self, db: &dyn AnalyzerDb, name: &str) -> Option { + match self { + Class::Contract(id) => id.function(db, name).map(|fun| fun.sig(db)), + Class::Struct(id) => id.function(db, name).map(|fun| fun.sig(db)), + Class::Impl(id) => id.function(db, name).map(|fun| fun.sig(db)), + Class::Trait(id) => id.function(db, name), + } + } + pub fn self_function(&self, db: &dyn AnalyzerDb, name: &str) -> Option { + let fun = self.function_sig(db, name)?; fun.takes_self(db).then(|| fun) } @@ -1166,18 +1328,30 @@ impl Class { match self { Class::Contract(inner) => inner.name(db), Class::Struct(inner) => inner.name(db), + Class::Trait(inner) => inner.name(db), + Class::Impl(inner) => inner.name(db), } } pub fn kind(&self) -> &str { match self { Class::Contract(_) => "contract", Class::Struct(_) => "struct", + Class::Trait(_) => "trait", + Class::Impl(_) => "impl", } } - pub fn as_item(&self) -> Item { + pub fn as_item(&self, db: &dyn AnalyzerDb) -> Item { match self { Class::Contract(id) => Item::Type(TypeDef::Contract(*id)), Class::Struct(id) => Item::Type(TypeDef::Struct(*id)), + Class::Trait(id) => Item::Type(TypeDef::Trait(*id)), + // Coercing into an Item of the basis of the receiver doesn't seem ideal but can hopefully + // be addressed when we get rid of `Class`. + Class::Impl(id) => id + .receiver(db) + .as_struct() + .map(|val| Item::Type(TypeDef::Struct(val.id))) + .expect("missing receiver"), } } } @@ -1213,6 +1387,13 @@ impl StructId { self.data(db).module } + pub fn get_impl_for(&self, db: &dyn AnalyzerDb, trait_: TraitId) -> Option { + self.module(db) + .impls(db) + .get(&(trait_, Type::Struct((*self.typ(db)).clone()))) + .cloned() + } + pub fn typ(&self, db: &dyn AnalyzerDb) -> Rc { db.struct_type(*self) } @@ -1324,6 +1505,308 @@ impl StructFieldId { } } +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct Impl { + pub trait_id: TraitId, + pub receiver: Type, + pub module: ModuleId, + pub ast: Node, +} + +#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] +pub struct ImplId(pub(crate) u32); +impl_intern_key!(ImplId); +impl ImplId { + pub fn data(&self, db: &dyn AnalyzerDb) -> Rc { + db.lookup_intern_impl(*self) + } + pub fn span(&self, db: &dyn AnalyzerDb) -> Span { + self.data(db).ast.span + } + + pub fn module(&self, db: &dyn AnalyzerDb) -> ModuleId { + self.data(db).module + } + + pub fn all_functions(&self, db: &dyn AnalyzerDb) -> Rc<[FunctionId]> { + db.impl_all_functions(*self) + } + + pub fn trait_id(&self, db: &dyn AnalyzerDb) -> TraitId { + self.data(db).trait_id + } + + pub fn receiver(&self, db: &dyn AnalyzerDb) -> Type { + self.data(db).receiver.clone() + } + + pub fn ast(&self, db: &dyn AnalyzerDb) -> Node { + self.data(db).ast.clone() + } + + pub fn functions(&self, db: &dyn AnalyzerDb) -> Rc> { + db.impl_function_map(*self).value + } + pub fn function(&self, db: &dyn AnalyzerDb, name: &str) -> Option { + self.functions(db).get(name).copied() + } + pub fn parent(&self, db: &dyn AnalyzerDb) -> Item { + Item::Module(self.data(db).module) + } + pub fn name(&self, db: &dyn AnalyzerDb) -> SmolStr { + format!( + "{}_{}", + self.trait_id(db).name(db), + self.receiver(db).name() + ) + .into() + } + + fn validate_type_or_trait_is_in_ingot( + &self, + db: &dyn AnalyzerDb, + sink: &mut impl DiagnosticSink, + type_module: Option, + ) { + let impl_module = self.data(db).module; + let is_allowed = match type_module { + None => impl_module == self.data(db).trait_id.module(db), + Some(val) => val == impl_module || self.data(db).trait_id.module(db) == impl_module, + }; + + if !is_allowed { + sink.push(&errors::fancy_error( + "illegal `impl`. Either type or trait must be in the same ingot as the `impl`", + vec![Label::primary( + self.data(db).ast.span, + format!( + "Neither `{}` nor `{}` are in the ingot of the `impl` block", + self.data(db).receiver, + self.data(db).trait_id.name(db) + ), + )], + vec![], + )) + } + } + + pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) { + match &self.data(db).receiver { + Type::Contract(_) + | Type::Map(_) + | Type::SelfContract(_) + | Type::Trait(_) + | Type::Generic(_) + // TODO: We should find a way to support these types. We only support implementing traits for structs + // so far because it simplifies things regarding the assign location of the underlying type. + | Type::Base(_) + | Type::Array(_) + | Type::Tuple(_) + | Type::String(_) => sink.push(&errors::fancy_error( + format!( + "`impl` blocks aren't allowed for {}", + self.data(db).receiver + ), + vec![Label::primary( + self.data(db).ast.span, + "illegal `impl` block", + )], + vec![], + )), + Type::Struct(val) => { + self.validate_type_or_trait_is_in_ingot(db, sink, Some(val.id.module(db))) + } + } + + if !self.trait_id(db).is_public(db) && self.trait_id(db).module(db) != self.module(db) { + let trait_module_name = self.trait_id(db).module(db).name(db); + let trait_name = self.trait_id(db).name(db); + sink.push(&errors::fancy_error( + &format!( + "the trait `{}` is private", + trait_name, + ), + vec![ + Label::primary(self.trait_id(db).data(db).ast.kind.name.span, "this trait is not `pub`"), + ], + vec![ + format!("`{}` can only be used within `{}`", trait_name, trait_module_name), + format!("Hint: use `pub trait {trait_}` to make `{trait_}` visible from outside of `{module}`", trait_=trait_name, module=trait_module_name), + ], + )); + } + + for impl_fn in self.all_functions(db).iter() { + impl_fn.sink_diagnostics(db, sink); + + if let Some(trait_fn) = self.trait_id(db).function(db, &impl_fn.name(db)) { + if impl_fn.signature(db).params != trait_fn.signature(db).params { + // TODO: This could be a nicer, more detailed report + sink.push(&errors::fancy_error( + format!( + "method `{}` has incompatible parameters for `{}` of trait `{}`", + impl_fn.name(db), + trait_fn.name(db), + self.trait_id(db).name(db) + ), + vec![ + Label::primary( + impl_fn.data(db).ast.kind.sig.span, + "signature of method in `impl` block", + ), + Label::primary( + trait_fn.data(db).ast.span, + format!( + "signature of method in trait `{}`", + self.trait_id(db).name(db) + ), + ), + ], + vec![], + )); + } + + if impl_fn.signature(db).return_type != trait_fn.signature(db).return_type { + // TODO: This could be a nicer, more detailed report + sink.push(&errors::fancy_error( + format!( + "method `{}` has an incompatible return type for `{}` of trait `{}`", + impl_fn.name(db), + trait_fn.name(db), + self.trait_id(db).name(db) + ), + vec![ + Label::primary( + impl_fn.data(db).ast.kind.sig.span, + "signature of method in `impl` block", + ), + Label::primary( + trait_fn.data(db).ast.span, + format!( + "signature of method in trait `{}`", + self.trait_id(db).name(db) + ), + ), + ], + vec![], + )); + } + } else { + sink.push(&errors::fancy_error( + format!( + "method `{}` is not a member of trait `{}`", + impl_fn.name(db), + self.trait_id(db).name(db) + ), + vec![Label::primary( + impl_fn.data(db).ast.span, + format!("not a member of trait `{}`", self.trait_id(db).name(db)), + )], + vec![], + )) + } + } + + for trait_fn in self.trait_id(db).all_functions(db).iter() { + if self.function(db, &trait_fn.name(db)).is_none() { + sink.push(&errors::fancy_error( + format!( + "not all members of trait `{}` implemented, missing: `{}`", + self.trait_id(db).name(db), + trait_fn.name(db) + ), + vec![Label::primary( + trait_fn.data(db).ast.span, + "this trait function is missing in `impl` block", + )], + vec![], + )) + } + } + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct Trait { + pub ast: Node, + pub module: ModuleId, +} + +#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] +pub struct TraitId(pub(crate) u32); +impl_intern_key!(TraitId); +impl TraitId { + pub fn data(&self, db: &dyn AnalyzerDb) -> Rc { + db.lookup_intern_trait(*self) + } + pub fn span(&self, db: &dyn AnalyzerDb) -> Span { + self.data(db).ast.span + } + pub fn name(&self, db: &dyn AnalyzerDb) -> SmolStr { + self.data(db).ast.name().into() + } + pub fn name_span(&self, db: &dyn AnalyzerDb) -> Span { + self.data(db).ast.kind.name.span + } + + pub fn is_public(&self, db: &dyn AnalyzerDb) -> bool { + self.data(db).ast.kind.pub_qual.is_some() + } + + pub fn module(&self, db: &dyn AnalyzerDb) -> ModuleId { + self.data(db).module + } + + pub fn is_implemented_for(&self, db: &dyn AnalyzerDb, ty: &Type) -> bool { + self.module(db) + .all_impls(db) + .iter() + .any(|val| &val.trait_id(db) == self && &val.receiver(db) == ty) + } + + pub fn typ(&self, db: &dyn AnalyzerDb) -> Rc { + db.trait_type(*self) + } + pub fn is_in_std(&self, db: &dyn AnalyzerDb) -> bool { + self.module(db).is_in_std(db) + } + + pub fn parent(&self, db: &dyn AnalyzerDb) -> Item { + Item::Module(self.data(db).module) + } + pub fn all_functions(&self, db: &dyn AnalyzerDb) -> Rc<[FunctionSigId]> { + db.trait_all_functions(*self) + } + + pub fn functions(&self, db: &dyn AnalyzerDb) -> Rc> { + db.trait_function_map(*self).value + } + + pub fn function(&self, db: &dyn AnalyzerDb, name: &str) -> Option { + self.functions(db).get(name).copied() + } + + pub fn as_class(&self) -> Class { + Class::Trait(*self) + } + + pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) { + db.trait_all_functions(*self).iter().for_each(|id| { + if !id.takes_self(db) { + sink.push(&errors::fancy_error( + "associated functions aren't yet supported in traits", + vec![Label::primary( + id.data(db).ast.span, + "function doesn't take `self`", + )], + vec!["Hint: add a `self` param to this function".into()], + )); + } + id.sink_diagnostics(db, sink) + }); + } +} + #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct Event { pub ast: Node, diff --git a/crates/analyzer/src/namespace/scopes.rs b/crates/analyzer/src/namespace/scopes.rs index 48f03ece9c..cdf4573ffc 100644 --- a/crates/analyzer/src/namespace/scopes.rs +++ b/crates/analyzer/src/namespace/scopes.rs @@ -309,6 +309,8 @@ impl<'a> AnalyzerContext for FunctionScope<'a> { .data(self.db) .ast .kind + .sig + .kind .args .iter() .find_map(|param| (param.name() == name).then(|| param.name_span())) diff --git a/crates/analyzer/src/namespace/types.rs b/crates/analyzer/src/namespace/types.rs index 4ce27b9fc8..0d9a70b51a 100644 --- a/crates/analyzer/src/namespace/types.rs +++ b/crates/analyzer/src/namespace/types.rs @@ -1,6 +1,6 @@ use crate::context::AnalyzerContext; use crate::errors::TypeError; -use crate::namespace::items::{Class, ContractId, StructId}; +use crate::namespace::items::{Class, ContractId, StructId, TraitId}; use crate::AnalyzerDb; use fe_common::Span; @@ -47,6 +47,8 @@ pub enum Type { /// of `self` within a contract function. SelfContract(Contract), Struct(Struct), + Trait(Trait), + Generic(Generic), } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -125,6 +127,26 @@ impl Struct { } } +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct Trait { + pub name: SmolStr, + pub id: TraitId, +} +impl Trait { + pub fn from_id(id: TraitId, db: &dyn AnalyzerDb) -> Self { + Self { + name: id.name(db), + id, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Generic { + pub name: SmolStr, + pub bounds: Vec, +} + #[derive(Clone, PartialEq, Eq, Hash)] pub struct Contract { pub name: SmolStr, @@ -388,6 +410,8 @@ impl Type { Type::Tuple(inner) => inner.to_string().into(), Type::String(inner) => inner.to_string().into(), Type::Struct(inner) => inner.name.clone(), + Type::Trait(inner) => inner.name.clone(), + Type::Generic(inner) => inner.name.clone(), Type::Contract(inner) | Type::SelfContract(inner) => inner.name.clone(), } } @@ -456,8 +480,9 @@ impl Type { | Type::Tuple(_) | Type::String(_) | Type::Struct(_) + | Type::Generic(_) | Type::Contract(_) => true, - Type::Map(_) | Type::SelfContract(_) => false, + Type::Map(_) | Type::SelfContract(_) | Type::Trait(_) => false, } } } @@ -470,6 +495,8 @@ pub trait TypeDowncast { fn as_int(&self) -> Option; fn as_primitive(&self) -> Option; fn as_class(&self) -> Option; + fn as_generic(&self) -> Option; + fn as_struct(&self) -> Option; } impl TypeDowncast for Type { @@ -513,6 +540,22 @@ impl TypeDowncast for Type { match self { Type::Struct(inner) => Some(Class::Struct(inner.id)), Type::Contract(inner) | Type::SelfContract(inner) => Some(Class::Contract(inner.id)), + Type::Generic(inner) if !inner.bounds.is_empty() => { + // FIXME: This won't hold when we support multiple bounds or traits can be implemented for non-struct types + inner.bounds.first().map(TraitId::as_class) + } + _ => None, + } + } + fn as_struct(&self) -> Option { + match self { + Type::Struct(val) => Some(val.clone()), + _ => None, + } + } + fn as_generic(&self) -> Option { + match self { + Type::Generic(generic) => Some(generic.clone()), _ => None, } } @@ -540,6 +583,12 @@ impl TypeDowncast for Option<&Type> { fn as_class(&self) -> Option { self.and_then(TypeDowncast::as_class) } + fn as_struct(&self) -> Option { + self.and_then(TypeDowncast::as_struct) + } + fn as_generic(&self) -> Option { + self.and_then(TypeDowncast::as_generic) + } } impl From for Type { @@ -637,6 +686,8 @@ impl fmt::Display for Type { Type::Contract(inner) => inner.fmt(f), Type::SelfContract(inner) => inner.fmt(f), Type::Struct(inner) => inner.fmt(f), + Type::Trait(inner) => inner.fmt(f), + Type::Generic(inner) => inner.fmt(f), } } } @@ -730,6 +781,23 @@ impl fmt::Debug for Struct { } } +impl fmt::Display for Trait { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name) + } +} +impl fmt::Debug for Trait { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Struct").field("name", &self.name).finish() + } +} + +impl fmt::Display for Generic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name) + } +} + impl FromStr for Base { type Err = strum::ParseError; diff --git a/crates/analyzer/src/operations.rs b/crates/analyzer/src/operations.rs index ea86687a17..b8251ee90d 100644 --- a/crates/analyzer/src/operations.rs +++ b/crates/analyzer/src/operations.rs @@ -15,6 +15,8 @@ pub fn index(value: Type, index: Type) -> Result { | Type::String(_) | Type::Contract(_) | Type::SelfContract(_) + | Type::Trait(_) + | Type::Generic(_) | Type::Struct(_) => Err(IndexingError::NotSubscriptable), } } diff --git a/crates/analyzer/src/traversal/call_args.rs b/crates/analyzer/src/traversal/call_args.rs index 9238a0a045..b89b69a8a9 100644 --- a/crates/analyzer/src/traversal/call_args.rs +++ b/crates/analyzer/src/traversal/call_args.rs @@ -1,6 +1,6 @@ use crate::context::{AnalyzerContext, DiagnosticVoucher}; use crate::errors::{FatalError, TypeError}; -use crate::namespace::types::{EventField, FunctionParam, Type}; +use crate::namespace::types::{EventField, FunctionParam, Generic, Type}; use crate::traversal::expressions::assignable_expr; use fe_common::{diagnostics::Label, utils::humanize::pluralize_conditionally}; use fe_common::{Span, Spanned}; @@ -151,7 +151,7 @@ pub fn validate_named_args( let param_type = param.typ()?; let val_attrs = assignable_expr(context, &arg.kind.value, Some(¶m_type.clone()))?; - if param_type != val_attrs.typ { + if !validate_arg_type(context, arg, &val_attrs.typ, ¶m_type) { let msg = if let Some(label) = param.label() { format!("incorrect type for `{}` argument `{}`", name, label) } else { @@ -165,3 +165,35 @@ pub fn validate_named_args( } Ok(()) } + +fn validate_arg_type( + context: &mut dyn AnalyzerContext, + arg: &Node, + arg_type: &Type, + param_type: &Type, +) -> bool { + if let Type::Generic(Generic { bounds, .. }) = param_type { + for bound in bounds { + if !bound.is_implemented_for(context.db(), arg_type) { + context.error( + &format!( + "the trait bound `{}: {}` is not satisfied", + arg_type, + bound.name(context.db()) + ), + arg.span, + &format!( + "the trait `{}` is not implemented for `{}`", + bound.name(context.db()), + arg_type + ), + ); + + return false; + } + } + true + } else { + arg_type == param_type + } +} diff --git a/crates/analyzer/src/traversal/expressions.rs b/crates/analyzer/src/traversal/expressions.rs index fa2a01f553..6f2783a312 100644 --- a/crates/analyzer/src/traversal/expressions.rs +++ b/crates/analyzer/src/traversal/expressions.rs @@ -3,6 +3,7 @@ use crate::context::{AnalyzerContext, CallType, ExpressionAttributes, Location, use crate::errors::{FatalError, IndexingError}; use crate::namespace::items::{Class, FunctionId, Item, TypeDef}; use crate::namespace::scopes::BlockScopeType; +use crate::namespace::types::Trait; use crate::namespace::types::{ Array, Base, Contract, FeString, Integer, Struct, Tuple, Type, TypeDowncast, U256, }; @@ -178,7 +179,7 @@ pub fn assignable_expr( attributes.move_location = Some(Location::Value); } } - Array(_) | Tuple(_) | String(_) | Struct(_) => { + Array(_) | Tuple(_) | String(_) | Struct(_) | Trait(_) | Generic(_) => { if attributes.final_location() != Location::Memory { context.fancy_error( "value must be copied to memory", @@ -313,6 +314,14 @@ fn expr_named_thing( Type::Struct(Struct::from_id(id, context.db())), Location::Memory, )), + Class::Impl(id) => Ok(ExpressionAttributes::new( + id.receiver(context.db()), + Location::Memory, + )), + Class::Trait(id) => Ok(ExpressionAttributes::new( + Type::Trait(Trait::from_id(id, context.db())), + Location::Memory, + )), Class::Contract(id) => Ok(ExpressionAttributes::new( Type::SelfContract(Contract::from_id(id, context.db())), Location::Value, @@ -1120,27 +1129,18 @@ fn expr_call_type_constructor( Type::Struct(struct_type) => { return expr_call_struct_constructor(context, name_span, struct_type, args) } - Type::Base(Base::Bool) => { - return Err(FatalError::new(context.error( - "`bool` type is not callable", - name_span, - "", - ))) - } - Type::Map(_) => { + Type::Base(Base::Bool) + | Type::Array(_) + | Type::Map(_) + | Type::Generic(_) + | Type::Trait(_) => { return Err(FatalError::new(context.error( - "`Map` type is not callable", - name_span, - "", - ))) - } - Type::Array(_) => { - return Err(FatalError::new(context.error( - "`Array` type is not callable", + &format!("`{}` type is not callable", typ.name()), name_span, "", ))) } + _ => {} } @@ -1220,6 +1220,8 @@ fn expr_call_type_constructor( Type::Struct(_) => unreachable!(), // handled above Type::Map(_) => unreachable!(), // handled above Type::Array(_) => unreachable!(), // handled above + Type::Trait(_) => unreachable!(), // handled above + Type::Generic(_) => unreachable!(), // handled above Type::SelfContract(_) => unreachable!(), /* unnameable; contract names all become * Type::Contract */ }; @@ -1316,7 +1318,7 @@ fn expr_call_method( if matches!(class, Class::Contract(_)) { check_for_call_to_special_fns(context, &field.kind, field.span)?; } - if let Some(method) = class.function(context.db(), &field.kind) { + if let Some(method) = class.function_sig(context.db(), &field.kind) { let is_self = is_self_value(target); if is_self && !method.takes_self(context.db()) { @@ -1352,12 +1354,11 @@ fn expr_call_method( let calltype = match class { Class::Contract(contract) => { + let method = contract + .function(context.db(), &method.name(context.db())) + .unwrap(); if is_self { - CallType::ValueMethod { - is_self, - class, - method, - } + CallType::ValueMethod { class, method } } else { // External contract address must be loaded onto the stack. context.update_expression( @@ -1373,7 +1374,10 @@ fn expr_call_method( } } } - Class::Struct(_) => { + Class::Struct(val) => { + let method = val + .function(context.db(), &method.name(context.db())) + .unwrap(); if matches!(target_attributes.final_location(), Location::Storage { .. }) { context.fancy_error( "struct functions can only be called on structs in memory", @@ -1387,12 +1391,22 @@ fn expr_call_method( vec![], ); } - CallType::ValueMethod { - is_self, - class, - method, - } + CallType::ValueMethod { class, method } + } + Class::Impl(val) => { + let method = val + .function(context.db(), &method.name(context.db())) + .unwrap(); + CallType::ValueMethod { class, method } } + Class::Trait(trait_id) => CallType::TraitValueMethod { + trait_id, + method, + generic_type: target_attributes + .typ + .as_generic() + .expect("expected generic type"), + }, }; let return_type = sig.return_type.clone()?; diff --git a/crates/analyzer/tests/analysis.rs b/crates/analyzer/tests/analysis.rs index 439d83ea14..2fcf9772dc 100644 --- a/crates/analyzer/tests/analysis.rs +++ b/crates/analyzer/tests/analysis.rs @@ -345,7 +345,13 @@ fn build_snapshot(db: &dyn AnalyzerDb, module: items::ModuleId) -> String { .collect(), ] .concat(), - + Item::Type(TypeDef::Trait(val)) => val + .all_functions(db) + .iter() + .flat_map(|fun| { + build_debug_diagnostics(&[(fun.data(db).ast.span, &fun.signature(db))]) + }) + .collect::>(), Item::Function(id) => function_diagnostics(*id, db), Item::Constant(id) => vec![build_display_diagnostic(id.span(db), &id.typ(db).unwrap())], Item::Event(id) => event_diagnostics(*id, db), diff --git a/crates/analyzer/tests/errors.rs b/crates/analyzer/tests/errors.rs index 38b5e7b466..00d5793db3 100644 --- a/crates/analyzer/tests/errors.rs +++ b/crates/analyzer/tests/errors.rs @@ -215,6 +215,7 @@ test_file! { bad_string } test_file! { bad_tuple_attr1 } test_file! { bad_tuple_attr2 } test_file! { bad_tuple_attr3 } +test_file! { call_generic_function_with_unsatisfied_bound} test_file! { call_builtin_object } test_file! { call_create_with_wrong_type } test_file! { call_create2_with_wrong_type } @@ -243,14 +244,19 @@ test_file! { duplicate_typedef_in_module } test_file! { duplicate_var_in_child_scope } test_file! { duplicate_var_in_contract_method } test_file! { duplicate_var_in_for_loop } +test_file! { duplicate_generic_params } test_file! { emit_bad_args } test_file! { external_call_type_error } test_file! { external_call_wrong_number_of_params } +test_file! { contract_function_with_generic_params } test_file! { indexed_event } test_file! { invalid_compiler_version } test_file! { invalid_block_field } test_file! { invalid_chain_field } test_file! { invalid_contract_field } +test_file! { invalid_generic_bound } +test_file! { invalid_impl_type } +test_file! { invalid_impl_location } test_file! { invalid_msg_field } test_file! { invalid_string_field } test_file! { invalid_struct_field } @@ -288,6 +294,12 @@ test_file! { struct_private_constructor } test_file! { struct_call_bad_args } test_file! { struct_call_without_kw_args } test_file! { struct_recursive_cycles } +test_file! { trait_impl_mismatch } +test_file! { trait_fn_without_self } +test_file! { trait_fn_with_generic_params } +test_file! { traits_as_fields } +test_file! { trait_conflicting_impls } +test_file! { traits_with_wrong_bounds } test_file! { non_pub_init } test_file! { init_wrong_return_type } test_file! { init_duplicate_def } diff --git a/crates/analyzer/tests/snapshots/errors__bad_visibility.snap b/crates/analyzer/tests/snapshots/errors__bad_visibility.snap index 1470def937..27fd67801f 100644 --- a/crates/analyzer/tests/snapshots/errors__bad_visibility.snap +++ b/crates/analyzer/tests/snapshots/errors__bad_visibility.snap @@ -4,9 +4,9 @@ expression: error_string_ingot(&path) --- error: the type `MyInt` is private - ┌─ compile_errors/bad_visibility/src/main.fe:5:33 + ┌─ compile_errors/bad_visibility/src/main.fe:8:33 │ -5 │ pub fn priv_type_alias() -> MyInt { +8 │ pub fn priv_type_alias() -> MyInt { │ ^^^^^ this type is not `pub` │ ┌─ compile_errors/bad_visibility/src/foo.fe:1:6 @@ -18,9 +18,9 @@ error: the type `MyInt` is private = Hint: use `pub` to make `MyInt` visible from outside of `foo` error: the type `MyInt` is private - ┌─ compile_errors/bad_visibility/src/main.fe:6:16 + ┌─ compile_errors/bad_visibility/src/main.fe:9:16 │ -6 │ let x: MyInt = 1 +9 │ let x: MyInt = 1 │ ^^^^^ this type is not `pub` │ ┌─ compile_errors/bad_visibility/src/foo.fe:1:6 @@ -32,9 +32,9 @@ error: the type `MyInt` is private = Hint: use `pub` to make `MyInt` visible from outside of `foo` error: the constant `MY_CONST` is private - ┌─ compile_errors/bad_visibility/src/main.fe:11:16 + ┌─ compile_errors/bad_visibility/src/main.fe:14:16 │ -11 │ return MY_CONST +14 │ return MY_CONST │ ^^^^^^^^ this constant is not `pub` │ ┌─ compile_errors/bad_visibility/src/foo.fe:3:7 @@ -46,9 +46,9 @@ error: the constant `MY_CONST` is private = Hint: use `pub` to make `MY_CONST` visible from outside of `foo` error: the constant `MY_CONST` is private - ┌─ compile_errors/bad_visibility/src/main.fe:11:16 + ┌─ compile_errors/bad_visibility/src/main.fe:14:16 │ -11 │ return MY_CONST +14 │ return MY_CONST │ ^^^^^^^^ this constant is not `pub` │ ┌─ compile_errors/bad_visibility/src/foo.fe:3:1 @@ -60,131 +60,140 @@ error: the constant `MY_CONST` is private = Hint: use `pub const MY_CONST` to make `MY_CONST` visible from outside of `foo` error: the event `MyEvent` is private - ┌─ compile_errors/bad_visibility/src/main.fe:15:14 + ┌─ compile_errors/bad_visibility/src/main.fe:18:14 │ -15 │ emit MyEvent(ctx, x: 1) +18 │ emit MyEvent(ctx, x: 1) │ ^^^^^^^ this event is not `pub` │ - ┌─ compile_errors/bad_visibility/src/foo.fe:5:7 + ┌─ compile_errors/bad_visibility/src/foo.fe:7:7 │ - 5 │ event MyEvent { + 7 │ event MyEvent { │ ------- `MyEvent` is defined here │ = `MyEvent` can only be used within `foo` = Hint: use `pub` to make `MyEvent` visible from outside of `foo` error: the event `MyEvent` is private - ┌─ compile_errors/bad_visibility/src/main.fe:15:14 + ┌─ compile_errors/bad_visibility/src/main.fe:18:14 │ -15 │ emit MyEvent(ctx, x: 1) +18 │ emit MyEvent(ctx, x: 1) │ ^^^^^^^ this event is not `pub` │ - ┌─ compile_errors/bad_visibility/src/foo.fe:5:1 + ┌─ compile_errors/bad_visibility/src/foo.fe:7:1 │ - 5 │ â•­ event MyEvent { - 6 │ │ x: i32 - 7 │ │ } + 7 │ â•­ event MyEvent { + 8 │ │ x: i32 + 9 │ │ } │ ╰─' `MyEvent` is defined here │ = `MyEvent` can only be used within `foo` = Hint: use `pub event MyEvent` to make `MyEvent` visible from outside of `foo` error: cannot find value `ctx` in this scope - ┌─ compile_errors/bad_visibility/src/main.fe:15:22 + ┌─ compile_errors/bad_visibility/src/main.fe:18:22 │ -15 │ emit MyEvent(ctx, x: 1) +18 │ emit MyEvent(ctx, x: 1) │ ^^^ undefined error: the type `MyStruct` is private - ┌─ compile_errors/bad_visibility/src/main.fe:19:16 + ┌─ compile_errors/bad_visibility/src/main.fe:22:16 │ -19 │ let s: MyStruct = MyStruct(x: 1) +22 │ let s: MyStruct = MyStruct(x: 1) │ ^^^^^^^^ this type is not `pub` │ - ┌─ compile_errors/bad_visibility/src/foo.fe:9:8 + ┌─ compile_errors/bad_visibility/src/foo.fe:11:8 │ - 9 │ struct MyStruct { +11 │ struct MyStruct { │ -------- `MyStruct` is defined here │ = `MyStruct` can only be used within `foo` = Hint: use `pub` to make `MyStruct` visible from outside of `foo` error: the type `MyStruct` is private - ┌─ compile_errors/bad_visibility/src/main.fe:19:27 + ┌─ compile_errors/bad_visibility/src/main.fe:22:27 │ -19 │ let s: MyStruct = MyStruct(x: 1) +22 │ let s: MyStruct = MyStruct(x: 1) │ ^^^^^^^^ this type is not `pub` │ - ┌─ compile_errors/bad_visibility/src/foo.fe:9:8 + ┌─ compile_errors/bad_visibility/src/foo.fe:11:8 │ - 9 │ struct MyStruct { +11 │ struct MyStruct { │ -------- `MyStruct` is defined here │ = `MyStruct` can only be used within `foo` = Hint: use `pub` to make `MyStruct` visible from outside of `foo` error: Can not call private constructor of struct `MyStruct` - ┌─ compile_errors/bad_visibility/src/foo.fe:10:5 + ┌─ compile_errors/bad_visibility/src/foo.fe:12:5 │ -10 │ x: i32 +12 │ x: i32 │ ^^^^^^ Field `x` is private │ = Suggestion: implement a method `new(...)` on struct `MyStruct` to call the constructor and return the struct error: the function `my_func` is private - ┌─ compile_errors/bad_visibility/src/main.fe:23:9 + ┌─ compile_errors/bad_visibility/src/main.fe:26:9 │ -23 │ my_func() +26 │ my_func() │ ^^^^^^^ this function is not `pub` │ - ┌─ compile_errors/bad_visibility/src/foo.fe:13:4 + ┌─ compile_errors/bad_visibility/src/foo.fe:15:4 │ -13 │ fn my_func() {} +15 │ fn my_func() {} │ ------- `my_func` is defined here │ = `my_func` can only be used within `foo` = Hint: use `pub` to make `my_func` visible from outside of `foo` error: the type `MyContract` is private - ┌─ compile_errors/bad_visibility/src/main.fe:27:16 + ┌─ compile_errors/bad_visibility/src/main.fe:30:16 │ -27 │ let _: MyContract = MyContract(addr) +30 │ let _: MyContract = MyContract(addr) │ ^^^^^^^^^^ this type is not `pub` │ - ┌─ compile_errors/bad_visibility/src/foo.fe:15:10 + ┌─ compile_errors/bad_visibility/src/foo.fe:17:10 │ -15 │ contract MyContract { +17 │ contract MyContract { │ ---------- `MyContract` is defined here │ = `MyContract` can only be used within `foo` = Hint: use `pub` to make `MyContract` visible from outside of `foo` error: the type `MyContract` is private - ┌─ compile_errors/bad_visibility/src/main.fe:27:29 + ┌─ compile_errors/bad_visibility/src/main.fe:30:29 │ -27 │ let _: MyContract = MyContract(addr) +30 │ let _: MyContract = MyContract(addr) │ ^^^^^^^^^^ this type is not `pub` │ - ┌─ compile_errors/bad_visibility/src/foo.fe:15:10 + ┌─ compile_errors/bad_visibility/src/foo.fe:17:10 │ -15 │ contract MyContract { +17 │ contract MyContract { │ ---------- `MyContract` is defined here │ = `MyContract` can only be used within `foo` = Hint: use `pub` to make `MyContract` visible from outside of `foo` error: the type `MyContract` is private - ┌─ compile_errors/bad_visibility/src/main.fe:28:9 + ┌─ compile_errors/bad_visibility/src/main.fe:31:9 │ -28 │ MyContract.create(ctx, 1) +31 │ MyContract.create(ctx, 1) │ ^^^^^^^^^^ this type is not `pub` │ - ┌─ compile_errors/bad_visibility/src/foo.fe:15:10 + ┌─ compile_errors/bad_visibility/src/foo.fe:17:10 │ -15 │ contract MyContract { +17 │ contract MyContract { │ ---------- `MyContract` is defined here │ = `MyContract` can only be used within `foo` = Hint: use `pub` to make `MyContract` visible from outside of `foo` +error: the trait `MyTrait` is private + ┌─ compile_errors/bad_visibility/src/foo.fe:5:7 + │ +5 │ trait MyTrait {} + │ ^^^^^^^ this trait is not `pub` + │ + = `MyTrait` can only be used within `foo` + = Hint: use `pub trait MyTrait` to make `MyTrait` visible from outside of `foo` + diff --git a/crates/analyzer/tests/snapshots/errors__call_generic_function_with_unsatisfied_bound.snap b/crates/analyzer/tests/snapshots/errors__call_generic_function_with_unsatisfied_bound.snap new file mode 100644 index 0000000000..abb2fdd727 --- /dev/null +++ b/crates/analyzer/tests/snapshots/errors__call_generic_function_with_unsatisfied_bound.snap @@ -0,0 +1,18 @@ +--- +source: crates/analyzer/tests/errors.rs +expression: "error_string(&path, test_files::fixture(path))" + +--- +error: the trait bound `Bar: Dummy` is not satisfied + ┌─ compile_errors/call_generic_function_with_unsatisfied_bound.fe:16:13 + │ +16 │ foo.bar(Bar()) + │ ^^^^^ the trait `Dummy` is not implemented for `Bar` + +error: incorrect type for `bar` argument at position 0 + ┌─ compile_errors/call_generic_function_with_unsatisfied_bound.fe:16:13 + │ +16 │ foo.bar(Bar()) + │ ^^^^^ this has type `Bar`; expected type `T` + + diff --git a/crates/analyzer/tests/snapshots/errors__call_non_pub_fn_on_external_contract.snap b/crates/analyzer/tests/snapshots/errors__call_non_pub_fn_on_external_contract.snap index e5f8c8da80..013e0ac3e2 100644 --- a/crates/analyzer/tests/snapshots/errors__call_non_pub_fn_on_external_contract.snap +++ b/crates/analyzer/tests/snapshots/errors__call_non_pub_fn_on_external_contract.snap @@ -5,13 +5,11 @@ expression: "error_string(&path, test_files::fixture(path))" --- error: the function `do_private_thingz` on `contract Foo` is private ┌─ compile_errors/call_non_pub_fn_on_external_contract.fe:13:25 - │ - 6 │ â•­ fn do_private_thingz(self) { - 7 │ │ self.val = 100 - 8 │ │ } - │ ╰─────' `do_private_thingz` is defined here - · │ -13 │ Foo(address(0)).do_private_thingz() - │ ^^^^^^^^^^^^^^^^^ this function is not `pub` + │ + 6 │ fn do_private_thingz(self) { + │ -------------------------- `do_private_thingz` is defined here + · +13 │ Foo(address(0)).do_private_thingz() + │ ^^^^^^^^^^^^^^^^^ this function is not `pub` diff --git a/crates/analyzer/tests/snapshots/errors__contract_function_with_generic_params.snap b/crates/analyzer/tests/snapshots/errors__contract_function_with_generic_params.snap new file mode 100644 index 0000000000..4b9ef16c72 --- /dev/null +++ b/crates/analyzer/tests/snapshots/errors__contract_function_with_generic_params.snap @@ -0,0 +1,14 @@ +--- +source: crates/analyzer/tests/errors.rs +expression: "error_string(&path, test_files::fixture(path))" + +--- +error: generic function parameters aren't yet supported outside of struct functions + ┌─ compile_errors/contract_function_with_generic_params.fe:4:15 + │ +4 │ pub fn bar(val: T) {} + │ ^^^^^^^^^^ This can not appear here + │ + = Hint: Struct functions can have generic parameters + + diff --git a/crates/analyzer/tests/snapshots/errors__duplicate_generic_params.snap b/crates/analyzer/tests/snapshots/errors__duplicate_generic_params.snap new file mode 100644 index 0000000000..4127fa4d18 --- /dev/null +++ b/crates/analyzer/tests/snapshots/errors__duplicate_generic_params.snap @@ -0,0 +1,14 @@ +--- +source: crates/analyzer/tests/errors.rs +expression: "error_string(&path, test_files::fixture(path))" + +--- +error: duplicate generic parameter + ┌─ compile_errors/duplicate_generic_params.fe:10:10 + │ +10 │ fn foo(val1: T, val2: T) { + │ ^ - `T` redefined here + │ │ + │ `T` first defined here + + diff --git a/crates/analyzer/tests/snapshots/errors__invalid_generic_bound.snap b/crates/analyzer/tests/snapshots/errors__invalid_generic_bound.snap new file mode 100644 index 0000000000..61fb23cfc0 --- /dev/null +++ b/crates/analyzer/tests/snapshots/errors__invalid_generic_bound.snap @@ -0,0 +1,12 @@ +--- +source: crates/analyzer/tests/errors.rs +expression: "error_string(&path, test_files::fixture(path))" + +--- +error: expected trait, found type `bool` + ┌─ compile_errors/invalid_generic_bound.fe:2:19 + │ +2 │ pub fn bar(val: T) {} + │ ^^^^ not a trait + + diff --git a/crates/analyzer/tests/snapshots/errors__invalid_impl_location.snap b/crates/analyzer/tests/snapshots/errors__invalid_impl_location.snap new file mode 100644 index 0000000000..ea4026845a --- /dev/null +++ b/crates/analyzer/tests/snapshots/errors__invalid_impl_location.snap @@ -0,0 +1,12 @@ +--- +source: crates/analyzer/tests/errors.rs +expression: "error_string(&path, test_files::fixture(path))" + +--- +error: illegal `impl`. Either type or trait must be in the same ingot as the `impl` + ┌─ compile_errors/invalid_impl_location.fe:4:1 + │ +4 │ impl Dummy for Context {} + │ ^^^^^^^^^^^^^^^^^^^^^^ Neither `Context` nor `Dummy` are in the ingot of the `impl` block + + diff --git a/crates/analyzer/tests/snapshots/errors__invalid_impl_type.snap b/crates/analyzer/tests/snapshots/errors__invalid_impl_type.snap new file mode 100644 index 0000000000..14c71a7d97 --- /dev/null +++ b/crates/analyzer/tests/snapshots/errors__invalid_impl_type.snap @@ -0,0 +1,12 @@ +--- +source: crates/analyzer/tests/errors.rs +expression: "error_string(&path, test_files::fixture(path))" + +--- +error: `impl` blocks aren't allowed for Map + ┌─ compile_errors/invalid_impl_type.fe:3:1 + │ +3 │ impl SomeTrait for Map {} + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ illegal `impl` block + + diff --git a/crates/analyzer/tests/snapshots/errors__map_constructor.snap b/crates/analyzer/tests/snapshots/errors__map_constructor.snap index 94105f7492..e572c4063e 100644 --- a/crates/analyzer/tests/snapshots/errors__map_constructor.snap +++ b/crates/analyzer/tests/snapshots/errors__map_constructor.snap @@ -3,7 +3,7 @@ source: crates/analyzer/tests/errors.rs expression: "error_string(\"[snippet]\", &src)" --- -error: `Map` type is not callable +error: `Map` type is not callable ┌─ [snippet]:3:3 │ 3 │ Map() diff --git a/crates/analyzer/tests/snapshots/errors__trait_conflicting_impls.snap b/crates/analyzer/tests/snapshots/errors__trait_conflicting_impls.snap new file mode 100644 index 0000000000..faf8b54b23 --- /dev/null +++ b/crates/analyzer/tests/snapshots/errors__trait_conflicting_impls.snap @@ -0,0 +1,14 @@ +--- +source: crates/analyzer/tests/errors.rs +expression: "error_string(&path, test_files::fixture(path))" + +--- +error: duplicate `impl` blocks for trait `Foo` for type `Bar` + ┌─ compile_errors/trait_conflicting_impls.fe:6:1 + │ +6 │ impl Foo for Bar {} + │ ^^^^^^^^^^^^^^^^ `` first defined here +7 │ impl Foo for Bar {} + │ ---------------- `` redefined here + + diff --git a/crates/analyzer/tests/snapshots/errors__trait_fn_with_generic_params.snap b/crates/analyzer/tests/snapshots/errors__trait_fn_with_generic_params.snap new file mode 100644 index 0000000000..f580c42e0a --- /dev/null +++ b/crates/analyzer/tests/snapshots/errors__trait_fn_with_generic_params.snap @@ -0,0 +1,14 @@ +--- +source: crates/analyzer/tests/errors.rs +expression: "error_string(&path, test_files::fixture(path))" + +--- +error: generic function parameters aren't yet supported outside of struct functions + ┌─ compile_errors/trait_fn_with_generic_params.fe:4:18 + │ +4 │ fn generic_fn(self, val: T); + │ ^^^^^^^^ This can not appear here + │ + = Hint: Struct functions can have generic parameters + + diff --git a/crates/analyzer/tests/snapshots/errors__trait_fn_without_self.snap b/crates/analyzer/tests/snapshots/errors__trait_fn_without_self.snap new file mode 100644 index 0000000000..44802d22a2 --- /dev/null +++ b/crates/analyzer/tests/snapshots/errors__trait_fn_without_self.snap @@ -0,0 +1,14 @@ +--- +source: crates/analyzer/tests/errors.rs +expression: "error_string(&path, test_files::fixture(path))" + +--- +error: associated functions aren't yet supported in traits + ┌─ compile_errors/trait_fn_without_self.fe:2:5 + │ +2 │ fn this_has_no_self(); + │ ^^^^^^^^^^^^^^^^^^^^^ function doesn't take `self` + │ + = Hint: add a `self` param to this function + + diff --git a/crates/analyzer/tests/snapshots/errors__trait_impl_mismatch.snap b/crates/analyzer/tests/snapshots/errors__trait_impl_mismatch.snap new file mode 100644 index 0000000000..feb3959fe5 --- /dev/null +++ b/crates/analyzer/tests/snapshots/errors__trait_impl_mismatch.snap @@ -0,0 +1,36 @@ +--- +source: crates/analyzer/tests/errors.rs +expression: "error_string(&path, test_files::fixture(path))" + +--- +error: method `this_has_wrong_args_in_impl` has incompatible parameters for `this_has_wrong_args_in_impl` of trait `Foo` + ┌─ compile_errors/trait_impl_mismatch.fe:3:5 + │ + 3 │ fn this_has_wrong_args_in_impl(self, val: u8, val2: bool); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ signature of method in trait `Foo` + · +11 │ fn this_has_wrong_args_in_impl(self, val: u16, val2: Array) {} + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ signature of method in `impl` block + +error: method `this_has_wrong_return_type_in_impl` has an incompatible return type for `this_has_wrong_return_type_in_impl` of trait `Foo` + ┌─ compile_errors/trait_impl_mismatch.fe:4:5 + │ + 4 │ fn this_has_wrong_return_type_in_impl(self) -> bool; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ signature of method in trait `Foo` + · +12 │ fn this_has_wrong_return_type_in_impl(self) -> u8 { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ signature of method in `impl` block + +error: method `this_does_not_exist_in_trait` is not a member of trait `Foo` + ┌─ compile_errors/trait_impl_mismatch.fe:15:5 + │ +15 │ fn this_does_not_exist_in_trait() {} + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not a member of trait `Foo` + +error: not all members of trait `Foo` implemented, missing: `this_misses_in_impl` + ┌─ compile_errors/trait_impl_mismatch.fe:2:5 + │ +2 │ fn this_misses_in_impl(self); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this trait function is missing in `impl` block + + diff --git a/crates/analyzer/tests/snapshots/errors__traits_as_fields.snap b/crates/analyzer/tests/snapshots/errors__traits_as_fields.snap new file mode 100644 index 0000000000..2f6755a385 --- /dev/null +++ b/crates/analyzer/tests/snapshots/errors__traits_as_fields.snap @@ -0,0 +1,18 @@ +--- +source: crates/analyzer/tests/errors.rs +expression: "error_string(&path, test_files::fixture(path))" + +--- +error: struct field type must have a fixed size + ┌─ compile_errors/traits_as_fields.fe:4:5 + │ +4 │ val: Foo + │ ^^^^^^^^ this can't be used as an struct field + +error: traits can not be used as contract fields + ┌─ compile_errors/traits_as_fields.fe:8:5 + │ +8 │ val: Foo + │ ^^^^^^^^ trait `Foo` can not appear here + + diff --git a/crates/analyzer/tests/snapshots/errors__traits_with_wrong_bounds.snap b/crates/analyzer/tests/snapshots/errors__traits_with_wrong_bounds.snap new file mode 100644 index 0000000000..c8f8b24146 --- /dev/null +++ b/crates/analyzer/tests/snapshots/errors__traits_with_wrong_bounds.snap @@ -0,0 +1,14 @@ +--- +source: crates/analyzer/tests/errors.rs +expression: "error_string(&path, test_files::fixture(path))" + +--- +error: unbounded generic parameters aren't yet supported + ┌─ compile_errors/traits_with_wrong_bounds.fe:4:22 + │ +4 │ pub fn no_bounds(val: T) {} + │ ^ `T` needs to be bound by some trait + │ + = Hint: Change `T` to `T: SomeTrait` + + diff --git a/crates/codegen/src/db/queries/function.rs b/crates/codegen/src/db/queries/function.rs index 53ab8cc158..d33613c9e4 100644 --- a/crates/codegen/src/db/queries/function.rs +++ b/crates/codegen/src/db/queries/function.rs @@ -1,5 +1,6 @@ use std::rc::Rc; +use fe_analyzer::namespace::items::Class; use fe_mir::ir::{FunctionBody, FunctionId, FunctionSignature}; use crate::{db::CodegenDb, yul::legalize}; @@ -19,7 +20,29 @@ pub fn legalized_body(db: &dyn CodegenDb, function: FunctionId) -> Rc Rc { let module = function.signature(db.upcast()).module_id; let module_name = module.name(db.upcast()); - let func_name = function.name_with_class(db.upcast()).replace("::", "$"); + + let analyzer_func = function.analyzer_func(db.upcast()); + let func_name = format!( + "{}{}", + analyzer_func.name(db.upcast()), + function.type_suffix(db.upcast()) + ); + + let func_name = match analyzer_func.class(db.upcast()) { + Some(Class::Impl(id)) => { + let class_name = format!( + "{}${}", + id.trait_id(db.upcast()).name(db.upcast()), + id.receiver(db.upcast()).name() + ); + format!("{}${}", class_name, func_name) + } + Some(class) => { + let class_name = class.name(db.upcast()); + format!("{}${}", class_name, func_name) + } + _ => func_name, + }; format!("{}${}", module_name, func_name).into() } diff --git a/crates/library/std/src/traits.fe b/crates/library/std/src/traits.fe new file mode 100644 index 0000000000..896bb3cf80 --- /dev/null +++ b/crates/library/std/src/traits.fe @@ -0,0 +1,3 @@ +# Dummy trait used in testing. We can remove this once we have more useful traits + +pub trait Dummy {} \ No newline at end of file diff --git a/crates/mir/src/db.rs b/crates/mir/src/db.rs index e2c2525a7f..11cc30612b 100644 --- a/crates/mir/src/db.rs +++ b/crates/mir/src/db.rs @@ -51,6 +51,12 @@ pub trait MirDb: AnalyzerDb + Upcast + UpcastMut &self, analyzer_func: analyzer_items::FunctionId, ) -> ir::FunctionId; + #[salsa::invoke(queries::function::mir_lowered_monomorphized_func_signature)] + fn mir_lowered_monomorphized_func_signature( + &self, + analyzer_func: analyzer_items::FunctionId, + concrete_args: Vec, + ) -> ir::FunctionId; #[salsa::invoke(queries::function::mir_lowered_func_body)] fn mir_lowered_func_body(&self, func: ir::FunctionId) -> Rc; } diff --git a/crates/mir/src/db/queries/function.rs b/crates/mir/src/db/queries/function.rs index d863f36d18..e9484dade2 100644 --- a/crates/mir/src/db/queries/function.rs +++ b/crates/mir/src/db/queries/function.rs @@ -1,12 +1,14 @@ use std::rc::Rc; use fe_analyzer::namespace::items as analyzer_items; +use fe_analyzer::namespace::items::Class; +use fe_analyzer::namespace::types as analyzer_types; use smol_str::SmolStr; use crate::{ db::MirDb, ir::{self, function::Linkage, FunctionSignature, TypeId}, - lower::function::{lower_func_body, lower_func_signature}, + lower::function::{lower_func_body, lower_func_signature, lower_monomorphized_func_signature}, }; pub fn mir_lowered_func_signature( @@ -16,6 +18,14 @@ pub fn mir_lowered_func_signature( lower_func_signature(db, analyzer_func) } +pub fn mir_lowered_monomorphized_func_signature( + db: &dyn MirDb, + analyzer_func: analyzer_items::FunctionId, + concrete_args: Vec, +) -> ir::FunctionId { + lower_monomorphized_func_signature(db, analyzer_func, &concrete_args) +} + pub fn mir_lowered_func_body(db: &dyn MirDb, func: ir::FunctionId) -> Rc { lower_func_body(db, func) } @@ -47,7 +57,21 @@ impl ir::FunctionId { } pub fn is_contract_init(self, db: &dyn MirDb) -> bool { - self.analyzer_func(db).is_constructor(db.upcast()) + self.analyzer_func(db) + .data(db.upcast()) + .sig + .is_constructor(db.upcast()) + } + + /// Returns a type suffix if a generic function was monomorphized + pub fn type_suffix(&self, db: &dyn MirDb) -> SmolStr { + self.signature(db) + .resolved_generics + .values() + .fold(String::new(), |acc, param| { + format!("{}_{}", acc, param.name()) + }) + .into() } pub fn name(&self, db: &dyn MirDb) -> SmolStr { @@ -56,15 +80,28 @@ impl ir::FunctionId { } /// Returns `class_name::fn_name` if a function is a method else `fn_name`. - pub fn name_with_class(self, db: &dyn MirDb) -> SmolStr { + pub fn debug_name(self, db: &dyn MirDb) -> SmolStr { let analyzer_func = self.analyzer_func(db); - let func_name = analyzer_func.name(db.upcast()); + let func_name = format!( + "{}{}", + analyzer_func.name(db.upcast()), + self.type_suffix(db) + ); - if let Some(class) = analyzer_func.class(db.upcast()) { - let class_name = class.name(db.upcast()); - format!("{}::{}", class_name, func_name).into() - } else { - func_name + match analyzer_func.class(db.upcast()) { + Some(Class::Impl(id)) => { + let class_name = format!( + "<{} as {}>", + id.receiver(db.upcast()).name(), + id.trait_id(db.upcast()).name(db.upcast()) + ); + format!("{}::{}", class_name, func_name).into() + } + Some(class) => { + let class_name = class.name(db.upcast()); + format!("{}::{}", class_name, func_name).into() + } + _ => func_name.into(), } } diff --git a/crates/mir/src/db/queries/types.rs b/crates/mir/src/db/queries/types.rs index 5a97972c07..82dc89982a 100644 --- a/crates/mir/src/db/queries/types.rs +++ b/crates/mir/src/db/queries/types.rs @@ -27,6 +27,10 @@ impl TypeId { db.lookup_mir_intern_type(self) } + pub fn analyzer_ty(self, db: &dyn MirDb) -> Option { + self.data(db).analyzer_ty.clone() + } + pub fn projection_ty(self, db: &dyn MirDb, access: &Value) -> TypeId { let ty = self.deref(db); match &ty.data(db).as_ref().kind { diff --git a/crates/mir/src/graphviz/function.rs b/crates/mir/src/graphviz/function.rs index 85840e2c0e..424c90492a 100644 --- a/crates/mir/src/graphviz/function.rs +++ b/crates/mir/src/graphviz/function.rs @@ -52,7 +52,7 @@ impl FunctionNode { let body = self.func.body(db); let sig_data = self.func.signature(db); - let mut sig = format!("fn {}(", self.func.name_with_class(db)); + let mut sig = format!("fn {}(", self.func.debug_name(db)); let params = &sig_data.params; let param_len = params.len(); diff --git a/crates/mir/src/ir/function.rs b/crates/mir/src/ir/function.rs index 1943b8345f..7fefda79bb 100644 --- a/crates/mir/src/ir/function.rs +++ b/crates/mir/src/ir/function.rs @@ -1,9 +1,11 @@ use fe_analyzer::namespace::items as analyzer_items; +use fe_analyzer::namespace::types as analyzer_types; use fe_common::impl_intern_key; use fxhash::FxHashMap; use id_arena::Arena; use num_bigint::BigInt; use smol_str::SmolStr; +use std::collections::BTreeMap; use crate::db::MirDb; @@ -20,6 +22,7 @@ use super::{ #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FunctionSignature { pub params: Vec, + pub resolved_generics: BTreeMap, pub return_type: Option, pub module_id: analyzer_items::ModuleId, pub analyzer_func_id: analyzer_items::FunctionId, diff --git a/crates/mir/src/lower/function.rs b/crates/mir/src/lower/function.rs index 2c0fd7a0fb..ef6e3445d3 100644 --- a/crates/mir/src/lower/function.rs +++ b/crates/mir/src/lower/function.rs @@ -1,9 +1,12 @@ -use std::rc::Rc; +use std::{collections::BTreeMap, rc::Rc}; use fe_analyzer::{ builtins::{ContractTypeMethod, GlobalFunction, ValueMethod}, context::CallType as AnalyzerCallType, - namespace::{items as analyzer_items, types as analyzer_types}, + namespace::{ + items as analyzer_items, + types::{self as analyzer_types, Type, TypeDowncast}, + }, }; use fe_common::numeric::Literal; use fe_parser::{ast, node::Node}; @@ -28,20 +31,53 @@ use crate::{ type ScopeId = Id; pub fn lower_func_signature(db: &dyn MirDb, func: analyzer_items::FunctionId) -> FunctionId { + lower_monomorphized_func_signature(db, func, &[]) +} +pub fn lower_monomorphized_func_signature( + db: &dyn MirDb, + func: analyzer_items::FunctionId, + concrete_args: &[Type], +) -> FunctionId { // TODO: Remove this when an analyzer's function signature contains `self` type. let mut params = vec![]; + let mut concrete_args: &[Type] = concrete_args; let has_self = func.takes_self(db.upcast()); + if has_self { let self_ty = func.self_typ(db.upcast()).unwrap(); let source = self_arg_source(db, func); params.push(make_param(db, "self", self_ty, source)); + // similarly, when in the future analyzer params contain `self` we won't need to adjust the concrete args anymore + if !concrete_args.is_empty() { + concrete_args = &concrete_args[1..] + } } - let analyzer_signature = func.signature(db.upcast()); - params.extend(analyzer_signature.params.iter().map(|param| { + let mut resolved_generics: BTreeMap = BTreeMap::new(); + + for (index, param) in analyzer_signature.params.iter().enumerate() { let source = arg_source(db, func, ¶m.name); - make_param(db, param.name.clone(), param.typ.clone().unwrap(), source) - })); + let provided_type = concrete_args + .get(index) + .cloned() + .unwrap_or_else(|| param.typ.clone().unwrap()); + + params.push(make_param( + db, + param.clone().name, + provided_type.clone(), + source, + )); + + if let analyzer_types::FunctionParam { + typ: Ok(Type::Generic(generic)), + .. + } = param + { + resolved_generics.insert(generic.clone(), provided_type); + } + } + let return_type = db.mir_lowered_type(analyzer_signature.return_type.clone().unwrap()); let linkage = if func.is_public(db.upcast()) { @@ -56,6 +92,7 @@ pub fn lower_func_signature(db: &dyn MirDb, func: analyzer_items::FunctionId) -> let sig = FunctionSignature { params, + resolved_generics, return_type: Some(return_type), module_id: func.module(db.upcast()), analyzer_func_id: func, @@ -79,6 +116,7 @@ struct BodyLowerHelper<'db, 'a> { db: &'db dyn MirDb, builder: BodyBuilder, ast: &'a Node, + func: FunctionId, analyzer_body: &'a fe_analyzer::context::FunctionBody, scopes: Arena, current_scope: ScopeId, @@ -98,17 +136,33 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> { // constants. let root = Scope::root(db, func, &mut builder); let current_scope = scopes.alloc(root); - Self { db, builder, ast, + func, analyzer_body, scopes, current_scope, } } + fn lower_analyzer_type(&self, analyzer_ty: analyzer_types::Type) -> TypeId { + // If the analyzer type is generic we first need to resolve it to its concrete type before lowering to a MIR type + if let analyzer_types::Type::Generic(generic) = analyzer_ty { + let resolved_type = self + .func + .signature(self.db) + .resolved_generics + .get(&generic) + .expect("expected generic to be resolved") + .clone(); + return self.db.mir_lowered_type(resolved_type); + } + + self.db.mir_lowered_type(analyzer_ty) + } + fn lower(mut self) -> FunctionBody { for stmt in &self.ast.kind.body { self.lower_stmt(stmt) @@ -141,9 +195,7 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> { } ast::FuncStmt::ConstantDecl { name, value, .. } => { - let ty = self - .db - .mir_lowered_type(self.analyzer_body.var_types[&name.id].clone()); + let ty = self.lower_analyzer_type(self.analyzer_body.var_types[&name.id].clone()); let value = self.analyzer_body.expressions[&value.id] .const_value @@ -310,9 +362,7 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> { ) { match &var.kind { ast::VarDeclTarget::Name(name) => { - let ty = self - .db - .mir_lowered_type(self.analyzer_body.var_types[&var.id].clone()); + let ty = self.lower_analyzer_type(self.analyzer_body.var_types[&var.id].clone()); let local = Local::user_local(name.clone(), ty, var.into()); let value = self.builder.declare(local); @@ -353,9 +403,7 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> { ) { match &var.kind { ast::VarDeclTarget::Name(name) => { - let ty = self - .db - .mir_lowered_type(self.analyzer_body.var_types[&var.id].clone()); + let ty = self.lower_analyzer_type(self.analyzer_body.var_types[&var.id].clone()); let local = Local::user_local(name.clone(), ty, var.into()); let lhs = self.builder.declare(local); @@ -453,7 +501,7 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> { let entry_bb = self.builder.make_block(); let exit_bb = self.builder.make_block(); let iter_elem_ty = self.analyzer_body.var_types[&loop_variable.id].clone(); - let iter_elem_ty = self.db.mir_lowered_type(iter_elem_ty); + let iter_elem_ty = self.lower_analyzer_type(iter_elem_ty); self.builder.jump(preheader_bb, SourceInfo::dummy()); @@ -701,7 +749,7 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> { fn expr_ty(&self, expr: &Node) -> TypeId { let analyzer_ty = self.analyzer_body.expressions[&expr.id].typ.clone(); - self.db.mir_lowered_type(analyzer_ty) + self.lower_analyzer_type(analyzer_ty) } fn lower_bool_op( @@ -848,11 +896,55 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> { let mut method_args = vec![self.lower_method_receiver(func)]; method_args.append(&mut args); - let func_id = self.db.mir_lowered_func_signature(*method); + let func_id = if method.is_generic(self.db.upcast()) { + let concrete_args = method_args + .iter() + .map(|val| { + self.builder + .value_ty(*val) + .analyzer_ty(self.db) + .expect("invalid parameter") + }) + .collect::>(); + self.db + .mir_lowered_monomorphized_func_signature(*method, concrete_args) + } else { + self.db.mir_lowered_func_signature(*method) + }; + self.builder .call(func_id, method_args, CallType::Internal, source) } + AnalyzerCallType::TraitValueMethod { + method, + trait_id, + generic_type, + .. + } => { + let mut method_args = vec![self.lower_method_receiver(func)]; + method_args.append(&mut args); + + let struct_type = self + .func + .signature(self.db) + .resolved_generics + .get(generic_type) + .as_struct() + .expect("unexpected implementer of trait"); + + let impl_ = struct_type + .id + .get_impl_for(self.db.upcast(), *trait_id) + .expect("missing impl"); + let function = impl_ + .function(self.db.upcast(), &method.name(self.db.upcast())) + .expect("missing function"); + + let func_id = self.db.mir_lowered_func_signature(function); + self.builder + .call(func_id, method_args, CallType::Internal, source) + } AnalyzerCallType::External { function, .. } => { let mut method_args = vec![self.lower_method_receiver(func)]; method_args.append(&mut args); @@ -1129,6 +1221,8 @@ fn self_arg_source(db: &dyn MirDb, func: analyzer_items::FunctionId) -> SourceIn func.data(db.upcast()) .ast .kind + .sig + .kind .args .iter() .find(|arg| matches!(arg.kind, ast::FunctionArg::Self_)) @@ -1140,6 +1234,8 @@ fn arg_source(db: &dyn MirDb, func: analyzer_items::FunctionId, arg_name: &str) func.data(db.upcast()) .ast .kind + .sig + .kind .args .iter() .find_map(|arg| match &arg.kind { diff --git a/crates/mir/src/lower/types.rs b/crates/mir/src/lower/types.rs index d9d4e00180..db0f611f4c 100644 --- a/crates/mir/src/lower/types.rs +++ b/crates/mir/src/lower/types.rs @@ -18,6 +18,12 @@ pub fn lower_type(db: &dyn MirDb, analyzer_ty: &analyzer_types::Type) -> TypeId analyzer_types::Type::Contract(_) => TypeKind::Address, analyzer_types::Type::SelfContract(contract) => lower_contract(db, contract), analyzer_types::Type::Struct(struct_) => lower_struct(db, struct_), + analyzer_types::Type::Trait(_) => { + panic!("traits should only appear in generic bounds for now") + } + analyzer_types::Type::Generic(_) => { + panic!("should be lowered in `lower_types_in_functions`") + } }; intern_type(db, ty_kind, Some(analyzer_ty)) diff --git a/crates/mir/src/pretty_print/inst.rs b/crates/mir/src/pretty_print/inst.rs index d047a54494..6bd8eaf042 100644 --- a/crates/mir/src/pretty_print/inst.rs +++ b/crates/mir/src/pretty_print/inst.rs @@ -99,7 +99,7 @@ impl PrettyPrint for InstId { args, call_type, } => { - let name = func.name_with_class(db); + let name = func.debug_name(db); write!(w, "{}@{}(", name, call_type)?; args.as_slice().pretty_print(db, store, w)?; write!(w, ")") diff --git a/crates/parser/src/ast.rs b/crates/parser/src/ast.rs index d91784a14e..69b58a4290 100644 --- a/crates/parser/src/ast.rs +++ b/crates/parser/src/ast.rs @@ -21,6 +21,8 @@ pub enum ModuleStmt { Contract(Node), Constant(Node), Struct(Node), + Trait(Node), + Impl(Node), Function(Node), Event(Node), ParseError(Span), @@ -87,6 +89,20 @@ pub struct Struct { pub pub_qual: Option, } +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] +pub struct Trait { + pub name: Node, + pub functions: Vec>, + pub pub_qual: Option, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] +pub struct Impl { + pub impl_trait: Node, + pub receiver: Node, + pub functions: Vec>, +} + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] pub enum TypeDesc { Unit, @@ -128,10 +144,23 @@ pub enum GenericParameter { Unbounded(Node), Bounded { name: Node, - bound: Node, + bound: Node, }, } +impl GenericParameter { + pub fn name(&self) -> SmolStr { + self.name_node().kind + } + + pub fn name_node(&self) -> Node { + match self { + GenericParameter::Unbounded(node) => node.clone(), + GenericParameter::Bounded { name, .. } => name.clone(), + } + } +} + impl Spanned for GenericParameter { fn span(&self) -> Span { match self { @@ -166,7 +195,7 @@ pub struct Event { } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] -pub struct Function { +pub struct FunctionSignature { // qualifier order: `pub unsafe fn` pub pub_: Option, pub unsafe_: Option, @@ -174,6 +203,11 @@ pub struct Function { pub generic_params: Node>, pub args: Vec>, pub return_type: Option>, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] +pub struct Function { + pub sig: Node, pub body: Vec>, } @@ -370,6 +404,12 @@ impl Node { } } +impl Node { + pub fn name(&self) -> &str { + &self.kind.name.kind + } +} + impl Node { pub fn name(&self) -> &str { &self.kind.name.kind @@ -384,7 +424,7 @@ impl Node { impl Node { pub fn name(&self) -> &str { - &self.kind.name.kind + &self.kind.sig.kind.name.kind } } @@ -415,6 +455,8 @@ impl Spanned for ModuleStmt { match self { ModuleStmt::Pragma(inner) => inner.span, ModuleStmt::Use(inner) => inner.span, + ModuleStmt::Trait(inner) => inner.span, + ModuleStmt::Impl(inner) => inner.span, ModuleStmt::TypeAlias(inner) => inner.span, ModuleStmt::Contract(inner) => inner.span, ModuleStmt::Constant(inner) => inner.span, @@ -461,6 +503,8 @@ impl fmt::Display for ModuleStmt { match self { ModuleStmt::Pragma(node) => write!(f, "{}", node.kind), ModuleStmt::Use(node) => write!(f, "{}", node.kind), + ModuleStmt::Trait(node) => write!(f, "{}", node.kind), + ModuleStmt::Impl(node) => write!(f, "{}", node.kind), ModuleStmt::TypeAlias(node) => write!(f, "{}", node.kind), ModuleStmt::Contract(node) => write!(f, "{}", node.kind), ModuleStmt::Constant(node) => write!(f, "{}", node.kind), @@ -534,6 +578,26 @@ impl fmt::Display for ConstantDecl { } } +impl fmt::Display for Trait { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f, "trait {}:", self.name.kind)?; + + Ok(()) + } +} + +impl fmt::Display for Impl { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!( + f, + "impl {} for {}", + self.impl_trait.kind, self.receiver.kind + )?; + + Ok(()) + } +} + impl fmt::Display for TypeAlias { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let TypeAlias { @@ -685,15 +749,14 @@ impl fmt::Display for Node { impl fmt::Display for Function { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let Function { + let FunctionSignature { pub_, unsafe_, name, generic_params, args, return_type, - body, - } = self; + } = &self.sig.kind; if pub_.is_some() { write!(f, "pub ")?; @@ -711,7 +774,7 @@ impl fmt::Display for Function { write!(f, " -> {}", return_type.kind)?; } write!(f, " {{")?; - write_nodes_line_wrapped(&mut indented(f), body)?; + write_nodes_line_wrapped(&mut indented(f), &self.body)?; write!(f, "}}") } } diff --git a/crates/parser/src/grammar/functions.rs b/crates/parser/src/grammar/functions.rs index 0ab78b9028..4f55e5fc3d 100644 --- a/crates/parser/src/grammar/functions.rs +++ b/crates/parser/src/grammar/functions.rs @@ -2,15 +2,18 @@ use super::expressions::{parse_call_args, parse_expr}; use super::types::parse_type_desc; use crate::ast::{ - BinOperator, Expr, FuncStmt, Function, FunctionArg, GenericParameter, RegularFunctionArg, - VarDeclTarget, + BinOperator, Expr, FuncStmt, Function, FunctionArg, FunctionSignature, GenericParameter, + RegularFunctionArg, TypeDesc, VarDeclTarget, }; use crate::node::{Node, Span}; use crate::{Label, ParseFailed, ParseResult, Parser, TokenKind}; -/// Parse a function definition. The optional `pub` qualifier must be parsed by +/// Parse a function definition without a body. The optional `pub` qualifier must be parsed by /// the caller, and passed in. Next token must be `unsafe` or `fn`. -pub fn parse_fn_def(par: &mut Parser, mut pub_qual: Option) -> ParseResult> { +pub fn parse_fn_sig( + par: &mut Parser, + mut pub_qual: Option, +) -> ParseResult> { let unsafe_qual = par.optional(TokenKind::Unsafe).map(|tok| tok.span); if let Some(pub_) = par.optional(TokenKind::Pub) { let unsafe_span = @@ -84,22 +87,35 @@ pub fn parse_fn_def(par: &mut Parser, mut pub_qual: Option) -> ParseResult None }; - // TODO: allow multi-line return type? `fn f()\n ->\n u8` - par.enter_block(span, "function definition")?; - let body = parse_block_stmts(par)?; - let rbrace = par.expect(TokenKind::BraceClose, "missing `}` in fn definition")?; - Ok(Node::new( - Function { + FunctionSignature { pub_: pub_qual, unsafe_: unsafe_qual, name: name.into(), args, generic_params, return_type, + }, + span, + )) +} + +/// Parse a function definition. The optional `pub` qualifier must be parsed by +/// the caller, and passed in. Next token must be `unsafe` or `fn`. +pub fn parse_fn_def(par: &mut Parser, pub_qual: Option) -> ParseResult> { + let sig = parse_fn_sig(par, pub_qual)?; + + // TODO: allow multi-line return type? `fn f()\n ->\n u8` + par.enter_block(sig.span, "function definition")?; + let body = parse_block_stmts(par)?; + let rbrace = par.expect(TokenKind::BraceClose, "missing `}` in fn definition")?; + + Ok(Node::new( + Function { + sig: sig.clone(), body, }, - span + rbrace.span, + sig.span + rbrace.span, )) } @@ -115,7 +131,12 @@ pub fn parse_generic_param(par: &mut Parser) -> ParseResult { let bound = par.assert(Name); Ok(GenericParameter::Bounded { name: Node::new(name.text.into(), name.span), - bound: Node::new(bound.text.into(), bound.span), + bound: Node::new( + TypeDesc::Base { + base: bound.text.into(), + }, + bound.span, + ), }) } None => Ok(GenericParameter::Unbounded(Node::new( diff --git a/crates/parser/src/grammar/module.rs b/crates/parser/src/grammar/module.rs index 283f5acb89..b322eb0a1f 100644 --- a/crates/parser/src/grammar/module.rs +++ b/crates/parser/src/grammar/module.rs @@ -2,7 +2,8 @@ use super::contracts::parse_contract_def; use super::expressions::parse_expr; use super::functions::parse_fn_def; use super::types::{ - parse_event_def, parse_path_tail, parse_struct_def, parse_type_alias, parse_type_desc, + parse_event_def, parse_impl_def, parse_path_tail, parse_struct_def, parse_trait_def, + parse_type_alias, parse_type_desc, }; use crate::ast::{ConstantDecl, Module, ModuleStmt, Pragma, Use, UseTree}; use crate::node::{Node, Span}; @@ -42,6 +43,8 @@ pub fn parse_module_stmt(par: &mut Parser) -> ParseResult { TokenKind::Use => ModuleStmt::Use(parse_use(par)?), TokenKind::Contract => ModuleStmt::Contract(parse_contract_def(par, None)?), TokenKind::Struct => ModuleStmt::Struct(parse_struct_def(par, None)?), + TokenKind::Trait => ModuleStmt::Trait(parse_trait_def(par, None)?), + TokenKind::Impl => ModuleStmt::Impl(parse_impl_def(par)?), TokenKind::Type => ModuleStmt::TypeAlias(parse_type_alias(par, None)?), TokenKind::Const => ModuleStmt::Constant(parse_constant(par, None)?), @@ -54,6 +57,7 @@ pub fn parse_module_stmt(par: &mut Parser) -> ParseResult { ModuleStmt::Function(parse_fn_def(par, Some(pub_span))?) } TokenKind::Struct => ModuleStmt::Struct(parse_struct_def(par, Some(pub_span))?), + TokenKind::Trait => ModuleStmt::Trait(parse_trait_def(par, Some(pub_span))?), TokenKind::Type => ModuleStmt::TypeAlias(parse_type_alias(par, Some(pub_span))?), TokenKind::Const => ModuleStmt::Constant(parse_constant(par, Some(pub_span))?), TokenKind::Contract => { diff --git a/crates/parser/src/grammar/types.rs b/crates/parser/src/grammar/types.rs index 3353df8245..c3c44c60cb 100644 --- a/crates/parser/src/grammar/types.rs +++ b/crates/parser/src/grammar/types.rs @@ -1,6 +1,6 @@ -use crate::ast::{self, EventField, Field, GenericArg, Path, TypeAlias, TypeDesc}; +use crate::ast::{self, EventField, Field, GenericArg, Impl, Path, Trait, TypeAlias, TypeDesc}; use crate::grammar::expressions::parse_expr; -use crate::grammar::functions::parse_fn_def; +use crate::grammar::functions::{parse_fn_def, parse_fn_sig}; use crate::node::{Node, Span}; use crate::Token; use crate::{ParseFailed, ParseResult, Parser, TokenKind}; @@ -64,6 +64,113 @@ pub fn parse_struct_def( )) } +/// Parse a trait definition. +/// # Panics +/// Panics if the next token isn't `trait`. +pub fn parse_trait_def(par: &mut Parser, trait_pub_qual: Option) -> ParseResult> { + let trait_tok = par.assert(TokenKind::Trait); + + // trait Event {} + let trait_name = par.expect_with_notes( + TokenKind::Name, + "failed to parse trait definition", + |_| vec!["Note: `trait` must be followed by a name, which must start with a letter and contain only letters, numbers, or underscores".into()], + )?; + + let header_span = trait_tok.span + trait_name.span; + let mut functions = vec![]; + par.enter_block(header_span, "trait definition")?; + + loop { + match par.peek_or_err()? { + TokenKind::Fn => { + // TODO: Traits should also be allowed to have functions that do contain a body. + functions.push(parse_fn_sig(par, None)?); + par.expect_with_notes( + TokenKind::Semi, + "failed to parse trait definition", + |_| vec!["Note: trait functions must appear without body and followed by a semicolon.".into()], + )?; + par.eat_newlines(); + } + TokenKind::BraceClose => { + par.next()?; + break; + } + _ => { + let tok = par.next()?; + par.unexpected_token_error(&tok, "failed to parse trait definition body", vec![]); + return Err(ParseFailed); + } + }; + } + + let span = header_span + trait_pub_qual; + Ok(Node::new( + Trait { + name: Node::new(trait_name.text.into(), trait_name.span), + functions, + pub_qual: trait_pub_qual, + }, + span, + )) +} + +/// Parse an impl block. +/// # Panics +/// Panics if the next token isn't `impl`. +pub fn parse_impl_def(par: &mut Parser) -> ParseResult> { + let impl_tok = par.assert(TokenKind::Impl); + + // impl SomeTrait for SomeType {} + let trait_name = + par.expect_with_notes(TokenKind::Name, "failed to parse `impl` definition", |_| { + vec!["Note: `impl` must be followed by the name of a trait".into()] + })?; + + let for_tok = + par.expect_with_notes(TokenKind::For, "failed to parse `impl` definition", |_| { + vec![format!( + "Note: `impl {}` must be followed by the keyword `for`", + trait_name.text + )] + })?; + + let receiver = parse_type_desc(par)?; + let mut functions = vec![]; + + let header_span = impl_tok.span + trait_name.span + for_tok.span + receiver.span; + + par.enter_block(header_span, "impl definition")?; + + loop { + par.eat_newlines(); + match par.peek_or_err()? { + TokenKind::Fn => { + functions.push(parse_fn_def(par, None)?); + } + TokenKind::BraceClose => { + par.next()?; + break; + } + _ => { + let tok = par.next()?; + par.unexpected_token_error(&tok, "failed to parse `impl` definition body", vec![]); + return Err(ParseFailed); + } + }; + } + + Ok(Node::new( + Impl { + impl_trait: Node::new(trait_name.text.into(), trait_name.span), + receiver, + functions, + }, + header_span, + )) +} + /// Parse a type alias definition, e.g. `type MyMap = Map`. /// # Panics /// Panics if the next token isn't `type`. diff --git a/crates/parser/src/lexer/token.rs b/crates/parser/src/lexer/token.rs index c5d8d65f3c..92b380f251 100644 --- a/crates/parser/src/lexer/token.rs +++ b/crates/parser/src/lexer/token.rs @@ -79,6 +79,8 @@ pub enum TokenKind { Idx, #[token("if")] If, + #[token("impl")] + Impl, #[token("pragma")] Pragma, #[token("for")] @@ -93,6 +95,8 @@ pub enum TokenKind { SelfValue, #[token("struct")] Struct, + #[token("trait")] + Trait, #[token("type")] Type, #[token("unsafe")] @@ -230,6 +234,7 @@ impl TokenKind { Event => "keyword `event`", Idx => "keyword `idx`", If => "keyword `if`", + Impl => "keyword `impl`", Pragma => "keyword `pragma`", For => "keyword `for`", Pub => "keyword `pub`", @@ -237,6 +242,7 @@ impl TokenKind { Revert => "keyword `revert`", SelfValue => "keyword `self`", Struct => "keyword `struct`", + Trait => "keyword `trait`", Type => "keyword `type`", Unsafe => "keyword `unsafe`", While => "keyword `while`", @@ -255,7 +261,7 @@ impl TokenKind { Colon => "symbol `:`", ColonColon => "symbol `::`", Comma => "symbol `,`", - Semi => "symbol ``", + Semi => "symbol `;`", Plus => "symbol `+`", Minus => "symbol `-`", Star => "symbol `*`", diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__contract_def.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__contract_def.snap index 9155669950..3b28b98e5b 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__contract_def.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__contract_def.snap @@ -1,6 +1,6 @@ --- source: crates/parser/tests/cases/parse_ast.rs -expression: "ast_string(stringify!(contract_def), try_parse_module,\n r#\"contract Foo {\n x: address\n pub y: u8\n pub const z: Map\n\n pub fn foo() -> u8 {\n return 10\n }\n event Bar {\n idx from: address\n }\n}\n\"#)" +expression: "ast_string(stringify!(contract_def), try_parse_module,\n r#\"contract Foo {\n x: address\n pub y: u8\n pub const z: Map\n\n pub fn foo() -> u8 {\n return 10\n }\n event Bar {\n idx from: address\n }\n}\n\"#)" --- Node( @@ -133,35 +133,43 @@ Node( body: [ Function(Node( kind: Function( - pub_: Some(Span( - start: 75, - end: 78, - )), - unsafe_: None, - name: Node( - kind: "foo", - span: Span( - start: 82, - end: 85, - ), - ), - generic_params: Node( - kind: [], - span: Span( - start: 82, - end: 85, - ), - ), - args: [], - return_type: Some(Node( - kind: Base( - base: "u8", + sig: Node( + kind: FunctionSignature( + pub_: Some(Span( + start: 75, + end: 78, + )), + unsafe_: None, + name: Node( + kind: "foo", + span: Span( + start: 82, + end: 85, + ), + ), + generic_params: Node( + kind: [], + span: Span( + start: 82, + end: 85, + ), + ), + args: [], + return_type: Some(Node( + kind: Base( + base: "u8", + ), + span: Span( + start: 91, + end: 93, + ), + )), ), span: Span( - start: 91, + start: 75, end: 93, ), - )), + ), body: [ Node( kind: Return( diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def.snap index 9805e19812..d4d286490b 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def.snap @@ -1,6 +1,6 @@ --- source: crates/parser/tests/cases/parse_ast.rs -expression: "ast_string(stringify!(fn_def), try_parse_module,\n \"fn transfer(from sender: address, to recip: address, _ val: u64) -> bool {\\n false \\n}\")" +expression: "ast_string(stringify!(fn_def), try_parse_module,\n \"fn transfer(from sender: address, to recip: address, _ val: u64) -> bool {\\n false \\n}\")" --- Node( @@ -8,126 +8,134 @@ Node( body: [ Function(Node( kind: Function( - pub_: None, - unsafe_: None, - name: Node( - kind: "transfer", - span: Span( - start: 3, - end: 11, - ), - ), - generic_params: Node( - kind: [], - span: Span( - start: 3, - end: 11, - ), - ), - args: [ - Node( - kind: Regular(RegularFunctionArg( - label: Some(Node( - kind: "from", - span: Span( - start: 12, - end: 16, - ), - )), - name: Node( - kind: "sender", - span: Span( - start: 17, - end: 23, - ), + sig: Node( + kind: FunctionSignature( + pub_: None, + unsafe_: None, + name: Node( + kind: "transfer", + span: Span( + start: 3, + end: 11, ), - typ: Node( - kind: Base( - base: "address", - ), - span: Span( - start: 25, - end: 32, - ), + ), + generic_params: Node( + kind: [], + span: Span( + start: 3, + end: 11, ), - )), - span: Span( - start: 17, - end: 32, ), - ), - Node( - kind: Regular(RegularFunctionArg( - label: Some(Node( - kind: "to", - span: Span( - start: 34, - end: 36, - ), - )), - name: Node( - kind: "recip", + args: [ + Node( + kind: Regular(RegularFunctionArg( + label: Some(Node( + kind: "from", + span: Span( + start: 12, + end: 16, + ), + )), + name: Node( + kind: "sender", + span: Span( + start: 17, + end: 23, + ), + ), + typ: Node( + kind: Base( + base: "address", + ), + span: Span( + start: 25, + end: 32, + ), + ), + )), span: Span( - start: 37, - end: 42, + start: 17, + end: 32, ), ), - typ: Node( - kind: Base( - base: "address", - ), + Node( + kind: Regular(RegularFunctionArg( + label: Some(Node( + kind: "to", + span: Span( + start: 34, + end: 36, + ), + )), + name: Node( + kind: "recip", + span: Span( + start: 37, + end: 42, + ), + ), + typ: Node( + kind: Base( + base: "address", + ), + span: Span( + start: 44, + end: 51, + ), + ), + )), span: Span( - start: 44, + start: 37, end: 51, ), ), - )), - span: Span( - start: 37, - end: 51, - ), - ), - Node( - kind: Regular(RegularFunctionArg( - label: Some(Node( - kind: "_", - span: Span( - start: 53, - end: 54, - ), - )), - name: Node( - kind: "val", + Node( + kind: Regular(RegularFunctionArg( + label: Some(Node( + kind: "_", + span: Span( + start: 53, + end: 54, + ), + )), + name: Node( + kind: "val", + span: Span( + start: 55, + end: 58, + ), + ), + typ: Node( + kind: Base( + base: "u64", + ), + span: Span( + start: 60, + end: 63, + ), + ), + )), span: Span( start: 55, - end: 58, - ), - ), - typ: Node( - kind: Base( - base: "u64", - ), - span: Span( - start: 60, end: 63, ), ), + ], + return_type: Some(Node( + kind: Base( + base: "bool", + ), + span: Span( + start: 68, + end: 72, + ), )), - span: Span( - start: 55, - end: 63, - ), - ), - ], - return_type: Some(Node( - kind: Base( - base: "bool", ), span: Span( - start: 68, + start: 0, end: 72, ), - )), + ), body: [ Node( kind: Expr( diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_generic.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_generic.snap index ae957dd09f..3fd4275830 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_generic.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_generic.snap @@ -1,6 +1,6 @@ --- source: crates/parser/tests/cases/parse_ast.rs -expression: "ast_string(stringify!(fn_def_generic), try_parse_module,\n \"fn foo(this: T, that: R, _ val: u64) -> bool { false }\")" +expression: "ast_string(stringify!(fn_def_generic), try_parse_module,\n \"fn foo(this: T, that: R, _ val: u64) -> bool { false }\")" --- Node( @@ -8,138 +8,148 @@ Node( body: [ Function(Node( kind: Function( - pub_: None, - unsafe_: None, - name: Node( - kind: "foo", - span: Span( - start: 3, - end: 6, - ), - ), - generic_params: Node( - kind: [ - Unbounded(Node( - kind: "T", + sig: Node( + kind: FunctionSignature( + pub_: None, + unsafe_: None, + name: Node( + kind: "foo", span: Span( - start: 7, - end: 8, - ), - )), - Bounded( - name: Node( - kind: "R", - span: Span( - start: 10, - end: 11, - ), + start: 3, + end: 6, ), - bound: Node( - kind: "Event", - span: Span( - start: 13, - end: 18, + ), + generic_params: Node( + kind: [ + Unbounded(Node( + kind: "T", + span: Span( + start: 7, + end: 8, + ), + )), + Bounded( + name: Node( + kind: "R", + span: Span( + start: 10, + end: 11, + ), + ), + bound: Node( + kind: Base( + base: "Event", + ), + span: Span( + start: 13, + end: 18, + ), + ), ), + ], + span: Span( + start: 6, + end: 19, ), ), - ], - span: Span( - start: 6, - end: 19, - ), - ), - args: [ - Node( - kind: Regular(RegularFunctionArg( - label: None, - name: Node( - kind: "this", + args: [ + Node( + kind: Regular(RegularFunctionArg( + label: None, + name: Node( + kind: "this", + span: Span( + start: 20, + end: 24, + ), + ), + typ: Node( + kind: Base( + base: "T", + ), + span: Span( + start: 26, + end: 27, + ), + ), + )), span: Span( start: 20, - end: 24, - ), - ), - typ: Node( - kind: Base( - base: "T", - ), - span: Span( - start: 26, end: 27, ), ), - )), - span: Span( - start: 20, - end: 27, - ), - ), - Node( - kind: Regular(RegularFunctionArg( - label: None, - name: Node( - kind: "that", + Node( + kind: Regular(RegularFunctionArg( + label: None, + name: Node( + kind: "that", + span: Span( + start: 29, + end: 33, + ), + ), + typ: Node( + kind: Base( + base: "R", + ), + span: Span( + start: 35, + end: 36, + ), + ), + )), span: Span( start: 29, - end: 33, - ), - ), - typ: Node( - kind: Base( - base: "R", - ), - span: Span( - start: 35, end: 36, ), ), - )), - span: Span( - start: 29, - end: 36, - ), - ), - Node( - kind: Regular(RegularFunctionArg( - label: Some(Node( - kind: "_", - span: Span( - start: 38, - end: 39, - ), - )), - name: Node( - kind: "val", + Node( + kind: Regular(RegularFunctionArg( + label: Some(Node( + kind: "_", + span: Span( + start: 38, + end: 39, + ), + )), + name: Node( + kind: "val", + span: Span( + start: 40, + end: 43, + ), + ), + typ: Node( + kind: Base( + base: "u64", + ), + span: Span( + start: 45, + end: 48, + ), + ), + )), span: Span( start: 40, - end: 43, - ), - ), - typ: Node( - kind: Base( - base: "u64", - ), - span: Span( - start: 45, end: 48, ), ), + ], + return_type: Some(Node( + kind: Base( + base: "bool", + ), + span: Span( + start: 53, + end: 57, + ), )), - span: Span( - start: 40, - end: 48, - ), - ), - ], - return_type: Some(Node( - kind: Base( - base: "bool", ), span: Span( - start: 53, + start: 0, end: 57, ), - )), + ), body: [ Node( kind: Expr( diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_pub.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_pub.snap index ce4cf5375b..2715639f1a 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_pub.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_pub.snap @@ -1,6 +1,6 @@ --- source: crates/parser/tests/cases/parse_ast.rs -expression: "ast_string(stringify!(fn_def_pub), try_parse_module,\n \"pub fn foo21(x: bool, y: address,) -> bool { x }\")" +expression: "ast_string(stringify!(fn_def_pub), try_parse_module,\n \"pub fn foo21(x: bool, y: address,) -> bool { x }\")" --- Node( @@ -8,86 +8,94 @@ Node( body: [ Function(Node( kind: Function( - pub_: Some(Span( - start: 0, - end: 3, - )), - unsafe_: None, - name: Node( - kind: "foo21", - span: Span( - start: 7, - end: 12, - ), - ), - generic_params: Node( - kind: [], - span: Span( - start: 7, - end: 12, - ), - ), - args: [ - Node( - kind: Regular(RegularFunctionArg( - label: None, - name: Node( - kind: "x", - span: Span( - start: 13, - end: 14, - ), + sig: Node( + kind: FunctionSignature( + pub_: Some(Span( + start: 0, + end: 3, + )), + unsafe_: None, + name: Node( + kind: "foo21", + span: Span( + start: 7, + end: 12, ), - typ: Node( - kind: Base( - base: "bool", - ), - span: Span( - start: 16, - end: 20, - ), + ), + generic_params: Node( + kind: [], + span: Span( + start: 7, + end: 12, ), - )), - span: Span( - start: 13, - end: 20, ), - ), - Node( - kind: Regular(RegularFunctionArg( - label: None, - name: Node( - kind: "y", + args: [ + Node( + kind: Regular(RegularFunctionArg( + label: None, + name: Node( + kind: "x", + span: Span( + start: 13, + end: 14, + ), + ), + typ: Node( + kind: Base( + base: "bool", + ), + span: Span( + start: 16, + end: 20, + ), + ), + )), span: Span( - start: 22, - end: 23, + start: 13, + end: 20, ), ), - typ: Node( - kind: Base( - base: "address", - ), + Node( + kind: Regular(RegularFunctionArg( + label: None, + name: Node( + kind: "y", + span: Span( + start: 22, + end: 23, + ), + ), + typ: Node( + kind: Base( + base: "address", + ), + span: Span( + start: 25, + end: 32, + ), + ), + )), span: Span( - start: 25, + start: 22, end: 32, ), ), + ], + return_type: Some(Node( + kind: Base( + base: "bool", + ), + span: Span( + start: 38, + end: 42, + ), )), - span: Span( - start: 22, - end: 32, - ), - ), - ], - return_type: Some(Node( - kind: Base( - base: "bool", ), span: Span( - start: 38, + start: 0, end: 42, ), - )), + ), body: [ Node( kind: Expr( diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_pub_unsafe.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_pub_unsafe.snap index 40008a51ad..3116b53ba5 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_pub_unsafe.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_pub_unsafe.snap @@ -1,6 +1,6 @@ --- source: crates/parser/tests/cases/parse_ast.rs -expression: "ast_string(stringify!(fn_def_pub_unsafe), try_parse_module,\n \"pub unsafe fn foo21(x: bool, y: address,) -> bool{x}\")" +expression: "ast_string(stringify!(fn_def_pub_unsafe), try_parse_module,\n \"pub unsafe fn foo21(x: bool, y: address,) -> bool{x}\")" --- Node( @@ -8,89 +8,97 @@ Node( body: [ Function(Node( kind: Function( - pub_: Some(Span( - start: 0, - end: 3, - )), - unsafe_: Some(Span( - start: 4, - end: 10, - )), - name: Node( - kind: "foo21", - span: Span( - start: 14, - end: 19, - ), - ), - generic_params: Node( - kind: [], - span: Span( - start: 14, - end: 19, - ), - ), - args: [ - Node( - kind: Regular(RegularFunctionArg( - label: None, - name: Node( - kind: "x", - span: Span( - start: 20, - end: 21, - ), + sig: Node( + kind: FunctionSignature( + pub_: Some(Span( + start: 0, + end: 3, + )), + unsafe_: Some(Span( + start: 4, + end: 10, + )), + name: Node( + kind: "foo21", + span: Span( + start: 14, + end: 19, ), - typ: Node( - kind: Base( - base: "bool", - ), - span: Span( - start: 23, - end: 27, - ), + ), + generic_params: Node( + kind: [], + span: Span( + start: 14, + end: 19, ), - )), - span: Span( - start: 20, - end: 27, ), - ), - Node( - kind: Regular(RegularFunctionArg( - label: None, - name: Node( - kind: "y", + args: [ + Node( + kind: Regular(RegularFunctionArg( + label: None, + name: Node( + kind: "x", + span: Span( + start: 20, + end: 21, + ), + ), + typ: Node( + kind: Base( + base: "bool", + ), + span: Span( + start: 23, + end: 27, + ), + ), + )), span: Span( - start: 29, - end: 30, + start: 20, + end: 27, ), ), - typ: Node( - kind: Base( - base: "address", - ), + Node( + kind: Regular(RegularFunctionArg( + label: None, + name: Node( + kind: "y", + span: Span( + start: 29, + end: 30, + ), + ), + typ: Node( + kind: Base( + base: "address", + ), + span: Span( + start: 32, + end: 39, + ), + ), + )), span: Span( - start: 32, + start: 29, end: 39, ), ), + ], + return_type: Some(Node( + kind: Base( + base: "bool", + ), + span: Span( + start: 45, + end: 49, + ), )), - span: Span( - start: 29, - end: 39, - ), - ), - ], - return_type: Some(Node( - kind: Base( - base: "bool", ), span: Span( - start: 45, + start: 0, end: 49, ), - )), + ), body: [ Node( kind: Expr( diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_unsafe.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_unsafe.snap index c381419f85..c76075ff42 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_unsafe.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_unsafe.snap @@ -1,6 +1,6 @@ --- source: crates/parser/tests/cases/parse_ast.rs -expression: "ast_string(stringify!(fn_def_unsafe), try_parse_module,\n \"unsafe fn foo21(x: bool, y: address,) -> bool {\\n x\\n}\")" +expression: "ast_string(stringify!(fn_def_unsafe), try_parse_module,\n \"unsafe fn foo21(x: bool, y: address,) -> bool {\\n x\\n}\")" --- Node( @@ -8,86 +8,94 @@ Node( body: [ Function(Node( kind: Function( - pub_: None, - unsafe_: Some(Span( - start: 0, - end: 6, - )), - name: Node( - kind: "foo21", - span: Span( - start: 10, - end: 15, - ), - ), - generic_params: Node( - kind: [], - span: Span( - start: 10, - end: 15, - ), - ), - args: [ - Node( - kind: Regular(RegularFunctionArg( - label: None, - name: Node( - kind: "x", - span: Span( - start: 16, - end: 17, - ), + sig: Node( + kind: FunctionSignature( + pub_: None, + unsafe_: Some(Span( + start: 0, + end: 6, + )), + name: Node( + kind: "foo21", + span: Span( + start: 10, + end: 15, ), - typ: Node( - kind: Base( - base: "bool", - ), - span: Span( - start: 19, - end: 23, - ), + ), + generic_params: Node( + kind: [], + span: Span( + start: 10, + end: 15, ), - )), - span: Span( - start: 16, - end: 23, ), - ), - Node( - kind: Regular(RegularFunctionArg( - label: None, - name: Node( - kind: "y", + args: [ + Node( + kind: Regular(RegularFunctionArg( + label: None, + name: Node( + kind: "x", + span: Span( + start: 16, + end: 17, + ), + ), + typ: Node( + kind: Base( + base: "bool", + ), + span: Span( + start: 19, + end: 23, + ), + ), + )), span: Span( - start: 25, - end: 26, + start: 16, + end: 23, ), ), - typ: Node( - kind: Base( - base: "address", - ), + Node( + kind: Regular(RegularFunctionArg( + label: None, + name: Node( + kind: "y", + span: Span( + start: 25, + end: 26, + ), + ), + typ: Node( + kind: Base( + base: "address", + ), + span: Span( + start: 28, + end: 35, + ), + ), + )), span: Span( - start: 28, + start: 25, end: 35, ), ), + ], + return_type: Some(Node( + kind: Base( + base: "bool", + ), + span: Span( + start: 41, + end: 45, + ), )), - span: Span( - start: 25, - end: 35, - ), - ), - ], - return_type: Some(Node( - kind: Base( - base: "bool", ), span: Span( - start: 41, + start: 0, end: 45, ), - )), + ), body: [ Node( kind: Expr( diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__guest_book.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__guest_book.snap index 268c529205..d145fbed9b 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__guest_book.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__guest_book.snap @@ -1,6 +1,6 @@ --- source: crates/parser/tests/cases/parse_ast.rs -expression: "ast_string(stringify!(guest_book), try_parse_module,\n r#\"\ntype BookMsg = Array\n\ncontract GuestBook {\n pub guest_book: Map\n\n event Signed {\n idx book_msg: BookMsg\n }\n\n pub fn sign(self, book_msg: BookMsg) {\n self.guest_book[msg.sender] = book_msg\n\n emit Signed(book_msg: book_msg)\n }\n pub fn get_msg(self, addr: address) -> BookMsg {\n return self.guest_book[addr]\n }\n}\"#)" +expression: "ast_string(stringify!(guest_book), try_parse_module,\n r#\"\ntype BookMsg = Array\n\ncontract GuestBook {\n pub guest_book: Map\n\n event Signed {\n idx book_msg: BookMsg\n }\n\n pub fn sign(self, book_msg: BookMsg) {\n self.guest_book[msg.sender] = book_msg\n\n emit Signed(book_msg: book_msg)\n }\n pub fn get_msg(self, addr: address) -> BookMsg {\n return self.guest_book[addr]\n }\n}\"#)" --- Node( @@ -177,60 +177,68 @@ Node( )), Function(Node( kind: Function( - pub_: Some(Span( - start: 159, - end: 162, - )), - unsafe_: None, - name: Node( - kind: "sign", - span: Span( - start: 166, - end: 170, - ), - ), - generic_params: Node( - kind: [], - span: Span( - start: 166, - end: 170, - ), - ), - args: [ - Node( - kind: Self_, - span: Span( - start: 171, - end: 175, + sig: Node( + kind: FunctionSignature( + pub_: Some(Span( + start: 159, + end: 162, + )), + unsafe_: None, + name: Node( + kind: "sign", + span: Span( + start: 166, + end: 170, + ), ), - ), - Node( - kind: Regular(RegularFunctionArg( - label: None, - name: Node( - kind: "book_msg", + generic_params: Node( + kind: [], + span: Span( + start: 166, + end: 170, + ), + ), + args: [ + Node( + kind: Self_, span: Span( - start: 177, - end: 185, + start: 171, + end: 175, ), ), - typ: Node( - kind: Base( - base: "BookMsg", - ), + Node( + kind: Regular(RegularFunctionArg( + label: None, + name: Node( + kind: "book_msg", + span: Span( + start: 177, + end: 185, + ), + ), + typ: Node( + kind: Base( + base: "BookMsg", + ), + span: Span( + start: 187, + end: 194, + ), + ), + )), span: Span( - start: 187, + start: 177, end: 194, ), ), - )), - span: Span( - start: 177, - end: 194, - ), + ], + return_type: None, ), - ], - return_type: None, + span: Span( + start: 159, + end: 195, + ), + ), body: [ Node( kind: Assign( @@ -353,68 +361,76 @@ Node( )), Function(Node( kind: Function( - pub_: Some(Span( - start: 296, - end: 299, - )), - unsafe_: None, - name: Node( - kind: "get_msg", - span: Span( - start: 303, - end: 310, - ), - ), - generic_params: Node( - kind: [], - span: Span( - start: 303, - end: 310, - ), - ), - args: [ - Node( - kind: Self_, - span: Span( - start: 311, - end: 315, + sig: Node( + kind: FunctionSignature( + pub_: Some(Span( + start: 296, + end: 299, + )), + unsafe_: None, + name: Node( + kind: "get_msg", + span: Span( + start: 303, + end: 310, + ), ), - ), - Node( - kind: Regular(RegularFunctionArg( - label: None, - name: Node( - kind: "addr", + generic_params: Node( + kind: [], + span: Span( + start: 303, + end: 310, + ), + ), + args: [ + Node( + kind: Self_, span: Span( - start: 317, - end: 321, + start: 311, + end: 315, ), ), - typ: Node( - kind: Base( - base: "address", - ), + Node( + kind: Regular(RegularFunctionArg( + label: None, + name: Node( + kind: "addr", + span: Span( + start: 317, + end: 321, + ), + ), + typ: Node( + kind: Base( + base: "address", + ), + span: Span( + start: 323, + end: 330, + ), + ), + )), span: Span( - start: 323, + start: 317, end: 330, ), ), + ], + return_type: Some(Node( + kind: Base( + base: "BookMsg", + ), + span: Span( + start: 335, + end: 342, + ), )), - span: Span( - start: 317, - end: 330, - ), - ), - ], - return_type: Some(Node( - kind: Base( - base: "BookMsg", ), span: Span( - start: 335, + start: 296, end: 342, ), - )), + ), body: [ Node( kind: Return( diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__module_level_events.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__module_level_events.snap index 90e00d2467..9f0a3c4b3c 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__module_level_events.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__module_level_events.snap @@ -1,6 +1,6 @@ --- source: crates/parser/tests/cases/parse_ast.rs -expression: "ast_string(stringify!(module_level_events), try_parse_module,\n r#\"\nuse std::context::Context\n\nevent Transfer {\n idx sender: address\n idx receiver: address\n value: u256\n}\ncontract Foo {\n fn transfer(ctx: Context, to: address, value: u256) {\n emit Transfer(ctx, sender: msg.sender, receiver: to, value)\n }\n}\n\"#)" +expression: "ast_string(stringify!(module_level_events), try_parse_module,\n r#\"\nuse std::context::Context\n\nevent Transfer {\n idx sender: address\n idx receiver: address\n value: u256\n}\ncontract Foo {\n fn transfer(ctx: Context, to: address, value: u256) {\n emit Transfer(ctx, sender: msg.sender, receiver: to, value)\n }\n}\n\"#)" --- Node( @@ -154,100 +154,108 @@ Node( body: [ Function(Node( kind: Function( - pub_: None, - unsafe_: None, - name: Node( - kind: "transfer", - span: Span( - start: 135, - end: 143, - ), - ), - generic_params: Node( - kind: [], - span: Span( - start: 135, - end: 143, - ), - ), - args: [ - Node( - kind: Regular(RegularFunctionArg( - label: None, - name: Node( - kind: "ctx", - span: Span( - start: 144, - end: 147, - ), + sig: Node( + kind: FunctionSignature( + pub_: None, + unsafe_: None, + name: Node( + kind: "transfer", + span: Span( + start: 135, + end: 143, ), - typ: Node( - kind: Base( - base: "Context", - ), - span: Span( - start: 149, - end: 156, - ), + ), + generic_params: Node( + kind: [], + span: Span( + start: 135, + end: 143, ), - )), - span: Span( - start: 144, - end: 156, ), - ), - Node( - kind: Regular(RegularFunctionArg( - label: None, - name: Node( - kind: "to", + args: [ + Node( + kind: Regular(RegularFunctionArg( + label: None, + name: Node( + kind: "ctx", + span: Span( + start: 144, + end: 147, + ), + ), + typ: Node( + kind: Base( + base: "Context", + ), + span: Span( + start: 149, + end: 156, + ), + ), + )), span: Span( - start: 158, - end: 160, + start: 144, + end: 156, ), ), - typ: Node( - kind: Base( - base: "address", - ), + Node( + kind: Regular(RegularFunctionArg( + label: None, + name: Node( + kind: "to", + span: Span( + start: 158, + end: 160, + ), + ), + typ: Node( + kind: Base( + base: "address", + ), + span: Span( + start: 162, + end: 169, + ), + ), + )), span: Span( - start: 162, + start: 158, end: 169, ), ), - )), - span: Span( - start: 158, - end: 169, - ), - ), - Node( - kind: Regular(RegularFunctionArg( - label: None, - name: Node( - kind: "value", + Node( + kind: Regular(RegularFunctionArg( + label: None, + name: Node( + kind: "value", + span: Span( + start: 171, + end: 176, + ), + ), + typ: Node( + kind: Base( + base: "u256", + ), + span: Span( + start: 178, + end: 182, + ), + ), + )), span: Span( start: 171, - end: 176, - ), - ), - typ: Node( - kind: Base( - base: "u256", - ), - span: Span( - start: 178, end: 182, ), ), - )), - span: Span( - start: 171, - end: 182, - ), + ], + return_type: None, ), - ], - return_type: None, + span: Span( + start: 132, + end: 183, + ), + ), body: [ Node( kind: Emit( diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__module_stmts.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__module_stmts.snap index 95c9accc16..579339eaa3 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__module_stmts.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__module_stmts.snap @@ -1,6 +1,6 @@ --- source: crates/parser/tests/cases/parse_ast.rs -expression: "ast_string(stringify!(module_stmts), try_parse_module,\n r#\"\npragma 0.5.0\n\nuse foo::bar::{\n bing as bong,\n food::*\n}\n\ntype X = Map\n\npub fn double(x: u8) -> u8 {\n return x * 2\n}\n\nfn secret() -> u8 { return 0xBEEF }\n\ncontract A {\n pub const x: u256 = 10\n}\n\ncontract B {\n pub x: X\n}\"#)" +expression: "ast_string(stringify!(module_stmts), try_parse_module,\n r#\"\npragma 0.5.0\n\nuse foo::bar::{\n bing as bong,\n food::*\n}\n\ntype X = Map\n\npub fn double(x: u8) -> u8 {\n return x * 2\n}\n\nfn secret() -> u8 { return 0xBEEF }\n\ncontract A {\n pub const x: u256 = 10\n}\n\ncontract B {\n pub x: X\n}\"#)" --- Node( @@ -161,61 +161,69 @@ Node( )), Function(Node( kind: Function( - pub_: Some(Span( - start: 87, - end: 90, - )), - unsafe_: None, - name: Node( - kind: "double", - span: Span( - start: 94, - end: 100, - ), - ), - generic_params: Node( - kind: [], - span: Span( - start: 94, - end: 100, - ), - ), - args: [ - Node( - kind: Regular(RegularFunctionArg( - label: None, - name: Node( - kind: "x", - span: Span( - start: 101, - end: 102, - ), + sig: Node( + kind: FunctionSignature( + pub_: Some(Span( + start: 87, + end: 90, + )), + unsafe_: None, + name: Node( + kind: "double", + span: Span( + start: 94, + end: 100, ), - typ: Node( - kind: Base( - base: "u8", - ), + ), + generic_params: Node( + kind: [], + span: Span( + start: 94, + end: 100, + ), + ), + args: [ + Node( + kind: Regular(RegularFunctionArg( + label: None, + name: Node( + kind: "x", + span: Span( + start: 101, + end: 102, + ), + ), + typ: Node( + kind: Base( + base: "u8", + ), + span: Span( + start: 104, + end: 106, + ), + ), + )), span: Span( - start: 104, + start: 101, end: 106, ), ), + ], + return_type: Some(Node( + kind: Base( + base: "u8", + ), + span: Span( + start: 111, + end: 113, + ), )), - span: Span( - start: 101, - end: 106, - ), - ), - ], - return_type: Some(Node( - kind: Base( - base: "u8", ), span: Span( - start: 111, + start: 87, end: 113, ), - )), + ), body: [ Node( kind: Return( @@ -263,32 +271,40 @@ Node( )), Function(Node( kind: Function( - pub_: None, - unsafe_: None, - name: Node( - kind: "secret", - span: Span( - start: 139, - end: 145, - ), - ), - generic_params: Node( - kind: [], - span: Span( - start: 139, - end: 145, - ), - ), - args: [], - return_type: Some(Node( - kind: Base( - base: "u8", + sig: Node( + kind: FunctionSignature( + pub_: None, + unsafe_: None, + name: Node( + kind: "secret", + span: Span( + start: 139, + end: 145, + ), + ), + generic_params: Node( + kind: [], + span: Span( + start: 139, + end: 145, + ), + ), + args: [], + return_type: Some(Node( + kind: Base( + base: "u8", + ), + span: Span( + start: 151, + end: 153, + ), + )), ), span: Span( - start: 151, + start: 136, end: 153, ), - )), + ), body: [ Node( kind: Return( diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__pub_contract_def.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__pub_contract_def.snap index 21cc0bead1..ae34137516 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__pub_contract_def.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__pub_contract_def.snap @@ -1,6 +1,6 @@ --- source: crates/parser/tests/cases/parse_ast.rs -expression: "ast_string(stringify!(pub_contract_def), try_parse_module,\n r#\"\npub contract Foo {\n pub fn foo() -> u8 {\n return 10\n }\n}\"#)" +expression: "ast_string(stringify!(pub_contract_def), try_parse_module,\n r#\"\npub contract Foo {\n pub fn foo() -> u8 {\n return 10\n }\n}\"#)" --- Node( @@ -19,35 +19,43 @@ Node( body: [ Function(Node( kind: Function( - pub_: Some(Span( - start: 24, - end: 27, - )), - unsafe_: None, - name: Node( - kind: "foo", - span: Span( - start: 31, - end: 34, - ), - ), - generic_params: Node( - kind: [], - span: Span( - start: 31, - end: 34, - ), - ), - args: [], - return_type: Some(Node( - kind: Base( - base: "u8", + sig: Node( + kind: FunctionSignature( + pub_: Some(Span( + start: 24, + end: 27, + )), + unsafe_: None, + name: Node( + kind: "foo", + span: Span( + start: 31, + end: 34, + ), + ), + generic_params: Node( + kind: [], + span: Span( + start: 31, + end: 34, + ), + ), + args: [], + return_type: Some(Node( + kind: Base( + base: "u8", + ), + span: Span( + start: 40, + end: 42, + ), + )), ), span: Span( - start: 40, + start: 24, end: 42, ), - )), + ), body: [ Node( kind: Return( diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__struct_def.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__struct_def.snap index c19c7f19ee..9e8461d9cc 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__struct_def.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__struct_def.snap @@ -1,6 +1,6 @@ --- source: crates/parser/tests/cases/parse_ast.rs -expression: "ast_string(stringify!(struct_def), try_parse_module,\n r#\"struct S {\n x: address\n pub y: u8\n z: u8\n pub a: Map\n\n pub fn foo(self) -> u8 {\n return self.z + self.y\n }\n unsafe fn bar() {}\n}\"#)" +expression: "ast_string(stringify!(struct_def), try_parse_module,\n r#\"struct S {\n x: address\n pub y: u8\n z: u8\n pub a: Map\n\n pub fn foo(self) -> u8 {\n return self.z + self.y\n }\n unsafe fn bar() {}\n}\"#)" --- Node( @@ -160,43 +160,51 @@ Node( functions: [ Node( kind: Function( - pub_: Some(Span( - start: 69, - end: 72, - )), - unsafe_: None, - name: Node( - kind: "foo", - span: Span( - start: 76, - end: 79, - ), - ), - generic_params: Node( - kind: [], - span: Span( - start: 76, - end: 79, - ), - ), - args: [ - Node( - kind: Self_, - span: Span( - start: 80, - end: 84, + sig: Node( + kind: FunctionSignature( + pub_: Some(Span( + start: 69, + end: 72, + )), + unsafe_: None, + name: Node( + kind: "foo", + span: Span( + start: 76, + end: 79, + ), ), - ), - ], - return_type: Some(Node( - kind: Base( - base: "u8", + generic_params: Node( + kind: [], + span: Span( + start: 76, + end: 79, + ), + ), + args: [ + Node( + kind: Self_, + span: Span( + start: 80, + end: 84, + ), + ), + ], + return_type: Some(Node( + kind: Base( + base: "u8", + ), + span: Span( + start: 89, + end: 91, + ), + )), ), span: Span( - start: 89, + start: 69, end: 91, ), - )), + ), body: [ Node( kind: Return( @@ -274,27 +282,35 @@ Node( ), Node( kind: Function( - pub_: None, - unsafe_: Some(Span( - start: 127, - end: 133, - )), - name: Node( - kind: "bar", - span: Span( - start: 137, - end: 140, + sig: Node( + kind: FunctionSignature( + pub_: None, + unsafe_: Some(Span( + start: 127, + end: 133, + )), + name: Node( + kind: "bar", + span: Span( + start: 137, + end: 140, + ), + ), + generic_params: Node( + kind: [], + span: Span( + start: 137, + end: 140, + ), + ), + args: [], + return_type: None, ), - ), - generic_params: Node( - kind: [], span: Span( - start: 137, - end: 140, + start: 127, + end: 142, ), ), - args: [], - return_type: None, body: [], ), span: Span( diff --git a/crates/test-files/fixtures/compile_errors/bad_visibility/src/foo.fe b/crates/test-files/fixtures/compile_errors/bad_visibility/src/foo.fe index 08fc81c6e4..2be73b7d16 100644 --- a/crates/test-files/fixtures/compile_errors/bad_visibility/src/foo.fe +++ b/crates/test-files/fixtures/compile_errors/bad_visibility/src/foo.fe @@ -2,6 +2,8 @@ type MyInt = i32 const MY_CONST: MyInt = 1 +trait MyTrait {} + event MyEvent { x: i32 } diff --git a/crates/test-files/fixtures/compile_errors/bad_visibility/src/main.fe b/crates/test-files/fixtures/compile_errors/bad_visibility/src/main.fe index ab55b559c8..82ecfb93ce 100644 --- a/crates/test-files/fixtures/compile_errors/bad_visibility/src/main.fe +++ b/crates/test-files/fixtures/compile_errors/bad_visibility/src/main.fe @@ -1,6 +1,9 @@ -use foo::{MyInt, MY_CONST, MyEvent, MyStruct, my_func, MyContract} +use foo::{MyInt, MY_CONST, MyEvent, MyStruct, MyTrait, my_func, MyContract} use std::context::Context +struct SomeThing {} +impl MyTrait for SomeThing {} + contract Main { pub fn priv_type_alias() -> MyInt { let x: MyInt = 1 diff --git a/crates/test-files/fixtures/compile_errors/call_generic_function_with_unsatisfied_bound.fe b/crates/test-files/fixtures/compile_errors/call_generic_function_with_unsatisfied_bound.fe new file mode 100644 index 0000000000..a3b65ddab0 --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/call_generic_function_with_unsatisfied_bound.fe @@ -0,0 +1,18 @@ +trait Dummy {} + +struct Bar {} + +struct Foo { + + pub fn bar(self, _ x: T) -> bool { + return true + } +} + +contract Meh { + + pub fn call_bar(self) { + let foo: Foo = Foo(); + foo.bar(Bar()) + } +} diff --git a/crates/test-files/fixtures/compile_errors/contract_function_with_generic_params.fe b/crates/test-files/fixtures/compile_errors/contract_function_with_generic_params.fe new file mode 100644 index 0000000000..8339a96c37 --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/contract_function_with_generic_params.fe @@ -0,0 +1,5 @@ +trait Dummy {} + +contract Foo { + pub fn bar(val: T) {} +} diff --git a/crates/test-files/fixtures/compile_errors/duplicate_generic_params.fe b/crates/test-files/fixtures/compile_errors/duplicate_generic_params.fe new file mode 100644 index 0000000000..f5e4f741d9 --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/duplicate_generic_params.fe @@ -0,0 +1,13 @@ +trait Computable { + fn compute(self, val: u256) -> u256; +} + +trait Computable2 { + fn compute2(self, val: u256) -> u256; +} + +struct Yay { + fn foo(val1: T, val2: T) { + return + } +} diff --git a/crates/test-files/fixtures/compile_errors/invalid_generic_bound.fe b/crates/test-files/fixtures/compile_errors/invalid_generic_bound.fe new file mode 100644 index 0000000000..5d67b8364e --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/invalid_generic_bound.fe @@ -0,0 +1,3 @@ +struct Foo { + pub fn bar(val: T) {} +} \ No newline at end of file diff --git a/crates/test-files/fixtures/compile_errors/invalid_impl_location.fe b/crates/test-files/fixtures/compile_errors/invalid_impl_location.fe new file mode 100644 index 0000000000..e25354073a --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/invalid_impl_location.fe @@ -0,0 +1,4 @@ +use std::traits::Dummy +use std::context::Context + +impl Dummy for Context {} diff --git a/crates/test-files/fixtures/compile_errors/invalid_impl_type.fe b/crates/test-files/fixtures/compile_errors/invalid_impl_type.fe new file mode 100644 index 0000000000..befc605985 --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/invalid_impl_type.fe @@ -0,0 +1,3 @@ +trait SomeTrait {} + +impl SomeTrait for Map {} \ No newline at end of file diff --git a/crates/test-files/fixtures/compile_errors/trait_conflicting_impls.fe b/crates/test-files/fixtures/compile_errors/trait_conflicting_impls.fe new file mode 100644 index 0000000000..08f7f2dbdc --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/trait_conflicting_impls.fe @@ -0,0 +1,7 @@ +trait Foo {} + +struct Bar { +} + +impl Foo for Bar {} +impl Foo for Bar {} \ No newline at end of file diff --git a/crates/test-files/fixtures/compile_errors/trait_fn_with_generic_params.fe b/crates/test-files/fixtures/compile_errors/trait_fn_with_generic_params.fe new file mode 100644 index 0000000000..e28336151d --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/trait_fn_with_generic_params.fe @@ -0,0 +1,5 @@ +trait Bar {} + +trait Foo { + fn generic_fn(self, val: T); +} diff --git a/crates/test-files/fixtures/compile_errors/trait_fn_without_self.fe b/crates/test-files/fixtures/compile_errors/trait_fn_without_self.fe new file mode 100644 index 0000000000..906b15483f --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/trait_fn_without_self.fe @@ -0,0 +1,3 @@ +trait Foo { + fn this_has_no_self(); +} diff --git a/crates/test-files/fixtures/compile_errors/trait_impl_mismatch.fe b/crates/test-files/fixtures/compile_errors/trait_impl_mismatch.fe new file mode 100644 index 0000000000..0344138e69 --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/trait_impl_mismatch.fe @@ -0,0 +1,16 @@ +trait Foo { + fn this_misses_in_impl(self); + fn this_has_wrong_args_in_impl(self, val: u8, val2: bool); + fn this_has_wrong_return_type_in_impl(self) -> bool; +} + +struct Bar {} + +impl Foo for Bar { + + fn this_has_wrong_args_in_impl(self, val: u16, val2: Array) {} + fn this_has_wrong_return_type_in_impl(self) -> u8 { + return 0 + } + fn this_does_not_exist_in_trait() {} +} \ No newline at end of file diff --git a/crates/test-files/fixtures/compile_errors/traits_as_fields.fe b/crates/test-files/fixtures/compile_errors/traits_as_fields.fe new file mode 100644 index 0000000000..b69165b213 --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/traits_as_fields.fe @@ -0,0 +1,9 @@ +trait Foo {} + +struct Bar { + val: Foo +} + +contract Nope { + val: Foo +} \ No newline at end of file diff --git a/crates/test-files/fixtures/compile_errors/traits_with_wrong_bounds.fe b/crates/test-files/fixtures/compile_errors/traits_with_wrong_bounds.fe new file mode 100644 index 0000000000..6276fa1195 --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/traits_with_wrong_bounds.fe @@ -0,0 +1,5 @@ +trait Some {} + +struct Bar { + pub fn no_bounds(val: T) {} +} diff --git a/crates/test-files/fixtures/features/generic_functions.fe b/crates/test-files/fixtures/features/generic_functions.fe new file mode 100644 index 0000000000..b9bead861a --- /dev/null +++ b/crates/test-files/fixtures/features/generic_functions.fe @@ -0,0 +1,42 @@ +trait Computable { + fn compute(self, val: u256) -> u256; +} + +struct Mac {} + +impl Computable for Mac { + fn compute(self, val: u256) -> u256 { + return 1 + val + } +} + +struct Linux { + pub counter: u256 + pub fn get_counter(self) -> u256 { + return self.counter + } + pub fn something_static() -> u256 { + return 5 + } +} + +impl Computable for Linux { + fn compute(self, val: u256) -> u256 { + return val + Linux::something_static() + self.get_counter() + } +} + +struct Runner { + + pub fn run(self, _ val: T) -> u256 { + return val.compute(val: 1000) + } +} + +contract Example { + pub fn generic_compute(self) { + let runner: Runner = Runner(); + assert runner.run(Mac()) == 1001 + assert runner.run(Linux(counter: 10)) == 1015 + } +} diff --git a/crates/tests/src/features.rs b/crates/tests/src/features.rs index 7db271b4f7..e1ddc1b5ae 100644 --- a/crates/tests/src/features.rs +++ b/crates/tests/src/features.rs @@ -2038,3 +2038,11 @@ fn ctx_init_in_call() { } }); } + +#[test] +fn generics() { + with_executor(&|mut executor| { + let harness = deploy_contract(&mut executor, "generic_functions.fe", "Example", &[]); + harness.test_function(&mut executor, "generic_compute", &[], None); + }); +} diff --git a/newsfragments/710.feature.md b/newsfragments/710.feature.md new file mode 100644 index 0000000000..482eca1b7c --- /dev/null +++ b/newsfragments/710.feature.md @@ -0,0 +1,55 @@ +traits and generic function parameter + +Traits can now be defined, e.g: + +``` +trait Computable { + fn compute(self, val: u256) -> u256; +} +``` + +For now, traits can only be implemented for structs. +The mechanism to implement a trait is via an `impl` block e.g: + +``` +struct Linux { + pub counter: u256 + pub fn get_counter(self) -> u256 { + return self.counter + } + pub fn something_static() -> u256 { + return 5 + } +} + +impl Computable for Linux { + fn compute(self, val: u256) -> u256 { + return val + Linux::something_static() + self.get_counter() + } +} +``` + +Traits can only appear as bounds for generic functions e.g.: + +``` +struct Runner { + + pub fn run(self, _ val: T) -> u256 { + return val.compute(val: 1000) + } +} +``` + +Only `struct` functions (not `contract` functions) can have generic parameters. +The `run` method of `Runner` can be called with any type that implements `Computable` e.g. + +``` +contract Example { + + pub fn generic_compute(self) { + let runner: Runner = Runner(); + assert runner.run(Mac()) == 1001 + assert runner.run(Linux(counter: 10)) == 1015 + } +} +```