diff --git a/crates/analyzer/src/db/queries/contracts.rs b/crates/analyzer/src/db/queries/contracts.rs index 9be44cb3e4..a48f9db2b0 100644 --- a/crates/analyzer/src/db/queries/contracts.rs +++ b/crates/analyzer/src/db/queries/contracts.rs @@ -235,7 +235,8 @@ pub fn contract_all_events(db: &dyn AnalyzerDb, contract: ContractId) -> Rc<[Eve ast::ContractStmt::Function(_) => None, ast::ContractStmt::Event(node) => Some(db.intern_event(Rc::new(items::Event { ast: node.clone(), - contract, + module: contract.module(db), + contract: Some(contract), }))), }) .collect() diff --git a/crates/analyzer/src/db/queries/module.rs b/crates/analyzer/src/db/queries/module.rs index 5182279b1c..3bb3e4d89c 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, Function, Item, ModuleConstant, ModuleConstantId, ModuleId, ModuleSource, - Struct, StructId, TypeAlias, TypeDef, + Contract, ContractId, Event, Function, Item, ModuleConstant, ModuleConstantId, ModuleId, + ModuleSource, Struct, StructId, TypeAlias, TypeDef, }; use crate::namespace::scopes::ItemScope; use crate::namespace::types::{self, Type}; @@ -98,7 +98,11 @@ pub fn module_all_items(db: &dyn AnalyzerDb, module: ModuleId) -> Rc<[Item]> { } ast::ModuleStmt::Pragma(_) => None, ast::ModuleStmt::Use(_) => None, - ast::ModuleStmt::Event(_) => todo!(), + ast::ModuleStmt::Event(node) => Some(Item::Event(db.intern_event(Rc::new(Event { + ast: node.clone(), + module, + contract: None, + })))), ast::ModuleStmt::ParseError(_) => None, }) .collect() diff --git a/crates/analyzer/src/namespace/items.rs b/crates/analyzer/src/namespace/items.rs index 582397d277..99ed2a12e1 100644 --- a/crates/analyzer/src/namespace/items.rs +++ b/crates/analyzer/src/namespace/items.rs @@ -1311,7 +1311,8 @@ impl StructFieldId { #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct Event { pub ast: Node, - pub contract: ContractId, + pub module: ModuleId, + pub contract: Option, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] @@ -1332,10 +1333,14 @@ impl EventId { db.event_type(*self).value } pub fn module(&self, db: &dyn AnalyzerDb) -> ModuleId { - self.data(db).contract.module(db) + self.data(db).module } pub fn parent(&self, db: &dyn AnalyzerDb) -> Item { - Item::Type(TypeDef::Contract(self.data(db).contract)) + if let Some(contract_id) = self.data(db).contract { + Item::Type(TypeDef::Contract(contract_id)) + } else { + Item::Module(self.module(db)) + } } pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) { sink.push_all(db.event_type(*self).diagnostics.iter()); diff --git a/crates/analyzer/tests/analysis.rs b/crates/analyzer/tests/analysis.rs index c6bdc06540..6839383f0e 100644 --- a/crates/analyzer/tests/analysis.rs +++ b/crates/analyzer/tests/analysis.rs @@ -201,6 +201,7 @@ test_analysis! { create_contract, "features/create_contract.fe"} test_analysis! { create_contract_from_init, "features/create_contract_from_init.fe"} test_analysis! { empty, "features/empty.fe"} test_analysis! { events, "features/events.fe"} +test_analysis! { module_level_events, "features/module_level_events.fe"} test_analysis! { external_contract, "features/external_contract.fe"} test_analysis! { for_loop_with_break, "features/for_loop_with_break.fe"} test_analysis! { for_loop_with_continue, "features/for_loop_with_continue.fe"} @@ -308,12 +309,11 @@ fn build_snapshot(db: &dyn AnalyzerDb, module: items::ModuleId) -> String { )], Item::Type(TypeDef::Struct(struct_)) => [ label_in_non_overlapping_groups( - &struct_ - .fields(db) - .values() - .map(|field| (field.data(db).ast.span, field.typ(db).unwrap())) - .collect::>(), - + &struct_ + .fields(db) + .values() + .map(|field| (field.data(db).ast.span, field.typ(db).unwrap())) + .collect::>(), ), struct_ .functions(db) @@ -348,12 +348,9 @@ fn build_snapshot(db: &dyn AnalyzerDb, module: items::ModuleId) -> String { Item::Function(id) => function_diagnostics(*id, db), Item::Constant(id) => vec![build_display_diagnostic(id.span(db), &id.typ(db).unwrap())], - - - // Events can't be defined at the module level yet. - Item::Event(_) + Item::Event(id) => event_diagnostics(*id, db), // Built-in stuff - | Item::Type(TypeDef::Primitive(_)) + Item::Type(TypeDef::Primitive(_)) | Item::GenericType(_) | Item::BuiltinFunction(_) | Item::Intrinsic(_) diff --git a/crates/analyzer/tests/snapshots/analysis__module_level_events.snap b/crates/analyzer/tests/snapshots/analysis__module_level_events.snap new file mode 100644 index 0000000000..4fdd86b7c6 --- /dev/null +++ b/crates/analyzer/tests/snapshots/analysis__module_level_events.snap @@ -0,0 +1,102 @@ +--- +source: crates/analyzer/tests/analysis.rs +expression: "build_snapshot(&db, module)" + +--- +note: + ┌─ module_level_events.fe:2:5 + │ +2 │ idx sender: address + │ ^^^^^^^^^^^^^^^^^^^ address +3 │ idx receiver: address + │ ^^^^^^^^^^^^^^^^^^^^^ address +4 │ value: u256 + │ ^^^^^^^^^^^ u256 + +note: + ┌─ module_level_events.fe:6:5 + │ +6 │ ╭ fn transfer(to : address, value : u256): +7 │ │ emit Transfer(sender=msg.sender, receiver=to, value) + │ ╰────────────────────────────────────────────────────────────^ attributes hash: 5430479256040439660 + │ + = FunctionSignature { + self_decl: None, + params: [ + FunctionParam { + name: "to", + typ: Ok( + Base( + Address, + ), + ), + }, + FunctionParam { + name: "value", + typ: Ok( + Base( + Numeric( + U256, + ), + ), + ), + }, + ], + return_type: Ok( + Base( + Unit, + ), + ), + } + +note: + ┌─ module_level_events.fe:7:30 + │ +7 │ emit Transfer(sender=msg.sender, receiver=to, value) + │ ^^^^^^^^^^ ^^ ^^^^^ u256: Value + │ │ │ + │ │ address: Value + │ address: Value + +note: + ┌─ module_level_events.fe:7:9 + │ +7 │ emit Transfer(sender=msg.sender, receiver=to, value) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ attributes hash: 17986960071624595337 + │ + = Event { + name: "Transfer", + fields: [ + EventField { + name: "sender", + typ: Ok( + Base( + Address, + ), + ), + is_indexed: true, + }, + EventField { + name: "receiver", + typ: Ok( + Base( + Address, + ), + ), + is_indexed: true, + }, + EventField { + name: "value", + typ: Ok( + Base( + Numeric( + U256, + ), + ), + ), + is_indexed: false, + }, + ], + } + + diff --git a/crates/lowering/src/mappers/contracts.rs b/crates/lowering/src/mappers/contracts.rs index 3a543bcd88..dac2735352 100644 --- a/crates/lowering/src/mappers/contracts.rs +++ b/crates/lowering/src/mappers/contracts.rs @@ -1,7 +1,6 @@ use crate::context::ModuleContext; -use crate::mappers::{functions, types}; -use crate::utils::ZeroSpanNode; -use fe_analyzer::namespace::items::{ContractFieldId, ContractId, EventId}; +use crate::mappers::{events, functions, types}; +use fe_analyzer::namespace::items::{ContractFieldId, ContractId}; use fe_parser::ast; use fe_parser::node::Node; @@ -17,7 +16,7 @@ pub fn contract_def(context: &mut ModuleContext, contract: ContractId) -> Node>(); let mut functions = contract @@ -64,40 +63,3 @@ fn contract_field(context: &mut ModuleContext, field: ContractFieldId) -> Node Node { - let ast_fields = &event.data(context.db).ast.kind.fields; - let fields = event - .typ(context.db) - .fields - .iter() - .zip(ast_fields.iter()) - .map(|(field, node)| { - ast::EventField { - is_idx: field.is_indexed, - name: field.name.clone().into_node(), - typ: types::type_desc( - context, - node.kind.typ.clone(), - &field - .typ - .as_ref() - .expect("event field type error") - .clone() - .into(), - ), - } - .into_node() - }) - .collect(); - - let node = &event.data(context.db).ast; - Node::new( - ast::Event { - name: node.kind.name.clone(), - fields, - pub_qual: None, - }, - node.span, - ) -} diff --git a/crates/lowering/src/mappers/events.rs b/crates/lowering/src/mappers/events.rs new file mode 100644 index 0000000000..46568cf5a1 --- /dev/null +++ b/crates/lowering/src/mappers/events.rs @@ -0,0 +1,43 @@ +use crate::context::ModuleContext; +use crate::mappers::types; +use crate::utils::ZeroSpanNode; +use fe_analyzer::namespace::items::EventId; +use fe_parser::ast; +use fe_parser::node::Node; + +pub fn event_def(context: &mut ModuleContext, event: EventId) -> Node { + let ast_fields = &event.data(context.db).ast.kind.fields; + let fields = event + .typ(context.db) + .fields + .iter() + .zip(ast_fields.iter()) + .map(|(field, node)| { + ast::EventField { + is_idx: field.is_indexed, + name: field.name.clone().into_node(), + typ: types::type_desc( + context, + node.kind.typ.clone(), + &field + .typ + .as_ref() + .expect("event field type error") + .clone() + .into(), + ), + } + .into_node() + }) + .collect(); + + let node = &event.data(context.db).ast; + Node::new( + ast::Event { + name: node.kind.name.clone(), + fields, + pub_qual: None, + }, + node.span, + ) +} diff --git a/crates/lowering/src/mappers/mod.rs b/crates/lowering/src/mappers/mod.rs index 5930039614..877b40c5b6 100644 --- a/crates/lowering/src/mappers/mod.rs +++ b/crates/lowering/src/mappers/mod.rs @@ -1,4 +1,5 @@ mod contracts; +mod events; mod expressions; mod functions; pub mod module; diff --git a/crates/lowering/src/mappers/module.rs b/crates/lowering/src/mappers/module.rs index 9fe179a00e..966318e4b2 100644 --- a/crates/lowering/src/mappers/module.rs +++ b/crates/lowering/src/mappers/module.rs @@ -1,5 +1,5 @@ use crate::context::ModuleContext; -use crate::mappers::{contracts, functions, structs, types}; +use crate::mappers::{contracts, events, functions, structs, types}; use crate::names; use crate::utils::ZeroSpanNode; use fe_analyzer::namespace::items::{Item, ModuleId, TypeDef}; @@ -57,7 +57,7 @@ pub fn module(db: &dyn AnalyzerDb, module: ModuleId) -> ast::Module { ))), Item::GenericType(_) => todo!("generic types can't be defined in fe yet"), - Item::Event(_) => todo!("events can't be defined at the module level yet"), + Item::Event(id) => Some(ast::ModuleStmt::Event(events::event_def(&mut context, *id))), Item::BuiltinFunction(_) | Item::Intrinsic(_) | Item::Object(_) => { unreachable!("special built-in stuff") } diff --git a/crates/lowering/tests/lowering.rs b/crates/lowering/tests/lowering.rs index 2700e28d17..3572431d32 100644 --- a/crates/lowering/tests/lowering.rs +++ b/crates/lowering/tests/lowering.rs @@ -44,5 +44,6 @@ test_file! { module_fn, "lowering/module_fn.fe" } test_file! { struct_fn, "lowering/struct_fn.fe" } test_file! { ternary, "lowering/ternary.fe" } test_file! { and_or, "lowering/and_or.fe" } +test_file! { module_level_events, "lowering/module_level_events.fe" } // TODO: the analyzer rejects lowered nested tuples. // test_file!(array_tuple, "lowering/array_tuple.fe"); diff --git a/crates/lowering/tests/snapshots/lowering__module_level_events.snap b/crates/lowering/tests/snapshots/lowering__module_level_events.snap new file mode 100644 index 0000000000..59749ccd66 --- /dev/null +++ b/crates/lowering/tests/snapshots/lowering__module_level_events.snap @@ -0,0 +1,14 @@ +--- +source: crates/lowering/tests/lowering.rs +expression: lowered_code + +--- +event Transfer: + idx sender: address + idx receiver: address + value: u256 + +contract Foo: + fn transfer(to: address, value: u256) -> (): + emit Transfer(sender=msg.sender, receiver=to, value) + return () diff --git a/crates/parser/tests/cases/parse_ast.rs b/crates/parser/tests/cases/parse_ast.rs index e747673eae..039068f898 100644 --- a/crates/parser/tests/cases/parse_ast.rs +++ b/crates/parser/tests/cases/parse_ast.rs @@ -235,3 +235,13 @@ contract GuestBook: pub fn get_msg(self, addr: address) -> BookMsg: return self.guest_book[addr] "# } + +test_parse! { module_level_events, try_parse_module, r#" +event Transfer: + idx sender: address + idx receiver: address + value: u256 +contract Foo: + fn transfer(to : address, value : u256): + emit Transfer(sender=msg.sender, receiver=to, value) +"# } 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 new file mode 100644 index 0000000000..210a37df1e --- /dev/null +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__module_level_events.snap @@ -0,0 +1,295 @@ +--- +source: crates/parser/tests/cases/parse_ast.rs +expression: "ast_string(stringify!(module_level_events), module::parse_module,\n r#\"\nevent Transfer:\n idx sender: address\n idx receiver: address\n value: u256\ncontract Foo:\n fn transfer(to : address, value : u256):\n emit Transfer(sender=msg.sender, receiver=to, value)\n\"#)" + +--- +Node( + kind: Module( + body: [ + Event(Node( + kind: Event( + name: Node( + kind: "Transfer", + span: Span( + start: 7, + end: 15, + ), + ), + fields: [ + Node( + kind: EventField( + is_idx: true, + name: Node( + kind: "sender", + span: Span( + start: 25, + end: 31, + ), + ), + typ: Node( + kind: Base( + base: "address", + ), + span: Span( + start: 33, + end: 40, + ), + ), + ), + span: Span( + start: 21, + end: 40, + ), + ), + Node( + kind: EventField( + is_idx: true, + name: Node( + kind: "receiver", + span: Span( + start: 49, + end: 57, + ), + ), + typ: Node( + kind: Base( + base: "address", + ), + span: Span( + start: 59, + end: 66, + ), + ), + ), + span: Span( + start: 45, + end: 66, + ), + ), + Node( + kind: EventField( + is_idx: false, + name: Node( + kind: "value", + span: Span( + start: 71, + end: 76, + ), + ), + typ: Node( + kind: Base( + base: "u256", + ), + span: Span( + start: 78, + end: 82, + ), + ), + ), + span: Span( + start: 71, + end: 82, + ), + ), + ], + pub_qual: None, + ), + span: Span( + start: 1, + end: 82, + ), + )), + Contract(Node( + kind: Contract( + name: Node( + kind: "Foo", + span: Span( + start: 92, + end: 95, + ), + ), + fields: [], + body: [ + Function(Node( + kind: Function( + pub_: None, + unsafe_: None, + name: Node( + kind: "transfer", + span: Span( + start: 104, + end: 112, + ), + ), + args: [ + Node( + kind: Regular(RegularFunctionArg( + name: Node( + kind: "to", + span: Span( + start: 113, + end: 115, + ), + ), + typ: Node( + kind: Base( + base: "address", + ), + span: Span( + start: 118, + end: 125, + ), + ), + )), + span: Span( + start: 113, + end: 125, + ), + ), + Node( + kind: Regular(RegularFunctionArg( + name: Node( + kind: "value", + span: Span( + start: 127, + end: 132, + ), + ), + typ: Node( + kind: Base( + base: "u256", + ), + span: Span( + start: 135, + end: 139, + ), + ), + )), + span: Span( + start: 127, + end: 139, + ), + ), + ], + return_type: None, + body: [ + Node( + kind: Emit( + name: Node( + kind: "Transfer", + span: Span( + start: 155, + end: 163, + ), + ), + args: Node( + kind: [ + Node( + kind: CallArg( + label: Some(Node( + kind: "sender", + span: Span( + start: 164, + end: 170, + ), + )), + value: Node( + kind: Attribute( + value: Node( + kind: Name("msg"), + span: Span( + start: 171, + end: 174, + ), + ), + attr: Node( + kind: "sender", + span: Span( + start: 175, + end: 181, + ), + ), + ), + span: Span( + start: 171, + end: 181, + ), + ), + ), + span: Span( + start: 164, + end: 181, + ), + ), + Node( + kind: CallArg( + label: Some(Node( + kind: "receiver", + span: Span( + start: 183, + end: 191, + ), + )), + value: Node( + kind: Name("to"), + span: Span( + start: 192, + end: 194, + ), + ), + ), + span: Span( + start: 183, + end: 194, + ), + ), + Node( + kind: CallArg( + label: None, + value: Node( + kind: Name("value"), + span: Span( + start: 196, + end: 201, + ), + ), + ), + span: Span( + start: 196, + end: 201, + ), + ), + ], + span: Span( + start: 163, + end: 202, + ), + ), + ), + span: Span( + start: 150, + end: 202, + ), + ), + ], + ), + span: Span( + start: 101, + end: 202, + ), + )), + ], + pub_qual: None, + ), + span: Span( + start: 83, + end: 202, + ), + )), + ], + ), + span: Span( + start: 0, + end: 202, + ), +) diff --git a/crates/test-files/fixtures/features/module_level_events.fe b/crates/test-files/fixtures/features/module_level_events.fe new file mode 100644 index 0000000000..e4375738dd --- /dev/null +++ b/crates/test-files/fixtures/features/module_level_events.fe @@ -0,0 +1,7 @@ +event Transfer: + idx sender: address + idx receiver: address + value: u256 +contract Foo: + fn transfer(to : address, value : u256): + emit Transfer(sender=msg.sender, receiver=to, value) \ No newline at end of file diff --git a/crates/test-files/fixtures/lowering/module_level_events.fe b/crates/test-files/fixtures/lowering/module_level_events.fe new file mode 100644 index 0000000000..e4375738dd --- /dev/null +++ b/crates/test-files/fixtures/lowering/module_level_events.fe @@ -0,0 +1,7 @@ +event Transfer: + idx sender: address + idx receiver: address + value: u256 +contract Foo: + fn transfer(to : address, value : u256): + emit Transfer(sender=msg.sender, receiver=to, value) \ No newline at end of file diff --git a/newsfragments/80.feature.md b/newsfragments/80.feature.md new file mode 100644 index 0000000000..571beaf5f3 --- /dev/null +++ b/newsfragments/80.feature.md @@ -0,0 +1,17 @@ +Support module level events + +Example: +``` +event Transfer: + idx sender: address + idx receiver: address + value: u256 + +contract Foo: + fn transferFoo(to : address, value : u256): + emit Transfer(sender=msg.sender, receiver=to, value) + +contract Bar: + fn transferBar(to : address, value : u256): + emit Transfer(sender=msg.sender, receiver=to, value) +```