Skip to content

Add a static type() method to every type - #5272

Merged
antoniosarosi merged 11 commits into
antoniosarosi/reflect-kind-classesfrom
antoniosarosi/reflect-type-method
Oct 16, 2026
Merged

antoniosarosi merged 11 commits into
antoniosarosi/reflect-kind-classesfrom
antoniosarosi/reflect-type-method

Conversation

@antoniosarosi

@antoniosarosi antoniosarosi commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Every type now has a static type() method that returns its reflect.Type, the short form of reflect.Type.of<T>(). It comes from a new prelude interface in the reflect stdlib, implemented for every type:

interface Reflectable {
    function type() -> root.Type throws never {
        root.Type.of<Self>()
    }
}

implement<T> Reflectable for T {}

Before:

let foo = reflect.Class.new("Foo", { "a": reflect.Type.of<int>() });
if (t == reflect.Type.of<Box<string>>()) { ... }

After:

let foo = reflect.Class.new("Foo", { "a": int.type() });
if (t == Box<string>.type()) { ... }

It works on any type written as one name, optionally qualified or with generic arguments: int.type(), Foo.type(), root.models.Base.type(), Box<int[]>.type(). reflect.Type.of<T>() stays for everything else: arrays, optionals, unions, maps, function types, interfaces and type parameters (int[].type() and int?.type() do not parse, and T.type() does not resolve).

type() never takes over a type that already exists:

class Event {
    type: string,            // `event.type` is still this field
}

interface Shape {
    function type(self) -> string throws never;
}

implement Shape for Circle {
    function type(self) -> string { "circle" }
}

Circle { r: 1 }.type()      // "circle", not ambiguous
value.type()                // reflect.AnyClass: still the value's runtime type
builder.type()              // reflect.class.Builder: still the pending type

To get that, the compiler treats reflect.Reflectable specially in member lookup: value receivers never see it (it only has a static method), and on a type qualifier it gives way to any other interface that declares the same name.

The second commit rewrites reflect.Type.of<X>() to X.type() across the repo where X is a single named type, with a script.

Part of the reflection API stack. TYPE_SYSTEM.md needs a maintainer update for the two lookup rules; proposed text is in the first comment.

Summary by CodeRabbit

  • New Features

    • Added a .type() accessor for declared types, including primitives, classes, enums, and generic types.
    • Added a reflection-specific invalid-argument error for invalid reflection calls.
  • Bug Fixes

    • Improved type-member resolution to avoid ambiguity with existing type members.
    • Updated reflection field access and enum-value handling for more consistent results.

@antoniosarosi

Copy link
Copy Markdown
Contributor Author

Proposed TYPE_SYSTEM.md text for a maintainer (new subsection at the end of ## Interfaces, after ### Members of union-typed receivers):

Proposed new subsection at the end of ## Interfaces, after ### Members of union-typed receivers:

### `reflect.Reflectable`

The reflect stdlib implements `reflect.Reflectable` for every concrete type through a blanket implementation (`implement<T> Reflectable for T {}`). Its one member is the static `type()`, which returns the type's runtime value: `int.type()`, `Foo.type()` and `Box<int[]>.type()` equal `reflect.Type.of<int>()`, `reflect.Type.of<Foo>()` and `reflect.Type.of<Box<int[]>>()`. Like any blanket implementation it covers concrete types only, so interfaces, unions and type parameters have no `type()` (`reflect.Type.of<T>()` covers them).

Two lookup rules keep it from changing what existing code means:

- It contributes nothing to a value receiver's member surface. `x.type` keeps naming `x`'s own field or method, and a union receiver does not gain `type` from it as a shared interface.
- On a type qualifier it yields to every other declaration of the name: a class's own `type` method, or a `type` member of another interface the type implements, resolves as if `reflect.Reflectable` were not implemented. That interface and `reflect.Reflectable` both declaring `type` is not an ambiguity.

@vercel

vercel Bot commented Oct 15, 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 16, 2026 5:28pm UTC

Request Review

@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.

@ghost ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Waiting for required CodeTurtle file reviews. This is not an approval.

Open CodeTurtle review

@ghost

ghost commented Oct 15, 2026 •

Copy link
Copy Markdown

CodeTurtle review

View report · Head 93064820adab02e7d67bdeb3b5b0fb4133426ed5 · Policy e5b507c9177945b545e684f48562ae46eb1e00db

Evaluation incomplete; errors or ownership gaps require attention.

0/5 review requirements satisfied (respecting Any/All rules).

Reviewer Parts Progress Review
@hellovai baml_language/crates/baml_builtins2/baml_std/ai/ns_internal/media_output.baml, baml_language/crates/baml_builtins2/baml_std/openai/ns_internal/ns_decisions/decisions.baml, baml_language/crates/baml_builtins2/baml_std/openai/ns_internal/responses.baml, baml_language/crates/baml_builtins2/baml_std/reflect/reflect.baml, baml_language/crates/baml_builtins2/baml_std/typesafeai/ns_internal/jev.baml 0/5 reviewed Open assigned review
@aaronvg baml_language/crates/baml_builtins2/baml_std/ai/ns_internal/media_output.baml, baml_language/crates/baml_builtins2/baml_std/openai/ns_internal/ns_decisions/decisions.baml, baml_language/crates/baml_builtins2/baml_std/openai/ns_internal/responses.baml, baml_language/crates/baml_builtins2/baml_std/reflect/reflect.baml, baml_language/crates/baml_builtins2/baml_std/typesafeai/ns_internal/jev.baml 0/5 reviewed Open assigned review
@2kai2kai2 baml_language/crates/baml_builtins2/baml_std/ai/ns_internal/media_output.baml, baml_language/crates/baml_builtins2/baml_std/openai/ns_internal/ns_decisions/decisions.baml, baml_language/crates/baml_builtins2/baml_std/openai/ns_internal/responses.baml, baml_language/crates/baml_builtins2/baml_std/reflect/reflect.baml, baml_language/crates/baml_builtins2/baml_std/typesafeai/ns_internal/jev.baml 0/5 reviewed Open assigned review

Each file requires an explicit decision. Changed files need fresh decisions; unchanged files retain sign-offs. Changes requests remain until cleared by their reviewer or an audited administrator override.

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

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: 9653ea0e-2975-4d09-aac5-13d19545d383
📥 Commits

Reviewing files that changed from the base of the PR and between 0e1a58d and 01ff2d3.

⛔ Files ignored due to path filters: 14
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__control_describe_shallow_namespace_item_works.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__dispatch_root_prefix_resolves_user_item.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__fixed_describe_deep_namespace_item.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_builtin_package_listing.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_describe_class.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_describe_class_shows_associated_type_bindings.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_describe_class_with_methods.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_describe_class_with_static_methods.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_describe_enum.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_describe_enum_impl_surface.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_describe_generic_class_with_methods.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_describe_ns_item.snap is excluded by !**/*.snap
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_describe_same_named_impl_methods_are_labeled_per_impl.snap is excluded by !**/*.snap
  • 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: 15
  • baml_language/crates/baml_builtins2/baml_std/ai/ns_errors/errors.baml
  • baml_language/crates/baml_builtins2/baml_std/baml/baml.toml
  • baml_language/crates/baml_builtins2/baml_std/baml/conversions.baml
  • baml_language/crates/baml_builtins2/baml_std/baml/core.baml
  • baml_language/crates/baml_builtins2/baml_std/reflect/baml.toml
  • baml_language/crates/baml_builtins2/baml_std/reflect/kinds.baml
  • baml_language/crates/baml_builtins2/baml_std/reflect/ns_errors/errors.baml
  • baml_language/crates/baml_builtins2/baml_std/reflect/reflect.baml
  • baml_language/crates/baml_builtins2/baml_std/reflect/type.baml
  • baml_language/crates/baml_compiler2_hir/src/package.rs
  • baml_language/crates/baml_db/src/db.rs
  • baml_language/crates/baml_tests/baml_src/ns_type_kinds/type_kinds.baml
  • baml_language/crates/baml_tests/tests/hir_ty_package_interface.rs
  • baml_language/crates/baml_tests/tests/reflect_without_baml_dependency.rs
  • baml_language/crates/bex_vm/src/package_reflect/type_kinds.rs
💤 Files with no reviewable changes: 1
  • baml_language/crates/baml_builtins2/baml_std/reflect/baml.toml

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

📝 Walkthrough

The change adds a .type() accessor for concrete types and adjusts compiler resolution for the type member. It also updates reflection package boundaries and errors, then migrates builtin, test, and tool call sites from reflect.Type.of<T>() where supported.

Changes: Concrete Type Reflection

Layer / File(s) Summary
Reflection package boundary and error handling
baml_language/crates/baml_builtins2/baml_std/{baml,reflect}/..., baml_language/crates/baml_builtins2/baml_std/ai/ns_errors/errors.baml, baml_language/crates/baml_compiler2_hir/src/package.rs, baml_language/crates/baml_db/src/db.rs, baml_language/crates/baml_tests/tests/{hir_ty_package_interface.rs,reflect_without_baml_dependency.rs}, baml_language/crates/bex_vm/src/package_reflect/type_kinds.rs
baml now depends on reflect, and reflect no longer depends on baml. Reflection field iteration and type naming are updated. reflect.InvalidArgument is added and used for invalid enum values.
Concrete type accessor and compiler resolution
baml_language/crates/baml_builtins2/baml_std/baml/core.baml, baml_language/crates/baml_compiler2_hir_ty/src/{impls.rs,infer/obligations.rs,interfaces.rs,method_resolution.rs}, baml_language/crates/baml_compiler2_mir/src/lower.rs, baml_language/crates/baml_tests/baml_src/ns_reflect_type_method/*, baml_language/crates/baml_ide/src/completion.rs
baml.Concrete declares type() -> reflect.Type. Compiler lookup filters or demotes this member during interface and implementation resolution. Tests cover concrete, generic, namespaced, and existing type members.
Builtin and caller migration to .type()
baml_language/crates/baml_builtins2/baml_std/ai/ns_internal/*, baml_language/crates/baml_builtins2/baml_std/openai/ns_internal/*, baml_language/crates/baml_builtins2/baml_std/typesafeai/ns_internal/*, baml_language/crates/baml_tests/*, baml_language/sdk_tests/fixtures/*, baml_language/tools/type_quiz/*
Supported type references now use .type() instead of reflect.Type.of<T>(). Tests and tools retain their existing assertions and expected results, with an added invalid-enum-argument assertion.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Possibly related PRs

  • BoundaryML/baml#4325: Establishes the runtime reflect.Type and reflection model extended by this change.
  • BoundaryML/baml#4330: Changes runtime type construction and moves reflection into baml.reflect, which this change further extends.
  • BoundaryML/baml#4334: Adds read-back operations for runtime type values used by the new .type() accessors.
Merge Risk: ⚪ Minimal · up to 01ff2

No actionable issue remains identified; the PR is mergeable after normal checks.

Security architecture risk: 🔵 Low · up to 01ff2

This changes type reflection syntax and a public error type without a demonstrated increase in privileges. No introduced security issue was established, but compatibility and incomplete coverage leave some uncertainty.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — For the examined accessor path, caller-controlled type syntax reaches the existing Type.of intrinsic rather than a new privileged operation. The routed test functions are not additional production entrypoints. This bounds the assessed exposure to existing language reflection capabilities, without establishing deployment-wide isolation guarantees.

Trust Boundaries and Controls

  • observed — Non-stdlib dependency validation rejects reserved builtin package names, preventing ordinary dependency edges from replacing baml or reflect. Written package names may be lexically shadowed, while compiler-written language-package paths are identity-based. These controls counter the suspected builtin-replacement path.

Resilience and Maintainability Implications

  • observed — The AI client retains a closed raw-error union and explicitly maps both the legacy BAML InvalidArgument and the new reflection InvalidArgument to the same InvalidRequest representation. The examined normalization boundary therefore preserves the existing classification for this error transition.
Pre-merge checks: Passed 5
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly summarizes the main change: adding a static type() method for named types. It is concise and directly related to the pull request, although some type categories remain excluded.
Docstring Coverage Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 30 functions across 20 files. (9 skipped: 9…
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 83.33% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 30 functions across 20 files. (9 skipped: 9 unsupported.)

✨ Finishing Touches
📝 Generate docstrings
  • 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 taps a type-shaped key,
And finds a class beneath the tree.
Through fields and enums, paths now wind,
While .type() keeps each shape in mind.
The moonlit tests all pass along,
And carrots join the closing song.

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

@ghost ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Waiting for required CodeTurtle file reviews. This is not an approval.

Open CodeTurtle review

@ghost ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Waiting for required CodeTurtle file reviews. This is not an approval.

Open CodeTurtle review

@ghost ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Waiting for required CodeTurtle file reviews. This is not an approval.

Open CodeTurtle review

@github-actions

github-actions Bot commented Oct 15, 2026 •

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 🔒 61.6 MB 26.2 MB file 73.1 MB -11.5 MB (-15.7%) OK
❌ packed-program Linux 🔒 39.8 MB 16.2 MB file 29.2 MB +10.6 MB (+36.5%) FAIL
❌ bridge_wasm WASM 26.0 MB 🔒 7.6 MB gzip 5.7 MB +2.0 MB (+34.9%) 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: 39.8 MB exceeds limit of 30.1 MB (exceeded by +9.7 MB, policy: max_file_bytes)
  • packed-program (Linux) file_delta_pct: +36.5% exceeds limit of 3.0% (exceeded by +33.5pp, policy: max_delta_pct)
  • bridge_wasm (WASM) gzip_bytes: 7.6 MB exceeds limit of 5.9 MB (exceeded by +1.8 MB, policy: max_gzip_bytes)
  • bridge_wasm (WASM) gzip_delta_pct: +34.9% exceeds limit of 3.0% (exceeded by +31.9pp, policy: max_delta_pct)

Add/update baselines:

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

[artifacts.bridge_wasm]
file_bytes = 26033179
gzip_bytes = 7638335

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

[artifacts.packed-program]
file_bytes = 39778952
gzip_bytes = 16215886

Generated by cargo size-gate · workflow run

@antoniosarosi
antoniosarosi changed the base branch from canary to antoniosarosi/reflect-kind-classes October 15, 2026 21:03
@antoniosarosi
antoniosarosi force-pushed the antoniosarosi/reflect-type-method branch from 9306482 to c8d3d85 Compare October 15, 2026 22:07
@antoniosarosi
antoniosarosi added this pull request to stack #5283 October 15, 2026 22:59
/// `Box<int[]>.type()`. Array, optional, union, map and function types, and
/// generic type parameters, keep the `reflect.Type.of<T>()` form. A type that
/// declares its own `type` method keeps resolving to that method.
interface Reflectable {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we not put this on baml.Concrete instead of having a second blanket implemented interface?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I creates a cycle, reflect depends on baml, we need a baml.Type.of equivalent to put it there.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think that reflect needs to depend on baml, it should probably be the other way around. If that doesn't work, we can move Concrete into reflect

`int.type()`, `Foo.type()` and `Box<int[]>.type()` return the type's
reflect.Type, the same value as `reflect.Type.of<X>()`. The method comes
from `reflect.Reflectable`, a prelude interface implemented for every
type.

Member lookup treats `reflect.Reflectable` specially so existing `type`
members keep their meaning: value receivers never see it (its only method
is static), and on a type qualifier it yields to any other interface that
declares `type`. A field or method called `type`, `reflect.AnyClass.type()`
and `reflect.class.Builder.type()` all resolve as before.
Mechanical rewrite of the sites where X is a declared type written as one
name, optionally qualified or with generic arguments. Arrays, optionals,
unions, maps, function types, interfaces, enum variants, aliases of
non-named types and type parameters keep `reflect.Type.of<X>()`, as do the
dedicated `reflect.Type.of` tests and the compile-error projects.
`baml.Concrete` already has a blanket implementation for every concrete
type, so it carries the static `type()` now and `reflect.Reflectable` is
gone. `baml` cannot call `reflect.Type.of` (`reflect` depends on `baml`),
so `Concrete.type()` calls a new internal native, `baml._type_of<T>()`,
which reads the call's type argument like `baml.json.from_string<T>`.

The lookup carve-out now keys on Concrete's `type` member: value receivers
never see it (the type checker's lookups and MIR's union dispatch view),
and on a type qualifier or a bound it yields to any other interface that
declares `type`. A type parameter bounded by `Concrete` now has `T.type()`.
reflect.Enum.get_value declared and threw baml.errors.InvalidArgument, the only
place the reflect package named anything from baml. It now throws a new
reflect-local reflect.errors.InvalidArgument with the same shape, so reflect can
stop depending on baml. ai.errors.normalize classifies it like the baml class,
since provider internals call get_value inside client error handling.
reflect must not depend on baml, so the implementations baml declares are not
visible inside reflect's bodies. Two reflect.AnyClass bodies relied on one:

- get_field, has_field and list_fields iterated with for-in over a list. The
  type checker accepts that structurally, but code generation looks up the
  list's baml.iter.Iterable implementation among the packages reflect depends
  on, and fails to compile without baml. They now use index loops.
- name() called to_string() on a reflect.Type. Without the baml edge that call
  binds to baml.ToString's default body instead of the reflect.Type override
  and crashes the VM. It now calls reflect.Type's own _to_string_impl().
reflect now names nothing from baml, so the dependency can point the other way.
reflect's manifest drops baml and baml's manifest adds reflect.

With the edge, baml.Concrete.type() calls reflect.Type.of<Self>() directly,
which compiles to the LoadType intrinsic. The internal baml._type_of<T>() native
that stood in for it is deleted. The member-lookup carve-out for Concrete's
type member is unchanged.

reflect bodies still call methods on builtin carriers such as Array.push and
String.length. Those resolve through the installed language packages, not the
manifest graph, so they need no edge.

The stdlib manifest test expects the new baml -> reflect edge, and the
undeclared-package probe uses log, since baml now reaches reflect.
The stdlib diagnostics check cannot see a reflect body that leans on an
implementation declared in baml: a for-in over a list fails only at code
generation, and to_string() on a reflect.Type silently binds to the
baml.ToString default and crashes the VM. The new test compiles the stdlib and
runs a program through every reflect.AnyClass body, Concrete.type() and the
error path of Enum.get_value. Reintroducing either pattern makes it fail.
Describe listings shift core.baml and reflect source lines and list the new
reflect.errors.InvalidArgument. The baml package items no longer include
_type_of.
@antoniosarosi
antoniosarosi added this pull request to the merge queue Oct 16, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue because a pull request earlier in the stack was removed Oct 16, 2026
@antoniosarosi
antoniosarosi added this pull request to the merge queue Oct 16, 2026
Merged via the queue into canary with commit 439fce6 Oct 16, 2026
99 checks passed
@antoniosarosi
antoniosarosi deleted the antoniosarosi/reflect-type-method branch October 16, 2026 21:24
antoniosarosi added a commit that referenced this pull request Oct 16, 2026
"Scoped runtime type bindings" still documented the removed
`type T = unreflect(e)` statement, E0168 for unreflect elsewhere, and the
bounded form as future work. It now documents `let type T = e`, the
bounded `let type T extends I = e` with its optional diverging `else`,
the runtime bound check (a bound, not an interface type), and E0098 for
the removed spellings, as proposed on #5278 with its correction. A
TypeView operand binds the type its as_type() returns.

A new subsection documents the static type() that baml.Concrete gives
every concrete type, with its two lookup rules, rewritten from the #5272
proposal for the final design: type() lives on baml.Concrete and returns
reflect.Type.of<Self>(); there is no reflect.Reflectable.
meefs pushed a commit to meefs/baml that referenced this pull request Oct 17, 2026
Fixes the open review items from the reflection stack (#5283), one
commit per item.

- **A user's own `reflect.TypeView` works everywhere a `TypeView` is
accepted** (BoundaryML#5277). The natives only unwrapped the nine built-in kind
views. Every entry point that takes a `TypeView` now calls `as_type()`
in BAML before it reaches the native, and lowering does the same for a
`let type` operand.

  ```baml
  class IntView {
      implements reflect.TypeView {
function as_type(self) -> reflect.Type throws never { int.type() }
      }
  }

  reflect.Class.new("Row", { "n": IntView {} })
// before: throws "class fields must be type values or
reflect.WithMeta<reflect.Type> rows"
  // after:  a class with `n: int`

  let type T = IntView {};
  // before: VM internal error: type error: expected type, got instance
  // after:  T is int
  ```

- **TYPE_SYSTEM.md** (BoundaryML#5278, BoundaryML#5272). "Scoped runtime type bindings"
documents `let type`, including the bounded form, as proposed on BoundaryML#5278
with its correction. A new subsection documents `type()` on
`baml.Concrete`.

- **reflect natives throw `reflect.errors.InvalidArgument`, never
`baml.errors.InvalidArgument`** (BoundaryML#5272). This covers an uninitialized
`reflect.Package` or `reflect.Session`, a bad compile artifact and a
failed runtime link. No `throws` clause changes.

  ```baml
  reflect.Package { _inner: 1 }.get_class("Foo")
  // before: throws baml.errors.InvalidArgument
  // after:  throws reflect.errors.InvalidArgument
  ```

- **A bounded `let type` needs an `else` block** (BoundaryML#5278). Its bounds are
checked when it runs, so it has to say what a miss does. A miss always
runs the `else` block now; it never throws. The `BOUND_MISMATCH`
instruction is gone, so `FORMAT_VERSION` goes to 20.

  ```baml
  let type U extends Named = t;
// before: throws reflect.errors.TypeMismatch if the type does not
implement Named
// after: error E0111: `let type U extends …` checks its bounds when it
runs,
  //         so it needs an `else` block for a type that misses them

  let type U extends Named = t else { return null };  // unchanged
  ```

- **`let type` no longer takes a class builder's pending type** (BoundaryML#5278).
It threw `CompilationError` at run time when the builder was not built
yet. Call `build()` and bind the class.

  ```baml
  let builder = reflect.Class.builder("Node");
  let type T = builder.type();
// before: throws reflect.errors.CompilationError until builder.build()
has run
// after: error E0001: `let type` cannot bind a class builder's pending
type;
  //         call `build()` and bind the class it returns

  let type T = builder.build();  // ok
  ```


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

* **New Features**
* Reflection APIs now accept caller-defined type views across class,
union, map, package, builder, and interface-checking operations.
* Runtime type bindings can use types provided by those views. The
`baml.Concrete` interface also provides a static `type()` member for
eligible receivers.
* **Breaking Changes**
* Bounded `let type` bindings now require a diverging `else` branch,
which runs when a type fails its bounds.
* Class-builder pending types can no longer be bound directly; call
`build()` first. The previous static alias form and `unreflect` syntax
now produce an error.
* **Bug Fixes**
* Invalid reflection receivers now report
`reflect.errors.InvalidArgument` consistently.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

This branch was successfully deployed

1 active deployment
Preview – developer-docs — 01ff2d33 Deployed Oct 16, 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.

2 participants