diff --git a/Cargo.lock b/Cargo.lock index 7683fcf18b..d0de3bcb1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -486,6 +486,7 @@ dependencies = [ "fe-common", "fe-driver", "fe-parser", + "indexmap", "walkdir", ] @@ -531,7 +532,10 @@ version = "0.11.0-alpha" dependencies = [ "codespan-reporting", "difference", + "fe-library", "hex", + "include_dir", + "indexmap", "num-traits", "once_cell", "ron", @@ -553,6 +557,7 @@ dependencies = [ "fe-yulc", "fe-yulgen", "hex", + "indexmap", "primitive-types", "serde_json", "solc", @@ -601,6 +606,13 @@ dependencies = [ "serde_json", ] +[[package]] +name = "fe-library" +version = "0.11.0-alpha" +dependencies = [ + "include_dir", +] + [[package]] name = "fe-lowering" version = "0.11.0-alpha" diff --git a/crates/analyzer/src/db.rs b/crates/analyzer/src/db.rs index c28d3da1bb..1de08538a9 100644 --- a/crates/analyzer/src/db.rs +++ b/crates/analyzer/src/db.rs @@ -59,6 +59,8 @@ pub trait AnalyzerDb { fn ingot_all_modules(&self, ingot: IngotId) -> Rc>; #[salsa::invoke(queries::ingots::ingot_main_module)] fn ingot_main_module(&self, ingot: IngotId) -> Analysis>; + #[salsa::invoke(queries::ingots::ingot_lib_module)] + fn ingot_lib_module(&self, ingot: IngotId) -> Analysis>; // Module #[salsa::invoke(queries::module::module_all_items)] diff --git a/crates/analyzer/src/db/queries/ingots.rs b/crates/analyzer/src/db/queries/ingots.rs index 3340503548..93b2868b1d 100644 --- a/crates/analyzer/src/db/queries/ingots.rs +++ b/crates/analyzer/src/db/queries/ingots.rs @@ -42,25 +42,26 @@ pub fn ingot_all_modules(db: &dyn AnalyzerDb, ingot_id: IngotId) -> Rc>() .into_iter() - .map(|dir| { - let module = Module { - name: dir - .file_name() - .expect("missing file name") - .to_str() - .expect("could not convert dir name to string") - .to_string(), - ast: ast::Module { body: vec![] }, - context: ModuleContext::Ingot(ingot_id), - file_content: ModuleFileContent::Dir { - dir_path: dir + .filter_map(|dir| { + if let Some(file_name) = dir.file_name() { + let module = Module { + name: file_name .to_str() - .expect("could not convert dir path to string") + .expect("could not convert dir name to string") .to_string(), - }, - }; - - db.intern_module(Rc::new(module)) + ast: ast::Module { body: vec![] }, + context: ModuleContext::Ingot(ingot_id), + file_content: ModuleFileContent::Dir { + dir_path: dir + .to_str() + .expect("could not convert dir path to string") + .to_string(), + }, + }; + Some(db.intern_module(Rc::new(module))) + } else { + None + } }) .collect::>(); @@ -103,3 +104,39 @@ pub fn ingot_main_module(db: &dyn AnalyzerDb, ingot_id: IngotId) -> Analysis Analysis> { + let lib_id = ingot_id + .all_modules(db) + .iter() + .find(|module_id| { + module_id.name(db) == "lib" && { + if let Some(parent_id) = module_id.parent_module(db) { + parent_id.name(db) == "src" + } else { + false + } + } + }) + .copied(); + + Analysis { + value: lib_id, + diagnostics: Rc::new({ + if lib_id.is_none() { + vec![Diagnostic { + severity: Severity::Error, + message: format!( + "The ingot named \"{}\" is missing a lib module. \ + \nPlease add a `src/lib.fe` file to the base directory.", + ingot_id.name(db) + ), + labels: vec![], + notes: vec![], + }] + } else { + vec![] + } + }), + } +} diff --git a/crates/analyzer/src/db/queries/module.rs b/crates/analyzer/src/db/queries/module.rs index 5be0bc7c6b..af57c50163 100644 --- a/crates/analyzer/src/db/queries/module.rs +++ b/crates/analyzer/src/db/queries/module.rs @@ -106,6 +106,12 @@ pub fn module_item_map( let mut map = IndexMap::::new(); + for (name, ingot) in module.global_ingots(db).into_iter() { + if map.insert(name, Item::Ingot(ingot)).is_some() { + panic!("included library conflicts with other name?") + } + } + for item in module.all_items(db).iter() { let item_name = item.name(db); if let Some(builtin) = builtin_items.get(&item_name) { @@ -131,23 +137,35 @@ pub fn module_item_map( continue; } - match map.entry(item_name) { + match map.entry(item_name.clone()) { Entry::Occupied(entry) => { - diagnostics.push(errors::fancy_error( - "duplicate type name", - vec![ - Label::primary( - entry.get().name_span(db).unwrap(), - format!("`{}` first defined here", entry.key()), + if let Some(entry_name_span) = entry.get().name_span(db) { + diagnostics.push(errors::duplicate_name_error( + &format!( + "a {} named \"{}\" has already been defined", + entry.get().item_kind_display_name(), + item_name ), - Label::secondary( + &item_name, + entry_name_span, + item.name_span(db) + .expect("used-defined item does not have name span"), + )); + } else { + diagnostics.push(errors::fancy_error( + &format!( + "a {} named \"{}\" has already been defined", + entry.get().item_kind_display_name(), + item_name + ), + vec![Label::primary( item.name_span(db) - .expect("built-in conflicts with user-defined name?"), + .expect("used-defined item does not have name span"), format!("`{}` redefined here", entry.key()), - ), - ], - vec![], - )); + )], + vec![], + )); + } } Entry::Vacant(entry) => { entry.insert(*item); @@ -248,6 +266,25 @@ pub fn module_used_item_map( diagnostics.extend(items.diagnostics.deref().clone()); for (name, (name_span, item)) in items.value.iter() { + if let Some(ingot) = module.global_ingots(db).get(name) { + if *item == Item::Ingot(*ingot) { + diagnostics.push(errors::fancy_error( + format!("the ingot named \"{}\" is already defined", name), + vec![ + Label::primary(*name_span, "redefined here"), + Label::secondary(*name_span, "hint: delete this import"), + ], + vec![], + )) + } else { + diagnostics.push(errors::error( + format!("an ingot named \"{}\" has already been defined", name), + *name_span, + format!("conflicts with the ingot named \"{}\"", name), + )) + } + } + if let Some((other_name_span, other_item)) = accum.insert(name.to_owned(), (*name_span, *item)) { diff --git a/crates/analyzer/src/namespace/items.rs b/crates/analyzer/src/namespace/items.rs index 6720f8cfbb..ea91c55552 100644 --- a/crates/analyzer/src/namespace/items.rs +++ b/crates/analyzer/src/namespace/items.rs @@ -7,13 +7,15 @@ use crate::namespace::types::{self, GenericType}; use crate::traversal::pragma::check_pragma_version; use crate::AnalyzerDb; use fe_common::diagnostics::Diagnostic; -use fe_common::files::{SourceFile, SourceFileId}; +use fe_common::files::{FileStore, SourceFile, SourceFileId}; use fe_parser::ast; use fe_parser::ast::Expr; use fe_parser::node::{Node, Span}; use indexmap::indexmap; use indexmap::IndexMap; use std::collections::BTreeMap; +use std::ops::Deref; +use std::path::Path; use std::rc::Rc; /// A named item. This does not include things inside of @@ -101,7 +103,10 @@ impl Item { pub fn items(&self, db: &dyn AnalyzerDb) -> Rc> { match self { - Item::Ingot(_) => todo!("cannot access items in ingots yet"), + Item::Ingot(ingot) => ingot + .lib_module(db) + .expect("ingot is missing a lib file") + .items(db), Item::Module(module) => module.items(db), Item::Type(_) => todo!("cannot access items in types yet"), Item::GenericType(_) @@ -204,13 +209,68 @@ impl Item { #[derive(Debug, PartialEq, Eq, Hash, Clone, Default)] pub struct Global { - ingots: BTreeMap, + pub ingots: BTreeMap, +} + +impl Global { + pub fn try_new( + db: &dyn AnalyzerDb, + files: &FileStore, + deps: &IndexMap>, + ) -> Result, Vec> { + let mut diagnostics = vec![]; + let mut fatal_diagnostics = vec![]; + + let ingots = deps + .into_iter() + .filter_map(|(name, file_ids)| { + // dep map is left empty for now + match IngotId::try_new(db, files, name, file_ids, &indexmap! {}) { + Ok(analysis) => { + diagnostics.extend(analysis.diagnostics.deref().clone()); + Some((name.to_owned(), analysis.value)) + } + Err(diagnostics) => { + fatal_diagnostics.extend(diagnostics); + None + } + } + }) + .collect(); + + if fatal_diagnostics.is_empty() { + Ok(Analysis { + value: Global { ingots }, + diagnostics: Rc::new(diagnostics), + }) + } else { + Err(fatal_diagnostics) + } + } } #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] pub struct GlobalId(pub(crate) u32); impl_intern_key!(GlobalId); -impl GlobalId {} +impl GlobalId { + pub fn try_new( + db: &dyn AnalyzerDb, + files: &FileStore, + deps: &IndexMap>, + ) -> Result, Vec> { + match Global::try_new(db, files, deps) { + Ok(analysis) => Ok(Analysis { + value: db.intern_global(Rc::new(analysis.value)), + diagnostics: analysis.diagnostics, + }), + Err(diagnostics) => Err(diagnostics), + } + } + + pub fn data(&self, db: &dyn AnalyzerDb) -> Rc { + db.lookup_intern_global(*self) + } +} #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct Ingot { @@ -221,10 +281,71 @@ pub struct Ingot { pub fe_files: BTreeMap, } +impl Ingot { + pub fn try_new( + db: &dyn AnalyzerDb, + files: &FileStore, + name: &str, + file_ids: &[SourceFileId], + deps: &IndexMap>, + ) -> Result, Vec> { + let global_analysis = GlobalId::try_new(db, files, deps)?; + + let mut diagnostics = global_analysis.diagnostics.deref().clone(); + let mut fatal_diagnostics = vec![]; + + let ingot = Self { + name: name.to_string(), + global: global_analysis.value, + fe_files: file_ids + .iter() + .filter_map(|file_id| { + let file = files.get_file(*file_id).expect("missing file for ID"); + match fe_parser::parse_file(*file_id, &file.content) { + Ok((ast, parser_diagnostics)) => { + diagnostics.extend(parser_diagnostics); + Some((*file_id, (file.to_owned(), ast))) + } + Err(diagnostics) => { + fatal_diagnostics.extend(diagnostics); + None + } + } + }) + .collect(), + }; + + if fatal_diagnostics.is_empty() { + Ok(Analysis { + value: ingot, + diagnostics: Rc::new(diagnostics), + }) + } else { + Err(fatal_diagnostics) + } + } +} + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] pub struct IngotId(pub(crate) u32); impl_intern_key!(IngotId); impl IngotId { + pub fn try_new( + db: &dyn AnalyzerDb, + files: &FileStore, + name: &str, + file_ids: &[SourceFileId], + deps: &IndexMap>, + ) -> Result, Vec> { + match Ingot::try_new(db, files, name, file_ids, deps) { + Ok(analysis) => Ok(Analysis { + value: db.intern_ingot(Rc::new(analysis.value)), + diagnostics: analysis.diagnostics, + }), + Err(diagnostics) => Err(diagnostics), + } + } + pub fn data(&self, db: &dyn AnalyzerDb) -> Rc { db.lookup_intern_ingot(*self) } @@ -237,6 +358,10 @@ impl IngotId { db.ingot_main_module(*self).value } + pub fn lib_module(&self, db: &dyn AnalyzerDb) -> Option { + db.ingot_lib_module(*self).value + } + pub fn diagnostics(&self, db: &dyn AnalyzerDb) -> Vec { let mut diagnostics = vec![]; self.sink_diagnostics(db, &mut diagnostics); @@ -285,10 +410,59 @@ pub struct Module { pub ast: ast::Module, } +impl Module { + pub fn try_new( + db: &dyn AnalyzerDb, + files: &FileStore, + file_id: SourceFileId, + deps: &IndexMap>, + ) -> Result, Vec> { + let global_analysis = GlobalId::try_new(db, files, deps)?; + let mut diagnostics = global_analysis.diagnostics.deref().clone(); + + let file = files.get_file(file_id).expect("missing file"); + let name = Path::new(&file.name) + .file_stem() + .expect("missing file name") + .to_string_lossy() + .to_string(); + + let (ast, parser_diagnostics) = fe_parser::parse_file(file_id, &file.content)?; + diagnostics.extend(parser_diagnostics); + + let module = Module { + name, + context: ModuleContext::Global(global_analysis.value), + file_content: ModuleFileContent::File { file: file_id }, + ast, + }; + + Ok(Analysis { + value: module, + diagnostics: Rc::new(diagnostics), + }) + } +} + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] pub struct ModuleId(pub(crate) u32); impl_intern_key!(ModuleId); impl ModuleId { + pub fn try_new( + db: &dyn AnalyzerDb, + files: &FileStore, + file_id: SourceFileId, + deps: &IndexMap>, + ) -> Result, Vec> { + match Module::try_new(db, files, file_id, deps) { + Ok(analysis) => Ok(Analysis { + value: db.intern_module(Rc::new(analysis.value)), + diagnostics: analysis.diagnostics, + }), + Err(diagnostics) => Err(diagnostics), + } + } + pub fn data(&self, db: &dyn AnalyzerDb) -> Rc { db.lookup_intern_module(*self) } @@ -374,6 +548,22 @@ impl ModuleId { db.module_contracts(*self) } + pub fn global(&self, db: &dyn AnalyzerDb) -> GlobalId { + match self.context(db) { + ModuleContext::Ingot(ingot) => ingot.data(db).global, + ModuleContext::Global(global) => global, + } + } + + pub fn global_ingots(&self, db: &dyn AnalyzerDb) -> IndexMap { + self.global(db) + .data(db) + .ingots + .clone() + .into_iter() + .collect() + } + /// All structs, including duplicates pub fn all_structs(&self, db: &dyn AnalyzerDb) -> Rc> { db.module_structs(*self) diff --git a/crates/analyzer/tests/analysis.rs b/crates/analyzer/tests/analysis.rs index bb8546d14e..4e9a27a4ea 100644 --- a/crates/analyzer/tests/analysis.rs +++ b/crates/analyzer/tests/analysis.rs @@ -1,5 +1,5 @@ use fe_analyzer::namespace::items::{ - self, Global, Ingot, Item, Module, ModuleContext, ModuleFileContent, TypeDef, + self, Global, IngotId, Item, Module, ModuleContext, ModuleFileContent, TypeDef, }; use fe_analyzer::namespace::types::{Event, FixedSize}; use fe_analyzer::{AnalyzerDb, TestDb}; @@ -7,6 +7,7 @@ use fe_common::diagnostics::{diagnostics_string, print_diagnostics, Diagnostic, use fe_common::files::FileStore; use fe_parser::node::NodeId; use fe_parser::node::Span; +use indexmap::indexmap; use indexmap::IndexMap; use insta::assert_snapshot; use smallvec::SmallVec; @@ -75,38 +76,13 @@ macro_rules! test_analysis_ingot { #[wasm_bindgen_test] fn $name() { let files = test_files::build_filestore($path); + let file_ids = files.all_files(); let db = TestDb::default(); - let global = Global::default(); - let global_id = db.intern_global(Rc::new(global)); - - let ingot = Ingot { - name: "test_ingot".to_string(), - global: global_id, - fe_files: files - .files - .values() - .into_iter() - .map(|file| { - let ast = match fe_parser::parse_file(file.id, &file.content) { - Ok((ast, diags)) => { - if !diags.is_empty() { - print_diagnostics(&diags, &files); - panic!("non-fatal parsing error"); - } - ast - } - Err(diags) => { - print_diagnostics(&diags, &files); - panic!("parsing failed"); - } - }; - (file.id, (file.clone(), ast)) - }) - .collect(), - }; - let ingot_id = db.intern_ingot(Rc::new(ingot)); + let ingot_id = IngotId::try_new(&db, &files, $path, &file_ids, &indexmap! {}) + .expect("failed to create new ingot") + .value; let snapshot = ingot_id .all_modules(&db) diff --git a/crates/analyzer/tests/errors.rs b/crates/analyzer/tests/errors.rs index f61b8cc762..309f6e3dfe 100644 --- a/crates/analyzer/tests/errors.rs +++ b/crates/analyzer/tests/errors.rs @@ -1,14 +1,11 @@ //! Tests for contracts that should cause compile errors -use fe_analyzer::namespace::items; -use fe_analyzer::namespace::items::{Global, ModuleFileContent}; -use fe_analyzer::AnalyzerDb; +use fe_analyzer::namespace::items::{IngotId, ModuleId}; use fe_analyzer::TestDb; -use fe_common::diagnostics::{diagnostics_string, print_diagnostics}; +use fe_common::diagnostics::diagnostics_string; use fe_common::files::FileStore; -use fe_parser::parse_file; +use indexmap::indexmap; use insta::assert_snapshot; -use std::rc::Rc; use test_files::build_filestore; use wasm_bindgen_test::wasm_bindgen_test; @@ -16,27 +13,11 @@ fn error_string(path: &str, src: &str) -> String { let mut files = FileStore::new(); let id = files.add_file(path, src); - let ast = match fe_parser::parse_file(id, src) { - Ok((module, _)) => module, - Err(diags) => { - print_diagnostics(&diags, &files); - panic!("parsing failed"); - } - }; - let db = TestDb::default(); - let global = Global::default(); - let global_id = db.intern_global(Rc::new(global)); - - let module = items::Module { - name: path.to_string(), - context: items::ModuleContext::Global(global_id), - file_content: ModuleFileContent::File { file: id }, - ast, - }; - - let module_id = db.intern_module(Rc::new(module)); + let module_id = ModuleId::try_new(&db, &files, id, &indexmap! {}) + .expect("failed to create new module") + .value; match fe_analyzer::analyze_module(&db, module_id) { Ok(_) => panic!("expected analysis to fail with an error"), @@ -45,30 +26,15 @@ fn error_string(path: &str, src: &str) -> String { } fn error_string_ingot(path: &str) -> String { - let files = build_filestore(path); + let mut files = build_filestore(path); + let file_ids = files.all_files(); + let deps = files.add_included_libraries(); let db = TestDb::default(); - let global = Global::default(); - let global_id = db.intern_global(Rc::new(global)); - - let ingot = items::Ingot { - name: path.to_string(), - global: global_id, - fe_files: files - .files - .values() - .into_iter() - .map(|file| { - ( - file.id, - (file.clone(), parse_file(file.id, &file.content).unwrap().0), - ) - }) - .collect(), - }; - - let ingot_id = db.intern_ingot(Rc::new(ingot)); + let ingot_id = IngotId::try_new(&db, &files, path, &file_ids, &deps) + .expect("failed to create new ingot") + .value; match fe_analyzer::analyze_ingot(&db, ingot_id) { Ok(_) => panic!("expected analysis to fail with an error"), diff --git a/crates/analyzer/tests/snapshots/analysis__basic_ingot.snap b/crates/analyzer/tests/snapshots/analysis__basic_ingot.snap index ec069d9667..29e9a7893c 100644 --- a/crates/analyzer/tests/snapshots/analysis__basic_ingot.snap +++ b/crates/analyzer/tests/snapshots/analysis__basic_ingot.snap @@ -30,92 +30,6 @@ note: │ ^^^^^^^^^^^^^ u256 -note: - ┌─ ingots/basic_ingot/src/bing.fe:2:5 - │ -2 │ my_address: address - │ ^^^^^^^^^^^^^^^^^^^ address - -note: - ┌─ ingots/basic_ingot/src/bing.fe:4:1 - │ -4 │ ╭ fn get_42_backend() -> u256: -5 │ │ return 42 - │ ╰─────────────^ attributes hash: 17979516652885443340 - │ - = FunctionSignature { - self_decl: None, - params: [], - return_type: Ok( - Base( - Numeric( - U256, - ), - ), - ), - } - -note: - ┌─ ingots/basic_ingot/src/bing.fe:5:12 - │ -5 │ return 42 - │ ^^ u256: Value - -note: - ┌─ ingots/basic_ingot/src/bing.fe:8:4 - │ -8 │ ╭ pub fn add(x: u256, y: u256) -> u256: -9 │ │ return x + y - │ ╰───────────────────^ attributes hash: 4022593831796629401 - │ - = FunctionSignature { - self_decl: None, - params: [ - FunctionParam { - name: "x", - typ: Ok( - Base( - Numeric( - U256, - ), - ), - ), - }, - FunctionParam { - name: "y", - typ: Ok( - Base( - Numeric( - U256, - ), - ), - ), - }, - ], - return_type: Ok( - Base( - Numeric( - U256, - ), - ), - ), - } - -note: - ┌─ ingots/basic_ingot/src/bing.fe:9:15 - │ -9 │ return x + y - │ ^ ^ u256: Value - │ │ - │ u256: Value - -note: - ┌─ ingots/basic_ingot/src/bing.fe:9:15 - │ -9 │ return x + y - │ ^^^^^ u256: Value - - note: ┌─ ingots/basic_ingot/src/main.fe:9:5 │ @@ -241,7 +155,7 @@ note: ┌─ ingots/basic_ingot/src/main.fe:16:16 │ 16 │ return get_42_backend() - │ ^^^^^^^^^^^^^^ Pure(FunctionId(0)) + │ ^^^^^^^^^^^^^^ Pure(FunctionId(5)) note: ┌─ ingots/basic_ingot/src/main.fe:18:5 @@ -368,9 +282,89 @@ note: ┌─ ingots/basic_ingot/src/main.fe:26:34 │ 26 │ let bing: BingContract = BingContract.create(0) - │ ^^^^^^^^^^^^^^^^^^^ BuiltinAssociatedFunction { contract: ContractId(0), function: Create } + │ ^^^^^^^^^^^^^^^^^^^ BuiltinAssociatedFunction { contract: ContractId(1), function: Create } 27 │ return bing.add(40, 50) - │ ^^^^^^^^ External { contract: ContractId(0), function: FunctionId(1) } + │ ^^^^^^^^ External { contract: ContractId(1), function: FunctionId(6) } + + +note: + ┌─ ingots/basic_ingot/src/bing.fe:2:5 + │ +2 │ my_address: address + │ ^^^^^^^^^^^^^^^^^^^ address + +note: + ┌─ ingots/basic_ingot/src/bing.fe:4:1 + │ +4 │ ╭ fn get_42_backend() -> u256: +5 │ │ return std::get_42() + │ ╰────────────────────────^ attributes hash: 17979516652885443340 + │ + = FunctionSignature { + self_decl: None, + params: [], + return_type: Ok( + Base( + Numeric( + U256, + ), + ), + ), + } + +note: + ┌─ ingots/basic_ingot/src/bing.fe:8:4 + │ +8 │ ╭ pub fn add(x: u256, y: u256) -> u256: +9 │ │ return x + y + │ ╰───────────────────^ attributes hash: 4022593831796629401 + │ + = FunctionSignature { + self_decl: None, + params: [ + FunctionParam { + name: "x", + typ: Ok( + Base( + Numeric( + U256, + ), + ), + ), + }, + FunctionParam { + name: "y", + typ: Ok( + Base( + Numeric( + U256, + ), + ), + ), + }, + ], + return_type: Ok( + Base( + Numeric( + U256, + ), + ), + ), + } + +note: + ┌─ ingots/basic_ingot/src/bing.fe:9:15 + │ +9 │ return x + y + │ ^ ^ u256: Value + │ │ + │ u256: Value + +note: + ┌─ ingots/basic_ingot/src/bing.fe:9:15 + │ +9 │ return x + y + │ ^^^^^ u256: Value diff --git a/crates/analyzer/tests/snapshots/errors__bad_ingot.snap b/crates/analyzer/tests/snapshots/errors__bad_ingot.snap index 5dddefee65..09ac85ffab 100644 --- a/crates/analyzer/tests/snapshots/errors__bad_ingot.snap +++ b/crates/analyzer/tests/snapshots/errors__bad_ingot.snap @@ -3,18 +3,6 @@ source: crates/analyzer/tests/errors.rs expression: error_string_ingot(&path) --- -error: cannot glob import from type - ┌─ compile_errors/bad_ingot/src/foo.fe:1:11 - │ -1 │ use bing::Bong::* - │ ^^^^ prefix item must be a module - -error: unresolved path item - ┌─ compile_errors/bad_ingot/src/foo.fe:2:11 - │ -2 │ use bing::Tong - │ ^^^^ not found - error: unresolved path item ┌─ compile_errors/bad_ingot/src/main.fe:2:5 │ @@ -63,10 +51,43 @@ error: a type with the same name has already been imported 9 │ contract Bar: │ --- `Bar` redefined here +error: a ingot named "std" has already been defined + ┌─ compile_errors/bad_ingot/src/main.fe:14:4 + │ +14 │ fn std(): + │ ^^^ `std` redefined here + error: incorrect type for `Foo` argument `my_num` ┌─ compile_errors/bad_ingot/src/main.fe:12:27 │ 12 │ return Foo(my_num=true) │ ^^^^ this has type `bool`; expected type `u256` +error: the ingot named "std" is already defined + ┌─ compile_errors/bad_ingot/src/bing.fe:1:5 + │ +1 │ use std + │ ^^^ + │ │ + │ redefined here + │ hint: delete this import + +error: cannot glob import from type + ┌─ compile_errors/bad_ingot/src/foo.fe:1:11 + │ +1 │ use bing::Bong::* + │ ^^^^ prefix item must be a module + +error: unresolved path item + ┌─ compile_errors/bad_ingot/src/foo.fe:2:11 + │ +2 │ use bing::Tong + │ ^^^^ not found + +error: an ingot named "std" has already been defined + ┌─ compile_errors/bad_ingot/src/foo.fe:3:13 + │ +3 │ use bing as std + │ ^^^ conflicts with the ingot named "std" + diff --git a/crates/analyzer/tests/snapshots/errors__duplicate_contract_in_module.snap b/crates/analyzer/tests/snapshots/errors__duplicate_contract_in_module.snap index ec375d8b69..5a8984d169 100644 --- a/crates/analyzer/tests/snapshots/errors__duplicate_contract_in_module.snap +++ b/crates/analyzer/tests/snapshots/errors__duplicate_contract_in_module.snap @@ -3,7 +3,7 @@ source: crates/analyzer/tests/errors.rs expression: "error_string(&path, &src)" --- -error: duplicate type name +error: a type named "Foo" has already been defined ┌─ compile_errors/duplicate_contract_in_module.fe:1:10 │ 1 │ contract Foo: diff --git a/crates/analyzer/tests/snapshots/errors__duplicate_struct_in_module.snap b/crates/analyzer/tests/snapshots/errors__duplicate_struct_in_module.snap index 2bf4765d23..81ce110e7b 100644 --- a/crates/analyzer/tests/snapshots/errors__duplicate_struct_in_module.snap +++ b/crates/analyzer/tests/snapshots/errors__duplicate_struct_in_module.snap @@ -3,7 +3,7 @@ source: crates/analyzer/tests/errors.rs expression: "error_string(&path, &src)" --- -error: duplicate type name +error: a type named "MyStruct" has already been defined ┌─ compile_errors/duplicate_struct_in_module.fe:1:8 │ 1 │ struct MyStruct: diff --git a/crates/analyzer/tests/snapshots/errors__duplicate_typedef_in_module.snap b/crates/analyzer/tests/snapshots/errors__duplicate_typedef_in_module.snap index fadc11bf37..99556ecb85 100644 --- a/crates/analyzer/tests/snapshots/errors__duplicate_typedef_in_module.snap +++ b/crates/analyzer/tests/snapshots/errors__duplicate_typedef_in_module.snap @@ -3,7 +3,7 @@ source: crates/analyzer/tests/errors.rs expression: "error_string(&path, &src)" --- -error: duplicate type name +error: a type named "bar" has already been defined ┌─ compile_errors/duplicate_typedef_in_module.fe:2:6 │ 2 │ type bar = u8 diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 3d5b3b99bf..c5b9892313 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -7,6 +7,7 @@ license = "Apache-2.0" repository = "https://github.com/ethereum/fe" [dependencies] +fe-library = {path = "../library", version = "^0.11.0-alpha"} tiny-keccak = { version = "2.0", features = ["keccak"] } hex = "0.4" codespan-reporting = "0.11.1" @@ -15,3 +16,5 @@ ron = "0.5.1" difference = "2.0" num-traits = "0.2.14" once_cell = "1.8.0" +include_dir = "0.6.0" +indexmap = "1.6.2" diff --git a/crates/common/src/files.rs b/crates/common/src/files.rs index 0412c04c81..bd5441a801 100644 --- a/crates/common/src/files.rs +++ b/crates/common/src/files.rs @@ -2,6 +2,9 @@ use crate::utils::keccak; use crate::Span; use codespan_reporting as cs; use cs::files::Error as CsError; +use include_dir::Dir; +use indexmap::indexmap; +use indexmap::IndexMap; use std::collections::HashMap; use std::convert::TryInto; use std::ops::Range; @@ -86,6 +89,37 @@ impl FileStore { id } + /// Adds an included dir to the file store. + pub fn add_included_dir(&mut self, dir: &Dir) -> Vec { + let mut file_ids = vec![]; + + for file in dir.files() { + file_ids.push( + self.add_file( + file.path() + .to_str() + .expect("cannot convert file path to string"), + file.contents_utf8() + .expect("could not get utf8 encoded file content"), + ), + ); + } + + for sub_dir in dir.dirs() { + file_ids.extend(self.add_included_dir(sub_dir)) + } + + file_ids + } + + /// Adds the included libraries to the file store and returns a mapping of + /// library names to file ids. + pub fn add_included_libraries(&mut self) -> IndexMap> { + indexmap! { + "std".to_string() => self.add_included_dir(&fe_library::STD) + } + } + pub fn load_file(&mut self, path: &str) -> io::Result<(String, SourceFileId)> { let content = self.loader.load_file(Path::new(&path))?; let id = self.add_file(path, &content); diff --git a/crates/driver/src/lib.rs b/crates/driver/src/lib.rs index 54d54444d4..e5aad406d8 100644 --- a/crates/driver/src/lib.rs +++ b/crates/driver/src/lib.rs @@ -1,14 +1,12 @@ -use fe_analyzer::namespace::items::{Global, Ingot, Module, ModuleContext, ModuleFileContent}; -use fe_analyzer::AnalyzerDb; +use fe_analyzer::context::Analysis; +use fe_analyzer::namespace::items::{IngotId, ModuleId}; use fe_common::diagnostics::Diagnostic; use fe_common::files::{FileStore, SourceFileId}; -use fe_parser::parse_file; use fe_yulgen::Db; use indexmap::IndexMap; #[cfg(feature = "solc-backend")] use serde_json::Value; -use std::path::Path; -use std::rc::Rc; +use std::ops::Deref; /// The artifacts of a compiled module. pub struct CompiledModule { @@ -35,34 +33,19 @@ pub struct CompileError(pub Vec); pub fn compile_module( files: &FileStore, file_id: SourceFileId, + deps: &IndexMap>, _with_bytecode: bool, _optimize: bool, ) -> Result { - let file = files.get_file(file_id).expect("missing file"); - let src = &file.content; - let mut errors = vec![]; - let (ast, parser_diagnostics) = parse_file(file_id, src).map_err(CompileError)?; - errors.extend(parser_diagnostics.into_iter()); - let src_ast = format!("{:#?}", &ast); - let db = Db::default(); - let global = Global::default(); - let global_id = db.intern_global(Rc::new(global)); - - let module = Module { - name: Path::new(&file.name) - .file_stem() - .expect("missing file name") - .to_string_lossy() - .to_string(), - context: ModuleContext::Global(global_id), - file_content: ModuleFileContent::File { file: file_id }, - ast, - }; - let module_id = db.intern_module(Rc::new(module)); + let Analysis { + value: module_id, + diagnostics: parser_diagnostics, + } = ModuleId::try_new(&db, files, file_id, deps).map_err(CompileError)?; + errors.extend(parser_diagnostics.deref().clone()); match fe_analyzer::analyze_module(&db, module_id) { Ok(_) => {} @@ -136,7 +119,7 @@ pub fn compile_module( .collect::>(); Ok(CompiledModule { - src_ast, + src_ast: format!("{:?}", module_id.ast(&db)), lowered_ast, contracts, }) @@ -150,6 +133,7 @@ pub fn compile_ingot( name: &str, files: &FileStore, file_ids: &[SourceFileId], + deps: &IndexMap>, _with_bytecode: bool, _optimize: bool, ) -> Result { @@ -157,24 +141,11 @@ pub fn compile_ingot( let db = Db::default(); - let global = Global::default(); - let global_id = db.intern_global(Rc::new(global)); - - let ingot = Ingot { - name: name.to_string(), - global: global_id, - fe_files: file_ids - .iter() - .map(|file_id| { - let file = files.get_file(*file_id).expect("missing file for ID"); - let (ast, parser_diagnostics) = - parse_file(*file_id, &file.content).map_err(CompileError)?; - errors.extend(parser_diagnostics.into_iter()); - Ok((*file_id, (file.to_owned(), ast))) - }) - .collect::>()?, - }; - let ingot_id = db.intern_ingot(Rc::new(ingot)); + let Analysis { + value: ingot_id, + diagnostics: parser_diagnostics, + } = IngotId::try_new(&db, files, name, file_ids, deps).map_err(CompileError)?; + errors.extend(parser_diagnostics.deref().clone()); match fe_analyzer::analyze_ingot(&db, ingot_id) { Ok(_) => {} diff --git a/crates/fe/Cargo.toml b/crates/fe/Cargo.toml index 3a3d444e77..f39d9caaf2 100644 --- a/crates/fe/Cargo.toml +++ b/crates/fe/Cargo.toml @@ -16,6 +16,7 @@ solc-backend = ["fe-driver/solc-backend"] [dependencies] clap = "2.33.3" walkdir = "2" +indexmap = "1.6.2" fe-common = {path = "../common", version = "^0.11.0-alpha"} fe-driver = {path = "../driver", version = "^0.11.0-alpha"} fe-parser = {path = "../parser", version = "^0.11.0-alpha"} diff --git a/crates/fe/src/main.rs b/crates/fe/src/main.rs index 9002bed945..6c451cc22a 100644 --- a/crates/fe/src/main.rs +++ b/crates/fe/src/main.rs @@ -88,6 +88,7 @@ pub fn main() { let (content, compiled_module) = if Path::new(input_path).is_file() { let mut files = FileStore::new(); + let deps = files.add_included_libraries(); let file = files.load_file(input_path).map_err(ioerr_to_string); let (content, id) = match file { @@ -98,17 +99,20 @@ pub fn main() { Ok(file) => file, }; - let compiled_module = match fe_driver::compile_module(&files, id, with_bytecode, optimize) { - Ok(module) => module, - Err(error) => { - eprintln!("Unable to compile {}.", input_path); - print_diagnostics(&error.0, &files); - std::process::exit(1) - } - }; + let compiled_module = + match fe_driver::compile_module(&files, id, &deps, with_bytecode, optimize) { + Ok(module) => module, + Err(error) => { + eprintln!("Unable to compile {}.", input_path); + print_diagnostics(&error.0, &files); + std::process::exit(1) + } + }; (content, compiled_module) } else { - let files = build_ingot_filestore_for_dir(input_path); + let mut files = build_ingot_filestore_for_dir(input_path); + let ingot_files = files.all_files(); + let deps = files.add_included_libraries(); if !Path::new(input_path).exists() { eprintln!("Input directory does not exist: `{}`.", input_path); @@ -118,7 +122,8 @@ pub fn main() { let compiled_module = match fe_driver::compile_ingot( input_path, &files, - &files.all_files(), + &ingot_files, + &deps, with_bytecode, optimize, ) { diff --git a/crates/library/Cargo.toml b/crates/library/Cargo.toml new file mode 100644 index 0000000000..6a19867981 --- /dev/null +++ b/crates/library/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "fe-library" +version = "0.11.0-alpha" +authors = ["The Fe Developers "] +edition = "2018" +license = "Apache-2.0" +repository = "https://github.com/ethereum/fe" + +[dependencies] +include_dir = "0.6.0" diff --git a/crates/library/src/lib.rs b/crates/library/src/lib.rs new file mode 100644 index 0000000000..36a51c8f98 --- /dev/null +++ b/crates/library/src/lib.rs @@ -0,0 +1,3 @@ +use include_dir::{include_dir, Dir}; + +pub const STD: Dir = include_dir!("std"); diff --git a/crates/library/std/src/lib.fe b/crates/library/std/src/lib.fe new file mode 100644 index 0000000000..8289e008e7 --- /dev/null +++ b/crates/library/std/src/lib.fe @@ -0,0 +1,2 @@ +fn get_42() -> u256: + return 42 \ No newline at end of file diff --git a/crates/test-files/fixtures/compile_errors/bad_ingot/src/bing.fe b/crates/test-files/fixtures/compile_errors/bad_ingot/src/bing.fe index b4a02e7635..d3d4c58dfc 100644 --- a/crates/test-files/fixtures/compile_errors/bad_ingot/src/bing.fe +++ b/crates/test-files/fixtures/compile_errors/bad_ingot/src/bing.fe @@ -1,2 +1,4 @@ +use std + struct Bong: pass \ No newline at end of file diff --git a/crates/test-files/fixtures/compile_errors/bad_ingot/src/foo.fe b/crates/test-files/fixtures/compile_errors/bad_ingot/src/foo.fe index 1455137c49..2123e26c88 100644 --- a/crates/test-files/fixtures/compile_errors/bad_ingot/src/foo.fe +++ b/crates/test-files/fixtures/compile_errors/bad_ingot/src/foo.fe @@ -1,5 +1,6 @@ use bing::Bong::* use bing::Tong +use bing as std struct Foo: my_num: u256 diff --git a/crates/test-files/fixtures/compile_errors/bad_ingot/src/main.fe b/crates/test-files/fixtures/compile_errors/bad_ingot/src/main.fe index 56b0cf06ff..1948895aff 100644 --- a/crates/test-files/fixtures/compile_errors/bad_ingot/src/main.fe +++ b/crates/test-files/fixtures/compile_errors/bad_ingot/src/main.fe @@ -10,3 +10,6 @@ contract Bar: pub fn a() -> Foo: return Foo(my_num=true) + +fn std(): + pass \ No newline at end of file diff --git a/crates/test-files/fixtures/ingots/basic_ingot/src/bing.fe b/crates/test-files/fixtures/ingots/basic_ingot/src/bing.fe index 81dda51a14..ca319d338f 100644 --- a/crates/test-files/fixtures/ingots/basic_ingot/src/bing.fe +++ b/crates/test-files/fixtures/ingots/basic_ingot/src/bing.fe @@ -2,7 +2,7 @@ struct Bing: my_address: address fn get_42_backend() -> u256: - return 42 + return std::get_42() contract BingContract: pub fn add(x: u256, y: u256) -> u256: diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index 261a05d4c9..78cdbe3ee5 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -21,6 +21,7 @@ primitive-types = {version = "0.9", default-features = false, features = ["rlp"] serde_json = "1.0.64" solc = {git = "https://github.com/g-r-a-n-t/solc-rust", optional = true} yultsur = {git = "https://github.com/g-r-a-n-t/yultsur"} +indexmap = "1.6.2" [features] solc-backend = ["fe-yulc", "solc"] diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index 5cf1ff7ed7..c1d4892721 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -249,8 +249,9 @@ pub fn deploy_contract( let src = test_files::fixture(fixture); let mut files = FileStore::new(); let id = files.add_file(fixture, src); + let deps = files.add_included_libraries(); - let compiled_module = match driver::compile_module(&files, id, true, true) { + let compiled_module = match driver::compile_module(&files, id, &deps, true, true) { Ok(module) => module, Err(error) => { fe_common::diagnostics::print_diagnostics(&error.0, &files); @@ -279,9 +280,11 @@ pub fn deploy_contract_from_ingot( contract_name: &str, init_params: &[ethabi::Token], ) -> ContractHarness { - let files = test_files::build_filestore(path); + let mut files = test_files::build_filestore(path); + let ingot_files = files.all_files(); + let deps = files.add_included_libraries(); - let compiled_module = match driver::compile_ingot(path, &files, &files.all_files(), true, true) + let compiled_module = match driver::compile_ingot(path, &files, &ingot_files, &deps, true, true) { Ok(module) => module, Err(error) => { @@ -483,9 +486,10 @@ pub fn compile_solidity_contract( #[allow(dead_code)] pub fn load_contract(address: H160, fixture: &str, contract_name: &str) -> ContractHarness { let mut files = FileStore::new(); + let deps = files.add_included_libraries(); let src = test_files::fixture(fixture); let id = files.add_file(fixture, src); - let compiled_module = match driver::compile_module(&files, id, true, true) { + let compiled_module = match driver::compile_module(&files, id, &deps, true, true) { Ok(module) => module, Err(err) => { print_diagnostics(&err.0, &files); diff --git a/crates/tests/src/crashes.rs b/crates/tests/src/crashes.rs index 0646da2514..906afec8dd 100644 --- a/crates/tests/src/crashes.rs +++ b/crates/tests/src/crashes.rs @@ -9,8 +9,9 @@ macro_rules! test_file { let path = concat!("crashes/", stringify!($name), ".fe"); let src = test_files::fixture(path); let mut files = FileStore::new(); + let deps = files.add_included_libraries(); let id = files.add_file(path, src); - fe_driver::compile_module(&files, id, true, true).ok(); + fe_driver::compile_module(&files, id, &deps, true, true).ok(); } }; } diff --git a/newsfragments/601.internal.md b/newsfragments/601.internal.md new file mode 100644 index 0000000000..bc97defcf1 --- /dev/null +++ b/newsfragments/601.internal.md @@ -0,0 +1,5 @@ +Added a globally available *dummy* std lib. + +This library contains a single `get_42` function, which can be called using `std::get_42()`. Once +low-level intrinsics have been added to the language, we can delete `get_42` and start adding +useful code.