Skip to content

Commit

Permalink
Merge pull request #755 from cburgdorf/christoph/feat/generics-for-all
Browse files Browse the repository at this point in the history
Allow traits to be implemented for almost all types
  • Loading branch information
sbillig authored Jul 1, 2022
2 parents 70e270a + 81aa152 commit b1cbf87
Show file tree
Hide file tree
Showing 19 changed files with 228 additions and 96 deletions.
12 changes: 5 additions & 7 deletions crates/analyzer/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ pub enum NamedThing {

/// The function's parent, if any. If `None`, `self` has been
/// used in a module-level function.
class: Option<Class>,
parent: Option<Item>,
span: Option<Span>,
},
// SelfType // when/if we add a `Self` type keyword
Expand Down Expand Up @@ -350,6 +350,8 @@ pub enum Location {
},
Memory,
Value,
// An unresolved location is used for generic types prior to monomorphization.
Unresolved,
}

impl Location {
Expand All @@ -358,12 +360,8 @@ impl Location {
pub fn assign_location(typ: &Type) -> Self {
match typ {
Type::Base(_) | Type::Contract(_) => Location::Value,
// For now assume that generics can only ever refer to structs
Type::Array(_)
| Type::Tuple(_)
| Type::String(_)
| Type::Struct(_)
| Type::Generic(_) => Location::Memory,
Type::Generic(_) => Location::Unresolved,
Type::Array(_) | Type::Tuple(_) | Type::String(_) | Type::Struct(_) => Location::Memory,
_ => panic!("Type can not be assigned, returned or passed"),
}
}
Expand Down
11 changes: 8 additions & 3 deletions crates/analyzer/src/db/queries/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,14 @@ 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::new(db, node, Some(items::Class::Contract(contract)), module),
))),
ast::ContractStmt::Function(node) => {
Some(db.intern_function(Rc::new(items::Function::new(
db,
node,
Some(Item::Type(TypeDef::Contract(contract))),
module,
))))
}
})
.collect()
}
Expand Down
16 changes: 8 additions & 8 deletions crates/analyzer/src/db/queries/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::db::{Analysis, AnalyzerDb};
use crate::display::Displayable;
use crate::errors::TypeError;
use crate::namespace::items::{
Class, DepGraph, DepGraphWrapper, DepLocality, FunctionId, FunctionSigId, Item, TypeDef,
DepGraph, DepGraphWrapper, DepLocality, FunctionId, FunctionSigId, Item, TypeDef,
};
use crate::namespace::scopes::{BlockScope, BlockScopeType, FunctionScope, ItemScope};
use crate::namespace::types::{self, CtxDecl, Generic, SelfDecl, Type, TypeId};
Expand All @@ -26,7 +26,7 @@ pub fn function_signature(
let def = &function.data(db).ast;

let mut scope = ItemScope::new(db, function.module(db));
let fn_parent = function.class(db);
let fn_parent = function.parent(db);

let mut self_decl = None;
let mut ctx_decl = None;
Expand All @@ -51,7 +51,7 @@ pub fn function_signature(
},
);

if !matches!(fn_parent, Some(Class::Struct(_))) && function.is_generic(db) {
if !matches!(fn_parent, Item::Type(TypeDef::Struct(_))) && function.is_generic(db) {
scope.fancy_error(
"generic function parameters aren't yet supported outside of struct functions",
vec![Label::primary(
Expand Down Expand Up @@ -87,11 +87,11 @@ pub fn function_signature(
.enumerate()
.filter_map(|(index, arg)| match &arg.kind {
ast::FunctionArg::Self_ => {
if fn_parent.is_none() {
if matches!(fn_parent, Item::Module(_)) {
scope.error(
"`self` can only be used in contract or struct functions",
"`self` can only be used in contract, struct, trait or impl functions",
arg.span,
"not allowed in functions defined outside of a contract or struct",
"not allowed in functions defined directly in a module",
);
} else {
self_decl = Some(SelfDecl::Mutable);
Expand Down Expand Up @@ -371,8 +371,8 @@ 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(db), DepLocality::Local));
if !function.sig(db).is_module_fn(db) {
directs.push((root, function.parent(db), DepLocality::Local));
}

let body = function.body(db);
Expand Down
4 changes: 2 additions & 2 deletions crates/analyzer/src/db/queries/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use indexmap::IndexMap;
use smol_str::SmolStr;

use crate::context::{Analysis, AnalyzerContext};
use crate::namespace::items::{Class, Function, FunctionId, ImplId};
use crate::namespace::items::{Function, FunctionId, ImplId, Item};
use crate::namespace::scopes::ItemScope;
use crate::AnalyzerDb;
use std::rc::Rc;
Expand All @@ -19,7 +19,7 @@ pub fn impl_all_functions(db: &dyn AnalyzerDb, impl_: ImplId) -> Rc<[FunctionId]
db.intern_function(Rc::new(Function::new(
db,
node,
Some(Class::Impl(impl_)),
Some(Item::Impl(impl_)),
impl_data.module,
)))
})
Expand Down
2 changes: 1 addition & 1 deletion crates/analyzer/src/db/queries/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ pub fn struct_all_functions(db: &dyn AnalyzerDb, struct_: StructId) -> Rc<[Funct
db.intern_function(Rc::new(items::Function::new(
db,
node,
Some(items::Class::Struct(struct_)),
Some(Item::Type(TypeDef::Struct(struct_))),
struct_data.module,
)))
})
Expand Down
4 changes: 2 additions & 2 deletions crates/analyzer/src/db/queries/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use indexmap::IndexMap;
use smol_str::SmolStr;

use crate::context::{Analysis, AnalyzerContext};
use crate::namespace::items::{FunctionSig, FunctionSigId, TraitId};
use crate::namespace::items::{FunctionSig, FunctionSigId, Item, TraitId};
use crate::namespace::scopes::ItemScope;
use crate::namespace::types::TypeId;
use crate::AnalyzerDb;
Expand All @@ -20,7 +20,7 @@ pub fn trait_all_functions(db: &dyn AnalyzerDb, trait_: TraitId) -> Rc<[Function
db.intern_function_sig(Rc::new(FunctionSig {
ast: node.clone(),
module: trait_.module(db),
parent: Some(trait_.as_class()),
parent: Some(Item::Trait(trait_)),
}))
})
.collect()
Expand Down
94 changes: 55 additions & 39 deletions crates/analyzer/src/namespace/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub enum Item {
// TypeDef, but it would have consequences.
Event(EventId),
Trait(TraitId),
Impl(ImplId),
Function(FunctionId),
Constant(ModuleConstantId),
// Needed until we can represent keccak256 as a FunctionId.
Expand All @@ -46,6 +47,7 @@ impl Item {
match self {
Item::Type(id) => id.name(db),
Item::Trait(id) => id.name(db),
Item::Impl(id) => id.name(db),
Item::GenericType(id) => id.name(),
Item::Event(id) => id.name(db),
Item::Function(id) => id.name(db),
Expand All @@ -65,9 +67,11 @@ impl Item {
Item::Event(id) => Some(id.name_span(db)),
Item::Function(id) => Some(id.name_span(db)),
Item::Constant(id) => Some(id.name_span(db)),
Item::BuiltinFunction(_) | Item::Intrinsic(_) | Item::Ingot(_) | Item::Module(_) => {
None
}
Item::BuiltinFunction(_)
| Item::Intrinsic(_)
| Item::Ingot(_)
| Item::Module(_)
| Item::Impl(_) => None,
}
}

Expand All @@ -78,6 +82,7 @@ impl Item {
| Self::Module(_)
| Self::BuiltinFunction(_)
| Self::Intrinsic(_)
| Self::Impl(_)
| Self::GenericType(_) => true,
Self::Type(id) => id.is_public(db),
Self::Trait(id) => id.is_public(db),
Expand All @@ -95,6 +100,7 @@ impl Item {
| Item::Intrinsic(_) => true,
Item::Type(_)
| Item::Trait(_)
| Item::Impl(_)
| Item::Event(_)
| Item::Function(_)
| Item::Constant(_)
Expand All @@ -115,6 +121,7 @@ impl Item {
match self {
Item::Type(_) | Item::GenericType(_) => "type",
Item::Trait(_) => "trait",
Item::Impl(_) => "impl",
Item::Event(_) => "event",
Item::Function(_) | Item::BuiltinFunction(_) => "function",
Item::Intrinsic(_) => "intrinsic function",
Expand All @@ -132,6 +139,7 @@ impl Item {
Item::GenericType(_)
| Item::Event(_)
| Item::Trait(_)
| Item::Impl(_)
| Item::Function(_)
| Item::Constant(_)
| Item::BuiltinFunction(_)
Expand All @@ -143,6 +151,7 @@ impl Item {
match self {
Item::Type(id) => id.parent(db),
Item::Trait(id) => Some(id.parent(db)),
Item::Impl(id) => Some(id.parent(db)),
Item::GenericType(_) => None,
Item::Event(id) => Some(id.parent(db)),
Item::Function(id) => Some(id.parent(db)),
Expand Down Expand Up @@ -223,10 +232,21 @@ impl Item {
}
}

pub fn as_class(&self) -> Option<Class> {
match self {
Item::Type(TypeDef::Contract(id)) => Some(Class::Contract(*id)),
Item::Type(TypeDef::Struct(id)) => Some(Class::Struct(*id)),
Item::Trait(id) => Some(Class::Trait(*id)),
Item::Impl(id) => Some(Class::Impl(*id)),
_ => None,
}
}

pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) {
match self {
Item::Type(id) => id.sink_diagnostics(db, sink),
Item::Trait(id) => id.sink_diagnostics(db, sink),
Item::Impl(id) => id.sink_diagnostics(db, sink),
Item::Event(id) => id.sink_diagnostics(db, sink),
Item::Function(id) => id.sink_diagnostics(db, sink),
Item::GenericType(_) | Item::BuiltinFunction(_) | Item::Intrinsic(_) => {}
Expand Down Expand Up @@ -698,7 +718,7 @@ impl ModuleId {

self.all_impls(db)
.iter()
.for_each(|val| val.sink_diagnostics(db, sink));
.for_each(|id| id.sink_diagnostics(db, sink));
}

#[doc(hidden)]
Expand Down Expand Up @@ -1067,7 +1087,7 @@ impl ContractFieldId {
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct FunctionSig {
pub ast: Node<ast::FunctionSignature>,
pub parent: Option<Class>,
pub parent: Option<Item>,
pub module: ModuleId,
}

Expand All @@ -1080,11 +1100,6 @@ impl FunctionSigId {
db.lookup_intern_function_sig(*self)
}

// This should probably be scrapped in favor of `parent(`
pub fn class(&self, db: &dyn AnalyzerDb) -> Option<Class> {
self.data(db).parent
}

pub fn takes_self(&self, db: &dyn AnalyzerDb) -> bool {
self.signature(db).self_decl.is_some()
}
Expand All @@ -1093,6 +1108,8 @@ impl FunctionSigId {
match self.parent(db) {
Item::Type(TypeDef::Contract(cid)) => Some(types::Type::SelfContract(cid).id(db)),
Item::Type(TypeDef::Struct(sid)) => Some(types::Type::Struct(sid).id(db)),
Item::Impl(id) => Some(id.receiver(db)),
Item::Type(TypeDef::Primitive(ty)) => Some(db.intern_type(Type::Base(ty))),
_ => None,
}
}
Expand Down Expand Up @@ -1131,19 +1148,26 @@ impl FunctionSigId {
self.data(db).ast.kind.pub_
}

pub fn parent(&self, db: &dyn AnalyzerDb) -> Item {
pub fn self_item(&self, db: &dyn AnalyzerDb) -> Option<Item> {
let data = self.data(db);
data.parent
.map(|class| class.as_item(db))
.unwrap_or(Item::Module(data.module))
}

pub fn parent(&self, db: &dyn AnalyzerDb) -> Item {
self.self_item(db)
.unwrap_or_else(|| Item::Module(self.module(db)))
}

pub fn is_trait_fn(&self, db: &dyn AnalyzerDb) -> bool {
matches!(self.parent(db), Item::Trait(_))
}

pub fn is_module_fn(&self, db: &dyn AnalyzerDb) -> bool {
matches!(self.parent(db), Item::Module(_))
}

pub fn is_impl_fn(&self, db: &dyn AnalyzerDb) -> bool {
matches!(self.class(db), Some(Class::Impl(_)))
matches!(self.parent(db), Item::Impl(_))
}

pub fn is_generic(&self, db: &dyn AnalyzerDb) -> bool {
Expand Down Expand Up @@ -1188,7 +1212,7 @@ impl Function {
pub fn new(
db: &dyn AnalyzerDb,
ast: &Node<ast::Function>,
parent: Option<Class>,
parent: Option<Item>,
module: ModuleId,
) -> Self {
let sig = db.intern_function_sig(Rc::new(FunctionSig {
Expand Down Expand Up @@ -1222,16 +1246,13 @@ impl FunctionId {
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<Class> {
self.sig(db).class(db)
}
pub fn self_type(&self, db: &dyn AnalyzerDb) -> Option<types::TypeId> {
self.sig(db).self_type(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)
}
Expand Down Expand Up @@ -1540,29 +1561,24 @@ impl ImplId {

pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) {
match &self.data(db).receiver.typ(db) {
Type::Contract(_)
| Type::Map(_)
| Type::SelfContract(_)
| 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.display(db)
),
vec![Label::primary(
self.data(db).ast.span,
"illegal `impl` block",
)],
vec![],
)),
Type::Contract(_) | Type::Map(_) | Type::SelfContract(_) | Type::Generic(_) => sink
.push(&errors::fancy_error(
format!(
"`impl` blocks aren't allowed for {}",
self.data(db).receiver.display(db)
),
vec![Label::primary(
self.data(db).ast.span,
"illegal `impl` block",
)],
vec![],
)),
Type::Struct(id) => {
self.validate_type_or_trait_is_in_ingot(db, sink, Some(id.module(db)))
}
Type::Base(_) | Type::Array(_) | Type::Tuple(_) | Type::String(_) => {
self.validate_type_or_trait_is_in_ingot(db, sink, None)
}
}

if !self.trait_id(db).is_public(db) && self.trait_id(db).module(db) != self.module(db) {
Expand Down
Loading

0 comments on commit b1cbf87

Please sign in to comment.