diff --git a/Cargo.lock b/Cargo.lock index ed86abbc22..9959c920a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -535,6 +535,7 @@ dependencies = [ "fe-common", "fe-driver", "fe-parser", + "indexmap", "walkdir", ] @@ -582,11 +583,15 @@ version = "0.11.0-alpha" dependencies = [ "codespan-reporting", "difference", + "fe-library", "hex", + "include_dir 0.6.2", + "indexmap", "num-traits", "once_cell", "ron", "serde", + "smol_str", "tiny-keccak", ] @@ -605,6 +610,7 @@ dependencies = [ "fe-yulgen", "getrandom 0.2.3", "hex", + "indexmap", "primitive-types", "serde_json", "solc", @@ -653,6 +659,13 @@ dependencies = [ "serde_json", ] +[[package]] +name = "fe-library" +version = "0.11.0-alpha" +dependencies = [ + "include_dir 0.6.2", +] + [[package]] name = "fe-lowering" version = "0.11.0-alpha" @@ -696,7 +709,7 @@ name = "fe-test-files" version = "0.11.0-alpha" dependencies = [ "fe-common", - "include_dir", + "include_dir 0.7.2", ] [[package]] @@ -803,6 +816,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "half" version = "1.8.2" @@ -898,6 +917,17 @@ dependencies = [ "syn", ] +[[package]] +name = "include_dir" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b56e147e6187d61e9d0f039f10e070d0c0a887e24fe0bb9ca3f29bfde62cab" +dependencies = [ + "glob", + "include_dir_impl", + "proc-macro-hack", +] + [[package]] name = "include_dir" version = "0.7.2" @@ -907,6 +937,19 @@ dependencies = [ "include_dir_macros", ] +[[package]] +name = "include_dir_impl" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a0c890c85da4bab7bce4204c707396bbd3c6c8a681716a51c8814cfc2b682df" +dependencies = [ + "anyhow", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "include_dir_macros" version = "0.7.2" @@ -1278,6 +1321,12 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + [[package]] name = "proc-macro2" version = "1.0.34" diff --git a/crates/analyzer/src/db.rs b/crates/analyzer/src/db.rs index 5540b9a279..2dd70e5f30 100644 --- a/crates/analyzer/src/db.rs +++ b/crates/analyzer/src/db.rs @@ -6,8 +6,6 @@ use crate::namespace::items::{ }; use crate::namespace::types; use fe_common::Span; -use fe_parser::ast; -use fe_parser::node::Node; use indexmap::map::IndexMap; use smol_str::SmolStr; use std::rc::Rc; @@ -59,6 +57,12 @@ 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>; + #[salsa::invoke(queries::ingots::ingot_root_module)] + fn ingot_root_module(&self, ingot: IngotId) -> Option; + #[salsa::invoke(queries::ingots::ingot_root_sub_modules)] + fn ingot_root_sub_modules(&self, ingot: IngotId) -> Rc>; // Module #[salsa::invoke(queries::module::module_all_items)] @@ -74,16 +78,8 @@ pub trait AnalyzerDb { &self, module: ModuleId, ) -> Analysis>>; - #[salsa::invoke(queries::module::module_resolve_use_tree)] - fn module_resolve_use_tree( - &self, - module: ModuleId, - tree: Node, - ) -> Analysis>>; #[salsa::invoke(queries::module::module_parent_module)] fn module_parent_module(&self, module: ModuleId) -> Option; - #[salsa::invoke(queries::module::module_adjacent_modules)] - fn module_adjacent_modules(&self, module: ModuleId) -> Rc>; #[salsa::invoke(queries::module::module_sub_modules)] fn module_sub_modules(&self, module: ModuleId) -> Rc>; diff --git a/crates/analyzer/src/db/queries/ingots.rs b/crates/analyzer/src/db/queries/ingots.rs index 854224dfe3..c25ba59fc5 100644 --- a/crates/analyzer/src/db/queries/ingots.rs +++ b/crates/analyzer/src/db/queries/ingots.rs @@ -3,7 +3,9 @@ use crate::namespace::items::{IngotId, Module, ModuleContext, ModuleFileContent, use crate::AnalyzerDb; use fe_common::diagnostics::{Diagnostic, Severity}; use fe_parser::ast; +use indexmap::map::IndexMap; use indexmap::set::IndexSet; +use smol_str::SmolStr; use std::path::Path; use std::rc::Rc; @@ -42,25 +44,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") - .into(), - 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") .into(), - }, - }; - - 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") + .into(), + }, + }; + Some(db.intern_module(Rc::new(module))) + } else { + None + } }) .collect::>(); @@ -72,15 +75,7 @@ pub fn ingot_main_module(db: &dyn AnalyzerDb, ingot_id: IngotId) -> Analysis Analysis Analysis> { + let lib_id = ingot_id + .all_modules(db) + .iter() + .find(|module_id| module_id.name(db) == "lib") + .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![] + } + }), + } +} + +pub fn ingot_root_module(db: &dyn AnalyzerDb, ingot_id: IngotId) -> Option { + db.ingot_main_module(ingot_id) + .value + .or(db.ingot_lib_module(ingot_id).value) +} + +pub fn ingot_root_sub_modules( + db: &dyn AnalyzerDb, + ingot_id: IngotId, +) -> Rc> { + let modules = ingot_id + .all_modules(db) + .iter() + .filter(|module_id| { + if Some(**module_id) == ingot_id.root_module(db) { + false + } else { + let mut in_src = false; + if let Some(parent) = Path::new(&module_id.ingot_path(db).to_string()).parent() { + if let Some(name) = parent.file_name() { + in_src = name == "src" + } + } + in_src + } + }) + .map(|module_id| (module_id.name(db), *module_id)) + .collect(); + + Rc::new(modules) +} diff --git a/crates/analyzer/src/db/queries/module.rs b/crates/analyzer/src/db/queries/module.rs index aede3e1423..a63bb4d4c3 100644 --- a/crates/analyzer/src/db/queries/module.rs +++ b/crates/analyzer/src/db/queries/module.rs @@ -1,4 +1,3 @@ -use crate::builtins; use crate::context::{Analysis, AnalyzerContext}; use crate::db::AnalyzerDb; use crate::errors::{self, TypeError}; @@ -12,7 +11,6 @@ use crate::traversal::types::type_desc; use fe_common::diagnostics::Label; use fe_common::Span; use fe_parser::ast; -use fe_parser::node::Node; use indexmap::indexmap; use indexmap::map::{Entry, IndexMap}; use smol_str::SmolStr; @@ -20,29 +18,6 @@ use std::collections::HashSet; use std::ops::Deref; use std::path::Path; use std::rc::Rc; -use strum::IntoEnumIterator; - -// Placeholder; someday std::prelude will be a proper module. -fn std_prelude_items() -> IndexMap { - let mut items = indexmap! { - SmolStr::new("bool") => Item::Type(TypeDef::Primitive(types::Base::Bool)), - SmolStr::new("address") => Item::Type(TypeDef::Primitive(types::Base::Address)), - }; - items.extend(types::Integer::iter().map(|typ| { - ( - typ.as_ref().into(), - Item::Type(TypeDef::Primitive(types::Base::Numeric(typ))), - ) - })); - items.extend(types::GenericType::iter().map(|typ| (typ.name(), Item::GenericType(typ)))); - items.extend( - builtins::GlobalFunction::iter() - .map(|fun| (fun.as_ref().into(), Item::BuiltinFunction(fun))), - ); - items - .extend(builtins::GlobalObject::iter().map(|obj| (obj.as_ref().into(), Item::Object(obj)))); - items -} pub fn module_all_items(db: &dyn AnalyzerDb, module: ModuleId) -> Rc> { let ast::Module { body } = &module.data(db).ast; @@ -93,25 +68,33 @@ pub fn module_item_map( db: &dyn AnalyzerDb, module: ModuleId, ) -> Analysis>> { - let builtin_items = std_prelude_items(); + // we must check for conflicts with global item names + let global_items = module.global_items(db); + + // sub modules and used items are included in this map let sub_modules = module .sub_modules(db) .iter() .map(|(name, id)| (name.clone(), Item::Module(*id))) .collect::>(); let used_items = db.module_used_item_map(module); - let mut diagnostics = (*used_items.diagnostics).clone(); + let mut diagnostics = used_items.diagnostics.deref().clone(); let mut map = IndexMap::::new(); for item in module.all_items(db).iter() { let item_name = item.name(db); - if let Some(builtin) = builtin_items.get(&item_name) { - let builtin_kind = builtin.item_kind_display_name(); + if let Some(global_item) = global_items.get(&item_name) { + let kind = item.item_kind_display_name(); + let other_kind = global_item.item_kind_display_name(); diagnostics.push(errors::error( - &format!("type name conflicts with built-in {}", builtin_kind), - item.name_span(db).expect("duplicate built-in names?"), - &format!("`{}` is a built-in {}", item_name, builtin_kind), + &format!( + "{} name conflicts with the {} named \"{}\"", + kind, other_kind, item_name + ), + item.name_span(db) + .expect("user defined item is missing a name span"), + &format!("`{}` is already defined", item_name), )); continue; } @@ -129,23 +112,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 + ), + &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 ), - Label::secondary( + 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); @@ -156,7 +151,6 @@ pub fn module_item_map( value: Rc::new( map.into_iter() .chain(sub_modules) - .chain(builtin_items) .chain( used_items .value @@ -187,7 +181,6 @@ pub fn module_structs(db: &dyn AnalyzerDb, module: ModuleId) -> Rc module .all_items(db) .iter() - // TODO: this needs dependency graph stuff .chain( module .used_items(db) @@ -231,18 +224,17 @@ pub fn module_used_item_map( db: &dyn AnalyzerDb, module: ModuleId, ) -> Analysis>> { - let mut diagnostics = vec![]; + // we must check for conflicts with the global items map + let global_items = module.global_items(db); + let mut diagnostics = vec![]; let ast::Module { body } = &module.data(db).ast; let items = body .iter() .fold(indexmap! {}, |mut accum, stmt| { if let ast::ModuleStmt::Use(use_stmt) = stmt { - let parent = module - .parent_module(db) - .expect("module does not have a parent"); - let items = db.module_resolve_use_tree(parent, use_stmt.kind.tree.clone()); + let items = module.resolve_use_tree(db, &use_stmt.kind.tree, true); diagnostics.extend(items.diagnostics.deref().clone()); for (name, (name_span, item)) in items.value.iter() { @@ -266,15 +258,16 @@ pub fn module_used_item_map( }) .into_iter() .filter_map(|(name, (name_span, item))| { - let builtin_items = std_prelude_items(); - - if let Some(builtin) = builtin_items.get(&name) { - let builtin_kind = builtin.item_kind_display_name(); + if let Some(global_item) = global_items.get(&name) { + let other_kind = global_item.item_kind_display_name(); diagnostics.push(errors::error( - &format!("import name conflicts with built-in {}", builtin_kind), + &format!( + "import name conflicts with the {} named \"{}\"", + other_kind, name + ), name_span, - &format!("`{}` is a built-in {}", name, builtin_kind), + &format!("`{}` is already defined", name), )); None @@ -290,110 +283,6 @@ pub fn module_used_item_map( } } -pub fn module_resolve_use_tree( - db: &dyn AnalyzerDb, - module: ModuleId, - tree: Node, -) -> Analysis>> { - let mut diagnostics = vec![]; - - match &tree.kind { - ast::UseTree::Glob { prefix } => { - let prefix_module = Item::Module(module).resolve_path(db, prefix); - diagnostics.extend(prefix_module.diagnostics.deref().clone()); - - let items = match prefix_module.value { - Some(Item::Module(module)) => (*module.items(db)) - .clone() - .into_iter() - .map(|(name, item)| (name, (tree.span, item))) - .collect(), - Some(item) => { - diagnostics.push(errors::error( - format!("cannot glob import from {}", item.item_kind_display_name()), - prefix.segments.last().expect("path is empty").span, - "prefix item must be a module", - )); - indexmap! {} - } - None => indexmap! {}, - }; - - Analysis { - value: Rc::new(items), - diagnostics: Rc::new(diagnostics), - } - } - ast::UseTree::Nested { prefix, children } => { - let prefix_module = Item::Module(module).resolve_path(db, prefix); - diagnostics.extend(prefix_module.diagnostics.deref().clone()); - - let items = match prefix_module.value { - Some(Item::Module(module)) => { - children.iter().fold(indexmap! {}, |mut accum, node| { - let child_items = db.module_resolve_use_tree(module, node.clone()); - diagnostics.extend(child_items.diagnostics.deref().clone()); - - for (name, (name_span, item)) in child_items.value.iter() { - if let Some((other_name_span, other_item)) = - accum.insert(name.to_owned(), (*name_span, *item)) - { - diagnostics.push(errors::duplicate_name_error( - &format!( - "a {} with the same name has already been imported", - other_item.item_kind_display_name() - ), - name, - other_name_span, - *name_span, - )); - } - } - - accum - }) - } - Some(item) => { - diagnostics.push(errors::error( - format!("cannot glob import from {}", item.item_kind_display_name()), - prefix.segments.last().unwrap().span, - "prefix item must be a module", - )); - indexmap! {} - } - None => indexmap! {}, - }; - - Analysis { - value: Rc::new(items), - diagnostics: Rc::new(diagnostics), - } - } - ast::UseTree::Simple { path, rename } => { - let item = Item::Module(module).resolve_path(db, path); - - let items = match item.value { - Some(item) => { - let (item_name, item_name_span) = if let Some(name) = rename { - (name.kind.clone(), name.span) - } else { - let name_segment_node = path.segments.last().expect("path is empty"); - (name_segment_node.kind.clone(), name_segment_node.span) - }; - - indexmap! { item_name => (item_name_span, item) } - } - None => indexmap! {}, - }; - - Analysis { - value: Rc::new(items), - diagnostics: item.diagnostics, - } - } - } -} - pub fn module_parent_module(db: &dyn AnalyzerDb, module: ModuleId) -> Option { match module.context(db) { ModuleContext::Ingot(ingot) => { @@ -416,17 +305,6 @@ pub fn module_parent_module(db: &dyn AnalyzerDb, module: ModuleId) -> Option Rc> { - if let Some(parent) = module.parent_module(db) { - parent.sub_modules(db) - } else { - Rc::new(indexmap! {}) - } -} - pub fn module_sub_modules( db: &dyn AnalyzerDb, module: ModuleId, @@ -449,8 +327,13 @@ pub fn module_sub_modules( .collect::>(); Rc::new(sub_modules) } - // file modules do not have sub-modules (for now, at least) - ModuleFileContent::File { .. } => Rc::new(indexmap! {}), + ModuleFileContent::File { .. } => { + if Some(module) == ingot.root_module(db) { + ingot.root_sub_modules(db) + } else { + Rc::new(indexmap! {}) + } + } } } // if we are compiling a module in the global context, then it will not have any sub-modules diff --git a/crates/analyzer/src/namespace/items.rs b/crates/analyzer/src/namespace/items.rs index 0baac8a132..38d5696c2f 100644 --- a/crates/analyzer/src/namespace/items.rs +++ b/crates/analyzer/src/namespace/items.rs @@ -7,7 +7,7 @@ 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}; @@ -15,7 +15,10 @@ use indexmap::indexmap; use indexmap::IndexMap; use smol_str::SmolStr; use std::collections::BTreeMap; +use std::ops::Deref; +use std::path::Path; use std::rc::Rc; +use strum::IntoEnumIterator; /// A named item. This does not include things inside of /// a function body. @@ -102,7 +105,7 @@ 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.items(db), Item::Module(module) => module.items(db), Item::Type(_) => todo!("cannot access items in types yet"), Item::GenericType(_) @@ -129,31 +132,6 @@ impl Item { } } - pub fn resolve_path(&self, db: &dyn AnalyzerDb, path: &ast::Path) -> Analysis> { - let mut curr_item = *self; - - for node in path.segments.iter() { - curr_item = match curr_item.items(db).get(&node.kind) { - Some(item) => *item, - None => { - return Analysis { - value: None, - diagnostics: Rc::new(vec![errors::error( - "unresolved path item", - node.span, - "not found", - )]), - } - } - } - } - - Analysis { - value: Some(curr_item), - diagnostics: Rc::new(vec![]), - } - } - pub fn path(&self, db: &dyn AnalyzerDb) -> Rc> { // The path is used to generate a yul identifier, // eg `foo::Bar::new` becomes `$$foo$Bar$new`. @@ -180,6 +158,35 @@ impl Item { } } + pub fn resolve_path_segments( + &self, + db: &dyn AnalyzerDb, + segments: &[Node], + ) -> Analysis> { + let mut curr_item = *self; + + for node in segments { + curr_item = match curr_item.items(db).get(&node.kind) { + Some(item) => *item, + None => { + return Analysis { + value: None, + diagnostics: Rc::new(vec![errors::error( + "unresolved path item", + node.span, + "not found", + )]), + } + } + } + } + + Analysis { + value: Some(curr_item), + diagnostics: Rc::new(vec![]), + } + } + /// Downcast utility function pub fn as_contract(&self) -> Option { match self { @@ -203,15 +210,92 @@ impl Item { } } +// Placeholder; someday std::prelude will be a proper module. +pub fn std_prelude_items() -> IndexMap { + let mut items = indexmap! { + SmolStr::new("bool") => Item::Type(TypeDef::Primitive(types::Base::Bool)), + SmolStr::new("address") => Item::Type(TypeDef::Primitive(types::Base::Address)), + }; + items.extend(types::Integer::iter().map(|typ| { + ( + typ.as_ref().into(), + Item::Type(TypeDef::Primitive(types::Base::Numeric(typ))), + ) + })); + items.extend(types::GenericType::iter().map(|typ| (typ.name(), Item::GenericType(typ)))); + items.extend( + builtins::GlobalFunction::iter() + .map(|fun| (fun.as_ref().into(), Item::BuiltinFunction(fun))), + ); + items + .extend(builtins::GlobalObject::iter().map(|obj| (obj.as_ref().into(), Item::Object(obj)))); + items +} + #[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 { @@ -222,10 +306,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.into(), + 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) } @@ -234,10 +379,30 @@ impl IngotId { self.data(db).name.clone() } + /// Returns the `src/main.fe` module, if it exists. pub fn main_module(&self, db: &dyn AnalyzerDb) -> Option { db.ingot_main_module(*self).value } + /// Returns the `src/lib.fe` module, if it exists. + pub fn lib_module(&self, db: &dyn AnalyzerDb) -> Option { + db.ingot_lib_module(*self).value + } + + /// Returns the `src/lib.fe` or `src/main.fe` module, whichever one exists. + pub fn root_module(&self, db: &dyn AnalyzerDb) -> Option { + db.ingot_root_module(*self) + } + + /// Returns all of the modules inside of `src`, except for the root module. + pub fn root_sub_modules(&self, db: &dyn AnalyzerDb) -> Rc> { + db.ingot_root_sub_modules(*self) + } + + pub fn items(&self, db: &dyn AnalyzerDb) -> Rc> { + self.root_module(db).expect("missing root module").items(db) + } + pub fn diagnostics(&self, db: &dyn AnalyzerDb) -> Vec { let mut diagnostics = vec![]; self.sink_diagnostics(db, &mut diagnostics); @@ -286,10 +451,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: name.into(), + 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) } @@ -322,42 +536,235 @@ impl ModuleId { self.data(db).context.clone() } - /// Returns a map of the named items in the module - pub fn items(&self, db: &dyn AnalyzerDb) -> Rc> { - db.module_item_map(*self).value - } - /// Includes duplicate names pub fn all_items(&self, db: &dyn AnalyzerDb) -> Rc> { db.module_all_items(*self) } + /// Returns a map of the named items in the module + pub fn items(&self, db: &dyn AnalyzerDb) -> Rc> { + db.module_item_map(*self).value + } + /// Returns a `name -> (name_span, external_item)` map for all `use` statements in a module. pub fn used_items(&self, db: &dyn AnalyzerDb) -> Rc> { db.module_used_item_map(*self).value } - /// Returns a `name -> (name_span, external_item)` map for a single `use` tree. + /// Returns all of the internal items, except for used items. This is used when resolving + /// use statements, as it does not create a query cycle. + pub fn non_used_internal_items(&self, db: &dyn AnalyzerDb) -> IndexMap { + let global_items = self.global_items(db); + let sub_modules = self.sub_modules(db); + + sub_modules + .deref() + .clone() + .into_iter() + .map(|(name, module)| (name, Item::Module(module))) + .chain(global_items) + .collect() + } + + /// Returns all of the internal items. Internal items refers to the set of items visible when + /// inside of a module. + pub fn internal_items(&self, db: &dyn AnalyzerDb) -> IndexMap { + let global_items = self.global_items(db); + let defined_items = self.items(db); + let sub_modules = self.sub_modules(db); + + sub_modules + .deref() + .clone() + .into_iter() + .map(|(name, module)| (name, Item::Module(module))) + .chain(global_items) + .chain(defined_items.deref().clone()) + .collect() + } + + /// Resolve a path that starts with an item defined in the module. + pub fn resolve_path(&self, db: &dyn AnalyzerDb, path: &ast::Path) -> Analysis> { + Item::Module(*self).resolve_path_segments(db, &path.segments) + } + + /// Resolve a path that starts with an internal item. We omit used items to avoid a query cycle. + pub fn resolve_path_non_used_internal( + &self, + db: &dyn AnalyzerDb, + path: &ast::Path, + ) -> Analysis> { + let segments = &path.segments; + let first_segment = &segments[0]; + + if let Some(curr_item) = self.non_used_internal_items(db).get(&first_segment.kind) { + curr_item.resolve_path_segments(db, &segments[1..]) + } else { + Analysis { + value: None, + diagnostics: Rc::new(vec![errors::error( + "unresolved path item", + first_segment.span, + "not found", + )]), + } + } + } + + /// Resolve a path that starts with an internal item. + pub fn resolve_path_internal( + &self, + db: &dyn AnalyzerDb, + path: &ast::Path, + ) -> Analysis> { + let segments = &path.segments; + let first_segment = &segments[0]; + + if let Some(curr_item) = self.internal_items(db).get(&first_segment.kind) { + curr_item.resolve_path_segments(db, &segments[1..]) + } else { + Analysis { + value: None, + diagnostics: Rc::new(vec![errors::error( + "unresolved path item", + first_segment.span, + "not found", + )]), + } + } + } + + /// Resolve a use tree entirely. We set internal to true if the first path item is internal. + /// + /// e.g. `foo::bar::{baz::bing}` + /// --- --- + /// ^ ^ baz is not internal + /// foo is internal pub fn resolve_use_tree( &self, db: &dyn AnalyzerDb, tree: &Node, - ) -> Rc> { - db.module_resolve_use_tree(*self, tree.to_owned()).value + internal: bool, + ) -> Analysis>> { + let mut diagnostics = vec![]; + + // Again, the path resolution method we use depends on whether or not the first item + // is internal. + let resolve_path = |module: &ModuleId, db: &dyn AnalyzerDb, path: &ast::Path| { + if internal { + module.resolve_path_non_used_internal(db, path) + } else { + module.resolve_path(db, path) + } + }; + + match &tree.kind { + ast::UseTree::Glob { prefix } => { + let prefix_module = resolve_path(self, db, prefix); + diagnostics.extend(prefix_module.diagnostics.deref().clone()); + + let items = match prefix_module.value { + Some(Item::Module(module)) => module + .items(db) + .deref() + .clone() + .into_iter() + .map(|(name, item)| (name, (tree.span, item))) + .collect(), + Some(item) => { + diagnostics.push(errors::error( + format!("cannot glob import from {}", item.item_kind_display_name()), + prefix.segments.last().expect("path is empty").span, + "prefix item must be a module", + )); + indexmap! {} + } + None => indexmap! {}, + }; + + Analysis { + value: Rc::new(items), + diagnostics: Rc::new(diagnostics), + } + } + ast::UseTree::Nested { prefix, children } => { + let prefix_module = resolve_path(self, db, prefix); + diagnostics.extend(prefix_module.diagnostics.deref().clone()); + + let items = match prefix_module.value { + Some(Item::Module(module)) => { + children.iter().fold(indexmap! {}, |mut accum, node| { + let child_items = module.resolve_use_tree(db, node, false); + diagnostics.extend(child_items.diagnostics.deref().clone()); + + for (name, (name_span, item)) in child_items.value.iter() { + if let Some((other_name_span, other_item)) = + accum.insert(name.to_owned(), (*name_span, *item)) + { + diagnostics.push(errors::duplicate_name_error( + &format!( + "a {} with the same name has already been imported", + other_item.item_kind_display_name() + ), + name, + other_name_span, + *name_span, + )); + } + } + + accum + }) + } + Some(item) => { + diagnostics.push(errors::error( + format!("cannot glob import from {}", item.item_kind_display_name()), + prefix.segments.last().unwrap().span, + "prefix item must be a module", + )); + indexmap! {} + } + None => indexmap! {}, + }; + + Analysis { + value: Rc::new(items), + diagnostics: Rc::new(diagnostics), + } + } + ast::UseTree::Simple { path, rename } => { + let item = resolve_path(self, db, path); + + let items = match item.value { + Some(item) => { + let (item_name, item_name_span) = if let Some(name) = rename { + (name.kind.clone(), name.span) + } else { + let name_segment_node = path.segments.last().expect("path is empty"); + (name_segment_node.kind.clone(), name_segment_node.span) + }; + + indexmap! { item_name => (item_name_span, item) } + } + None => indexmap! {}, + }; + + Analysis { + value: Rc::new(items), + diagnostics: item.diagnostics, + } + } + } } pub fn resolve_name(&self, db: &dyn AnalyzerDb, name: &str) -> Option { - self.items(db).get(name).copied() + self.internal_items(db).get(name).copied() } pub fn sub_modules(&self, db: &dyn AnalyzerDb) -> Rc> { db.module_sub_modules(*self) } - pub fn adjacent_modules(&self, db: &dyn AnalyzerDb) -> Rc> { - db.module_adjacent_modules(*self) - } - pub fn parent(&self, db: &dyn AnalyzerDb) -> Option { self.parent_module(db).map(Item::Module).or_else(|| { if let ModuleContext::Ingot(ingot) = self.data(db).context { @@ -377,7 +784,34 @@ impl ModuleId { db.module_contracts(*self) } - /// All structs, including duplicates + /// Returns the global item for this module. + pub fn global(&self, db: &dyn AnalyzerDb) -> GlobalId { + match self.context(db) { + ModuleContext::Ingot(ingot) => ingot.data(db).global, + ModuleContext::Global(global) => global, + } + } + + /// Returns the map of ingot deps, built-ins, and the ingot itself as "ingot". + pub fn global_items(&self, db: &dyn AnalyzerDb) -> IndexMap { + let mut items = self + .global(db) + .data(db) + .ingots + .clone() + .into_iter() + .map(|(name, ingot)| (name, Item::Ingot(ingot))) + .chain(std_prelude_items()) + .collect::>(); + + if let ModuleContext::Ingot(ingot) = self.context(db) { + items.insert("ingot".into(), Item::Ingot(ingot)); + } + + items + } + + /// All structs, including duplicatecrates/analyzer/src/db.rss pub fn all_structs(&self, db: &dyn AnalyzerDb) -> Rc> { db.module_structs(*self) } diff --git a/crates/analyzer/src/namespace/scopes.rs b/crates/analyzer/src/namespace/scopes.rs index 17564df69a..ba082e6d77 100644 --- a/crates/analyzer/src/namespace/scopes.rs +++ b/crates/analyzer/src/namespace/scopes.rs @@ -2,7 +2,7 @@ use crate::context::{AnalyzerContext, CallType, ExpressionAttributes, FunctionBody, NamedThing}; use crate::errors::{AlreadyDefined, TypeError}; -use crate::namespace::items::{Class, EventId, FunctionId, Item, ModuleId}; +use crate::namespace::items::{Class, EventId, FunctionId, ModuleId}; use crate::namespace::types::FixedSize; use crate::AnalyzerDb; use fe_common::diagnostics::Diagnostic; @@ -40,7 +40,7 @@ impl<'a> AnalyzerContext for ItemScope<'a> { self.diagnostics.push(diag) } fn resolve_path(&mut self, path: &ast::Path) -> Option { - let item = Item::Module(self.module).resolve_path(self.db(), path); + let item = self.module.resolve_path_internal(self.db(), path); for diagnostic in item.diagnostics.iter() { self.add_diagnostic(diagnostic.to_owned()) @@ -194,7 +194,10 @@ impl<'a> AnalyzerContext for FunctionScope<'a> { } fn resolve_path(&mut self, path: &ast::Path) -> Option { - let item = Item::Module(self.function.module(self.db())).resolve_path(self.db(), path); + let item = self + .function + .module(self.db()) + .resolve_path_internal(self.db(), path); for diagnostic in item.diagnostics.iter() { self.add_diagnostic(diagnostic.to_owned()) @@ -240,7 +243,11 @@ impl AnalyzerContext for BlockScope<'_, '_> { }) } fn resolve_path(&mut self, path: &ast::Path) -> Option { - let item = Item::Module(self.root.function.module(self.db())).resolve_path(self.db(), path); + let item = self + .root + .function + .module(self.db()) + .resolve_path_internal(self.db(), path); for diagnostic in item.diagnostics.iter() { self.add_diagnostic(diagnostic.to_owned()) diff --git a/crates/analyzer/tests/analysis.rs b/crates/analyzer/tests/analysis.rs index 4c148cff55..3527537ee4 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}; @@ -15,6 +15,7 @@ use std::collections::HashMap; use std::fmt::Debug; use std::fmt::Display; use std::hash::{Hash, Hasher}; +use std::ops::Deref; use std::rc::Rc; use wasm_bindgen_test::wasm_bindgen_test; @@ -74,39 +75,20 @@ macro_rules! test_analysis_ingot { #[test] #[wasm_bindgen_test] fn $name() { - let files = test_files::build_filestore($path); + let mut files = test_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 analysis = IngotId::try_new(&db, &files, $path, &file_ids, &deps) + .expect("failed to create new ingot"); - let ingot = Ingot { - name: "test_ingot".into(), - 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 = analysis.value; + + if !analysis.diagnostics.deref().is_empty() { + panic!("failed to compile the ingot: {:?}", analysis.diagnostics) + } let snapshot = ingot_id .all_modules(&db) diff --git a/crates/analyzer/tests/errors.rs b/crates/analyzer/tests/errors.rs index b2643185cc..ee93ccc2d2 100644 --- a/crates/analyzer/tests/errors.rs +++ b/crates/analyzer/tests/errors.rs @@ -1,42 +1,23 @@ //! 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 insta::assert_snapshot; -use std::rc::Rc; use test_files::build_filestore; use wasm_bindgen_test::wasm_bindgen_test; 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 deps = files.add_included_libraries(); let db = TestDb::default(); - let global = Global::default(); - let global_id = db.intern_global(Rc::new(global)); - - let module = items::Module { - name: path.into(), - 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, &deps) + .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.into(), - 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..ffa2143186 100644 --- a/crates/analyzer/tests/snapshots/analysis__basic_ingot.snap +++ b/crates/analyzer/tests/snapshots/analysis__basic_ingot.snap @@ -4,94 +4,92 @@ expression: snapshot --- note: - ┌─ ingots/basic_ingot/src/ding/dang.fe:1:1 - │ -1 │ type Dang = Array - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Array - - -note: - ┌─ ingots/basic_ingot/src/ding/dong.fe:2:3 + ┌─ ingots/basic_ingot/src/ding/dong.fe:4:3 │ -2 │ my_address: address +4 │ my_address: address │ ^^^^^^^^^^^^^^^^^^^ address -3 │ my_u256: u256 +5 │ my_u256: u256 │ ^^^^^^^^^^^^^ u256 -4 │ my_i8: i8 +6 │ my_i8: i8 │ ^^^^^^^^^ i8 - note: - ┌─ ingots/basic_ingot/src/bar/baz.fe:2:5 - │ -2 │ my_bool: bool - │ ^^^^^^^^^^^^^ bool -3 │ my_u256: u256 - │ ^^^^^^^^^^^^^ u256 - - -note: - ┌─ ingots/basic_ingot/src/bing.fe:2:5 - │ -2 │ my_address: address - │ ^^^^^^^^^^^^^^^^^^^ address - -note: - ┌─ ingots/basic_ingot/src/bing.fe:4:1 + ┌─ ingots/basic_ingot/src/ding/dong.fe:8:1 │ -4 │ ╭ fn get_42_backend() -> u256: -5 │ │ return 42 - │ ╰─────────────^ attributes hash: 17979516652885443340 +8 │ ╭ fn get_bing() -> Bing: +9 │ │ return Bing(my_address=address(0)) + │ ╰──────────────────────────────────────^ attributes hash: 6885974449891053434 │ = FunctionSignature { self_decl: None, params: [], return_type: Ok( - Base( - Numeric( - U256, - ), + Struct( + Struct { + name: "Bing", + id: StructId( + 4, + ), + field_count: 1, + }, ), ), } note: - ┌─ ingots/basic_ingot/src/bing.fe:5:12 + ┌─ ingots/basic_ingot/src/ding/dong.fe:9:36 + │ +9 │ return Bing(my_address=address(0)) + │ ^ u256: Value + +note: + ┌─ ingots/basic_ingot/src/ding/dong.fe:9:28 + │ +9 │ return Bing(my_address=address(0)) + │ ^^^^^^^^^^ address: Value + +note: + ┌─ ingots/basic_ingot/src/ding/dong.fe:9:12 + │ +9 │ return Bing(my_address=address(0)) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Bing: Memory + +note: + ┌─ ingots/basic_ingot/src/ding/dong.fe:9:28 + │ +9 │ return Bing(my_address=address(0)) + │ ^^^^^^^ TypeConstructor(Base(Address)) + +note: + ┌─ ingots/basic_ingot/src/ding/dong.fe:9:12 + │ +9 │ return Bing(my_address=address(0)) + │ ^^^^ TypeConstructor(Struct(Struct { name: "Bing", id: StructId(4), field_count: 1 })) + + +note: + ┌─ ingots/basic_ingot/src/ding/dang.fe:1:1 │ -5 │ return 42 - │ ^^ u256: Value +1 │ type Dang = Array + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Array + note: - ┌─ ingots/basic_ingot/src/bing.fe:8:4 + ┌─ ingots/basic_ingot/src/bing.fe:6:5 + │ +6 │ my_address: address + │ ^^^^^^^^^^^^^^^^^^^ address + +note: + ┌─ ingots/basic_ingot/src/bing.fe:8:1 │ -8 │ ╭ pub fn add(x: u256, y: u256) -> u256: -9 │ │ return x + y - │ ╰───────────────────^ attributes hash: 4022593831796629401 +8 │ ╭ fn get_42_backend() -> u256: +9 │ │ return std::get_42() + │ ╰────────────────────────^ attributes hash: 17979516652885443340 │ = FunctionSignature { self_decl: None, - params: [ - FunctionParam { - name: "x", - typ: Ok( - Base( - Numeric( - U256, - ), - ), - ), - }, - FunctionParam { - name: "y", - typ: Ok( - Base( - Numeric( - U256, - ), - ), - ), - }, - ], + params: [], return_type: Ok( Base( Numeric( @@ -102,69 +100,121 @@ note: } note: - ┌─ ingots/basic_ingot/src/bing.fe:9:15 + ┌─ ingots/basic_ingot/src/bing.fe:9:12 │ -9 │ return x + y - │ ^ ^ u256: Value - │ │ - │ u256: Value +9 │ return std::get_42() + │ ^^^^^^^^^^^^^ u256: Value note: - ┌─ ingots/basic_ingot/src/bing.fe:9:15 + ┌─ ingots/basic_ingot/src/bing.fe:9:12 │ -9 │ return x + y - │ ^^^^^ u256: Value - +9 │ return std::get_42() + │ ^^^^^^^^^^^ Pure(FunctionId(1)) note: - ┌─ ingots/basic_ingot/src/main.fe:9:5 + ┌─ ingots/basic_ingot/src/bing.fe:12:4 │ - 9 │ ╭ pub fn get_my_baz() -> Baz: -10 │ │ return Baz(my_bool=true, my_u256=26) - │ ╰────────────────────────────────────────────^ attributes hash: 12775921899186886669 +12 │ ╭ pub fn add(x: u256, y: u256) -> u256: +13 │ │ return x + y + │ ╰───────────────────^ attributes hash: 4022593831796629401 │ = FunctionSignature { self_decl: None, - params: [], - return_type: Ok( - Struct( - Struct { - name: "Baz", - id: StructId( - 1, + params: [ + FunctionParam { + name: "x", + typ: Ok( + Base( + Numeric( + U256, + ), ), - field_count: 2, - }, + ), + }, + FunctionParam { + name: "y", + typ: Ok( + Base( + Numeric( + U256, + ), + ), + ), + }, + ], + return_type: Ok( + Base( + Numeric( + U256, + ), ), ), } note: - ┌─ ingots/basic_ingot/src/main.fe:10:28 + ┌─ ingots/basic_ingot/src/bing.fe:13:15 │ -10 │ return Baz(my_bool=true, my_u256=26) - │ ^^^^ ^^ u256: Value - │ │ - │ bool: Value +13 │ return x + y + │ ^ ^ u256: Value + │ │ + │ u256: Value note: - ┌─ ingots/basic_ingot/src/main.fe:10:16 + ┌─ ingots/basic_ingot/src/bing.fe:13:15 │ -10 │ return Baz(my_bool=true, my_u256=26) - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Baz: Memory +13 │ return x + y + │ ^^^^^ u256: Value + note: - ┌─ ingots/basic_ingot/src/main.fe:10:16 - │ -10 │ return Baz(my_bool=true, my_u256=26) - │ ^^^ TypeConstructor(Struct(Struct { name: "Baz", id: StructId(1), field_count: 2 })) + ┌─ ingots/basic_ingot/src/main.fe:8:5 + │ +8 │ ╭ pub fn get_my_baz() -> Baz: +9 │ │ return Baz(my_bool=true, my_u256=26) + │ ╰────────────────────────────────────────────^ attributes hash: 12775921899186886669 + │ + = FunctionSignature { + self_decl: None, + params: [], + return_type: Ok( + Struct( + Struct { + name: "Baz", + id: StructId( + 1, + ), + field_count: 2, + }, + ), + ), + } note: - ┌─ ingots/basic_ingot/src/main.fe:12:5 + ┌─ ingots/basic_ingot/src/main.fe:9:28 + │ +9 │ return Baz(my_bool=true, my_u256=26) + │ ^^^^ ^^ u256: Value + │ │ + │ bool: Value + +note: + ┌─ ingots/basic_ingot/src/main.fe:9:16 + │ +9 │ return Baz(my_bool=true, my_u256=26) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Baz: Memory + +note: + ┌─ ingots/basic_ingot/src/main.fe:9:16 + │ +9 │ return Baz(my_bool=true, my_u256=26) + │ ^^^ TypeConstructor(Struct(Struct { name: "Baz", id: StructId(1), field_count: 2 })) + +note: + ┌─ ingots/basic_ingot/src/main.fe:11:5 │ -12 │ ╭ pub fn get_my_bing() -> Bong: -13 │ │ return Bong(my_address=address(42)) - │ ╰───────────────────────────────────────────^ attributes hash: 9604670028259107253 +11 │ ╭ pub fn get_my_bing() -> Bong: +12 │ │ return Bong(my_address=address(42)) + │ ╰───────────────────────────────────────────^ attributes hash: 6885974449891053434 │ = FunctionSignature { self_decl: None, @@ -174,7 +224,7 @@ note: Struct { name: "Bing", id: StructId( - 2, + 4, ), field_count: 1, }, @@ -183,40 +233,40 @@ note: } note: - ┌─ ingots/basic_ingot/src/main.fe:13:40 + ┌─ ingots/basic_ingot/src/main.fe:12:40 │ -13 │ return Bong(my_address=address(42)) +12 │ return Bong(my_address=address(42)) │ ^^ u256: Value note: - ┌─ ingots/basic_ingot/src/main.fe:13:32 + ┌─ ingots/basic_ingot/src/main.fe:12:32 │ -13 │ return Bong(my_address=address(42)) +12 │ return Bong(my_address=address(42)) │ ^^^^^^^^^^^ address: Value note: - ┌─ ingots/basic_ingot/src/main.fe:13:16 + ┌─ ingots/basic_ingot/src/main.fe:12:16 │ -13 │ return Bong(my_address=address(42)) +12 │ return Bong(my_address=address(42)) │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Bing: Memory note: - ┌─ ingots/basic_ingot/src/main.fe:13:32 + ┌─ ingots/basic_ingot/src/main.fe:12:32 │ -13 │ return Bong(my_address=address(42)) +12 │ return Bong(my_address=address(42)) │ ^^^^^^^ TypeConstructor(Base(Address)) note: - ┌─ ingots/basic_ingot/src/main.fe:13:16 + ┌─ ingots/basic_ingot/src/main.fe:12:16 │ -13 │ return Bong(my_address=address(42)) - │ ^^^^ TypeConstructor(Struct(Struct { name: "Bing", id: StructId(2), field_count: 1 })) +12 │ return Bong(my_address=address(42)) + │ ^^^^ TypeConstructor(Struct(Struct { name: "Bing", id: StructId(4), field_count: 1 })) note: - ┌─ ingots/basic_ingot/src/main.fe:15:5 + ┌─ ingots/basic_ingot/src/main.fe:14:5 │ -15 │ ╭ pub fn get_42() -> u256: -16 │ │ return get_42_backend() +14 │ ╭ pub fn get_42() -> u256: +15 │ │ return get_42_backend() │ ╰───────────────────────────────^ attributes hash: 17979516652885443340 │ = FunctionSignature { @@ -232,26 +282,57 @@ note: } note: - ┌─ ingots/basic_ingot/src/main.fe:16:16 + ┌─ ingots/basic_ingot/src/main.fe:15:16 │ -16 │ return get_42_backend() +15 │ return get_42_backend() │ ^^^^^^^^^^^^^^^^ u256: Value note: - ┌─ ingots/basic_ingot/src/main.fe:16:16 + ┌─ ingots/basic_ingot/src/main.fe:15:16 │ -16 │ return get_42_backend() - │ ^^^^^^^^^^^^^^ Pure(FunctionId(0)) +15 │ return get_42_backend() + │ ^^^^^^^^^^^^^^ Pure(FunctionId(3)) note: - ┌─ ingots/basic_ingot/src/main.fe:18:5 + ┌─ ingots/basic_ingot/src/main.fe:17:5 + │ +17 │ ╭ pub fn get_26() -> u256: +18 │ │ return std::bar::bar::get_26() + │ ╰──────────────────────────────────────^ attributes hash: 17979516652885443340 │ -18 │ ╭ pub fn get_my_dyng() -> dong::Dyng: -19 │ │ return dong::Dyng( -20 │ │ my_address=address(8), -21 │ │ my_u256=42, -22 │ │ my_i8=-1 -23 │ │ ) + = FunctionSignature { + self_decl: None, + params: [], + return_type: Ok( + Base( + Numeric( + U256, + ), + ), + ), + } + +note: + ┌─ ingots/basic_ingot/src/main.fe:18:16 + │ +18 │ return std::bar::bar::get_26() + │ ^^^^^^^^^^^^^^^^^^^^^^^ u256: Value + +note: + ┌─ ingots/basic_ingot/src/main.fe:18:16 + │ +18 │ return std::bar::bar::get_26() + │ ^^^^^^^^^^^^^^^^^^^^^ Pure(FunctionId(2)) + +note: + ┌─ ingots/basic_ingot/src/main.fe:20:5 + │ +20 │ ╭ pub fn get_my_dyng() -> dong::Dyng: +21 │ │ return dong::Dyng( +22 │ │ my_address=address(8), +23 │ │ my_u256=42, +24 │ │ my_i8=-1 +25 │ │ ) │ ╰─────────^ attributes hash: 12523642377619379671 │ = FunctionSignature { @@ -271,57 +352,57 @@ note: } note: - ┌─ ingots/basic_ingot/src/main.fe:20:32 + ┌─ ingots/basic_ingot/src/main.fe:22:32 │ -20 │ my_address=address(8), +22 │ my_address=address(8), │ ^ u256: Value note: - ┌─ ingots/basic_ingot/src/main.fe:20:24 + ┌─ ingots/basic_ingot/src/main.fe:22:24 │ -20 │ my_address=address(8), +22 │ my_address=address(8), │ ^^^^^^^^^^ address: Value -21 │ my_u256=42, +23 │ my_u256=42, │ ^^ u256: Value -22 │ my_i8=-1 +24 │ my_i8=-1 │ ^ u256: Value note: - ┌─ ingots/basic_ingot/src/main.fe:22:19 + ┌─ ingots/basic_ingot/src/main.fe:24:19 │ -22 │ my_i8=-1 +24 │ my_i8=-1 │ ^^ i8: Value note: - ┌─ ingots/basic_ingot/src/main.fe:19:16 + ┌─ ingots/basic_ingot/src/main.fe:21:16 │ -19 │ return dong::Dyng( +21 │ return dong::Dyng( │ ╭────────────────^ -20 │ │ my_address=address(8), -21 │ │ my_u256=42, -22 │ │ my_i8=-1 -23 │ │ ) +22 │ │ my_address=address(8), +23 │ │ my_u256=42, +24 │ │ my_i8=-1 +25 │ │ ) │ ╰─────────^ Dyng: Memory note: - ┌─ ingots/basic_ingot/src/main.fe:20:24 + ┌─ ingots/basic_ingot/src/main.fe:22:24 │ -20 │ my_address=address(8), +22 │ my_address=address(8), │ ^^^^^^^ TypeConstructor(Base(Address)) note: - ┌─ ingots/basic_ingot/src/main.fe:19:16 + ┌─ ingots/basic_ingot/src/main.fe:21:16 │ -19 │ return dong::Dyng( +21 │ return dong::Dyng( │ ^^^^^^^^^^ TypeConstructor(Struct(Struct { name: "Dyng", id: StructId(0), field_count: 3 })) note: - ┌─ ingots/basic_ingot/src/main.fe:25:5 + ┌─ ingots/basic_ingot/src/main.fe:27:5 │ -25 │ ╭ pub fn create_bing_contract() -> u256: -26 │ │ let bing: BingContract = BingContract.create(0) -27 │ │ return bing.add(40, 50) - │ ╰───────────────────────────────^ attributes hash: 17979516652885443340 +27 │ ╭ pub fn create_bing_contract() -> u256: +28 │ │ let bing_contract: BingContract = BingContract.create(0) +29 │ │ return bing_contract.add(40, 50) + │ ╰────────────────────────────────────────^ attributes hash: 17979516652885443340 │ = FunctionSignature { self_decl: None, @@ -336,41 +417,50 @@ note: } note: - ┌─ ingots/basic_ingot/src/main.fe:26:19 + ┌─ ingots/basic_ingot/src/main.fe:28:28 │ -26 │ let bing: BingContract = BingContract.create(0) - │ ^^^^^^^^^^^^ BingContract +28 │ let bing_contract: BingContract = BingContract.create(0) + │ ^^^^^^^^^^^^ BingContract note: - ┌─ ingots/basic_ingot/src/main.fe:26:54 + ┌─ ingots/basic_ingot/src/main.fe:28:63 │ -26 │ let bing: BingContract = BingContract.create(0) - │ ^ u256: Value +28 │ let bing_contract: BingContract = BingContract.create(0) + │ ^ u256: Value note: - ┌─ ingots/basic_ingot/src/main.fe:26:34 + ┌─ ingots/basic_ingot/src/main.fe:28:43 │ -26 │ let bing: BingContract = BingContract.create(0) - │ ^^^^^^^^^^^^^^^^^^^^^^ BingContract: Value -27 │ return bing.add(40, 50) - │ ^^^^ ^^ ^^ u256: Value - │ │ │ - │ │ u256: Value +28 │ let bing_contract: BingContract = BingContract.create(0) + │ ^^^^^^^^^^^^^^^^^^^^^^ BingContract: Value +29 │ return bing_contract.add(40, 50) + │ ^^^^^^^^^^^^^ ^^ ^^ u256: Value + │ │ │ + │ │ u256: Value │ BingContract: Value note: - ┌─ ingots/basic_ingot/src/main.fe:27:16 + ┌─ ingots/basic_ingot/src/main.fe:29:16 │ -27 │ return bing.add(40, 50) - │ ^^^^^^^^^^^^^^^^ u256: Value +29 │ return bing_contract.add(40, 50) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ u256: Value note: - ┌─ ingots/basic_ingot/src/main.fe:26:34 + ┌─ ingots/basic_ingot/src/main.fe:28:43 │ -26 │ let bing: BingContract = BingContract.create(0) - │ ^^^^^^^^^^^^^^^^^^^ BuiltinAssociatedFunction { contract: ContractId(0), function: Create } -27 │ return bing.add(40, 50) - │ ^^^^^^^^ External { contract: ContractId(0), function: FunctionId(1) } +28 │ let bing_contract: BingContract = BingContract.create(0) + │ ^^^^^^^^^^^^^^^^^^^ BuiltinAssociatedFunction { contract: ContractId(0), function: Create } +29 │ return bing_contract.add(40, 50) + │ ^^^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(4) } + + +note: + ┌─ ingots/basic_ingot/src/bar/baz.fe:2:5 + │ +2 │ my_bool: bool + │ ^^^^^^^^^^^^^ bool +3 │ my_u256: u256 + │ ^^^^^^^^^^^^^ u256 diff --git a/crates/analyzer/tests/snapshots/errors__bad_ingot.snap b/crates/analyzer/tests/snapshots/errors__bad_ingot.snap index 5dddefee65..2321c65d09 100644 --- a/crates/analyzer/tests/snapshots/errors__bad_ingot.snap +++ b/crates/analyzer/tests/snapshots/errors__bad_ingot.snap @@ -3,70 +3,73 @@ source: crates/analyzer/tests/errors.rs expression: error_string_ingot(&path) --- +error: import name conflicts with the ingot named "std" + ┌─ compile_errors/bad_ingot/src/bing.fe:1:5 + │ +1 │ use std + │ ^^^ `std` is already defined + error: cannot glob import from type - ┌─ compile_errors/bad_ingot/src/foo.fe:1:11 + ┌─ compile_errors/bad_ingot/src/foo.fe:1:18 │ -1 │ use bing::Bong::* - │ ^^^^ prefix item must be a module +1 │ use ingot::bing::Bong::* + │ ^^^^ prefix item must be a module error: unresolved path item - ┌─ compile_errors/bad_ingot/src/foo.fe:2:11 + ┌─ compile_errors/bad_ingot/src/foo.fe:2:18 + │ +2 │ use ingot::bing::Tong + │ ^^^^ not found + +error: import name conflicts with the ingot named "std" + ┌─ compile_errors/bad_ingot/src/foo.fe:3:20 │ -2 │ use bing::Tong - │ ^^^^ not found +3 │ use ingot::bing as std + │ ^^^ `std` is already defined error: unresolved path item - ┌─ compile_errors/bad_ingot/src/main.fe:2:5 + ┌─ compile_errors/bad_ingot/src/main.fe:1:5 │ -2 │ use bar::Baz +1 │ use bar::Baz │ ^^^ not found error: unresolved path item - ┌─ compile_errors/bad_ingot/src/main.fe:3:33 + ┌─ compile_errors/bad_ingot/src/main.fe:2:33 │ -3 │ use biz::bad::{Bur, Bud as Bar, Boo} +2 │ use biz::bad::{Bur, Bud as Bar, Boo} │ ^^^ not found error: unresolved path item - ┌─ compile_errors/bad_ingot/src/main.fe:4:10 + ┌─ compile_errors/bad_ingot/src/main.fe:3:10 │ -4 │ use biz::Bark +3 │ use biz::Bark │ ^^^^ not found error: unresolved path item - ┌─ compile_errors/bad_ingot/src/main.fe:5:5 + ┌─ compile_errors/bad_ingot/src/main.fe:4:5 │ -5 │ use none::* +4 │ use none::* │ ^^^^ not found error: a type with the same name has already been imported - ┌─ compile_errors/bad_ingot/src/main.fe:3:16 - │ -3 │ use biz::bad::{Bur, Bud as Bar, Boo} - │ ^^^ `Bur` first defined here - · -6 │ use bing::Bong as Bur - │ --- `Bur` redefined here - -error: import name conflicts with built-in type - ┌─ compile_errors/bad_ingot/src/main.fe:7:17 - │ -7 │ use foo::Bar as address - │ ^^^^^^^ `address` is a built-in type - -error: a type with the same name has already been imported - ┌─ compile_errors/bad_ingot/src/main.fe:3:28 + ┌─ compile_errors/bad_ingot/src/main.fe:2:28 │ -3 │ use biz::bad::{Bur, Bud as Bar, Boo} +2 │ use biz::bad::{Bur, Bud as Bar, Boo} │ ^^^ `Bar` first defined here · -9 │ contract Bar: +6 │ contract Bar: │ --- `Bar` redefined here -error: incorrect type for `Foo` argument `my_num` - ┌─ compile_errors/bad_ingot/src/main.fe:12:27 +error: function name conflicts with the ingot named "std" + ┌─ compile_errors/bad_ingot/src/main.fe:11:4 │ -12 │ return Foo(my_num=true) - │ ^^^^ this has type `bool`; expected type `u256` +11 │ fn std(): + │ ^^^ `std` is already defined + +error: incorrect type for `Foo` argument `my_num` + ┌─ compile_errors/bad_ingot/src/main.fe:9:32 + │ +9 │ return foo::Foo(my_num=true) + │ ^^^^ this has type `bool`; expected type `u256` 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/analyzer/tests/snapshots/errors__shadow_builtin_type.snap b/crates/analyzer/tests/snapshots/errors__shadow_builtin_type.snap index 8f48516f95..4c31ec3edf 100644 --- a/crates/analyzer/tests/snapshots/errors__shadow_builtin_type.snap +++ b/crates/analyzer/tests/snapshots/errors__shadow_builtin_type.snap @@ -3,53 +3,53 @@ source: crates/analyzer/tests/errors.rs expression: "error_string(&path, &src)" --- -error: type name conflicts with built-in type +error: type name conflicts with the type named "u256" ┌─ compile_errors/shadow_builtin_type.fe:1:6 │ 1 │ type u256 = u8 - │ ^^^^ `u256` is a built-in type + │ ^^^^ `u256` is already defined -error: type name conflicts with built-in type +error: type name conflicts with the type named "String" ┌─ compile_errors/shadow_builtin_type.fe:2:6 │ 2 │ type String = u256 - │ ^^^^^^ `String` is a built-in type + │ ^^^^^^ `String` is already defined -error: type name conflicts with built-in type +error: type name conflicts with the type named "Map" ┌─ compile_errors/shadow_builtin_type.fe:3:6 │ 3 │ type Map = u256 - │ ^^^ `Map` is a built-in type + │ ^^^ `Map` is already defined -error: type name conflicts with built-in type +error: type name conflicts with the type named "bool" ┌─ compile_errors/shadow_builtin_type.fe:4:6 │ 4 │ type bool = u256 - │ ^^^^ `bool` is a built-in type + │ ^^^^ `bool` is already defined -error: type name conflicts with built-in function +error: type name conflicts with the function named "keccak256" ┌─ compile_errors/shadow_builtin_type.fe:6:6 │ 6 │ type keccak256 = u8 - │ ^^^^^^^^^ `keccak256` is a built-in function + │ ^^^^^^^^^ `keccak256` is already defined -error: type name conflicts with built-in object +error: type name conflicts with the object named "block" ┌─ compile_errors/shadow_builtin_type.fe:7:6 │ 7 │ type block = u8 - │ ^^^^^ `block` is a built-in object + │ ^^^^^ `block` is already defined -error: type name conflicts with built-in object +error: type name conflicts with the object named "msg" ┌─ compile_errors/shadow_builtin_type.fe:8:6 │ 8 │ type msg = u8 - │ ^^^ `msg` is a built-in object + │ ^^^ `msg` is already defined -error: type name conflicts with built-in object +error: type name conflicts with the object named "chain" ┌─ compile_errors/shadow_builtin_type.fe:9:6 │ 9 │ type chain = u8 - │ ^^^^^ `chain` is a built-in object + │ ^^^^^ `chain` is already defined error: function parameter name `u8` conflicts with built-in type ┌─ compile_errors/shadow_builtin_type.fe:12:8 diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index d254c2c39b..c93790448c 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,6 @@ 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" +smol_str = "0.1.21" diff --git a/crates/common/src/files.rs b/crates/common/src/files.rs index b62a9b82e1..d63ac88cf5 100644 --- a/crates/common/src/files.rs +++ b/crates/common/src/files.rs @@ -2,6 +2,10 @@ 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 smol_str::SmolStr; use std::collections::HashMap; use std::ops::Range; use std::path::Path; @@ -85,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".into() => 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 b749969e2a..dbf0c72dbf 100644 --- a/crates/driver/src/lib.rs +++ b/crates/driver/src/lib.rs @@ -1,14 +1,13 @@ -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_parser::ast::SmolStr; 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 +34,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() - .into(), - 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 +120,7 @@ pub fn compile_module( .collect::>(); Ok(CompiledModule { - src_ast, + src_ast: format!("{:?}", module_id.ast(&db)), lowered_ast, contracts, }) @@ -150,6 +134,7 @@ pub fn compile_ingot( name: &str, files: &FileStore, file_ids: &[SourceFileId], + deps: &IndexMap>, _with_bytecode: bool, _optimize: bool, ) -> Result { @@ -157,24 +142,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.into(), - 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 8fccd3786a..1f0d95446c 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/build.rs b/crates/library/build.rs new file mode 100644 index 0000000000..acd7fd6af3 --- /dev/null +++ b/crates/library/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rerun-if-changed=library/"); +} 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/bar/bar.fe b/crates/library/std/src/bar/bar.fe new file mode 100644 index 0000000000..f750aac64b --- /dev/null +++ b/crates/library/std/src/bar/bar.fe @@ -0,0 +1,5 @@ +struct Bar: + pass + +fn get_26() -> u256: + return 26 \ No newline at end of file diff --git a/crates/library/std/src/foo.fe b/crates/library/std/src/foo.fe new file mode 100644 index 0000000000..c8c035e436 --- /dev/null +++ b/crates/library/std/src/foo.fe @@ -0,0 +1,2 @@ +struct Foo: + pass \ No newline at end of file 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/lowering/src/ast_utils.rs b/crates/lowering/src/ast_utils.rs index 753063508e..90f9744be9 100644 --- a/crates/lowering/src/ast_utils.rs +++ b/crates/lowering/src/ast_utils.rs @@ -4,16 +4,17 @@ use fe_parser::node::{Node, NodeId}; use crate::names; use crate::utils::ZeroSpanNode; +use std::ops::Deref; #[derive(Debug)] pub enum StmtOrExpr { - Stmt(Node), + Stmt(Box>), Expr(Node), } impl From> for StmtOrExpr { fn from(stmt: Node) -> Self { - StmtOrExpr::Stmt(stmt) + StmtOrExpr::Stmt(Box::new(stmt)) } } @@ -26,7 +27,7 @@ impl From> for StmtOrExpr { impl StmtOrExpr { pub fn as_stmt(&self) -> Node { match self { - StmtOrExpr::Stmt(stmt) => stmt.clone(), + StmtOrExpr::Stmt(stmt) => stmt.deref().clone(), _ => panic!("not a statement"), } } diff --git a/crates/parser/src/ast.rs b/crates/parser/src/ast.rs index 2b82552151..aa4264a891 100644 --- a/crates/parser/src/ast.rs +++ b/crates/parser/src/ast.rs @@ -163,6 +163,7 @@ pub struct RegularFunctionArg { } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] +#[allow(clippy::large_enum_variant)] pub enum FunctionArg { Regular(RegularFunctionArg), Zelf, 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..0fe277e8bb 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 ingot::bing::Bong::* +use ingot::bing::Tong +use ingot::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..d2ff9f71e9 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 @@ -1,12 +1,12 @@ -use foo::Foo use bar::Baz use biz::bad::{Bur, Bud as Bar, Boo} use biz::Bark use none::* -use bing::Bong as Bur -use foo::Bar as address contract Bar: - pub fn a() -> Foo: - return Foo(my_num=true) + pub fn a() -> foo::Foo: + return foo::Foo(my_num=true) + +fn std(): + pass \ No newline at end of file diff --git a/crates/test-files/fixtures/features/dummy_std.fe b/crates/test-files/fixtures/features/dummy_std.fe new file mode 100644 index 0000000000..347780f5ff --- /dev/null +++ b/crates/test-files/fixtures/features/dummy_std.fe @@ -0,0 +1,6 @@ +use std::get_42 +use std::foo + +contract Foo: + pub fn bar() -> u256: + return get_42() * std::bar::bar::get_26() \ 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..f3226880a8 100644 --- a/crates/test-files/fixtures/ingots/basic_ingot/src/bing.fe +++ b/crates/test-files/fixtures/ingots/basic_ingot/src/bing.fe @@ -1,8 +1,12 @@ +use std::foo::Foo +use std::bar::bar::Bar +# +# simply defining an item causes a cycle 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-files/fixtures/ingots/basic_ingot/src/ding/dong.fe b/crates/test-files/fixtures/ingots/basic_ingot/src/ding/dong.fe index bb60a2e6ee..d33d28d3f5 100644 --- a/crates/test-files/fixtures/ingots/basic_ingot/src/ding/dong.fe +++ b/crates/test-files/fixtures/ingots/basic_ingot/src/ding/dong.fe @@ -1,4 +1,9 @@ +use ingot::bing::Bing + struct Dyng: my_address: address my_u256: u256 - my_i8: i8 \ No newline at end of file + my_i8: i8 + +fn get_bing() -> Bing: + return Bing(my_address=address(0)) \ No newline at end of file diff --git a/crates/test-files/fixtures/ingots/basic_ingot/src/main.fe b/crates/test-files/fixtures/ingots/basic_ingot/src/main.fe index efd014263c..63fbd35ca3 100644 --- a/crates/test-files/fixtures/ingots/basic_ingot/src/main.fe +++ b/crates/test-files/fixtures/ingots/basic_ingot/src/main.fe @@ -2,7 +2,6 @@ use bar::baz::Baz use bing::Bing as Bong use bing::get_42_backend use ding::{dang::Dang as Dung, dong} - use bing::BingContract contract Foo: @@ -15,6 +14,9 @@ contract Foo: pub fn get_42() -> u256: return get_42_backend() + pub fn get_26() -> u256: + return std::bar::bar::get_26() + pub fn get_my_dyng() -> dong::Dyng: return dong::Dyng( my_address=address(8), @@ -23,5 +25,5 @@ contract Foo: ) pub fn create_bing_contract() -> u256: - let bing: BingContract = BingContract.create(0) - return bing.add(40, 50) \ No newline at end of file + let bing_contract: BingContract = BingContract.create(0) + return bing_contract.add(40, 50) \ No newline at end of file diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index 80c38706c7..7ae706ce98 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", rev = "da554b3", optional = true} yultsur = {git = "https://github.com/g-r-a-n-t/yultsur", rev = "ae85470"} +indexmap = "1.6.2" # used by ethabi, we need to force the js feature for wasm support getrandom = { version = "0.2.3", features = ["js"] } diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index 324f12f68b..78475123fd 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -253,8 +253,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); @@ -283,9 +284,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) => { @@ -492,9 +495,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/crates/tests/src/features.rs b/crates/tests/src/features.rs index dc017cb705..2ec4b465d2 100644 --- a/crates/tests/src/features.rs +++ b/crates/tests/src/features.rs @@ -313,6 +313,7 @@ fn test_arrays() { case("pure_fn.fe", &[uint_token(42), uint_token(26)], uint_token(68)), case("pure_fn_internal_call.fe", &[uint_token(42), uint_token(26)], uint_token(68)), case("pure_fn_standalone.fe", &[uint_token(5)], uint_token(210)), + case("dummy_std.fe", &[], uint_token(1092)), // unary invert case("return_invert_i256.fe", &[int_token(1)], int_token(-2)), case("return_invert_i128.fe", &[int_token(1)], int_token(-2)), diff --git a/crates/tests/src/ingots.rs b/crates/tests/src/ingots.rs index ec623b38a8..7d0171c3c2 100644 --- a/crates/tests/src/ingots.rs +++ b/crates/tests/src/ingots.rs @@ -29,6 +29,7 @@ fn test_basic_ingot() { ); harness.test_function(&mut executor, "get_42", &[], Some(&uint_token(42))); + harness.test_function(&mut executor, "get_26", &[], Some(&uint_token(26))); harness.test_function( &mut executor, "get_my_dyng", 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.