Skip to content

Commit

Permalink
Support events via Emittable trait
Browse files Browse the repository at this point in the history
  • Loading branch information
cburgdorf committed Sep 19, 2022
1 parent 65b47d7 commit 9703c88
Show file tree
Hide file tree
Showing 30 changed files with 227 additions and 151 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,16 @@ Run `fe --help` to explore further options.
The following is a simple contract implemented in Fe.

```rust
struct Signed {
book_msg: String<100>
}

contract GuestBook {
messages: Map<address, String<100>>

event Signed {
book_msg: String<100>
}

pub fn sign(self, ctx: Context, book_msg: String<100>) {
self.messages[ctx.msg_sender()] = book_msg
emit Signed(ctx, book_msg: book_msg)
ctx.emit(Signed(book_msg: book_msg))
}

pub fn get_msg(self, addr: address) -> String<100> {
Expand Down
2 changes: 2 additions & 0 deletions crates/analyzer/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
pub const EMITTABLE_TRAIT_NAME: &str = "Emittable";
pub const EMIT_FN_NAME: &str = "emit";
pub const INDEXED: &str = "indexed";
pub const MAX_INDEXED_EVENT_FIELDS: usize = 3;
46 changes: 45 additions & 1 deletion crates/analyzer/src/db/queries/structs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::builtins;
use crate::constants::MAX_INDEXED_EVENT_FIELDS;
use crate::context::AnalyzerContext;
use crate::db::Analysis;
use crate::errors::TypeError;
Expand All @@ -10,7 +11,8 @@ use crate::namespace::scopes::ItemScope;
use crate::namespace::types::{Type, TypeId};
use crate::traversal::types::type_desc;
use crate::AnalyzerDb;
use fe_parser::ast;
use fe_common::utils::humanize::pluralize_conditionally;
use fe_parser::{ast, Label};
use indexmap::map::{Entry, IndexMap};
use smol_str::SmolStr;
use std::rc::Rc;
Expand Down Expand Up @@ -39,10 +41,25 @@ pub fn struct_field_map(
let scope = ItemScope::new(db, struct_.module(db));
let mut fields = IndexMap::<SmolStr, StructFieldId>::new();

let mut indexed_count = 0;
let struct_name = struct_.name(db);
for field in db.struct_all_fields(struct_).iter() {
let node = &field.data(db).ast;

if field.is_indexed(db) {
indexed_count += 1;
}

// Multiple attributes are currently still rejected by the parser so we only need to check the name here
if !field.attributes(db).is_empty() && !field.is_indexed(db) {
let span = field.data(db).ast.kind.attributes.first().unwrap().span;
scope.error(
"Invalid attribute",
span,
"illegal name. Only `indexed` supported.",
);
}

match fields.entry(node.name().into()) {
Entry::Occupied(entry) => {
scope.duplicate_name_error(
Expand All @@ -58,6 +75,33 @@ pub fn struct_field_map(
}
}

if indexed_count > MAX_INDEXED_EVENT_FIELDS {
let excess_count = indexed_count - MAX_INDEXED_EVENT_FIELDS;

let mut labels = fields
.iter()
.filter_map(|(_, field)| {
field
.is_indexed(db)
.then(|| Label::primary(field.span(db), String::new()))
})
.collect::<Vec<Label>>();
labels.last_mut().unwrap().message = format!("{} indexed fields", indexed_count);

scope.fancy_error(
&format!(
"more than three indexed fields in `event {}`",
struct_.name(db)
),
labels,
vec![format!(
"Note: Remove the `indexed` attribute from at least {} {}.",
excess_count,
pluralize_conditionally("field", excess_count)
)],
);
}

Analysis::new(Rc::new(fields), scope.diagnostics.take().into())
}

Expand Down
1 change: 0 additions & 1 deletion crates/analyzer/src/db/queries/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ pub fn all_impls(db: &dyn AnalyzerDb, ty: TypeId) -> Rc<[ImplId]> {
.iter()
.flat_map(|module_id| module_id.all_impls(db).to_vec())
.collect::<Vec<_>>();

db.ingot_external_ingots(db.root_ingot())
.values()
.flat_map(|ingot| ingot.all_modules(db).to_vec())
Expand Down
11 changes: 10 additions & 1 deletion crates/analyzer/src/namespace/items.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::constants::INDEXED;
use crate::constants::{EMITTABLE_TRAIT_NAME, INDEXED};
use crate::context::{self, Analysis, Constant, NamedThing};
use crate::display::{DisplayWithDb, Displayable};
use crate::errors::{self, IncompleteItem, TypeError};
Expand Down Expand Up @@ -1878,6 +1878,11 @@ impl TraitId {
}

pub fn is_implemented_for(&self, db: &dyn AnalyzerDb, ty: TypeId) -> bool {
// All structs automagically implement the Emittable trait
if self.is_std_trait(db, EMITTABLE_TRAIT_NAME) && ty.is_struct(db) {
return true;
}

ty.get_all_impls(db)
.iter()
.any(|val| &val.trait_id(db) == self)
Expand All @@ -1887,6 +1892,10 @@ impl TraitId {
self.module(db).is_in_std(db)
}

pub fn is_std_trait(&self, db: &dyn AnalyzerDb, name: &str) -> bool {
self.is_in_std(db) && self.name(db).to_lowercase() == name.to_lowercase()
}

pub fn parent(&self, db: &dyn AnalyzerDb) -> Item {
Item::Module(self.data(db).module)
}
Expand Down
3 changes: 3 additions & 0 deletions crates/analyzer/src/namespace/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ impl TypeId {
pub fn is_string(&self, db: &dyn AnalyzerDb) -> bool {
self.typ(db).as_string().is_some()
}
pub fn is_struct(&self, db: &dyn AnalyzerDb) -> bool {
matches!(self.typ(db), Type::Struct(_))
}
pub fn as_struct(&self, db: &dyn AnalyzerDb) -> Option<StructId> {
self.typ(db).as_struct()
}
Expand Down
1 change: 1 addition & 0 deletions crates/analyzer/tests/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ test_file! { duplicate_var_in_for_loop }
test_file! { duplicate_generic_params }
test_file! { external_call_type_error }
test_file! { external_call_wrong_number_of_params }
test_file! { emittable_not_implementable }
test_file! { contract_function_with_generic_params }
test_file! { indexed_event }
test_file! { invalid_compiler_version }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
source: crates/analyzer/tests/errors.rs
expression: "error_string(&path, test_files::fixture(path))"

---
error: the struct `OutOfReachMarker` is private
┌─ compile_errors/emittable_not_implementable.fe:6:24
6fn emit(self, _ val: OutOfReachMarker) {
^^^^^^^^^^^^^^^^ this struct is not `pub`
┌─ src/context.fe:8:8
8struct OutOfReachMarker {}
---------------- `OutOfReachMarker` is defined here
= `OutOfReachMarker` can only be used within `context`
= Hint: use `pub` to make `OutOfReachMarker` visible from outside of `context`


39 changes: 21 additions & 18 deletions crates/codegen/src/db/queries/abi.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use fe_abi::{
contract::AbiContract,
event::AbiEvent,
event::{AbiEvent, AbiEventField},
function::{AbiFunction, AbiFunctionType},
types::{AbiTupleField, AbiType},
};
use fe_analyzer::namespace::items::ContractId;
use fe_analyzer::{constants::INDEXED, namespace::items::ContractId};
use fe_mir::ir::{self, FunctionId, TypeId};

use crate::db::CodegenDb;
Expand All @@ -26,12 +26,13 @@ pub fn abi_contract(db: &dyn CodegenDb, contract: ContractId) -> AbiContract {
}
}

let events = vec![];
// for &event in db.contract_all_events(contract).as_ref() {
// let mir_event = db.mir_lowered_event_type(event);
// let event = db.codegen_abi_event(mir_event);
// events.push(event);
// }
let mut events = vec![];
// We consider all structs that are defined or imported in the scope of the contract as possible events
for &event in db.module_structs(contract.module(db.upcast())).as_ref() {
let mir_event = db.mir_lowered_type(event.as_type(db.upcast()));
let event = db.codegen_abi_event(mir_event);
events.push(event);
}

AbiContract::new(funcs, events)
}
Expand Down Expand Up @@ -188,7 +189,7 @@ pub fn abi_type(db: &dyn CodegenDb, ty: TypeId) -> AbiType {
let fields = def
.fields
.iter()
.map(|(name, ty)| {
.map(|(name, _, ty)| {
let ty = db.codegen_abi_type(*ty);
AbiTupleField::new(name.to_string(), ty)
})
Expand All @@ -206,22 +207,24 @@ pub fn abi_type(db: &dyn CodegenDb, ty: TypeId) -> AbiType {
}

pub fn abi_event(db: &dyn CodegenDb, ty: TypeId) -> AbiEvent {
debug_assert!(ty.is_struct(db.upcast()));
let legalized_ty = db.codegen_legalized_type(ty);

let legalized_ty_data = legalized_ty.data(db.upcast());
let event_def = match &legalized_ty_data.kind {
ir::TypeKind::Struct(def) => def,
_ => unreachable!(),
};
let fields = vec![];
// let fields = event_def
// .fields
// .iter()
// .map(|(name, ty, indexed)| {
// let ty = db.codegen_abi_type(*ty);
// AbiEventField::new(name.to_string(), ty, *indexed)
// })
// .collect();

let fields = event_def
.fields
.iter()
.map(|(name, attr, ty)| {
let ty = db.codegen_abi_type(*ty);
let indexed = attr.iter().any(|attr| attr == INDEXED);
AbiEventField::new(name.to_string(), ty, indexed)
})
.collect();

AbiEvent::new(event_def.name.to_string(), fields, false)
}
Expand Down
8 changes: 4 additions & 4 deletions crates/codegen/src/db/queries/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ pub fn legalized_type(db: &dyn CodegenDb, ty: TypeId) -> TypeId {
.fields
.iter()
.cloned()
.filter_map(|(name, ty)| {
.filter_map(|(name, attrs, ty)| {
if ty.is_zero_sized(db.upcast()) {
None
} else {
Some((name, legalized_type(db, ty)))
Some((name, attrs, legalized_type(db, ty)))
}
})
.collect();
Expand All @@ -59,11 +59,11 @@ pub fn legalized_type(db: &dyn CodegenDb, ty: TypeId) -> TypeId {
.fields
.iter()
.cloned()
.filter_map(|(name, ty)| {
.filter_map(|(name, attrs, ty)| {
if ty.is_zero_sized(db.upcast()) {
None
} else {
Some((name, legalized_type(db, ty)))
Some((name, attrs, legalized_type(db, ty)))
}
})
.collect();
Expand Down
2 changes: 1 addition & 1 deletion crates/codegen/src/yul/runtime/revert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ fn type_signature_for_revert(db: &dyn CodegenDb, name: &str, ty: TypeId) -> yul:
ir::TypeKind::Struct(def) => def
.fields
.iter()
.map(|(_, ty)| ("".to_string(), db.codegen_abi_type(*ty)))
.map(|(_, _, ty)| ("".to_string(), db.codegen_abi_type(*ty)))
.collect(),
_ => {
let abi_ty = db.codegen_abi_type(deref_ty);
Expand Down
13 changes: 13 additions & 0 deletions crates/library/std/src/context.fe
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ use ingot::error::{
Error
}

struct OutOfReachMarker {}

// ctx.emit(my_event) should be the only way to emit an event. We achieve this by defining the
// private `OutOfReachMarker` here to which only the `Context` has access.
// Now there is no way to call `emit` directly on an Emittable.
pub trait Emittable {
fn emit(self, _ val: OutOfReachMarker);
}

pub struct Context {
pub fn base_fee(self) -> u256 {
unsafe { return evm::base_fee() }
Expand Down Expand Up @@ -76,4 +85,8 @@ pub struct Context {
}
}
}

pub fn emit<T: Emittable>(self, _ val: T) {
val.emit(OutOfReachMarker())
}
}
2 changes: 1 addition & 1 deletion crates/library/std/src/traits.fe
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Dummy trait used in testing. We can remove this once we have more useful traits

pub trait Dummy {}
pub trait Dummy {}
5 changes: 5 additions & 0 deletions crates/mir/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ pub trait MirDb: AnalyzerDb + Upcast<dyn AnalyzerDb> + UpcastMut<dyn AnalyzerDb>
analyzer_func: analyzer_items::FunctionId,
concrete_args: Vec<analyzer_types::TypeId>,
) -> ir::FunctionId;
#[salsa::invoke(queries::function::mir_lowered_pseudo_monomorphized_func_signature)]
fn mir_lowered_pseudo_monomorphized_func_signature(
&self,
analyzer_func: analyzer_items::FunctionId,
) -> ir::FunctionId;
#[salsa::invoke(queries::function::mir_lowered_func_body)]
fn mir_lowered_func_body(&self, func: ir::FunctionId) -> Rc<ir::FunctionBody>;
}
Expand Down
19 changes: 19 additions & 0 deletions crates/mir/src/db/queries/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,25 @@ pub fn mir_lowered_monomorphized_func_signature(
lower_monomorphized_func_signature(db, analyzer_func, &concrete_args)
}

/// Generate MIR function and monomorphize generic parameters as if they were called with unit type
/// NOTE: THIS SHOULD ONLY BE USED IN TEST CODE
pub fn mir_lowered_pseudo_monomorphized_func_signature(
db: &dyn MirDb,
analyzer_func: analyzer_items::FunctionId,
) -> ir::FunctionId {
let mut dummy_args = analyzer_func
.signature(db.upcast())
.params
.iter()
.map(|_| analyzer_types::TypeId::unit(db.upcast()))
.collect::<Vec<_>>();
if analyzer_func.takes_self(db.upcast()) {
dummy_args.insert(0, analyzer_types::TypeId::unit(db.upcast()))
}

lower_monomorphized_func_signature(db, analyzer_func, &dummy_args)
}

pub fn mir_lowered_func_body(db: &dyn MirDb, func: ir::FunctionId) -> Rc<ir::FunctionBody> {
lower_func_body(db, func)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/mir/src/db/queries/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub fn mir_lower_struct_all_functions(
struct_
.all_functions(db.upcast())
.iter()
.map(|func| db.mir_lowered_func_signature(*func))
.map(|func| db.mir_lowered_pseudo_monomorphized_func_signature(*func))
.collect::<Vec<_>>()
.into()
}
Loading

0 comments on commit 9703c88

Please sign in to comment.