Repository navigation
Add a static type() method to every type - #5272
antoniosarosi merged 11 commits into
Conversation
|
Proposed Proposed new subsection at the end of ### `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. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
⏭️ Performance benchmarks were skippedPerf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to To run them on this PR, do any of the following, then push a commit (or re-run CI):
|
ghost
left a comment
There was a problem hiding this comment.
Waiting for required CodeTurtle file reviews. This is not an approval.
CodeTurtle reviewView report · Head Evaluation incomplete; errors or ownership gaps require attention. 0/5 review requirements satisfied (respecting Any/All rules).
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. |
|
Note Reviews pausedIt 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
Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
ghost
left a comment
There was a problem hiding this comment.
Waiting for required CodeTurtle file reviews. This is not an approval.
ghost
left a comment
There was a problem hiding this comment.
Waiting for required CodeTurtle file reviews. This is not an approval.
ghost
left a comment
There was a problem hiding this comment.
Waiting for required CodeTurtle file reviews. This is not an approval.
Binary size checks failed❌ 2 violations · ✅ 1 passed
Details & how to fixViolations:
Add/update baselines:
[artifacts.bridge_wasm]
file_bytes = 26033179
gzip_bytes = 7638335
[artifacts.packed-program]
file_bytes = 39778952
gzip_bytes = 16215886Generated by |
9306482 to
c8d3d85
Compare
| /// `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 { |
There was a problem hiding this comment.
Can we not put this on baml.Concrete instead of having a second blanket implemented interface?
There was a problem hiding this comment.
I creates a cycle, reflect depends on baml, we need a baml.Type.of equivalent to put it there.
There was a problem hiding this comment.
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()`.
39bb4e4 to
0e1a58d
Compare
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.
"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.
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 -->
Every type now has a static
type()method that returns itsreflect.Type, the short form ofreflect.Type.of<T>(). It comes from a new prelude interface in the reflect stdlib, implemented for every type:Before:
After:
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()andint?.type()do not parse, andT.type()does not resolve).type()never takes over atypethat already exists:To get that, the compiler treats
reflect.Reflectablespecially 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>()toX.type()across the repo where X is a single named type, with a script.Part of the reflection API stack.
TYPE_SYSTEM.mdneeds a maintainer update for the two lookup rules; proposed text is in the first comment.Summary by CodeRabbit
New Features
.type()accessor for declared types, including primitives, classes, enums, and generic types.Bug Fixes
typemembers.