Skip to content

Commit

Permalink
Merge pull request ethereum#577 from sbillig/struct-fns
Browse files Browse the repository at this point in the history
Struct functions
  • Loading branch information
sbillig authored Nov 10, 2021
2 parents 861a97e + 024659c commit a8dfc1b
Show file tree
Hide file tree
Showing 194 changed files with 14,222 additions and 17,347 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/analyzer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ salsa = "0.16.1"
parking_lot_core = { version = "=0.8.0" } # used by salsa; version pinned for wasm compatibility
indexmap = "1.6.2"
if_chain = "1.0.1"
smallvec = { version = "1.6.1", features = ["union"] }

[dev-dependencies]
insta = "1.7.1"
Expand Down
21 changes: 14 additions & 7 deletions crates/analyzer/src/builtins.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use strum::{AsRefStr, EnumIter, EnumString};

#[derive(Debug, PartialEq, EnumString)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, EnumString, AsRefStr)]
#[strum(serialize_all = "snake_case")]
pub enum ValueMethod {
Clone,
Expand All @@ -10,29 +10,36 @@ pub enum ValueMethod {

#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumString, AsRefStr, Hash, EnumIter)]
#[strum(serialize_all = "snake_case")]
pub enum GlobalMethod {
pub enum GlobalFunction {
Keccak256,
SendValue,
Balance,
BalanceOf,
}

#[derive(Debug, PartialEq, EnumString, AsRefStr)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, EnumString, AsRefStr)]
#[strum(serialize_all = "snake_case")]
pub enum ContractTypeMethod {
Create,
Create2,
}

impl ContractTypeMethod {
pub fn arg_count(&self) -> usize {
match self {
ContractTypeMethod::Create => 1,
ContractTypeMethod::Create2 => 2,
}
}
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, EnumString, EnumIter, AsRefStr)]
#[strum(serialize_all = "lowercase")]
pub enum Object {
pub enum GlobalObject {
Block,
Chain,
Msg,
Tx,
#[strum(serialize = "self")]
Self_,
}

#[derive(Debug, PartialEq, EnumString)]
Expand Down Expand Up @@ -67,6 +74,6 @@ pub enum TxField {

#[derive(Debug, PartialEq, EnumString)]
#[strum(serialize_all = "snake_case")]
pub enum SelfField {
pub enum ContractSelfField {
Address,
}
110 changes: 89 additions & 21 deletions crates/analyzer/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::builtins::GlobalMethod;
use crate::builtins::{ContractTypeMethod, GlobalFunction, ValueMethod};
use crate::errors::{self, CannotMove, TypeError};
use crate::namespace::items::{DiagnosticSink, EventId, FunctionId, Item};
use crate::namespace::types::{FixedSize, Type};
use crate::namespace::items::{Class, ContractId, DiagnosticSink, EventId, FunctionId, Item};
use crate::namespace::types::{FixedSize, SelfDecl, Type};
use crate::AnalyzerDb;
use fe_common::diagnostics::Diagnostic;
pub use fe_common::diagnostics::Label;
Expand Down Expand Up @@ -96,6 +96,16 @@ pub trait AnalyzerContext {
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum NamedThing {
Item(Item),
SelfValue {
/// Function `self` parameter.
decl: Option<SelfDecl>,

/// The function's parent, if any. If `None`, `self` has been
/// used in a module-level function.
class: Option<Class>,
span: Option<Span>,
},
// SelfType // when/if we add a `Self` type keyword
Variable {
name: String,
typ: Result<FixedSize, TypeError>,
Expand All @@ -107,6 +117,7 @@ impl NamedThing {
pub fn name_span(&self, db: &dyn AnalyzerDb) -> Option<Span> {
match self {
NamedThing::Item(item) => item.name_span(db),
NamedThing::SelfValue { span, .. } => *span,
NamedThing::Variable { span, .. } => Some(*span),
}
}
Expand All @@ -115,13 +126,15 @@ impl NamedThing {
match self {
NamedThing::Item(item) => item.is_builtin(),
NamedThing::Variable { .. } => false,
NamedThing::SelfValue { .. } => false,
}
}

pub fn item_kind_display_name(&self) -> &str {
match self {
NamedThing::Item(item) => item.item_kind_display_name(),
NamedThing::Variable { .. } => "variable",
NamedThing::SelfValue { .. } => "value",
}
}
}
Expand Down Expand Up @@ -210,16 +223,15 @@ impl ExpressionAttributes {
/// Adds a move to value, if it is in storage or memory.
pub fn into_loaded(mut self) -> Result<Self, CannotMove> {
match self.typ {
Type::Base(_) => {}
Type::Contract(_) => {}
_ => return Err(CannotMove),
}
Type::Base(_) | Type::Contract(_) => {
if self.location != Location::Value {
self.move_location = Some(Location::Value);
}

if self.location != Location::Value {
self.move_location = Some(Location::Value);
Ok(self)
}
_ => Err(CannotMove),
}

Ok(self)
}

/// The final location of an expression after a possible move.
Expand All @@ -233,21 +245,77 @@ impl ExpressionAttributes {

impl fmt::Display for ExpressionAttributes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(
f,
"{}: {:?} => {:?}",
self.typ, self.location, self.move_location
)
if let Some(move_to) = self.move_location {
write!(f, "{}: {:?} => {:?}", self.typ, self.location, move_to)
} else {
write!(f, "{}: {:?}", self.typ, self.location)
}
}
}

/// The type of a function call.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CallType {
BuiltinFunction(GlobalMethod),
TypeConstructor { typ: Type },
SelfAttribute { func_name: String, self_span: Span },
BuiltinFunction(GlobalFunction),
BuiltinValueMethod(ValueMethod),

// create, create2 (will be methods of the context struct soon)
BuiltinAssociatedFunction {
contract: ContractId,
function: ContractTypeMethod,
},

// MyStruct.foo() (soon MyStruct::foo())
AssociatedFunction {
class: Class,
function: FunctionId,
},
ValueMethod {
is_self: bool,
class: Class,
method: FunctionId,
},
Pure(FunctionId),
ValueAttribute,
TypeAttribute { typ: Type, func_name: String },
TypeConstructor(Type),
}

impl CallType {
pub fn function(&self) -> Option<FunctionId> {
use CallType::*;
match self {
BuiltinFunction(_)
| BuiltinValueMethod(_)
| TypeConstructor(_)
| BuiltinAssociatedFunction { .. } => None,
AssociatedFunction { function: id, .. } | ValueMethod { method: id, .. } | Pure(id) => {
Some(*id)
}
}
}

pub fn function_name(&self, db: &dyn AnalyzerDb) -> String {
match self {
CallType::BuiltinFunction(f) => f.as_ref().to_string(),
CallType::BuiltinValueMethod(f) => f.as_ref().to_string(),
CallType::BuiltinAssociatedFunction { function, .. } => function.as_ref().to_string(),

CallType::AssociatedFunction { function: id, .. }
| CallType::ValueMethod { method: id, .. }
| CallType::Pure(id) => id.name(db),
CallType::TypeConstructor(typ) => typ.to_string(),
}
}

pub fn is_unsafe(&self, db: &dyn AnalyzerDb) -> bool {
// There are no built-in unsafe fns yet
self.function()
.map(|id| id.unsafe_span(db).is_some())
.unwrap_or(false)
}
}

impl fmt::Display for CallType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{:?}", self)
}
}
8 changes: 4 additions & 4 deletions crates/analyzer/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,6 @@ pub trait AnalyzerDb {
fn contract_function_map(&self, id: ContractId) -> Analysis<Rc<IndexMap<String, FunctionId>>>;
#[salsa::invoke(queries::contracts::contract_public_function_map)]
fn contract_public_function_map(&self, id: ContractId) -> Rc<IndexMap<String, FunctionId>>;
#[salsa::invoke(queries::contracts::contract_pure_function_map)]
fn contract_pure_function_map(&self, id: ContractId) -> Rc<IndexMap<String, FunctionId>>;
#[salsa::invoke(queries::contracts::contract_self_function_map)]
fn contract_self_function_map(&self, id: ContractId) -> Rc<IndexMap<String, FunctionId>>;
#[salsa::invoke(queries::contracts::contract_init_function)]
fn contract_init_function(&self, id: ContractId) -> Analysis<Option<FunctionId>>;

Expand Down Expand Up @@ -114,6 +110,10 @@ pub trait AnalyzerDb {
&self,
field: StructFieldId,
) -> Analysis<Result<types::FixedSize, TypeError>>;
#[salsa::invoke(queries::structs::struct_all_functions)]
fn struct_all_functions(&self, id: StructId) -> Rc<Vec<FunctionId>>;
#[salsa::invoke(queries::structs::struct_function_map)]
fn struct_function_map(&self, id: StructId) -> Analysis<Rc<IndexMap<String, FunctionId>>>;

// Event
#[salsa::invoke(queries::events::event_type)]
Expand Down
35 changes: 6 additions & 29 deletions crates/analyzer/src/db/queries/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use indexmap::map::{Entry, IndexMap};

use std::rc::Rc;

/// A `Vec` of every function defined in the contract, including duplicates and the init function.
pub fn contract_all_functions(db: &dyn AnalyzerDb, contract: ContractId) -> Rc<Vec<FunctionId>> {
let module = contract.module(db);
let body = &contract.data(db).ast.kind.body;
Expand All @@ -21,8 +22,8 @@ pub fn contract_all_functions(db: &dyn AnalyzerDb, contract: ContractId) -> Rc<V
ast::ContractStmt::Function(node) => {
Some(db.intern_function(Rc::new(items::Function {
ast: node.clone(),
contract: Some(contract),
module,
parent: Some(items::Class::Contract(contract)),
})))
}
})
Expand All @@ -37,7 +38,7 @@ pub fn contract_function_map(
let mut scope = ItemScope::new(db, contract.module(db));
let mut map = IndexMap::<String, FunctionId>::new();

for func in contract.all_functions(db).iter() {
for func in db.contract_all_functions(contract).iter() {
let def = &func.data(db).ast;
let def_name = def.name();
if def_name == "__init__" {
Expand Down Expand Up @@ -102,37 +103,11 @@ pub fn contract_public_function_map(
)
}

pub fn contract_pure_function_map(
db: &dyn AnalyzerDb,
contract: ContractId,
) -> Rc<IndexMap<String, FunctionId>> {
Rc::new(
contract
.functions(db)
.iter()
.filter_map(|(name, func)| func.is_pure(db).then(|| (name.clone(), *func)))
.collect(),
)
}

pub fn contract_self_function_map(
db: &dyn AnalyzerDb,
contract: ContractId,
) -> Rc<IndexMap<String, FunctionId>> {
Rc::new(
contract
.functions(db)
.iter()
.filter_map(|(name, func)| (!func.is_pure(db)).then(|| (name.clone(), *func)))
.collect(),
)
}

pub fn contract_init_function(
db: &dyn AnalyzerDb,
contract: ContractId,
) -> Analysis<Option<FunctionId>> {
let all_fns = contract.all_functions(db);
let all_fns = db.contract_all_functions(contract);
let mut init_fns = all_fns.iter().filter_map(|func| {
let def = &func.data(db).ast;
(def.name() == "__init__").then(|| (func, def.span))
Expand Down Expand Up @@ -180,6 +155,7 @@ pub fn contract_init_function(
}
}

/// A `Vec` of all events defined within the contract, including those with duplicate names.
pub fn contract_all_events(db: &dyn AnalyzerDb, contract: ContractId) -> Rc<Vec<EventId>> {
let body = &contract.data(db).ast.kind.body;
Rc::new(
Expand Down Expand Up @@ -227,6 +203,7 @@ pub fn contract_event_map(
}
}

/// All field ids, including those with duplicate names
pub fn contract_all_fields(db: &dyn AnalyzerDb, contract: ContractId) -> Rc<Vec<ContractFieldId>> {
let fields = contract
.data(db)
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
@@ -1,7 +1,7 @@
use crate::context::{AnalyzerContext, FunctionBody};
use crate::db::{Analysis, AnalyzerDb};
use crate::errors::TypeError;
use crate::namespace::items::FunctionId;
use crate::namespace::items::{Class, FunctionId};
use crate::namespace::scopes::{BlockScope, BlockScopeType, FunctionScope, ItemScope};
use crate::namespace::types::{self, FixedSize, SelfDecl};
use crate::traversal::functions::traverse_statements;
Expand All @@ -24,10 +24,10 @@ pub fn function_signature(
let def = &node.kind;

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

if_chain! {
if contract.is_some();
if let Some(Class::Contract(_)) = fn_parent;
if let Some(pub_span) = function.pub_span(db);
if let Some(unsafe_span) = function.unsafe_span(db);
then {
Expand All @@ -37,22 +37,22 @@ pub fn function_signature(
}
}

let mut self_decl = SelfDecl::None;
let mut self_decl = None;
let mut names = HashMap::new();
let params = def
.args
.iter()
.enumerate()
.filter_map(|(index, arg)| match &arg.kind {
ast::FunctionArg::Zelf => {
if contract.is_none() {
if fn_parent.is_none() {
scope.error(
"`self` can only be used in contract functions",
"`self` can only be used in contract or struct functions",
arg.span,
"not allowed in functions defined outside of a contract",
"not allowed in functions defined outside of a contract or struct",
);
} else {
self_decl = SelfDecl::Mutable;
self_decl = Some(SelfDecl::Mutable);
if index != 0 {
scope.error(
"`self` is not the first parameter",
Expand Down
Loading

0 comments on commit a8dfc1b

Please sign in to comment.