Skip to content

Consolidate name resolution through PackageResolutionContext#3300

Merged
hellovai merged 11 commits intocanaryfrom
vbv/consolidate-name-resolution-through-package-context
Mar 29, 2026
Merged

Consolidate name resolution through PackageResolutionContext#3300
hellovai merged 11 commits intocanaryfrom
vbv/consolidate-name-resolution-through-package-context

Conversation

@hellovai
Copy link
Copy Markdown
Contributor

@hellovai hellovai commented Mar 28, 2026

What problems was I solving

The BAML compiler had three independent name resolution paths that each implemented their own namespace logic. Two of them silently fell back to the root namespace when a name wasn't found in the current namespace — meaning Config written inside ns_llm/ would resolve to root-level Config without any explicit qualification. This implicit behavior would cause non-deterministic resolution if two namespaces defined the same short name.

Meanwhile, PackageResolutionContext (PRC) was designed to be the single authority for package-level resolution, but had zero call sites — all three ad-hoc paths bypassed it. Additionally, lookup_type_any_ns scanned all namespaces in FxHashMap iteration order (non-deterministic) to find a type by short name, despite all 9 call sites already holding a QualifiedTypeName with full namespace info.

After the initial consolidation, several secondary namespace bugs were uncovered: builder.rs hardcoded &[] (root namespace) for constructor, enum variant, and type-pattern lookups; match exhaustiveness couldn't resolve dotted enum names like root.Status.Ok; throw inference didn't thread namespace context; and PPIR stream alias expansion produced bare paths unresolvable from non-root namespaces.

What user-facing changes did I ship

  • Bare cross-namespace references now produce errors with "did you mean" hints. Writing Config from ns_llm/ no longer silently resolves to root — the compiler suggests root.Config instead.
  • LSP semantic tokens respect namespace context. Type coloring now uses the file's namespace path.
  • Cross-namespace enum matches work correctly. match (s) { root.Status.Ok => ... } from a non-root namespace now resolves properly instead of producing "unreachable arm" errors.
  • Type inference in namespaced files resolves constructors and type patterns correctly. Object literals like ExecutionContext { ... } in baml.llm now resolve instead of producing unknown.
  • Stream companion types resolve correctly for cross-namespace type aliases.

How I implemented it

Phase 1-7: Core consolidation (original commits)

  • Changed lookup_type/lookup_value API from ambiguous split-loop (path: &[Name]) to explicit (namespace: &[Name], item: &Name)
  • Removed lookup_type_any_ns and unqualify helpers
  • Removed bare-name fallback, added "did you mean" diagnostics
  • Wired resolve_name_at/resolve_path_at through PRC
  • Gave PRC::resolve_value full parity with resolve_type
  • Updated PPIR resolve_in_package to use new API
  • Threaded namespace context through MIR lowering and LSP tokens

Phase 8: Fix secondary namespace bugs (latest commit)

  • builder.rs: Changed constructor/enum/type-pattern lookups from &[] to &self.ns_context. Added enum_name_matches() to resolve dotted enum names like "root.Status" or "root.llm.Status" against QualifiedTypeName — used in pattern_match_cases, pattern_narrowed_type, and catch-arm matching.
  • throw_inference.rs: Threaded ns_context through collect_direct_throwsthrow_fact_from_exprresolve_path_to_ty. Added namespace-qualified lookup with root fallback for bare names.
  • resolve.rs: Fixed dep type lookup to try namespace_path then &[]. Changed resolve_path_at to use direct pkg_items lookup instead of routing through PRC (which drops the package prefix).
  • package_interface.rs: Filtered dep class field/method lookups by dep_name == class_pkg.
  • expand.rs: Added requalify_for_caller() to fix cross-namespace type alias stream expansion — when an alias body resolves to a type in a different namespace, the resulting path is qualified with root. so lower_type_expr_in_ns can find it.
  • signature.rs: Added param_type_spans to SignatureSourceMap for precise error spans on type annotations (previously spanned the whole name: Type parameter).

Test fixtures

  • namespaces_type_resolution: Comprehensive fixture with 4 namespaces (root, llm, auth, llm.openai), same-named types (Config, Error, Status/Model enums) in each. Tests bare-name resolution, cross-namespace root.X, constructors, enum matches, type aliases, throws, stream companions, and mixed-namespace signatures. 0 diagnostics.
  • namespaces_stream_direct_ref: Captures the known limitation that $stream companion types are not directly user-referenceable (future feature).
  • namespaces_bare_name_rejected: Verifies bare cross-namespace references produce errors with suggestions.

How to verify it

cd baml_language
cargo test -p baml_tests namespaces_type_resolution
cargo test -p baml_tests namespaces_bare_name_rejected
cargo test -p baml_tests namespaces_stream_direct_ref
cargo test -p baml_tests stream_types
cargo test -p baml_tests  # full suite: 947 tests

Description for the changelog

Remove implicit bare-name namespace fallback in BAML compiler; cross-namespace references now require explicit root. prefix with "did you mean" error suggestions. Fix namespace-aware type resolution for constructors, enum matches, throw inference, and stream companion expansion.

Summary by CodeRabbit

  • Bug Fixes

    • Bare names no longer implicitly resolve across namespaces; cross-namespace references must be explicitly qualified.
  • Improvements

    • Name/type/value resolution consistently respects file/package namespace context.
    • Unresolved-type diagnostics now include contextual suggestions when available.
    • Semantic classifications for some type tokens are more accurate with namespace-aware lookup.
  • Tests

    • New and updated test fixtures exercising namespace resolution, root-prefixed references, throws semantics, and stream-companion scenarios.

- Update namespaces_root_fallback fixture: bare Config → root.Config
- Create namespaces_bare_name_rejected fixture for bare cross-ns refs
- Add ignored tests: bare_name_cross_namespace_rejected, multi_segment_bare_path_rejected
- Capture baseline snapshots for both fixtures
Replace split-loop (path: &[Name]) signature with explicit
(namespace: &[Name], item: &Name) on PackageItems and PackageInterface.
Update all ~34 call sites. Behavior-preserving — same lookups happen,
just with explicit namespace/item split at the caller.
Remove the .or_else() bare fallback from lower_type_expr_in_ns — bare
names from child namespaces no longer silently resolve to root. Add
suggestions field to TirTypeError::UnresolvedType with namespace scan
to emit "did you mean root.Config?" hints. Un-ignore Phase 1 tests.
Replace all 9 lookup_type_any_ns(unqualify(qtn)) call sites with
lookup_type(qtn.namespace(), qtn.name()). Remove both unqualify
functions and both lookup_type_any_ns methods.
- Remove bare-name fallback from PRC::resolve_type for non-root ns
- Add root. prefix handling and dep search to PRC::resolve_value
- Route resolve_name_at through PRC::resolve_value/resolve_type
- Route resolve_path_at through PRC
Replace direct pkg_items.namespaces access with lookup_type(namespace,
item) calls. Change resolve_in_package signature to accept pre-split
(namespace, item) pair. Update all callers in resolve_qualified_key.
Use pkg_info.namespace_path instead of &[] in MIR lowering and LSP
semantic token resolution. Fixes self parameter and return type
resolution for functions in non-root namespaces.
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
beps Ready Ready Preview, Comment Mar 29, 2026 3:38am
promptfiddle Ready Ready Preview, Comment Mar 29, 2026 3:38am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9b097c00-df0d-438f-9a1f-810c51b592d4

📥 Commits

Reviewing files that changed from the base of the PR and between cdee8ec and 1c6e276.

📒 Files selected for processing (4)
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_caller_variant_contract.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_enum_variant_precise.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_enum_variant_violation.baml
  • baml_language/crates/baml_lsp2_actions_tests/test_files/syntax/throw/throws_mixed.baml

📝 Walkthrough

Walkthrough

Replaces path-splitting name resolution with explicit namespace-aware lookups across HIR/PPIR/TIR/LSP: APIs changed to accept (namespace: &[Name], item: &Name), removed cross-namespace short-name scans, updated diagnostics to include suggestions, and adjusted many call sites and tests to the new model.

Changes

Cohort / File(s) Summary
Core Lookup API
baml_language/crates/baml_compiler2_hir/src/package.rs
Changed lookup_type/lookup_value to (namespace: &[Name], item: &Name) and removed lookup_type_any_ns.
Package Interface & Resolution
baml_language/crates/baml_compiler2_tir/src/package_interface.rs, baml_language/crates/baml_compiler2_tir/src/resolve.rs
Reworked resolution to build explicit (namespace, item) pairs, removed unqualify, adjusted dep/root handling and value/type resolution call sites.
PPIR Expansion
baml_language/crates/baml_compiler2_ppir/src/expand.rs
Split paths into (namespace, item) for lookups; refactored resolve functions and added requalify_for_caller for alias requalification.
TIR Lowering & Builder
baml_language/crates/baml_compiler2_tir/src/{lower.rs,lower_type_expr.rs,builder.rs}
Lowering and builder now pass explicit namespace contexts to lower_type_expr_in_ns and call lookup_type(ns, item) / lookup_value(ns, item); updated self-type, pattern, and path-inference logic.
Inference / Errors
baml_language/crates/baml_compiler2_tir/src/{infer_context.rs,inference.rs}
TirTypeError::UnresolvedType now includes suggestions: Vec<String> and Display logic updated; lookup call sites adjusted to namespace+item form.
Throw/Resolve Paths
baml_language/crates/baml_compiler2_tir/src/throw_inference.rs
Threaded namespace context through throw collection and resolve_path_to_ty; enum-variant and type-lookup branches updated to namespace-aware split lookups.
LSP Consumers
baml_language/crates/baml_lsp2_actions/src/{completions.rs,definition.rs,tokens.rs,usages.rs}
LSP lookups changed to use lookup_type(namespace, item) and to derive package/namespace from QualifiedTypeName where applicable.
Signature Source Map
baml_language/crates/baml_compiler2_hir/src/signature.rs
Added param_type_spans: Vec<Option<TextRange>> to SignatureSourceMap and populate spans from parameter type expressions.
Tests & Fixtures
baml_language/crates/baml_tests/... (many files)
Updated unit tests to new lookup API; added and modified namespace-focused fixtures and tests validating root vs child namespace resolution, stream companion references, and rejection cases.
Misc. Consumers
baml_language/crates/baml_compiler2_ppir/src/..., baml_language/crates/baml_lsp2_actions_tests/...
Multiple call sites updated to split paths into namespace+item and adjusted diagnostics/test expectations accordingly.

Sequence Diagram(s)

sequenceDiagram
  participant Caller as Caller (lowering / ppir / tir / lsp)
  participant PRC as PackageResolutionContext
  participant PI as PackageInterface
  participant PIItems as PackageItems
  participant Deps as Dependency PackageItems

  Caller->>PRC: resolve_type/resolve_value(path, ns_context)
  PRC->>PI: resolve_in_own_then_deps(namespace, item)
  PI->>PIItems: lookup_type(namespace, item) / lookup_value(namespace, item)
  alt found in own package
    PIItems-->>PI: Definition
  else not found in own
    PI->>Deps: lookup_type(namespace, item) / lookup_value(namespace, item)
    Deps-->>PI: Definition or None
  end
  PI-->>PRC: ResolvedSource + Definition?
  PRC-->>Caller: resolution result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main objective: consolidating name resolution through PackageResolutionContext. It clearly identifies the primary architectural change across multiple compiler modules.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch vbv/consolidate-name-resolution-through-package-context

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 28, 2026

Binary size checks passed

7 passed

Artifact Platform Gzip Baseline Delta Status
bridge_cffi Linux 4.0 MB 5.7 MB -1.6 MB (-29.1%) OK
bridge_cffi-stripped Linux 2.5 MB 4.3 MB -1.8 MB (-41.3%) OK
bridge_cffi macOS 3.3 MB 4.6 MB -1.3 MB (-28.9%) OK
bridge_cffi-stripped macOS 2.0 MB 3.5 MB -1.5 MB (-42.0%) OK
bridge_cffi Windows 3.3 MB 4.6 MB -1.4 MB (-29.5%) OK
bridge_cffi-stripped Windows 2.1 MB 3.5 MB -1.5 MB (-41.7%) OK
bridge_wasm WASM 2.0 MB 3.0 MB -937.5 KB (-31.6%) OK

Generated by cargo size-gate · workflow run

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
baml_language/crates/baml_lsp2_actions/src/tokens.rs (1)

409-413: ⚠️ Potential issue | 🟡 Minor

Qualified type paths will still be highlighted incorrectly.

visit_type_expr() still resolves each WORD in isolation, so this lookup now always probes pkg_info.namespace_path. In a namespaced file, root.Config or other_ns.Foo will classify Config/Foo as Namespace instead of the underlying type because the qualifier-derived namespace is discarded here. Please resolve the full path before calling lookup_type.

baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs (1)

58-76: ⚠️ Potential issue | 🟠 Major

Handle root. before the generic namespace lookup.

Because folder-derived namespaces can also be ["root"], root.Config is currently sent through the ordinary (namespace, item) lookup first. In a project with ns_root/..., that resolves the namespaced item and the explicit-root branch never runs, so root.* can point at the wrong definition.

Suggested fix
-            let resolved = if !ns_context.is_empty() {
+            let resolved = if segments.len() >= 2 && segments[0].as_str() == "root" {
+                package_items.lookup_type(&segments[1..segments.len() - 1], item)
+            } else if !ns_context.is_empty() {
                 let ns: Vec<baml_base::Name> =
                     ns_context.iter().chain(seg_ns.iter()).cloned().collect();
                 package_items.lookup_type(&ns, item)
             } else {
                 package_items.lookup_type(seg_ns, item)
             };

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3999f4bd-3671-4ae5-8951-7d4f879f4c0b

📥 Commits

Reviewing files that changed from the base of the PR and between 1a2ed81 and 54d6d2a.

⛔ Files ignored due to path filters (20)
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__01_lexer__main.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__01_lexer__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__02_parser__main.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__02_parser__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__05_diagnostics.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__10_formatter__main.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__10_formatter__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_basic/baml_tests__namespaces_basic__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_root_fallback/baml_tests__namespaces_root_fallback__01_lexer__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_root_fallback/baml_tests__namespaces_root_fallback__02_parser__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_root_fallback/baml_tests__namespaces_root_fallback__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_root_fallback/baml_tests__namespaces_root_fallback__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_root_fallback/baml_tests__namespaces_root_fallback__10_formatter__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/stream_types/baml_tests__stream_types__04_tir.snap is excluded by !**/*.snap
📒 Files selected for processing (20)
  • baml_language/crates/baml_compiler2_hir/src/package.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler2_ppir/src/expand.rs
  • baml_language/crates/baml_compiler2_tir/src/builder.rs
  • baml_language/crates/baml_compiler2_tir/src/infer_context.rs
  • baml_language/crates/baml_compiler2_tir/src/inference.rs
  • baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs
  • baml_language/crates/baml_compiler2_tir/src/package_interface.rs
  • baml_language/crates/baml_compiler2_tir/src/resolve.rs
  • baml_language/crates/baml_compiler2_tir/src/throw_inference.rs
  • baml_language/crates/baml_lsp2_actions/src/completions.rs
  • baml_language/crates/baml_lsp2_actions/src/definition.rs
  • baml_language/crates/baml_lsp2_actions/src/tokens.rs
  • baml_language/crates/baml_lsp2_actions/src/usages.rs
  • baml_language/crates/baml_tests/projects/namespaces_bare_name_rejected/main.baml
  • baml_language/crates/baml_tests/projects/namespaces_bare_name_rejected/ns_llm/models.baml
  • baml_language/crates/baml_tests/projects/namespaces_root_fallback/ns_llm/models.baml
  • baml_language/crates/baml_tests/src/compiler2_hir.rs
  • baml_language/crates/baml_tests/src/compiler2_tir/mod.rs
  • baml_language/crates/baml_tests/src/compiler2_tir/phase5.rs

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
baml_language/crates/baml_compiler2_hir/src/package.rs (1)

167-181: ⚠️ Potential issue | 🟠 Major

Stabilize namespace enumeration before exposing PackageItems.namespaces.

ns_paths is collected in a std::collections::HashSet, so the FxHashMap built here is still fed by process-random iteration order. Downstream scans of package_items.namespaces — including the new "did you mean" builder in baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs and LSP completion collection — will therefore keep emitting suggestions in arbitrary order. Please sort namespace paths (or expose a sorted iterator) before user-facing code consumes this map.

baml_language/crates/baml_lsp2_actions/src/completions.rs (1)

372-385: ⚠️ Potential issue | 🟠 Major

Qualify completion inserts for out-of-namespace items.

These loops iterate pkg.namespaces.values(), so they drop the namespace path and always insert bare Config / UseConfig names. After this PR, accepting one of those from ns_llm/... produces an unresolved identifier whenever the target lives in root or a sibling namespace. Iterate with (ns_path, ns_items) and emit root.X / foo.bar.X insert text for anything outside pkg_info.namespace_path.

Also applies to: 808-855

baml_language/crates/baml_lsp2_actions/src/tokens.rs (1)

384-425: ⚠️ Potential issue | 🟡 Minor

Resolve the full qualified type path before classifying the terminal token.

Even after the borrow fix, resolve_type_name still only looks up a bare name in the current file namespace. In ns_llm/..., root.Config will therefore color Config as Namespace instead of Class. visit_type_expr needs to resolve the dotted path and classify only its last segment from that result.

baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs (1)

54-76: ⚠️ Potential issue | 🟠 Major

Resolve explicit root.* / package-qualified paths before relative namespace lookup.

This branch prepends ns_context before it knows whether the path is already qualified. In a namespace like foo, root.Config will first probe foo.root.Config, and baml.http.Response will first probe foo.baml.http.Response; if either exists, the fallback never runs and the explicit reference binds to the wrong symbol.

baml_language/crates/baml_compiler2_tir/src/builder.rs (1)

2952-2976: ⚠️ Potential issue | 🟠 Major

Foreign package classes can be shadowed by builtin stubs.

Line 2966 tries resolve_builtin_member for every non-own package class before resolving against the class that was just found in pkg_items. A dependency like foo.Array or foo.media.Image will bind to the baml member if the path collides, even though the access was explicitly package-qualified.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3a0fe9c3-b1c3-4222-b45e-082d4f1754a8

📥 Commits

Reviewing files that changed from the base of the PR and between 1a2ed81 and 175fe14.

⛔ Files ignored due to path filters (20)
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__01_lexer__main.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__01_lexer__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__02_parser__main.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__02_parser__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__05_diagnostics.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__10_formatter__main.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__10_formatter__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_basic/baml_tests__namespaces_basic__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_root_fallback/baml_tests__namespaces_root_fallback__01_lexer__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_root_fallback/baml_tests__namespaces_root_fallback__02_parser__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_root_fallback/baml_tests__namespaces_root_fallback__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_root_fallback/baml_tests__namespaces_root_fallback__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_root_fallback/baml_tests__namespaces_root_fallback__10_formatter__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/stream_types/baml_tests__stream_types__04_tir.snap is excluded by !**/*.snap
📒 Files selected for processing (20)
  • baml_language/crates/baml_compiler2_hir/src/package.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler2_ppir/src/expand.rs
  • baml_language/crates/baml_compiler2_tir/src/builder.rs
  • baml_language/crates/baml_compiler2_tir/src/infer_context.rs
  • baml_language/crates/baml_compiler2_tir/src/inference.rs
  • baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs
  • baml_language/crates/baml_compiler2_tir/src/package_interface.rs
  • baml_language/crates/baml_compiler2_tir/src/resolve.rs
  • baml_language/crates/baml_compiler2_tir/src/throw_inference.rs
  • baml_language/crates/baml_lsp2_actions/src/completions.rs
  • baml_language/crates/baml_lsp2_actions/src/definition.rs
  • baml_language/crates/baml_lsp2_actions/src/tokens.rs
  • baml_language/crates/baml_lsp2_actions/src/usages.rs
  • baml_language/crates/baml_tests/projects/namespaces_bare_name_rejected/main.baml
  • baml_language/crates/baml_tests/projects/namespaces_bare_name_rejected/ns_llm/models.baml
  • baml_language/crates/baml_tests/projects/namespaces_root_fallback/ns_llm/models.baml
  • baml_language/crates/baml_tests/src/compiler2_hir.rs
  • baml_language/crates/baml_tests/src/compiler2_tir/mod.rs
  • baml_language/crates/baml_tests/src/compiler2_tir/phase5.rs

…and match patterns

- builder.rs: use ns_context instead of &[] for constructor, enum variant, and
  type-pattern sugar lookups; add enum_name_matches() to resolve dotted enum
  names (e.g. "root.Status") against QualifiedTypeName in match exhaustiveness
- throw_inference.rs: thread ns_context through collect_direct_throws →
  throw_fact_from_expr → resolve_path_to_ty so throw types resolve in
  namespaced files
- resolve.rs: fix dep type lookup to try namespace_path then &[]; use direct
  pkg_items lookup for qualified paths instead of routing through PRC
- package_interface.rs: filter dep class field/method lookups by package name
- expand.rs: requalify_for_caller() to fix cross-namespace alias stream
  expansion; use alias definition's namespace when recursing into alias bodies
- signature.rs: add param_type_spans for precise error span on type annotations
- Add comprehensive namespaces_type_resolution test fixture (4 namespaces,
  same-named types, 0 diagnostics) and namespaces_stream_direct_ref fixture

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
baml_language/crates/baml_compiler2_tir/src/throw_inference.rs (1)

327-369: ⚠️ Potential issue | 🟠 Major

Direct throw type resolution still skips root and package-qualified paths.

These branches still resolve against the current package’s pkg_items only, so throw root.MyError, throw baml.http.Request, and root-file bare cross-namespace throws still collapse to Ty::Unknown. That pollutes FunctionThrowSets and can suppress downstream throws-contract errors. Please route throw fact resolution through PackageResolutionContext::resolve_type (or an equivalent package-aware helper) instead of treating leading segments as plain namespaces.

Also applies to: 389-430

baml_language/crates/baml_compiler2_tir/src/resolve.rs (1)

105-125: ⚠️ Potential issue | 🟠 Major

resolve_type successes can still fall through to Unknown.

Once res_ctx.resolve_type succeeds, the follow-up lookup only probes pkg_info.namespace_path and []. Root-file fallback hits like bare Response -> llm.Response therefore lose their Definition and come back Unknown, which breaks definition/usages on exactly the names this PR still resolves. Recover the Definition from the returned qualified type instead of guessing with namespace-local lookups.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: db5b0cc1-69fe-4859-a2fd-1799833deedf

📥 Commits

Reviewing files that changed from the base of the PR and between 175fe14 and 272bb1a.

⛔ Files ignored due to path filters (34)
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/__baml_std__/baml_tests____baml_std____04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_bare_name_rejected/baml_tests__namespaces_bare_name_rejected__05_diagnostics.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_stream_direct_ref/baml_tests__namespaces_stream_direct_ref__01_lexer__main.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_stream_direct_ref/baml_tests__namespaces_stream_direct_ref__01_lexer__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_stream_direct_ref/baml_tests__namespaces_stream_direct_ref__02_parser__main.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_stream_direct_ref/baml_tests__namespaces_stream_direct_ref__02_parser__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_stream_direct_ref/baml_tests__namespaces_stream_direct_ref__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_stream_direct_ref/baml_tests__namespaces_stream_direct_ref__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_stream_direct_ref/baml_tests__namespaces_stream_direct_ref__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_stream_direct_ref/baml_tests__namespaces_stream_direct_ref__05_diagnostics.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_stream_direct_ref/baml_tests__namespaces_stream_direct_ref__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_stream_direct_ref/baml_tests__namespaces_stream_direct_ref__10_formatter__main.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_stream_direct_ref/baml_tests__namespaces_stream_direct_ref__10_formatter__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__01_lexer__main.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__01_lexer__ns_auth_auth.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__01_lexer__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__01_lexer__ns_llm_ns_openai_client.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__02_parser__main.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__02_parser__ns_auth_auth.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__02_parser__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__02_parser__ns_llm_ns_openai_client.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__05_diagnostics.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__10_formatter__main.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__10_formatter__ns_auth_auth.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__10_formatter__ns_llm_models.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/namespaces_type_resolution/baml_tests__namespaces_type_resolution__10_formatter__ns_llm_ns_openai_client.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/unknown_type_error/baml_tests__unknown_type_error__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/unknown_type_error/baml_tests__unknown_type_error__05_diagnostics.snap is excluded by !**/*.snap
📒 Files selected for processing (14)
  • baml_language/crates/baml_compiler2_hir/src/signature.rs
  • baml_language/crates/baml_compiler2_ppir/src/expand.rs
  • baml_language/crates/baml_compiler2_tir/src/builder.rs
  • baml_language/crates/baml_compiler2_tir/src/inference.rs
  • baml_language/crates/baml_compiler2_tir/src/package_interface.rs
  • baml_language/crates/baml_compiler2_tir/src/resolve.rs
  • baml_language/crates/baml_compiler2_tir/src/throw_inference.rs
  • baml_language/crates/baml_tests/projects/namespaces_stream_direct_ref/main.baml
  • baml_language/crates/baml_tests/projects/namespaces_stream_direct_ref/ns_llm/models.baml
  • baml_language/crates/baml_tests/projects/namespaces_type_resolution/main.baml
  • baml_language/crates/baml_tests/projects/namespaces_type_resolution/ns_auth/auth.baml
  • baml_language/crates/baml_tests/projects/namespaces_type_resolution/ns_llm/models.baml
  • baml_language/crates/baml_tests/projects/namespaces_type_resolution/ns_llm/ns_openai/client.baml
  • baml_language/crates/baml_tests/src/compiler2_tir/phase3a.rs

- expand.rs: recursively requalify map key types in requalify_for_caller
- phase5.rs: assert "root.Config" suggestion text in bare_name_cross_namespace_rejected,
  assert "unresolved type: ns2.MyClass" in multi_segment_bare_path_rejected

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…riants

Enum variants in throws type position (e.g. throws Errors.AuthError) are
not yet supported in the type system. Update 4 LSP action tests to use the
enum type directly (throws Errors) and document the known limitation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@hellovai hellovai enabled auto-merge March 29, 2026 03:34
@hellovai hellovai added this pull request to the merge queue Mar 29, 2026
Merged via the queue into canary with commit 1388039 Mar 29, 2026
43 checks passed
@hellovai hellovai deleted the vbv/consolidate-name-resolution-through-package-context branch March 29, 2026 04:06
hellovai added a commit that referenced this pull request Mar 30, 2026
The InstanceOf instruction in bex_vm would call as_object_ptr on the
left operand, which throws a TypeError when the value is Null (since
Null maps to ObjectType::Any, not ObjectType::Instance). This caused
"Function call failed: VM type error: expected instance, got any"
errors in the WASM playground after PR #3300 consolidated name
resolution.

Instead of erroring, instanceof on null or non-instance values now
correctly evaluates to false, matching standard language semantics
(e.g. JavaScript's `null instanceof Foo === false`).

Made-with: Cursor
github-merge-queue bot pushed a commit that referenced this pull request Mar 30, 2026
## Summary

- The `CmpOp::InstanceOf` instruction in `bex_vm` would call
`as_object_ptr` on the left operand, which throws a `TypeError` when the
value is `Null` (since `Null` maps to `ObjectType::Any`, not
`ObjectType::Instance`). This caused **"Function call failed: VM type
error: expected instance, got any"** errors in the WASM playground after
PR #3300 consolidated name resolution.
- Instead of erroring, `instanceof` on null or non-instance values now
correctly evaluates to `false`, matching standard language semantics
(e.g. JavaScript's `null instanceof Foo === false`).

## Root Cause

The `InstanceOf` handler in `vm.rs` unconditionally called
`self.as_object_ptr(&left, ObjectType::Instance)`, which returns a
`TypeError` for any value that isn't an instance object. When the left
operand was `Value::Null` (whose type resolves to `ObjectType::Any`),
this produced the cryptic "expected instance, got any" error.

This was exposed (not caused) by PR #3300's name resolution changes —
the new fully-qualified name paths altered control flow so that
`instanceof` checks were reached with null values that previously took a
different code path.

## Fix

Replaced the unconditional `as_object_ptr` call with a nested `match`
that:
1. Checks if the left value is a `Value::Object` — if not, returns
`false`
2. Checks if the object is an `Object::Instance` — if not, returns
`false`
3. Only then performs the class pointer comparison

## Test plan

- [x] `cargo test --lib` passes across all workspace crates
- [x] `cargo test -p bex_vm --lib` passes
- [x] `cargo test -p bex_engine --lib` passes
- [x] `cargo clippy` passes (verified by pre-commit hook)
- [ ] Manual verification in WASM playground that the error is resolved

Made with [Cursor](https://cursor.com)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Fixed the `instanceof` operator to properly handle non-object values
and `null` by returning `false` instead of raising an error.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
codeshaunted pushed a commit that referenced this pull request Mar 30, 2026
## What problems was I solving

The BAML compiler had three independent name resolution paths that each
implemented their own namespace logic. Two of them silently fell back to
the root namespace when a name wasn't found in the current namespace —
meaning `Config` written inside `ns_llm/` would resolve to root-level
`Config` without any explicit qualification. This implicit behavior
would cause non-deterministic resolution if two namespaces defined the
same short name.

Meanwhile, `PackageResolutionContext` (PRC) was designed to be the
single authority for package-level resolution, but had zero call sites —
all three ad-hoc paths bypassed it. Additionally, `lookup_type_any_ns`
scanned all namespaces in `FxHashMap` iteration order
(non-deterministic) to find a type by short name, despite all 9 call
sites already holding a `QualifiedTypeName` with full namespace info.

After the initial consolidation, several secondary namespace bugs were
uncovered: builder.rs hardcoded `&[]` (root namespace) for constructor,
enum variant, and type-pattern lookups; match exhaustiveness couldn't
resolve dotted enum names like `root.Status.Ok`; throw inference didn't
thread namespace context; and PPIR stream alias expansion produced bare
paths unresolvable from non-root namespaces.

## What user-facing changes did I ship

- **Bare cross-namespace references now produce errors with "did you
mean" hints.** Writing `Config` from `ns_llm/` no longer silently
resolves to root — the compiler suggests `root.Config` instead.
- **LSP semantic tokens respect namespace context.** Type coloring now
uses the file's namespace path.
- **Cross-namespace enum matches work correctly.** `match (s) {
root.Status.Ok => ... }` from a non-root namespace now resolves properly
instead of producing "unreachable arm" errors.
- **Type inference in namespaced files resolves constructors and type
patterns correctly.** Object literals like `ExecutionContext { ... }` in
`baml.llm` now resolve instead of producing `unknown`.
- **Stream companion types resolve correctly for cross-namespace type
aliases.**

## How I implemented it

### Phase 1-7: Core consolidation (original commits)

- Changed `lookup_type`/`lookup_value` API from ambiguous split-loop
`(path: &[Name])` to explicit `(namespace: &[Name], item: &Name)`
- Removed `lookup_type_any_ns` and `unqualify` helpers
- Removed bare-name fallback, added "did you mean" diagnostics
- Wired `resolve_name_at`/`resolve_path_at` through PRC
- Gave `PRC::resolve_value` full parity with `resolve_type`
- Updated PPIR `resolve_in_package` to use new API
- Threaded namespace context through MIR lowering and LSP tokens

### Phase 8: Fix secondary namespace bugs (latest commit)

- **builder.rs**: Changed constructor/enum/type-pattern lookups from
`&[]` to `&self.ns_context`. Added `enum_name_matches()` to resolve
dotted enum names like `"root.Status"` or `"root.llm.Status"` against
`QualifiedTypeName` — used in `pattern_match_cases`,
`pattern_narrowed_type`, and catch-arm matching.
- **throw_inference.rs**: Threaded `ns_context` through
`collect_direct_throws` → `throw_fact_from_expr` → `resolve_path_to_ty`.
Added namespace-qualified lookup with root fallback for bare names.
- **resolve.rs**: Fixed dep type lookup to try `namespace_path` then
`&[]`. Changed `resolve_path_at` to use direct `pkg_items` lookup
instead of routing through PRC (which drops the package prefix).
- **package_interface.rs**: Filtered dep class field/method lookups by
`dep_name == class_pkg`.
- **expand.rs**: Added `requalify_for_caller()` to fix cross-namespace
type alias stream expansion — when an alias body resolves to a type in a
different namespace, the resulting path is qualified with `root.` so
`lower_type_expr_in_ns` can find it.
- **signature.rs**: Added `param_type_spans` to `SignatureSourceMap` for
precise error spans on type annotations (previously spanned the whole
`name: Type` parameter).

### Test fixtures

- **`namespaces_type_resolution`**: Comprehensive fixture with 4
namespaces (root, llm, auth, llm.openai), same-named types (`Config`,
`Error`, `Status`/`Model` enums) in each. Tests bare-name resolution,
cross-namespace `root.X`, constructors, enum matches, type aliases,
throws, stream companions, and mixed-namespace signatures. **0
diagnostics.**
- **`namespaces_stream_direct_ref`**: Captures the known limitation that
`$stream` companion types are not directly user-referenceable (future
feature).
- **`namespaces_bare_name_rejected`**: Verifies bare cross-namespace
references produce errors with suggestions.

## How to verify it

```bash
cd baml_language
cargo test -p baml_tests namespaces_type_resolution
cargo test -p baml_tests namespaces_bare_name_rejected
cargo test -p baml_tests namespaces_stream_direct_ref
cargo test -p baml_tests stream_types
cargo test -p baml_tests  # full suite: 947 tests
```

## Description for the changelog

Remove implicit bare-name namespace fallback in BAML compiler;
cross-namespace references now require explicit `root.` prefix with "did
you mean" error suggestions. Fix namespace-aware type resolution for
constructors, enum matches, throw inference, and stream companion
expansion.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Bare names no longer implicitly resolve across namespaces;
cross-namespace references must be explicitly qualified.

* **Improvements**
* Name/type/value resolution consistently respects file/package
namespace context.
* Unresolved-type diagnostics now include contextual suggestions when
available.
* Semantic classifications for some type tokens are more accurate with
namespace-aware lookup.

* **Tests**
* New and updated test fixtures exercising namespace resolution,
root-prefixed references, throws semantics, and stream-companion
scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
codeshaunted pushed a commit that referenced this pull request Mar 30, 2026
## Summary

- The `CmpOp::InstanceOf` instruction in `bex_vm` would call
`as_object_ptr` on the left operand, which throws a `TypeError` when the
value is `Null` (since `Null` maps to `ObjectType::Any`, not
`ObjectType::Instance`). This caused **"Function call failed: VM type
error: expected instance, got any"** errors in the WASM playground after
PR #3300 consolidated name resolution.
- Instead of erroring, `instanceof` on null or non-instance values now
correctly evaluates to `false`, matching standard language semantics
(e.g. JavaScript's `null instanceof Foo === false`).

## Root Cause

The `InstanceOf` handler in `vm.rs` unconditionally called
`self.as_object_ptr(&left, ObjectType::Instance)`, which returns a
`TypeError` for any value that isn't an instance object. When the left
operand was `Value::Null` (whose type resolves to `ObjectType::Any`),
this produced the cryptic "expected instance, got any" error.

This was exposed (not caused) by PR #3300's name resolution changes —
the new fully-qualified name paths altered control flow so that
`instanceof` checks were reached with null values that previously took a
different code path.

## Fix

Replaced the unconditional `as_object_ptr` call with a nested `match`
that:
1. Checks if the left value is a `Value::Object` — if not, returns
`false`
2. Checks if the object is an `Object::Instance` — if not, returns
`false`
3. Only then performs the class pointer comparison

## Test plan

- [x] `cargo test --lib` passes across all workspace crates
- [x] `cargo test -p bex_vm --lib` passes
- [x] `cargo test -p bex_engine --lib` passes
- [x] `cargo clippy` passes (verified by pre-commit hook)
- [ ] Manual verification in WASM playground that the error is resolved

Made with [Cursor](https://cursor.com)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Fixed the `instanceof` operator to properly handle non-object values
and `null` by returning `false` instead of raising an error.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant