diff --git a/core/src/schema/ir/validation/symbols.rs b/core/src/schema/ir/validation/symbols.rs index e460908..4910aff 100644 --- a/core/src/schema/ir/validation/symbols.rs +++ b/core/src/schema/ir/validation/symbols.rs @@ -38,4 +38,16 @@ impl<'a> SymbolTable<'a> { pub fn contains(&self, name: &str) -> bool { self.symbols.contains_key(name) } + + /// Whether `name` is the trailing segment of some `use ns::Name` import — + /// i.e. a bare reference that a `use` brought into scope, Rust-style. + /// `contains` is checked first; the qualified `ns::Name` form always + /// resolves through `contains`. + pub fn is_imported_bare(&self, name: &str) -> bool { + self.symbols.iter().any(|(key, kind)| { + *kind == SymbolType::Import + && key.contains("::") + && key.rsplit("::").next() == Some(name) + }) + } } diff --git a/core/src/schema/ir/validation/validator.rs b/core/src/schema/ir/validation/validator.rs index e4dd705..890472d 100644 --- a/core/src/schema/ir/validation/validator.rs +++ b/core/src/schema/ir/validation/validator.rs @@ -371,8 +371,10 @@ fn validate_type( return; } - // Check if type exists - if !symbols.contains(base_type) { + // Check if type exists — either as a local declaration / qualified + // import (`contains`), or as the bare name a `use ns::Name` brought + // into scope. + if !symbols.contains(base_type) && !symbols.is_imported_bare(base_type) { let mut message = format!("Unknown type '{}'", base_type); if let Some(suggestion) = suggest_similar_name(base_type, symbols) { message.push_str(&format!(" - did you mean '{}'?", suggestion)); diff --git a/core/tests/package/from_sources.rs b/core/tests/package/from_sources.rs index 10da295..2d9de78 100644 --- a/core/tests/package/from_sources.rs +++ b/core/tests/package/from_sources.rs @@ -52,20 +52,22 @@ fn multiple_schemas_are_all_interpreted() { #[test] fn cross_schema_use_resolves_across_the_added_schemas() { - // `use` brings the namespace into scope; the reference is qualified. (A bare - // `User` after `use types::User` is a separate, pre-existing `core` gap.) - let ctx = PackageSources::new() - .schema(["types"], "struct User {\n id: u64\n}\n") - .schema( - ["api"], - "use types::User\n\nstruct Session {\n user: types::User\n}\n", - ) - .compile() - .expect("cross-schema `use` should resolve — same pass as on disk"); + // Both the bare name (`use ns::Name` -> `Name`) and the qualified form + // (`ns::Name`) resolve. + for reference in ["User", "types::User"] { + let ctx = PackageSources::new() + .schema(["types"], "struct User {\n id: u64\n}\n") + .schema( + ["api"], + &format!("use types::User\n\nstruct Session {{\n user: {reference}\n}}\n"), + ) + .compile() + .unwrap_or_else(|e| panic!("`user: {reference}` should resolve: {e}")); - assert_eq!(ctx.schema_contexts.len(), 2); - for sc in &ctx.schema_contexts { - assert!(sc.borrow().frozen_schema.borrow().is_some()); + assert_eq!(ctx.schema_contexts.len(), 2); + for sc in &ctx.schema_contexts { + assert!(sc.borrow().frozen_schema.borrow().is_some()); + } } }