From 2141798d6f0ca784a28103706ee2e3992d826fc2 Mon Sep 17 00:00:00 2001 From: Kinflou Date: Mon, 31 Aug 2026 20:41:03 +0800 Subject: [PATCH] fix: `use ns::Name` binds the bare name `Name` in validation (#43) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After `use types::User`, a field `user: User` reported "Unknown type 'User'" — only the qualified `user: types::User` resolved. `use` should bind the trailing segment, Rust-style. `resolve_use_declaration` emits `FrozenUnit::Import("types::User", ..)` (fully-qualified). `SymbolTable::is_imported_bare(name)` now also matches a bare `name` against the trailing `::` segment of any in-scope `Import`, and `validate_type` consults it after `contains`. The qualified form is unchanged (it resolves through `contains`); glob (`use ns::*`) and whole-namespace (`use ns`) imports already expand to one `Import("ns::Sym")` per symbol, so they bind too. Not covered: `use ns::Name as Alias` — `resolve_use_declaration` still drops the alias. Tracked in #43. Test: `cross_schema_use_resolves_across_the_added_schemas` now checks both `user: User` and `user: types::User`. --- core/src/schema/ir/validation/symbols.rs | 12 ++++++++++ core/src/schema/ir/validation/validator.rs | 6 +++-- core/tests/package/from_sources.rs | 28 ++++++++++++---------- 3 files changed, 31 insertions(+), 15 deletions(-) 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()); + } } }