diff --git a/crates/cli/tests/build.rs b/crates/cli/tests/build.rs new file mode 100644 index 0000000..11cc5a2 --- /dev/null +++ b/crates/cli/tests/build.rs @@ -0,0 +1,50 @@ +use std::fs; +use std::path::PathBuf; +use std::process::Command; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[test] +fn builds_package_owned_overlap_fixture() { + let fixture = workspace_root().join("fixtures/projects/generated_names/dependency_module_overlap"); + let out_dir = unique_temp_dir("regulus_cli_overlap_build"); + fs::create_dir_all(&out_dir).expect("create output dir"); + + let output = Command::new(env!("CARGO_BIN_EXE_compiler_cli")) + .arg("build") + .arg(&fixture) + .arg("--out-dir") + .arg(&out_dir) + .output() + .expect("run compiler_cli build"); + + assert!( + output.status.success(), + "build failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + assert!( + out_dir.join("dependency_module_overlap.wasm").is_file(), + "expected wasm artifact in {}", + out_dir.display() + ); + + let _ = fs::remove_dir_all(out_dir); +} + +fn workspace_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .expect("crates dir") + .parent() + .expect("workspace root") + .to_path_buf() +} + +fn unique_temp_dir(prefix: &str) -> PathBuf { + let nanos = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time after epoch") + .as_nanos(); + std::env::temp_dir().join(format!("{prefix}_{}_{}", std::process::id(), nanos)) +} diff --git a/crates/core/src/ir.rs b/crates/core/src/ir.rs index 8e8734f..d88a9a0 100644 --- a/crates/core/src/ir.rs +++ b/crates/core/src/ir.rs @@ -177,6 +177,7 @@ pub enum LinkedNameKind { #[derive(Debug, Clone, PartialEq, Eq)] pub struct Import { + pub package: Option, pub module: String, pub alias: Option, pub unqualified: Vec, @@ -981,7 +982,7 @@ fn unsupported_dependency_member_diagnostics(project: &TypedProject) -> Diagnost if !matches!(symbol.namespace, Namespace::Value | Namespace::Constructor) { continue; } - let SymbolKind::Imported { module: dependency_module, member } = &symbol.kind else { + let SymbolKind::Imported { module: dependency_module, member, .. } = &symbol.kind else { continue; }; if dependency_modules.contains(dependency_module.as_str()) @@ -996,17 +997,17 @@ fn unsupported_dependency_member_diagnostics(project: &TypedProject) -> Diagnost } ReferenceTarget::QualifiedMember { module: module_symbol, member, .. } => { let symbol = module.resolved.symbols.symbol(*module_symbol); - let SymbolKind::Import { module: dependency_module } = &symbol.kind else { + let SymbolKind::Import { module: dependency_module, .. } = &symbol.kind else { continue; }; if !dependency_modules.contains(dependency_module.as_str()) { continue; } - let Some(interface) = project.interfaces.get(dependency_module) else { + let Some(entry) = project.interfaces.get(dependency_module) else { continue; }; - if !(interface.functions.contains_key(&member.text) - || interface.constructors.contains_key(&member.text)) + if !(entry.interface.functions.contains_key(&member.text) + || entry.interface.constructors.contains_key(&member.text)) { continue; } @@ -1079,26 +1080,12 @@ fn link_modules(modules: Vec) -> Result { struct BackendRenamePlan { renames: HashMap, - module_packages: HashMap>, linked_names: Vec, } fn global_backend_renames(modules: &[Module]) -> BackendRenamePlan { let mut renames = HashMap::new(); - let mut module_packages: HashMap> = HashMap::new(); let mut linked_names = Vec::new(); - for module in modules { - if let Some(identity) = &module.identity { - module_packages - .entry(identity.module.clone()) - .or_default() - .push(identity.package.clone()); - } - } - for packages in module_packages.values_mut() { - packages.sort(); - packages.dedup(); - } for module in modules { let Some(identity) = &module.identity else { continue }; let module_name = ModuleName::from_path(&identity.module); @@ -1203,7 +1190,7 @@ fn global_backend_renames(modules: &[Module]) -> BackendRenamePlan { } } } - BackendRenamePlan { renames, module_packages, linked_names } + BackendRenamePlan { renames, linked_names } } fn backend_key(package: &str, module: &str, member: &str) -> String { @@ -1299,16 +1286,17 @@ fn module_backend_renames(module: &Module, plan: &BackendRenamePlan) -> HashMap< .unwrap_or(import.module.as_str()) .to_string() }); - let Some(package) = imported_module_package(identity, &import.module, plan) else { - continue; - }; + let package = import.package.as_deref().unwrap_or(identity.package.as_str()); let prefix = backend_key(package, &import.module, ""); + let package_qualified_prefix = backend_key(package, &import.module, ""); for (source, backend) in global { let Some(member) = source.strip_prefix(&prefix) else { continue; }; + renames.insert(source.clone(), backend.clone()); renames.insert(format!("{local}.{member}"), backend.clone()); renames.insert(format!("{}.{}", import.module, member), backend.clone()); + renames.insert(format!("{package_qualified_prefix}{member}"), backend.clone()); if import .unqualified .iter() @@ -1322,21 +1310,11 @@ fn module_backend_renames(module: &Module, plan: &BackendRenamePlan) -> HashMap< renames } -fn imported_module_package<'a>( - current: &'a ModuleIdentity, imported_module: &str, plan: &'a BackendRenamePlan, -) -> Option<&'a str> { - let packages = plan.module_packages.get(imported_module)?; - if packages.iter().any(|package| package == ¤t.package) { - return Some(current.package.as_str()); - } - packages.first().map(String::as_str) -} - fn anonymous_function_index(name: &str) -> Option { name.strip_prefix("__anon_")?.parse().ok() } -fn rewrite_module_backend_names(module: &mut Module, renames: &HashMap, plan: &BackendRenamePlan) { +fn rewrite_module_backend_names(module: &mut Module, renames: &HashMap, _plan: &BackendRenamePlan) { for constant in &mut module.constants { rewrite_name(&mut constant.name, renames); } @@ -1346,7 +1324,7 @@ fn rewrite_module_backend_names(module: &mut Module, renames: &HashMap, renames: &HashMap, - plan: &BackendRenamePlan, -) { +fn rewrite_reference(reference: &mut Reference, renames: &HashMap) { rewrite_name(&mut reference.name, renames); match &mut reference.target { - ReferenceTargetName::LocalSymbol { package, module, name, .. } => { - if package.is_none() - && let (Some(identity), Some(module)) = (identity, module.as_deref()) - && let Some(owner) = imported_module_package(identity, module, plan) - { - *package = Some(owner.to_string()); - } + ReferenceTargetName::LocalSymbol { name, .. } => { rewrite_name(name, renames); } ReferenceTargetName::QualifiedMember { package, module, member, resolved } => { - if package.is_none() - && let Some(identity) = identity - && let Some(owner) = imported_module_package(identity, module, plan) - { - *package = Some(owner.to_string()); - } - let qualified = format!("{module}.{member}"); + let qualified = package + .as_deref() + .map(|package| backend_key(package, module, member)) + .unwrap_or_else(|| format!("{module}.{member}")); if let Some(backend) = renames.get(&qualified).cloned() { *member = backend.clone(); *resolved = Some(backend); diff --git a/crates/core/src/ir/lowerer.rs b/crates/core/src/ir/lowerer.rs index 0834a90..99c2c5f 100644 --- a/crates/core/src/ir/lowerer.rs +++ b/crates/core/src/ir/lowerer.rs @@ -15,7 +15,7 @@ pub fn lower(module: TypedModule) -> Result { } pub fn lower_with_project_interfaces( - module: TypedModule, interfaces: &HashMap, + module: TypedModule, interfaces: &HashMap, ) -> Result { Lowerer::new(module).with_project_interfaces(interfaces).lower() } @@ -67,8 +67,9 @@ impl Lowerer { } } - fn with_project_interfaces(mut self, interfaces: &HashMap) -> Self { - for (module, interface) in interfaces { + fn with_project_interfaces(mut self, interfaces: &HashMap) -> Self { + for (module, entry) in interfaces { + let interface = &entry.interface; for (name, type_) in &interface.functions { self.function_types .entry(format!("{module}.{name}")) @@ -81,9 +82,10 @@ impl Lowerer { } } for import in &self.module.resolved.ast.imports { - let Some(interface) = interfaces.get(&import.module.text) else { + let Some(entry) = interfaces.get(&import.module.text) else { continue; }; + let interface = &entry.interface; for imported in &import.unqualified { if !matches!(imported.kind, ast::UnqualifiedImportKind::Value) { continue; @@ -93,9 +95,11 @@ impl Lowerer { self.function_types .entry(local.clone()) .or_insert_with(|| type_.clone()); - self.imported_functions - .entry(local.clone()) - .or_insert_with(|| format!("{}.{}", import.module.text, imported.name.text)); + let lowered_name = self + .import_package(import) + .map(|package| format!("{package}:{}.{}", import.module.text, imported.name.text)) + .unwrap_or_else(|| format!("{}.{}", import.module.text, imported.name.text)); + self.imported_functions.entry(local.clone()).or_insert(lowered_name); } if let Some(labels) = interface.function_labels.get(&imported.name.text) { self.function_labels.entry(local).or_insert_with(|| labels.clone()); @@ -117,6 +121,7 @@ impl Lowerer { .imports .iter() .map(|import| Import { + package: self.import_package(import), module: import.module.text.clone(), alias: import.alias.as_ref().map(|alias| alias.text.clone()), unqualified: import @@ -286,13 +291,17 @@ impl Lowerer { ReferenceTarget::Symbol(id) => { let symbol = self.module.resolved.symbols.symbol(*id); let (module, name) = match &symbol.kind { - crate::resolve::SymbolKind::Imported { module, member } => { + crate::resolve::SymbolKind::Imported { module, member, .. } => { (Some(module.clone()), member.clone()) } _ => (None, symbol.name.clone()), }; + let package = match &symbol.kind { + crate::resolve::SymbolKind::Imported { package, .. } => package.clone(), + _ => None, + }; ReferenceTargetName::LocalSymbol { - package: None, + package, module, name, kind: ReferenceKind::from(&symbol.kind), @@ -301,8 +310,12 @@ impl Lowerer { ReferenceTarget::QualifiedMember { module, member, symbol } => { let module_symbol = self.module.resolved.symbols.symbol(*module); let resolved = symbol.map(|id| self.module.resolved.symbols.symbol(id).name.clone()); + let package = match &module_symbol.kind { + crate::resolve::SymbolKind::Import { package, .. } => package.clone(), + _ => None, + }; ReferenceTargetName::QualifiedMember { - package: None, + package, module: module_symbol.name.clone(), member: member.text.clone(), resolved, @@ -839,23 +852,56 @@ impl Lowerer { } } + fn import_local_name(&self, import: &ast::Import) -> String { + import + .alias + .as_ref() + .map(|alias| alias.text.clone()) + .unwrap_or_else(|| { + import + .module + .text + .rsplit('/') + .next() + .unwrap_or(&import.module.text) + .to_string() + }) + } + + fn import_package(&self, import: &ast::Import) -> Option { + let local = self.import_local_name(import); + self.module + .resolved + .symbols + .symbols + .iter() + .find(|symbol| symbol.namespace == crate::resolve::Namespace::Module && symbol.name == local) + .and_then(|symbol| match &symbol.kind { + crate::resolve::SymbolKind::Import { package, .. } => package.clone(), + _ => None, + }) + } + fn qualified_function(&self, function: &AstExpression) -> Option<(String, Type)> { let AstExpression::FieldAccess(access) = function else { return None }; let AstExpression::Variable(module) = access.record.as_ref() else { return None }; - let import = self.module.resolved.ast.imports.iter().find(|import| { - import - .alias - .as_ref() - .map(|alias| alias.text.as_str()) - .unwrap_or_else(|| import.module.text.rsplit('/').next().unwrap_or(&import.module.text)) - == module.text - }); + let import = self + .module + .resolved + .ast + .imports + .iter() + .find(|import| self.import_local_name(import) == module.text); let module_name = import.map(|import| import.module.text.as_str()).unwrap_or(&module.text); let qualified = format!("{}.{}", module_name, access.field.text); + let lowered = import + .and_then(|import| self.import_package(import)) + .map(|package| format!("{package}:{qualified}")) + .unwrap_or_else(|| qualified.clone()); self.function_types .get(&qualified) .cloned() - .map(|type_| (qualified, type_)) + .map(|type_| (lowered, type_)) } fn lower_call_with_callback( @@ -2655,6 +2701,37 @@ mod tests { .iter() .all(|name| name.generated_name.contains("$mod$x736861726564$")) ); + + let root_shared = value_names + .iter() + .find(|name| name.source_name == "dependency_module_overlap:shared.value") + .expect("root shared value") + .generated_name + .clone(); + let dependency_shared = value_names + .iter() + .find(|name| name.source_name == "overlap_dep:shared.value") + .expect("dependency shared value") + .generated_name + .clone(); + let root_caller = module + .functions + .iter() + .find(|function| function.name.ends_with("$fn$x726f6f745f76616c7565")) + .expect("root caller"); + let dependency_caller = module + .functions + .iter() + .find(|function| function.name.ends_with("$fn$x646570656e64656e63795f76616c7565")) + .expect("dependency caller"); + assert!(matches!( + root_caller.body.result.kind, + ExpressionKind::DirectCall(DirectCall { ref function, .. }) if function == &root_shared + )); + assert!(matches!( + dependency_caller.body.result.kind, + ExpressionKind::DirectCall(DirectCall { ref function, .. }) if function == &dependency_shared + )); } #[test] diff --git a/crates/core/src/loader/dependency.rs b/crates/core/src/loader/dependency.rs index ac14506..03b36d3 100644 --- a/crates/core/src/loader/dependency.rs +++ b/crates/core/src/loader/dependency.rs @@ -6,7 +6,7 @@ use serde::Deserialize; use crate::diagnostic::{Diagnostic, DiagnosticCode, Diagnostics}; use crate::project::{Dependency, DependencySource, DependencyToml, ModuleInfo, ProjectLoadProgress, SourceRoot}; use crate::source::{SourceFile, SourceFileId}; -use crate::types::ModuleInterface; +use crate::types::{InterfaceEntry, ModuleInterface}; use crate::{ast, parse, target}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -20,7 +20,7 @@ pub struct DependencyPackage { #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct DependencyInterfaces { pub packages: Vec, - pub modules: HashMap, + pub modules: HashMap, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -84,7 +84,7 @@ pub fn load_dependency_interfaces_with_progress( } let pkg = DependencyPackage { name: name.clone(), version, root: pkg_root.clone(), source }; output.packages.push(pkg); - match load_pkg_interfaces(&pkg_root, compile_target) { + match load_pkg_interfaces(name, &pkg_root, compile_target) { Ok(interfaces) => output.modules.extend(interfaces), Err(mut errs) => diagnostics.append(&mut errs), } @@ -174,8 +174,8 @@ fn path_dependency_root(root: &Path, dependency: &DependencyToml) -> Option Result, Diagnostics> { + package: &str, pkg_root: &Path, compile_target: target::CompileTarget, +) -> Result, Diagnostics> { let mut paths = Vec::new(); collect_gleam_files(&pkg_root.join("src"), &mut paths)?; paths.sort(); @@ -187,7 +187,11 @@ fn load_pkg_interfaces( let module_name = module_name_from_path(&pkg_root.join("src"), &path); match parse_module(&path, source_id, compile_target) { Ok(module) => { - interfaces.insert(module_name, ModuleInterface::from(&module)); + let interface = ModuleInterface::from(&module); + interfaces.insert( + module_name.clone(), + InterfaceEntry::new(package, module_name, interface), + ); } Err(mut errors) => diagnostics.append(&mut errors), } @@ -295,8 +299,10 @@ mod tests { ); assert_eq!(interfaces.packages[0].version.as_deref(), Some("2.0.0")); let interface = interfaces.modules.get("path_dep").expect("path_dep interface"); - assert!(interface.functions.contains_key("from_path")); - assert!(!interface.functions.contains_key("from_hex_cache")); + assert_eq!(interface.package, "path_dep"); + assert_eq!(interface.module, "path_dep"); + assert!(interface.interface.functions.contains_key("from_path")); + assert!(!interface.interface.functions.contains_key("from_hex_cache")); } #[test] @@ -440,6 +446,7 @@ mod tests { .modules .get("absolute_dep") .expect("absolute_dep interface") + .interface .functions .contains_key("from_absolute_path") ); diff --git a/crates/core/src/loader/registry.rs b/crates/core/src/loader/registry.rs index 124a450..cf65e5d 100644 --- a/crates/core/src/loader/registry.rs +++ b/crates/core/src/loader/registry.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use crate::ast::{self, Declaration}; -use crate::types::{ConstructorInfo, ModuleInterface, Type}; +use crate::types::{ConstructorInfo, InterfaceEntry, ModuleInterface, Type}; use crate::{labels::FunctionLabelMap, resolve::Namespace}; use crate::{source::Span, stdlib::StdlibRegistry}; @@ -17,14 +17,14 @@ impl ResolveInterfaceRegistry { } pub fn for_project<'a>( - dependency_interfaces: &HashMap, + root_package: &str, dependency_interfaces: &HashMap, modules: impl IntoIterator, ) -> Self { Self::default() .with_prelude_interface() .with_stdlib_interfaces() .with_dependency_interfaces(dependency_interfaces) - .with_project_interfaces(modules) + .with_project_interfaces(root_package, modules) } fn with_prelude_interface(mut self) -> Self { @@ -37,14 +37,20 @@ impl ResolveInterfaceRegistry { self } - fn with_dependency_interfaces(mut self, interfaces: &HashMap) -> Self { + fn with_dependency_interfaces(mut self, interfaces: &HashMap) -> Self { self.modules.extend(dependency_resolve_interfaces(interfaces)); self } - fn with_project_interfaces<'a>(mut self, modules: impl IntoIterator) -> Self { - self.modules - .extend(modules.into_iter().map(|(name, module)| (name.clone(), module.into()))); + fn with_project_interfaces<'a>( + mut self, root_package: &str, modules: impl IntoIterator, + ) -> Self { + self.modules.extend(modules.into_iter().map(|(name, module)| { + ( + name.clone(), + ResolveModuleInterface::from_project(root_package, name, module), + ) + })); self } @@ -81,6 +87,8 @@ pub struct ResolveModuleMember { #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct ResolveModuleInterface { + pub package: Option, + pub module: String, pub members: HashMap<(Namespace, String), ResolveModuleMember>, } @@ -95,7 +103,14 @@ impl ResolveModuleInterface { ) }) .collect(); - Self { members } + Self { package: None, module: "prelude".to_string(), members } + } + + fn from_project(package: &str, name: &str, module: &ast::Module) -> Self { + let mut interface = Self::from(module); + interface.package = Some(package.to_string()); + interface.module = name.to_string(); + interface } } @@ -170,13 +185,13 @@ impl From<&ast::Module> for ResolveModuleInterface { } } - Self { members } + Self { package: None, module: String::new(), members } } } #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct TypeInterfaceRegistry { - pub modules: HashMap, + pub modules: HashMap, prelude: ModuleInterface, } @@ -186,14 +201,14 @@ impl TypeInterfaceRegistry { } pub fn for_project<'a>( - dependency_interfaces: &HashMap, + root_package: &str, dependency_interfaces: &HashMap, project_modules: impl IntoIterator, ) -> Self { Self::default() .with_prelude_interface() .with_stdlib_interfaces() .with_dependency_interfaces(dependency_interfaces) - .with_project_interfaces(project_modules) + .with_project_interfaces(root_package, project_modules) } fn with_prelude_interface(mut self) -> Self { @@ -202,35 +217,40 @@ impl TypeInterfaceRegistry { } fn with_stdlib_interfaces(mut self) -> Self { - self.modules.extend( - StdlibRegistry::new() - .modules() - .map(|module| (module.name.to_string(), module.interface.clone())), - ); + self.modules.extend(StdlibRegistry::new().modules().map(|module| { + ( + module.name.to_string(), + InterfaceEntry::new("gleam_stdlib", module.name, module.interface.clone()), + ) + })); self } - fn with_dependency_interfaces(mut self, interfaces: &HashMap) -> Self { + fn with_dependency_interfaces(mut self, interfaces: &HashMap) -> Self { self.modules.extend(interfaces.clone()); self } - fn with_project_interfaces<'a>(mut self, modules: impl IntoIterator) -> Self { - self.modules.extend( - modules - .into_iter() - .map(|(name, module)| (name.to_string(), ModuleInterface::from(module))), - ); + fn with_project_interfaces<'a>( + mut self, root_package: &str, modules: impl IntoIterator, + ) -> Self { + self.modules.extend(modules.into_iter().map(|(name, module)| { + ( + name.to_string(), + InterfaceEntry::new(root_package, name, ModuleInterface::from(module)), + ) + })); self } pub fn get(&self, module: &str) -> Option<&ModuleInterface> { - self.modules.get(module) + self.modules.get(module).map(|entry| &entry.interface) } pub fn constructors(&self) -> HashMap { self.modules .values() + .map(|entry| &entry.interface) .chain(std::iter::once(&self.prelude)) .flat_map(|i| i.interface_constructors()) .collect() @@ -239,14 +259,14 @@ impl TypeInterfaceRegistry { pub fn values(&self) -> HashMap { self.modules .iter() - .flat_map(|(module, interface)| interface.qualified_values(module)) + .flat_map(|(module, entry)| entry.interface.qualified_values(module)) .collect() } pub fn function_labels(&self) -> FunctionLabelMap { self.modules .iter() - .flat_map(|(module, interface)| interface.qualified_function_labels(module)) + .flat_map(|(module, entry)| entry.interface.qualified_function_labels(module)) .collect() } } @@ -274,17 +294,25 @@ fn stdlib_resolve_interfaces() -> HashMap { ResolveModuleMember { public: true, span: ModuleInterface::module_span() }, ); } - (module.name.to_string(), ResolveModuleInterface { members }) + ( + module.name.to_string(), + ResolveModuleInterface { + package: Some("gleam_stdlib".to_string()), + module: module.name.to_string(), + members, + }, + ) }) .collect() } fn dependency_resolve_interfaces( - interfaces: &HashMap, + interfaces: &HashMap, ) -> HashMap { interfaces .iter() - .map(|(module, interface)| { + .map(|(module, entry)| { + let interface = &entry.interface; let mut members = HashMap::new(); for name in interface.functions.keys() { members.insert( @@ -310,7 +338,10 @@ fn dependency_resolve_interfaces( ); } } - (module.clone(), ResolveModuleInterface { members }) + ( + module.clone(), + ResolveModuleInterface { package: Some(entry.package.clone()), module: entry.module.clone(), members }, + ) }) .collect() } diff --git a/crates/core/src/project.rs b/crates/core/src/project.rs index 297916d..99efbfc 100644 --- a/crates/core/src/project.rs +++ b/crates/core/src/project.rs @@ -35,7 +35,7 @@ impl Project { pub struct PackageGraph { pub root_package: PackageNode, pub dependencies: Vec, - pub dependency_interfaces: HashMap, + pub dependency_interfaces: HashMap, pub dependency_sources: Vec, pub modules: Vec, } diff --git a/crates/core/src/resolve.rs b/crates/core/src/resolve.rs index dc62a57..07a27eb 100644 --- a/crates/core/src/resolve.rs +++ b/crates/core/src/resolve.rs @@ -22,16 +22,36 @@ pub enum Namespace { #[derive(Debug, Clone, PartialEq, Eq)] pub enum SymbolKind { - Function { public: bool }, - Constant { public: bool }, - ExternalFunction { public: bool }, - Import { module: String }, - Imported { module: String, member: String }, + Function { + public: bool, + }, + Constant { + public: bool, + }, + ExternalFunction { + public: bool, + }, + Import { + package: Option, + module: String, + }, + Imported { + package: Option, + module: String, + member: String, + }, Parameter, Local, - Type { public: bool, opaque: bool }, - Constructor { public: bool }, - Field { public: bool }, + Type { + public: bool, + opaque: bool, + }, + Constructor { + public: bool, + }, + Field { + public: bool, + }, Label, Prelude, } @@ -118,6 +138,7 @@ pub fn resolve_project(project: &Project) -> Result { - if let SymbolKind::Imported { module, member } = &self.symbols[symbol.0 as usize].kind.clone() { + if let SymbolKind::Imported { module, member, .. } = &self.symbols[symbol.0 as usize].kind.clone() { self.resolve_project_member( module, Namespace::Value, diff --git a/crates/core/src/types.rs b/crates/core/src/types.rs index 6cf7c31..96e0334 100644 --- a/crates/core/src/types.rs +++ b/crates/core/src/types.rs @@ -118,6 +118,19 @@ pub struct ModuleInterface { pub constructors: HashMap, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct InterfaceEntry { + pub package: String, + pub module: String, + pub interface: ModuleInterface, +} + +impl InterfaceEntry { + pub fn new(package: impl Into, module: impl Into, interface: ModuleInterface) -> Self { + Self { package: package.into(), module: module.into(), interface } + } +} + impl From<&ast::Module> for ModuleInterface { fn from(module: &ast::Module) -> Self { let mut interface = ModuleInterface::default(); @@ -218,7 +231,7 @@ pub struct TypedModule { pub struct TypedProject { pub package_name: String, pub modules: Vec, - pub interfaces: HashMap, + pub interfaces: HashMap, } fn collect_interface_declarations(declarations: &[Declaration], interface: &mut ModuleInterface) { @@ -363,6 +376,7 @@ pub fn check_project(project: &Project) -> Result { let resolved = resolve::resolve_project(project)?; let type_interfaces = TypeInterfaceRegistry::for_project( + &project.graph.root_package.name, &project.graph.dependency_interfaces, project .graph @@ -398,7 +412,14 @@ pub fn check_project(project: &Project) -> Result { external_function_labels.insert(name.clone(), labels.clone()); external_function_labels.insert(format!("{}.{}", module_info.name, name), labels.clone()); } - interfaces.insert(module_info.name.clone(), typed.interface.clone()); + interfaces.insert( + module_info.name.clone(), + InterfaceEntry::new( + project.graph.root_package.name.clone(), + module_info.name.clone(), + typed.interface.clone(), + ), + ); modules.push(TypedModule { package_name: Some(project.graph.root_package.name.clone()), module_name: Some(module_info.name.clone()), @@ -2932,7 +2953,7 @@ fn main() -> Int { let app = typed.interfaces.get("app").expect("app interface"); assert_eq!( - app.function_labels.get("with_value"), + app.interface.function_labels.get("with_value"), Some(&vec![Some("callback".into()), Some("value".into())]) ); } @@ -3111,11 +3132,12 @@ pub fn labelled(value thing: Thing) -> Int { assert!(project.graph.dependency_interfaces.contains_key("dep/foo")); let dependency_interface = typed.interfaces.get("dep/foo").expect("dependency interface"); assert_eq!( - dependency_interface.function_labels.get("labelled"), + dependency_interface.interface.function_labels.get("labelled"), Some(&vec![Some("value".into())]), ); assert_eq!( dependency_interface + .interface .constructors .get("Make") .and_then(|constructor| constructor.fields.first()) @@ -3186,7 +3208,7 @@ pub fn unbox(box: Boxed(Int)) -> Int { pair.type_, Type::Function { params: vec![], return_type: Box::new(Type::Tuple(vec![Type::Int, Type::String])) } ); - assert!(typed.interfaces["dep/box"].constructors.contains_key("Box")); + assert!(typed.interfaces["dep/box"].interface.constructors.contains_key("Box")); } #[test] diff --git a/docs/internal/tasks/14_project_compilation_and_dependencies.md b/docs/internal/tasks/14_project_compilation_and_dependencies.md index 91442f8..165852b 100644 --- a/docs/internal/tasks/14_project_compilation_and_dependencies.md +++ b/docs/internal/tasks/14_project_compilation_and_dependencies.md @@ -77,13 +77,13 @@ The goal here is to make linked project names deterministic and collision-free. ### Package-owned interfaces -- [ ] Add an `InterfaceEntry` wrapper with package, module, and interface data. -- [ ] Store dependency, project, and stdlib interfaces as owner-aware entries. -- [ ] Resolve imports and imported symbols to concrete interface owners. -- [ ] Lower references and calls from resolver-owned `package:module.member` +- [x] Add an `InterfaceEntry` wrapper with package, module, and interface data. +- [x] Store dependency, project, and stdlib interfaces as owner-aware entries. +- [x] Resolve imports and imported symbols to concrete interface owners. +- [x] Lower references and calls from resolver-owned `package:module.member` identities. -- [ ] Remove linker-side package-owner inference for imported modules. -- [ ] Add overlap fixtures where root and dependency packages expose the same +- [x] Remove linker-side package-owner inference for imported modules. +- [x] Add overlap fixtures where root and dependency packages expose the same module and member names. ### Dependency interfaces @@ -130,7 +130,7 @@ The goal here is to make linked project names deterministic and collision-free. - [ ] Support `--emit` values for `wasm`, `wat`, and useful debug artifacts. - [ ] Emit optional per-module debug dumps for AST, resolved AST, typed output, IR, and WAT where useful. -- [ ] Avoid writing partial final artifacts after failed compilation unless the +- [ ] Don't writing partial final artifacts after failed compilation unless the user explicitly requested debug dumps. ## Definition of Done diff --git a/fixtures/projects/generated_names/dependency_module_overlap/gleam.toml b/fixtures/projects/generated_names/dependency_module_overlap/gleam.toml index 79c5948..dff033b 100644 --- a/fixtures/projects/generated_names/dependency_module_overlap/gleam.toml +++ b/fixtures/projects/generated_names/dependency_module_overlap/gleam.toml @@ -4,4 +4,4 @@ description = "Generated-name fixture with project and dependency module-name ov licences = ["Apache-2.0"] [dependencies] -overlap_dep = { path = "build/packages/overlap_dep" } +overlap_dep = { path = "packages/overlap_dep" } diff --git a/fixtures/projects/generated_names/dependency_module_overlap/packages/overlap_dep/gleam.toml b/fixtures/projects/generated_names/dependency_module_overlap/packages/overlap_dep/gleam.toml new file mode 100644 index 0000000..41a9313 --- /dev/null +++ b/fixtures/projects/generated_names/dependency_module_overlap/packages/overlap_dep/gleam.toml @@ -0,0 +1,4 @@ +name = "overlap_dep" +version = "1.0.0" +description = "Dependency fixture with a module that overlaps the root package." +licences = ["Apache-2.0"] diff --git a/fixtures/projects/generated_names/dependency_module_overlap/packages/overlap_dep/src/consumer.gleam b/fixtures/projects/generated_names/dependency_module_overlap/packages/overlap_dep/src/consumer.gleam new file mode 100644 index 0000000..e5da4a4 --- /dev/null +++ b/fixtures/projects/generated_names/dependency_module_overlap/packages/overlap_dep/src/consumer.gleam @@ -0,0 +1,5 @@ +import shared + +pub fn dependency_value() -> Int { + shared.value() +} diff --git a/fixtures/projects/generated_names/dependency_module_overlap/packages/overlap_dep/src/shared.gleam b/fixtures/projects/generated_names/dependency_module_overlap/packages/overlap_dep/src/shared.gleam new file mode 100644 index 0000000..a9c3165 --- /dev/null +++ b/fixtures/projects/generated_names/dependency_module_overlap/packages/overlap_dep/src/shared.gleam @@ -0,0 +1,3 @@ +pub fn value() -> Int { + 2 +} diff --git a/fixtures/projects/generated_names/dependency_module_overlap/src/main.gleam b/fixtures/projects/generated_names/dependency_module_overlap/src/main.gleam new file mode 100644 index 0000000..4514acd --- /dev/null +++ b/fixtures/projects/generated_names/dependency_module_overlap/src/main.gleam @@ -0,0 +1,5 @@ +import shared + +pub fn root_value() -> Int { + shared.value() +}