Skip to content

Commit

Permalink
Support implementing trait functions in impl blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
cburgdorf committed Jun 16, 2022
1 parent dee378a commit 5ac761f
Show file tree
Hide file tree
Showing 63 changed files with 2,132 additions and 1,040 deletions.
27 changes: 23 additions & 4 deletions crates/analyzer/src/context.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -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"),
}
}
Expand Down Expand Up @@ -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,
Expand All @@ -479,6 +496,7 @@ impl CallType {
| BuiltinValueMethod { .. }
| TypeConstructor(_)
| Intrinsic(_)
| TraitValueMethod { .. }
| BuiltinAssociatedFunction { .. } => None,
AssociatedFunction { function: id, .. }
| ValueMethod { method: id, .. }
Expand All @@ -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(),
}
}
Expand Down
26 changes: 21 additions & 5 deletions crates/analyzer/src/db.rs
Original file line number Diff line number Diff line change
@@ -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, Impl, IngotId, Item,
ModuleConstantId, ModuleId, StructFieldId, StructId, TraitId, 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;
Expand All @@ -26,6 +26,8 @@ pub trait AnalyzerDb: SourceDb + Upcast<dyn SourceDb> + UpcastMut<dyn SourceDb>
#[salsa::interned]
fn intern_trait(&self, data: Rc<items::Trait>) -> TraitId;
#[salsa::interned]
fn intern_impl(&self, data: Rc<items::Impl>) -> ImplId;
#[salsa::interned]
fn intern_struct_field(&self, data: Rc<items::StructField>) -> StructFieldId;
#[salsa::interned]
fn intern_type_alias(&self, data: Rc<items::TypeAlias>) -> TypeAliasId;
Expand All @@ -34,6 +36,8 @@ pub trait AnalyzerDb: SourceDb + Upcast<dyn SourceDb> + UpcastMut<dyn SourceDb>
#[salsa::interned]
fn intern_contract_field(&self, data: Rc<items::ContractField>) -> ContractFieldId;
#[salsa::interned]
fn intern_function_sig(&self, data: Rc<items::FunctionSig>) -> FunctionSigId;
#[salsa::interned]
fn intern_function(&self, data: Rc<items::Function>) -> FunctionId;
#[salsa::interned]
fn intern_event(&self, data: Rc<items::Event>) -> EventId;
Expand Down Expand Up @@ -63,9 +67,11 @@ pub trait AnalyzerDb: SourceDb + Upcast<dyn SourceDb> + UpcastMut<dyn SourceDb>
#[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<[Impl]>;
fn module_all_impls(&self, module: ModuleId) -> Rc<[ImplId]>;
#[salsa::invoke(queries::module::module_item_map)]
fn module_item_map(&self, module: ModuleId) -> Analysis<Rc<IndexMap<SmolStr, Item>>>;
#[salsa::invoke(queries::module::module_impl_map)]
fn module_impl_map(&self, module: ModuleId) -> Analysis<Rc<IndexMap<(TraitId, Type), ImplId>>>;
#[salsa::invoke(queries::module::module_contracts)]
fn module_contracts(&self, module: ModuleId) -> Rc<[ContractId]>;
#[salsa::invoke(queries::module::module_structs)]
Expand Down Expand Up @@ -134,7 +140,7 @@ pub trait AnalyzerDb: SourceDb + Upcast<dyn SourceDb> + UpcastMut<dyn SourceDb>

// Function
#[salsa::invoke(queries::functions::function_signature)]
fn function_signature(&self, id: FunctionId) -> Analysis<Rc<types::FunctionSignature>>;
fn function_signature(&self, id: FunctionSigId) -> Analysis<Rc<types::FunctionSignature>>;
#[salsa::invoke(queries::functions::function_body)]
fn function_body(&self, id: FunctionId) -> Analysis<Rc<FunctionBody>>;
#[salsa::cycle(queries::functions::function_dependency_graph_cycle)]
Expand All @@ -161,6 +167,16 @@ pub trait AnalyzerDb: SourceDb + Upcast<dyn SourceDb> + UpcastMut<dyn SourceDb>
// Trait
#[salsa::invoke(queries::traits::trait_type)]
fn trait_type(&self, id: TraitId) -> Rc<types::Trait>;
#[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<Rc<IndexMap<SmolStr, FunctionSigId>>>;

// 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<Rc<IndexMap<SmolStr, FunctionId>>>;

// Event
#[salsa::invoke(queries::events::event_type)]
Expand Down
1 change: 1 addition & 0 deletions crates/analyzer/src/db/queries.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod contracts;
pub mod events;
pub mod functions;
pub mod impls;
pub mod ingots;
pub mod module;
pub mod structs;
Expand Down
22 changes: 13 additions & 9 deletions crates/analyzer/src/db/queries/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
}
Expand Down
81 changes: 63 additions & 18 deletions crates/analyzer/src/db/queries/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,27 @@ use crate::context::{AnalyzerContext, CallType, FunctionBody};
use crate::db::{Analysis, AnalyzerDb};
use crate::errors::TypeError;
use crate::namespace::items::{
Class, DepGraph, DepGraphWrapper, DepLocality, FunctionId, Item, TypeDef,
Class, DepGraph, DepGraphWrapper, DepLocality, FunctionId, FunctionSigId, Item, TypeDef,
};
use crate::namespace::scopes::{BlockScope, BlockScopeType, FunctionScope, ItemScope};
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;

/// Gather context information for a function definition and check for type
/// errors. Does not inspect the function body.
pub fn function_signature(
db: &dyn AnalyzerDb,
function: FunctionId,
function: FunctionSigId,
) -> Analysis<Rc<types::FunctionSignature>> {
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);
Expand All @@ -32,9 +32,27 @@ pub fn function_signature(
let mut names = HashMap::new();
let mut labels = HashMap::new();

if let (Some(Class::Contract(_)), true) = (fn_parent, function.is_generic(db)) {
function.data(db).ast.kind.generic_params.kind.iter().fold(
HashMap::<SmolStr, Node<_>>::new(),
|mut accum, param| {
if let Some(previous) = accum.get(&param.name()) {
scope.duplicate_name_error(
"duplicate generic parameter",
&param.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 in contract functions",
"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",
Expand All @@ -43,7 +61,26 @@ pub fn function_signature(
);
}

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()
Expand Down Expand Up @@ -73,7 +110,7 @@ pub fn function_signature(
_ => 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()),
))),
});

Expand Down Expand Up @@ -114,9 +151,9 @@ pub fn function_signature(
if label.kind != "_";
if let Some(dup_idx) = labels.get(&label.kind);
then {
let dup_arg: &Node<ast::FunctionArg> = &def.args[*dup_idx];
let dup_arg: &Node<ast::FunctionArg> = &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"),
Expand All @@ -138,9 +175,9 @@ pub fn function_signature(
);
None
} else if let Some(dup_idx) = names.get(&reg.name.kind) {
let dup_arg: &Node<ast::FunctionArg> = &def.args[*dup_idx];
let dup_arg: &Node<ast::FunctionArg> = &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)),
&reg.name.kind,
dup_arg.span,
arg.span,
Expand All @@ -159,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 {
Expand Down Expand Up @@ -207,7 +245,7 @@ pub fn function_signature(

pub fn resolve_function_param_type(
db: &dyn AnalyzerDb,
function: FunctionId,
function: FunctionSigId,
context: &mut dyn AnalyzerContext,
desc: &Node<ast::TypeDesc>,
) -> Result<Type, TypeError> {
Expand Down Expand Up @@ -254,11 +292,11 @@ pub fn function_body(db: &dyn AnalyzerDb, function: FunctionId) -> Analysis<Rc<F
"function body is missing a return or revert statement",
vec![
Label::primary(
def.name.span,
function.name_span(db),
"all paths of this function must `return` or `revert`",
),
Label::secondary(
def.return_type.as_ref().unwrap().span,
def.sig.kind.return_type.as_ref().unwrap().span,
format!("expected function to return `{}`", return_type),
),
],
Expand Down Expand Up @@ -346,7 +384,7 @@ pub fn function_dependency_graph(db: &dyn AnalyzerDb, function: FunctionId) -> 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);
Expand All @@ -359,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:
Expand Down
Loading

0 comments on commit 5ac761f

Please sign in to comment.