Skip to content

feat(reflect): list and look up a package's globals - #5361

Merged
rossirpaulo merged 1 commit into
paulo/global-variablesfrom
paulo/global-reflection
Oct 21, 2026
Merged

rossirpaulo merged 1 commit into
paulo/global-variablesfrom
paulo/global-reflection

Conversation

@rossirpaulo

@rossirpaulo rossirpaulo commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Stacked on #5325 (global variables). Adds reflection over a package's globals to reflect.Package:

let package = reflect.Package.current();

// Every global variable and `client` declaration, with its current value.
for (let name in package.globals().keys()) {
    if (package.globals()[name] is ai.Client) {
        // discovered a client
    }
}

let retries = package.get_global<int>("config.MAX_RETRIES");   // throws NotFound / TypeMismatch
let model = package.find_global<ai.Client>("Model");           // null for a missing name
globals() map<string, unknown>: the package's global variables and client declarations, keyed by package-local name (root.ns.NAME, as functions() and classes() key theirs), with their current values.
get_global<T>(name) The value as T. Throws reflect.errors.NotFound for a missing name, reflect.errors.TypeMismatch when the value is not a T (checked as reflect.Class.get<T> checks a field). The name may be written with or without root..
find_global<T>(name) As get_global, but null for a missing name.

Scope is the package the method is called on (and, for a with_types view, the packages it re-exports, as functions() does), never the whole program. Each value is the global itself, shared with every reader, as reading the global directly is.

Semantics worth reviewing

  • Clients are globals here. A client X = … declaration is the same top-level binding as a const global, so globals() lists both, and finding a package's clients is globals() plus an is ai.Client check.
  • Reads during $init. A lookup reads a global as LoadGlobal does. While $init runs, a global whose initializer has not run yet is the existing GlobalReadBeforeInit error from get_global/find_global, and globals() leaves it out instead of listing the placeholder. The check is one helper, BexVm::global_is_pending, which LoadGlobal now uses too. A lookup by name is no edge the static init order can follow, so this runtime check is what catches it.
  • Values, not types. globals() returns values rather than declared types: a package's runtime tables carry no per-global type, and values are what discovery needs.

Testing

  • Corpus (baml_src/ns_global_variables/ns_reflection, 8 tests): listing globals and clients, client discovery by value, get_global/find_global (with and without root., nullable T, interface T), a missing name, a type mismatch, a value read through reflection being the global itself, and a Package.compile package's own globals.
  • Engine (tests/global_variables.rs, 2 tests): get_global of a global whose initializer has not run is reported by name; globals() during $init leaves it out.
  • Suites run locally: full corpus 5687 passed + 2 intentional tolerated failures; baml_tests 1144/1144 with --unreferenced=reject clean; bex_vm + bex_vm_types + bex_heap 431/431; bex_engine 503/503; IDE + LSP 486/486; baml_builtins2 + baml_db 69/69; type checker + MIR + emit 240/240; baml_cli lib 421/421 and corpus runner 2/2. Workspace clippy as CI runs it is clean except the pre-existing sdk_test_codegen lint; cargo fmt --check is clean.
  • baml describe reflect.Package's snapshot only shifts three line numbers (the stdlib file grew).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added reflection APIs to list package globals and clients.
    • Added typed lookup for individual globals, with clear handling for missing names and type mismatches.
    • Reflection now supports shared values, nullable globals, and globals from compiled packages.
    • Uninitialized globals are excluded from listings and report initialization errors when accessed.
  • Documentation

    • Documented global reflection capabilities and usage.

`reflect.Package` gains:
- `globals()`: the package's global variables and `client` declarations,
  keyed by package-local name, with their current values.
- `get_global<T>(name)` / `find_global<T>(name)`: one global's value,
  checked against `T` as `reflect.Class.get<T>` checks a field
  (`NotFound` / `TypeMismatch`).

A lookup reads a global as `LoadGlobal` does: during `$init`, one whose
initializer has not run is `GlobalReadBeforeInit`, and `globals()` leaves
it out. The pending check is one helper, `BexVm::global_is_pending`, which
`LoadGlobal` now uses too.

Tests: corpus `ns_global_variables/ns_reflection` (listing, client discovery
by value, get/find, missing name, type mismatch, shared value, a compiled
package) and two engine tests for reads during `$init`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@rossirpaulo
rossirpaulo added this pull request to stack #5362 October 21, 2026 20:43
@vercel

vercel Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
developer-docs Ready Ready Preview Oct 21, 2026 8:44pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 633a7572-d90b-4c85-971f-3b1f6d4d8a91
📥 Commits

Reviewing files that changed from the base of the PR and between e687a28 and 8cb05cf.

⛔ Files ignored due to path filters: 1
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_reflect_package_listing.snap is excluded by !**/*.snap
📒 Files selected for processing: 7
  • baml_language/ARCHITECTURE.md
  • baml_language/crates/baml_builtins2/baml_std/reflect/reflect.baml
  • baml_language/crates/baml_tests/baml_src/ns_global_variables/ns_reflection/reflection.baml
  • baml_language/crates/baml_tests/tests/global_variables.rs
  • baml_language/crates/bex_vm/src/package_reflect/reflect.rs
  • baml_language/crates/bex_vm/src/vm.rs
  • skills/baml-core/SKILL.md

Included review availability: This review used your included allowance. 9 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 10 reviews per hour.

📝 Walkthrough

Package reflection now lists initialized globals and clients and supports typed lookups by name. The runtime handles globals pending initialization, and tests cover lookup behavior, errors, and initialization-time access.

Changes: Package Global Reflection

Layer / File(s) Summary
Reflection API and runtime lookup
baml_language/crates/baml_builtins2/baml_std/reflect/reflect.baml, baml_language/crates/bex_vm/src/vm.rs, baml_language/crates/bex_vm/src/package_reflect/reflect.rs, baml_language/ARCHITECTURE.md, skills/baml-core/SKILL.md
The Package API adds globals, get_global<T>, and find_global<T>. Runtime lookup checks initialization state and requested types; enumeration omits globals pending initialization. The architecture documentation and guidance describe the APIs.
Reflection behavior validation
baml_language/crates/baml_tests/baml_src/ns_global_variables/ns_reflection/reflection.baml, baml_language/crates/baml_tests/tests/global_variables.rs
Tests cover global and client listing, typed and nullable lookup, missing names, type mismatches, shared-value mutation, compiled-package access, and reads during initialization.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Package
  participant PackageReflectImpl
  participant BexVm
  Package->>PackageReflectImpl: invoke globals or named lookup
  PackageReflectImpl->>BexVm: inspect exported globals and initialization state
  BexVm-->>PackageReflectImpl: return initialized values or pending status
  PackageReflectImpl-->>Package: return values or lookup error
Loading
Merge Risk: 🔵 Low · up to 8cb05

A compiled package that reflects on a later global during initialization may receive the wrong value or fail to initialize. This narrow case warrants owner awareness but does not block merge.

Security architecture risk: 🔵 Low · up to 8cb05

The new lookups preserve package-scoped resolution and type checking. However, runtime-created packages can expose uninitialized globals as null through reflection instead of reporting an initialization-order error. This creates a bounded partial-initialization concern; no new cross-package access or secret-disclosure path was established.

Retained concerns

  • Medium · reliability · inferred: Runtime-owned packages do not preserve the new reflection initialization contract. Their cells start as null, but global_cell recognizes pending state only for program slots. An initializer can obtain its own package and enumerate a later binding as null; typed lookup checks that placeholder instead of reporting GlobalReadBeforeInit. This permits partially initialized state to influence execution rather than consistently stopping at the initialization boundary. Normal external access after successful compilation is protected by the completion continuation. The underlying direct-read behavior predates this PR; its exposure through reflection is new, and no security exploit is established.
Security review details

Security Blast Radius

  • inferred — The demonstrated access scope is executable BAML holding a package wrapper, that package's globals, and its explicitly re-exported bases. Returned mutable values can affect other readers of those same objects. The evidence does not establish remote attacker reachability, tenant exposure, or additional environment privileges.

Security Findings and Attack Paths

  • inferred — The supported failure path is reflection during runtime-package initialization observing a later cell's null placeholder. This can change initialization control flow or produce TypeMismatch instead of the expected initialization-order failure. No authentication bypass, cross-package disclosure, or secret-extraction consequence was demonstrated.

Trust Boundaries and Controls

  • observed — Package-wrapper validation, re-export-only traversal, explicit type-argument enforcement, and runtime value checking constrain the new lookup path. Program globals also retain the same read-before-initialization guard as direct reads.

Resilience and Maintainability Implications

  • observed — Normal package compilation returns its wrapper only after initializer completion marks owned cells initialized, limiting external observation of partial state. Existing direct runtime-owned loads already read those cells without pending checks; the PR adds reflective exposure rather than introducing that underlying storage behavior.
Pre-merge checks: Passed 4 · Inconclusive 1

Failed Failed checks: 1 inconclusive

Check name Status Explanation Resolution
Docstring Coverage Inconclusive Docstring coverage is 58.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 2 files. (5 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: adding reflection APIs to list and look up package globals.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 58.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 2 files. (5 skipped: 4 unsupported, 1 too large.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

✨ Finishing Touches: 💡 2
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.

A rabbit reads the globals with care,
Finds named values waiting there.
Pending ones stay out of sight,
Type checks keep each lookup right.
Then hops away beneath the moon.

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

@github-actions

Copy link
Copy Markdown

⏭️ Performance benchmarks were skipped

Perf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to canary/main.

To run them on this PR, do any of the following, then push a commit (or re-run CI):

  • Add RUN_CODSPEED=1 to the PR description, or
  • Include run-perf or /perf in the PR title or any commit message.

@github-actions

Copy link
Copy Markdown

Binary size checks failed

❌ 2 violations · ✅ 1 passed

⚠️ Please fix the size gate issues or acknowledge them by updating baselines.

Artifact Platform File Gzip Gated on Baseline Delta Status
✅ baml-cli Linux 🔒 62.2 MB 26.5 MB file 73.1 MB -10.9 MB (-14.9%) OK
❌ packed-program Linux 🔒 40.5 MB 16.5 MB file 29.2 MB +11.3 MB (+38.8%) FAIL
❌ bridge_wasm WASM 26.5 MB 🔒 7.8 MB gzip 5.7 MB +2.1 MB (+37.0%) FAIL

🔒 = the size this artifact is GATED on (ceiling + delta). Binaries gate on file size (installed binary); WASM gates on gzip (download size). The other size is shown for information only.

Details & how to fix

Violations:

  • packed-program (Linux) file_bytes: 40.5 MB exceeds limit of 30.1 MB (exceeded by +10.4 MB, policy: max_file_bytes)
  • packed-program (Linux) file_delta_pct: +38.8% exceeds limit of 3.0% (exceeded by +35.8pp, policy: max_delta_pct)
  • bridge_wasm (WASM) gzip_bytes: 7.8 MB exceeds limit of 5.9 MB (exceeded by +1.9 MB, policy: max_gzip_bytes)
  • bridge_wasm (WASM) gzip_delta_pct: +37.0% exceeds limit of 3.0% (exceeded by +34.0pp, policy: max_delta_pct)

Add/update baselines:

.ci/size-gate/wasm32-unknown-unknown.toml:

[artifacts.bridge_wasm]
file_bytes = 26478743
gzip_bytes = 7756766

.ci/size-gate/x86_64-unknown-linux-gnu.toml:

[artifacts.packed-program]
file_bytes = 40452232
gzip_bytes = 16465098

Generated by cargo size-gate · workflow run

@rossirpaulo
rossirpaulo marked this pull request as ready for review October 21, 2026 21:13
@rossirpaulo
rossirpaulo added this pull request to the merge queue Oct 21, 2026
Merged via the queue into canary with commit def2a0e Oct 21, 2026
96 of 106 checks passed
@rossirpaulo
rossirpaulo deleted the paulo/global-reflection branch October 21, 2026 22:06
meefs pushed a commit to meefs/baml that referenced this pull request Oct 21, 2026
## Summary

BAML now has **global variables**: a file-scope `const` declares one.

```baml
/// How many times to retry.
const MAX_RETRIES = 3;
const NAMES: string[] = ["ada", "grace"];
const GREETING = `hello, ${NAMES[0]}`;     // reads other globals
const DOUBLED = double(MAX_RETRIES);        // calls functions; forward references are fine

function retries_left(used: int) -> int {
    MAX_RETRIES - used                     // visible from every body in the namespace
}
```

A global is evaluated **once, at startup**, by the package's `$init`, in
dependency order. It is reachable bare from its own namespace and, like
any item, as `root.ns.NAME` or `dep.ns.NAME` from anywhere else,
including from a `Session` mounting the package (`app.NAME`).

A top-level `let` is an **error** (E0010, pointing at the keyword):
`let` declares a reassignable binding, which only a body has, while a
global cannot be reassigned. The binding still lowers as the global it
names, so its readers resolve and the error is reported once, with no
cascade. Inside a body, `const` is unchanged: an alias for `let` with
its existing warning.

The `$init` pipeline already existed for `client X = …`, but every layer
assumed only clients and sessions produced top-level bindings, and
several of those assumptions break once users write their own.

## Semantics

| | |
|---|---|
| **Shape** | One name, optionally annotated: `const NAME = value;` /
`const NAME: Type = value;`. Destructuring, `_`, alias chains and `const
… else` are refused at file scope. A top-level `let` is an error. |
| **Type** | The annotation, which the initializer is checked against;
otherwise the initializer's type, widened as at any binding site (`const
N = 5` is `int`). One definition, `globals::global_value_type`, serves
both in-package reads and the package interface. |
| **Scope / shadowing** | Value namespace of the file's namespace (E0011
on duplicates). Locals, parameters and lambda parameters shadow globals.
A type's statics and enum variants win over a global root, so
`float.nan` stays the static even beside a global named `float`. A
global named like a package is an item of its namespace, so it shadows
that package there, as any item does (BoundaryML#4543). `root` is reserved
(E0176), as for every name. |
| **Mutability** | The binding is immutable (`X = v`, `X += v` →
**E0179**). What it holds is an ordinary heap value shared by every call
(`X.field = v`, `X.push(…)`), like a JS `const`. Concurrent mutation is
memory-safe through the existing container locks and atomic field slots.
|
| **Initialization order** | Go's rule. A global depends on another when
its initializer refers to it lexically (lambda bodies included), or
refers to a same-package function, method or parameter default whose
body does, transitively. References come from inference's resolution
tables, not from spellings. Independent globals run in source order
(file path, then position), independent of file load order. Cycles are
**E0181**, reported once with the chain (`A → helper → B → A`). |
| **Initializer rules** | No io (E0163, existing), no `spawn`/`await`
(**E0182**), no top-level `return` (**E0180**). A throw nothing catches
is a **warning** (E0183), because it would stop the program from
starting. |

## Pre-existing bugs that globals would have exposed

Each of these was reproduced on `canary` before being fixed:

- **Assigning to a top-level binding crashed the compiler.** `C = …` on
a client passed the checker, then emit failed with an internal error
("TIR should have rejected this program"). `C.field = v` silently stored
into a `null` temp. Both are fixed: E0179 for the binding, and MIR now
loads the global before storing a field.
- **The `$init` order matched names.** `topological_sort_lets` treated
any single-segment path ending in `.name` as an edge. Shadowing locals
created false cycles (reported as an internal compiler error), and
dependencies through function calls were missed, so an initializer could
read another global's `null` placeholder. It is replaced by the semantic
analysis in `baml_compiler2_hir_ty::globals`.
- **Qualified references to a top-level binding didn't resolve**
(`other.MyClient.model` gave E0003). TIR now records the binding in
`InferenceResult::global_roots`, and MIR lowers the path as the bare
global.
- **`$init` aborted on an early yield.** Allocation pressure (32 MiB),
or wasm's periodic poll, made engine creation fail, so any moderately
heavy initializer broke the playground. `$init` now resumes; there is
nothing to park for, since no collector exists yet. A test confirms the
failure without the fix.
- **`$init` failures were opaque**: `uncaught throw:
Object(HeapPtr(0x…))`, or a full VM-state `Debug` dump for `spawn`. They
now name the global, render the thrown value, and include the BAML
traceback.
- **Method calls on a global** typed as bound-method *values*, so
generic methods with turbofish failed (`PKG.get_function<F>(…)`). They
now type like local receivers.

## Reflection and sessions

- Package interfaces export globals with their types
(`PackageInterface::values`, `ExternLetLoc`). A served or mounted
package's global is read as an import of the slot its own `$init`
filled.
- Tested in the corpus: a `Session` reads `app.global_variables.X`;
`Package.compile` of source with globals initializes them in order; a
throwing initializer in a compiled package reaches the caller as a
catchable error; `Package.current()` and `reflect.Type` values held in
globals work.
- Reflection over globals (`reflect.Package.globals()`, `get_global<T>`,
`find_global<T>`, clients included) is in the stacked PR BoundaryML#5361, to keep
this PR's public API surface unchanged.

## Runtime safety net

The static order cannot see a global read through a virtual interface
call or a function value. During `$init` only, the VM tracks which
global slots are still pending (`BexVm::pending_globals`), and a
`LoadGlobal` of one fails with "global `user.X` was read before its
initializer ran", naming the global being initialized. Post-`$init` VMs
pay one `None` check.

## Tooling

- **IDE:** hover shows `const NAME: Type` with `///` docs (locals still
show `let`). Goto-definition and find-references follow `root.ns.X` and
`dep.X` and skip shadowing locals (references are now confirmed through
`symbol_at`, which also fixes a local `let` named like an item being
counted as a reference to it). Hover, goto, references and member
completion work *inside* initializers, because the IDE paths are keyed
on body owners rather than functions. Unannotated globals get inlay type
hints, globals are `readonly` semantic tokens, and `baml describe` finds
globals and clients and renders a global's shape as `const NAME: Type`.
Top-level completion offers a `const ${1:name} = $0;` snippet in place
of `let`.
- **Formatter:** top-level bindings are formatted (they were printed
verbatim). Adjacent globals stay grouped; author blank lines are kept,
collapsed to one. The keyword is kept as written: the formatter does not
rewrite a top-level `let` to `const`.
- **Docs:** `ARCHITECTURE.md`, keyword docs (`let` covers locals;
`const` declares a global at file scope and behaves like `let` in a
body), the `baml-core` agent skill, the `bex_heap` README safety
invariant, and a website lesson whose example made an LLM call in a
global initializer (now E0163).

## Testing

- **Runtime corpus**, `baml_src/ns_global_variables`: 22 tests covering
annotations, dependency order (including through helpers and parameter
defaults), shadowing (including a global named like a package), shared
mutation, qualified access across namespaces (including a qualified
global inside a tagged template), sessions, compiled packages,
reflection values, spawn and generic methods. The full corpus passes:
5679 passed, 2 tolerated failures that are pre-existing.
- **Compile-error projects:** `global_variable_declarations`,
`global_variable_initializers` and `global_variable_assignment` replace
`top_level_let`. `global_variable_declarations` includes a rejected
top-level `let` that a function still reads, showing the single error
without a cascade.
- **Engine-level tests** (`tests/global_variables.rs`): startup failure
messages, the uninitialized-read check, allocation pressure during
`$init` (verified to fail without the fix), source-order init
independent of file load order, dependency-package globals, and a
namespaced global's failure named in full.
- **IDE** (13 tests, plus the top-level completion test, which now
checks the `const` snippet and the absence of `let`) and **formatter**
(6 tests, including one that a top-level `let` keeps its keyword).
- **Suites run locally** on the branch merged with `canary`
(`5e9826d29`):
  - `baml_tests`: 1142/1142, with `--unreferenced=reject` clean.
- Runtime corpus through `baml_cli` (`baml_corpus`): 2/2. Run directly
with `baml-cli test`, that is 5679 passed and 2 tolerated failures. The
2 are intentional `test_runners` fixtures.
  - `baml_compiler2_ast` + `baml_fmt`: 250/250.
  - `baml_ide`: 330/330.
  - `baml_compiler2_hir_ty` + `baml_lsp` + `baml_lsp_server`: 323/323.
- `baml_compiler2_hir` + `baml_compiler2_mir` + `baml_compiler2_emit`:
83/83.
  - `baml_db`: 63/63. `baml_builtins2`: 6/6 (parses the keyword docs).
  - `bex_engine` + `bex_vm` + `bex_heap` + `bex_vm_types`: 934/934.
- `baml_cli` `run_command` tests: 41/41. The rest of `baml_cli` wasn't
re-run. `feedback_auth_e2e` needs Apple Keychain access, which the
sandbox doesn't have.
- Workspace clippy, run as in CI, is clean except `sdk_test_codegen`,
which has a pre-existing `println!` lint unrelated to this change.
`cargo fmt --check` is clean.

## Review notes

- **Diagnostics changed in existing projects:** `init_io_reachable` is
unchanged (io takes priority over suspension when one call reaches
both). `top_level_let` is removed; its case, a rejected top-level `let`,
is now in `global_variable_declarations`.
- **The keyword is read from the CST.** `lower_top_level_let` finds the
statement's first `let`/`const` token. The parser nests it inside the
pattern node for most shapes, so the body-level `const` warning is told
to skip that one token. Nested introducers, such as an alias chain's,
still get the warning. `Session.eval` bindings and `client` declarations
are unchanged.
- **Merged with `canary`** (merge commit `c5c611edc`), adapting the
branch to canary's changes:
- `FunctionIdentity::InitLet` carries the global's `ItemPath` instead of
an ordinal. It renders as `$init_let.<pkg>.<ns>.<name>`, so `$init`
failures still name the global. This changes the shape of a serialized
function identity.
- Global paths are read through canary's `PathHead`, the same way
functions are. A child namespace's global is `root.ns.X` from its
parent; the relative `ns.X` form no longer resolves.
- The test-registry shadowing fix is canary's own (`" __registry"`), so
this PR no longer renames that parameter.
- A top-level `let type` keeps its own error, renamed `TopLevelLetType`,
with a message about `let type` rather than top-level `let`.
- Canary made `void` an alias for `null` and stopped reserving `baml`,
so `global_variable_declarations` now tests a global named `root` and
has no `void` case.
- **Review of the merge** (commit `234cbf889`): an independent review of
the merge found no dropped intent but some cleanup, all addressed here:
- A qualified global inside a tagged template (`root.ns.name`) was
lowered as the template's same-named body parameter; MIR now skips name
lookups for paths the checker resolved to a global. The new corpus test
failed before the fix.
- `ResolvedValue::ExternalLet`: both `resolve_value` implementations
answer a served package's exported global, so `global_root` is a prefix
walk over `resolve_value` rather than a second lookup.
- One record of how many path segments name a global
(`GlobalRoot.segments`), read through
`InferenceResult::path_segment_at_written`; `ResolvedPath.root_len` is
gone.
- The `$init_let` naming scheme is defined once in `bex_vm_types`
(`init_let_helper_name` / `init_let_helper_global`), used by the
function identity, emit's operand metadata and the engine.
- MIR's name-based global fallback was never reached across the corpus
and every affected suite (checked with a temporary panic), so it is
removed.
- A telemetry file the merge committed by accident is removed, and docs
and comments follow canary's naming rules.
- **Unrelated test fix** (commit `b15eed4a2`): `bex_engine`'s telemetry
`failure_tests` passed an empty folder to its fake recording writer, so
telemetry's blob store wrote into `crates/bex_engine/cas/`, inside the
source tree, on every run since shutdown GC pauses started being
recorded (BoundaryML#5167). That is how a blob got into this branch's merge. The
tests now use a temp folder; what they check is unchanged.
- **Kind labels are unchanged.** `DefinitionKind::Let` still prints as
`let` in E0011 ("defined 2 times as: let, function") and in `baml
describe` listings and JSON `kind` fields. Only the surface syntax
changes here.
- **E0183 is a warning on purpose.** Stdlib client constructors validate
their arguments (`ai.RoundRobin` throws `InvalidRequest`, Vertex throws
`ParseError`), so an error would have rejected valid clients. Clients
and session bindings are exempt.
- **Unannotated globals in a type cycle** no longer get the
order-dependent "type inference failed; write the type explicitly"
error. Every such cycle is also an initialization cycle, which is
reported deterministically as E0181. Clients and session bindings keep
the old behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)




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

## Summary by CodeRabbit

* **New Features**
* Added file-scope `const` globals with optional type annotations,
namespace support, shared mutable contents, and cross-package access.
* Globals initialize once at startup in dependency order, with support
for forward references and clear cycle or read-before-initialization
errors.
* Added editor support for global hover information, navigation,
references, completions, readonly highlighting, and inferred type hints.

* **Bug Fixes**
* Improved diagnostics for invalid declarations, reassignment,
unsupported initializer effects, uncaught errors, and returns.

* **Documentation**
* Updated language, skills, and website documentation with
global-variable behavior and restrictions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
meefs pushed a commit to meefs/baml that referenced this pull request Oct 21, 2026
…ndaryML#5364)

## Summary

Follow-up to BoundaryML#5325, addressing CodeRabbit's review of it (an "outside
diff range" note on `typescript2/app-website/lessons/everything.md`).

The lesson's Variables section now teaches `const` for a global at file
scope and `let` for a local inside a function. The "Annotating Variable
Types" example right after it still showed two bare top-level `let`
lines, which contradicts that and, copied as written, gives the new
"global variables are declared with `const`" error. It now shows the
annotation on a `const` global and on a `let` local inside a function:

```baml
// A global variable with a type annotation
const name: string = "Bammy";

function Example() {
    // A local variable with a type annotation
    let r: int | string = someComplicatedFunction();
}
```

Only this example changes. The lesson's other bare `let` snippets are
fragments illustrating arrays, maps and loops rather than declarations,
so they're left as they are.

## Notes

- Docs only; nothing compiles the lesson's code blocks.
- BoundaryML#5325 and BoundaryML#5361 have merged; this PR is based on `canary`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <[email protected]>

This branch was successfully deployed

1 active deployment
Preview – developer-docs — 8cb05cfb Deployed Oct 21, 2026 by vercel[bot]
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