Repository navigation
feat: generic instantiation expressions (let f = foo<int>) - #3640
Conversation
Reference a generic callable with explicit type args as a value (not a
call), typed as the specialized function. Ports typescript-go's
canFollowTypeArgumentsInExpression to disambiguate `<...>` in value
position; carries args through AST (Expr::GenericApply) and TIR
(specialize + clear generic_params so `f("s")` is a type error). Type
args are erased at runtime for now.
- Enforce BEP-044 generic bounds on instantiation values (not just calls).
- Lower non-PATH receivers (`(b).foo<int>`, `arr[0].foo<int>`) through the
chain dispatch instead of silently dropping to Expr::Missing.
Known gap (fixed next): runtime type-arg erasure makes type-reifying
natives fail through a value (`let f = baml.json.from_string<User>; f(s)`
-> "missing type argument"); `reflect.type_of<T>` yields "unknown".
…nterned)
`let f = foo<int>` now lowers to a pooled, interned `Object::GenericFunction`
{ function: GlobalIndex, type_args } instead of erasing the type args. Calling
the value resolves the base function via its global slot and seeds
`frame.type_args` (for bytecode bodies / reflect.type_of<T>) and
`pending_call_type_args` (for type-reifying natives), so an indirect call
behaves exactly like the direct call `foo<int>(...)`.
- Fixes the runtime failure for type-reifying natives through a value
(`let f = baml.json.from_string<User>; f(s)`) and makes
`reflect.type_of<T>` through a value return the bound type, not "unknown".
- Interning by (function, type_args) over the shared object pool gives
pointer-stable identity: `foo<int> === foo<int>`, distinct from `foo<string>`.
- Param-dependent (`foo<T>`) and exotic bases fall back to erasure.
- GenericFunction stores the base by GlobalIndex (not HeapPtr), so it is a
compile-time-poolable, GC-leaf value.
Also corrects two std-lib generic calls on indexed receivers
(`self.sub_clients[idx].execute_oneshot<T>(...)`) that previously lowered to
`<missing>`.
Two gaps found by adversarial review of the GenericFunction value type: - deep_equals had no (GenericFunction, GenericFunction) arm, so it fell to the `_ => false` catch-all. Interned values still compared equal via the pointer-identity fast path, but a non-pooled copy (e.g. from baml.deep_copy) wrongly compared unequal. Add a structural arm (function + type_args). - set_entry_point / set_entry_point_with_type_args panicked on a GenericFunction entry (catch-all `other`). Not reachable from BAML today (spawned values are called via CallIndirect), but add defensive arms that seed type_args and resolve the inner function's kind via its global slot.
`let g = foo<T>` where `T` is the enclosing function's runtime type param cannot be a compile-time-pooled constant, so previously it erased the type arg — `reflect.type_of<T>` (and type-reifying natives) through such a value returned "unknown"/wrong. Add a runtime-materialization path: a new `Rvalue::MakeGenericFunction` (carrying type-arg templates) and a `MakeGenericFunction` bytecode opcode that pops `ntypeargs` `Object::Type` values (resolved against the current frame's type_args by preceding LoadType instructions) and builds an `Object::GenericFunction`. Lowering now splits: - fully-concrete args -> pooled, interned `Constant::GenericFunction` (pointer-stable identity, unchanged) - param-dependent args -> `MakeGenericFunction` (fresh per eval, correct T) - exotic bases -> erase (unchanged) Now `let g = describe<T>; g()` inside `fn<T>` resolves T correctly.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughImplements explicit generic instantiation as value expressions ( ChangesGeneric instantiation expressions
Sequence DiagramsequenceDiagram
participant Parser
participant TypeInference
participant MIRLower
participant Emitter
participant VM
Parser->>Parser: Disambiguate foo<int> as GENERIC_ARGS
Parser->>TypeInference: Expr::GenericApply { base, type_args }
TypeInference->>TypeInference: Infer base type, validate generic arity
TypeInference->>TypeInference: Bind type args to params, enforce bounds
TypeInference->>MIRLower: Concrete Ty::Function with bindings
MIRLower->>MIRLower: Resolve base to ItemRef
alt All type args concrete
MIRLower->>Emitter: Constant::GenericFunction
else Any type arg frame-dependent
MIRLower->>Emitter: Rvalue::MakeGenericFunction
else Cannot resolve to ItemRef
MIRLower->>Emitter: Rvalue::MakeGenericFunctionFromValue
end
Emitter->>VM: Instruction::MakeGenericFunction or MakeGenericFunctionFromValue
VM->>VM: Pop ntypeargs Type objects
VM->>VM: Construct Object::GenericFunction
VM->>VM: Push specialized value to stack
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Cover `name.space.fun<string>` — a generic function in a nested (multi-segment) namespace, referenced by its fully-qualified name as a value. Adds a nested `ns_qualified` namespace under the instantiation-expr fixture and tests both the concrete-pooled reflect-through-value path and a plain identity through the qualified value. Confirms the multi-segment path resolves to the right ItemRef.
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
baml_language/crates/bex_vm_types/src/types.rs (1)
1696-1705:⚠️ Potential issue | 🟠 Major | ⚡ Quick winFix Borsh wire-format compatibility: append
ObjectWire::GenericFunction
borshenums use the variant ordinal (declaration order) as the tag when explicit discriminants aren’t provided (default behavior). SinceObjectWire::GenericFunctionis currently inserted beforeCell, it renumbers every laterObjectWirevariant, breaking backward compatibility for previously serialized payloads.Suggested fix
enum ObjectWire { Function(Box<Function>), Class(Box<Class>), Instance(Instance), Enum(Box<Enum>), Variant(Variant), Closure(Closure), BoundMethod(BoundMethod), - GenericFunction(GenericFunction), Cell(Cell), String(String), // `Arc<BigInt>` isn't directly Borsh-derivable (and we want the same // wire form `ConstValue::Bigint` uses), so the proxy holds the inner // `BigInt` by value. `num_bigint::BigInt` has no native borsh impl, so @@ Float(f64), Future(Future), UnscheduledFuture(UnscheduledFuture), Type(Box<baml_type::Ty>), + GenericFunction(GenericFunction), }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/bex_vm_types/src/types.rs` around lines 1696 - 1705, The ObjectWire enum changed variant ordering by placing GenericFunction before Cell, which shifts discriminant ordinals and breaks borsh compatibility; update the enum declaration (ObjectWire) so that the GenericFunction variant is appended after Cell (i.e., reorder/remove the current GenericFunction entry and add GenericFunction at the end of the variant list so all existing subsequent variants keep their original ordinals).
🧹 Nitpick comments (6)
baml_language/crates/baml_compiler2_mir/src/optimize.rs (2)
1356-1358: 💤 Low valueConsider updating comment to cover both variants.
The comment specifically mentions
LoadTypebut the match arm now handles bothLoadTypeandMakeGenericFunction. Consider a more generic comment like "No local operands — nothing to check."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/baml_compiler2_mir/src/optimize.rs` around lines 1356 - 1358, The comment in the match arm for crate::Rvalue::LoadType(_) | crate::Rvalue::MakeGenericFunction { .. } only mentions LoadType even though both variants are handled; update the comment to reflect both patterns (e.g., "No local operands — nothing to check.") next to that match arm so it accurately documents that neither LoadType nor MakeGenericFunction require local-operand checks.
392-394: 💤 Low valueConsider updating comment to cover both variants.
The comment mentions only
LoadTypebut the match arm now handles bothLoadTypeandMakeGenericFunction. Consider a more generic comment like "No local operands to scan."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/baml_compiler2_mir/src/optimize.rs` around lines 392 - 394, The comment for the match arm handling crate::Rvalue::LoadType(_) | crate::Rvalue::MakeGenericFunction { .. } is inaccurate — it only mentions LoadType; change it to a generic note such as "No local operands to scan" (or similar) so it correctly describes both Rvalue::LoadType and Rvalue::MakeGenericFunction; update the comment adjacent to that match arm in optimize.rs accordingly.baml_language/crates/bex_vm_types/src/bytecode.rs (1)
2393-2618: 💤 Low valueConsider adding a unit test for
MakeGenericFunctionencoding.The
compact_testsmodule thoroughly tests other instruction encodings but lacks coverage for the newMakeGenericFunctionopcode. A test would verify the 7-byte encoding (opcode + u32 function + u16 ntypeargs) and round-trip consistency.🧪 Suggested test
#[test] fn encode_make_generic_function() { let bc = make_bytecode( vec![Instruction::MakeGenericFunction { function: GlobalIndex::from_raw(42), ntypeargs: 3, }], vec![], ); let compact = bc.lower_to_compact(); // MakeGenericFunction = 7 bytes: opcode + u32 function + u16 ntypeargs assert_eq!(compact.code.len(), 7); assert_eq!(compact.code[0], OpCode::MakeGenericFunction as u8); let function = u32::from_le_bytes([ compact.code[1], compact.code[2], compact.code[3], compact.code[4], ]); assert_eq!(function, 42); let ntypeargs = u16::from_le_bytes([compact.code[5], compact.code[6]]); assert_eq!(ntypeargs, 3); }As per coding guidelines:
**/*.rs: Prefer writing Rust unit tests over integration tests where possible.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/bex_vm_types/src/bytecode.rs` around lines 2393 - 2618, The test suite misses coverage for the new MakeGenericFunction encoding; add a unit test in the compact_tests module using the existing helper make_bytecode that creates an Instruction::MakeGenericFunction { function: GlobalIndex::from_raw(42), ntypeargs: 3 }, call lower_to_compact(), and assert the compact.code length equals 7, compact.code[0] == OpCode::MakeGenericFunction as u8, the 4 bytes at compact.code[1..5] decode to 42 (u32 little-endian), and the 2 bytes at compact.code[5..7] decode to 3 (u16 little-endian) to verify the opcode + u32 function + u16 ntypeargs encoding and round-trip consistency.baml_language/crates/tools_onionskin/src/compiler.rs (1)
5251-5251: ⚡ Quick winConsider showing more information for GenericFunction objects.
The current display
<generic_function>is less informative than similar object types. For better debugging experience in the VM Runner, consider showing at least the function index, similar to howObject::Function(f)shows<fn {}>with the function name at line 5242.🔍 Suggested enhancement for debugging output
If
GenericFunctionhas an accessible function index field:- Object::GenericFunction(_) => "<generic_function>".to_string(), + Object::GenericFunction(gf) => format!("<generic_function fn_idx={}>", gf.function_index),Or if you want to show the function name (requires looking up in the program):
- Object::GenericFunction(_) => "<generic_function>".to_string(), + Object::GenericFunction(gf) => { + // Look up function name from program if available + format!("<generic_function {}>", /* function name from gf.function_index */) + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/tools_onionskin/src/compiler.rs` at line 5251, Replace the terse "<generic_function>" display for Object::GenericFunction(_) with a more informative string like the Object::Function(f) arm: extract the function index/name from the GenericFunction variant (e.g., use its tuple/field (like .0 or .index) or look up the function name in the program's function table) and format it as "<generic_fn {index}>" or "<generic_fn {name}>" for debugging; if no index/name is available, include at least the raw variant payload in the formatted string to avoid the uninformative constant.baml_language/crates/bex_heap/src/gc.rs (1)
396-405: ⚡ Quick winAdd a unit test that roots
Object::GenericFunctionthrough GC.This PR updates three GC leaf-match sites for the new object kind, but there is no direct test here proving a rooted
GenericFunctionsurvives collection unchanged and contributes no outgoing references. A small unit test would pin that contract and make the next missed arm obvious. Please also runcargo test --libbefore merge. As per coding guidelines, "Prefer writing Rust unit tests over integration tests where possible" and "Always runcargo test --libif you changed any Rust code".Also applies to: 545-546, 806-806
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/bex_heap/src/gc.rs` around lines 396 - 405, Add a unit test that constructs and roots an Object::GenericFunction, runs a GC collection, and asserts the GenericFunction instance survives and that no outgoing references are reported; specifically create a GenericFunction value (using the same constructor/allocator used elsewhere in tests), root it via the heap/rooting APIs used in gc.rs, call the GC entrypoint (e.g. the collect/mark-sweep function exercised by other tests), then assert the root still points to the same GenericFunction and that tracing/edge-count utilities show no outgoing pointers; add the test near other gc tests in the same crate, include cases covering the other mentioned sites (lines ~545 and ~806) if applicable, and run cargo test --lib before committing.baml_language/crates/baml_compiler_parser/src/parser.rs (1)
8472-8562: ⚡ Quick winAdd explicit newline-focused disambiguation tests.
The new logic is newline-sensitive, but the added cases don’t directly lock newline behavior (for example:
foo<int>\n.bar,foo<int>\n;, and comparison-style multiline forms). Adding those would harden this path against regressions.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/baml_compiler_parser/src/parser.rs` around lines 8472 - 8562, The new parser logic is sensitive to newlines but the tests don't cover newline disambiguation; add explicit tests that assert behavior when a closing `>` is followed by a newline then a token—specifically add cases like `foo<int>\n.bar` (should still produce GENERIC_ARGS, update or add a test near bare_generic_instantiation_followed_by_member_access), `foo<int>\n;` (should produce GENERIC_ARGS, add near bare_generic_instantiation_in_value_position), and multiline comparison forms like `a < b\n> c` or `a < b\n` (should NOT produce GENERIC_ARGS, add near comparison_chain_is_not_generic_args/simple_comparison_is_not_generic_args); in each new test call parse_source, assert_no_errors, and assert has_generic_args(...) or !has_generic_args(...) as appropriate to lock newline-sensitive behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@baml_language/crates/baml_compiler2_ast/src/ast.rs`:
- Around line 623-632: Add a match arm in ExprBody::display_expr_inner to handle
the Expr::GenericApply variant so diagnostics print the actual `foo<int>` shape
instead of `"..."`; when matching GenericApply { base, type_args } call the
existing expression-display routine for the base (the same helper used for other
Expr variants) and render the type_args by mapping each TypeExpr through the
existing type-expression display helper and joining with ", ", then format the
final string as "{base}<arg1, arg2, ...>" (ensuring empty type_args cannot occur
per invariant).
In `@baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs`:
- Around line 2029-2039: The code marks only the call-site GENERIC_ARGS node
(callee_generic_args) as consumed, but when lowering chains like
Container<int>.method<U>(x) the receiver's <int> remains unconsumed and can be
wrapped into Expr::GenericApply; update lowering to detect and also
mark/suppress receiver generic-args that are shadowed by a subsequent
method-level generic args before calling lower_expr_in_chain. Concretely, after
computing callee_generic_args (and before lower_expr_in_chain) use
find_callee_generic_args / lower_generic_args_node results to locate and insert
the receiver's GENERIC_ARGS text_range() into self.consumed_generic_args (or
otherwise suppress it) whenever a method-level generic arg is present so that
lower_expr_in_chain will not wrap the receiver path into Expr::GenericApply.
In `@baml_language/crates/baml_compiler2_mir/src/lower.rs`:
- Around line 5561-5650: The bug: lower_generic_apply currently erases type args
when try_resolve_generic_apply_base(base) returns None (e.g., local/captured
generic functions or bound method values), causing g<int>/obj.method<int> to
lower to an unspecialized callable; fix it by handling the "non-ItemRef" case
instead of just calling self.lower_expr(base, dest): lower the base into an
Operand (use self.lower_expr_to_operand / lower_expr into a temp/Place), then
emit a runtime specialization Rvalue that builds a specialized callable from
that value and the templates (e.g., add and use a new Rvalue variant like
MakeGenericFunctionFromValue { value: Operand, type_arg_templates: templates }
and assign it to dest inside lower_generic_apply), or alternatively make the
compiler reject such bases earlier; update lower_generic_apply,
try_resolve_generic_apply_base usage, and the builder/Rvalue enum to support the
runtime-specialize-from-value path.
In `@baml_language/crates/baml_compiler2_tir/src/builder.rs`:
- Around line 1580-1594: The current substitution creates a Ty::Function with
generic_params cleared, which turns ambient type variables from the enclosing
scope into fresh inference targets and allows calls like let f = foo<T>; f(1) to
re-infer T incorrectly; fix by preserving metadata that marks this signature as
an already-instantiated function (do not erase the fact it came from a generic
declaration) or by restricting call-site inference: either (A) carry through the
original function's generic parameter info instead of setting generic_params:
Vec::new() when building the Ty::Function in this substitution (so the
substituted signature still records which generics belong to the callee), or (B)
modify check_call_inner to only perform inference over the callee’s declared
generic parameters (the original generic_params from the function declaration)
rather than every Ty::TypeVar appearing in the substituted signature; locate the
construction in builder.rs around Ty::Function and the substitution call
crate::generics::substitute_ty, and adjust the data kept or the inference logic
in check_call_inner accordingly.
- Around line 1497-1517: The code reports TypeIsNotGeneric for generic
application even when the base already failed to type-check; update the
generic-application path in builder.rs to short-circuit when base_ty is
Ty::Unknown or Ty::Error (the result of self.infer_expr(base, body)) and simply
return that Unknown/Error instead of emitting a new
TirTypeError::TypeIsNotGeneric for the same expr_id; modify the match that
destructures base_ty (the Ty::Function else branch) to first check for
Unknown/Error and propagate it (returning the original Ty::Unknown/Ty::Error)
before reporting TypeIsNotGeneric (use Self::generic_apply_base_name for the
message as before).
In
`@baml_language/crates/baml_tests/baml_src/ns_instantiation_expr/instantiation_expr.baml`:
- Around line 21-31: The test identity_same_specialization uses
baml.deep_equals(a, b) which only checks structural equality and doesn't prove
pointer-stable interning; replace that call with an identity-sensitive check
(e.g. use a pointer-equality helper such as baml.ptr_eq(a, b) or the language's
identity operator) so the test truly asserts the two values are the same pooled
object, or alternatively move the identity check into a Rust-side harness that
inspects the runtime object identity for identity_same_specialization and update
the test same_specialization_is_identical to call that harness.
In `@baml_language/crates/bex_vm/src/vm.rs`:
- Around line 1417-1423: The entry point handling needs to normalize
GenericFunction to its specialized Function before storing/dispatching: update
set_entry_point_with_type_args (and any callers that accept a raw function
object) to resolve Object::GenericFunction -> its concrete
Function/specialization (using the same logic as get_object/set_entry_point did
for type_args) so the VM never retains a GenericFunction pointer as an entry
point; ensure push_trampoline_frame and native/sysop entry paths look up the
callee by the normalized concrete Function pointer (not the GenericFunction),
and remove the panic path by matching Object::Function after normalization.
Reference: get_object, set_entry_point_with_type_args, set_entry_point,
Object::GenericFunction, push_trampoline_frame.
---
Outside diff comments:
In `@baml_language/crates/bex_vm_types/src/types.rs`:
- Around line 1696-1705: The ObjectWire enum changed variant ordering by placing
GenericFunction before Cell, which shifts discriminant ordinals and breaks borsh
compatibility; update the enum declaration (ObjectWire) so that the
GenericFunction variant is appended after Cell (i.e., reorder/remove the current
GenericFunction entry and add GenericFunction at the end of the variant list so
all existing subsequent variants keep their original ordinals).
---
Nitpick comments:
In `@baml_language/crates/baml_compiler_parser/src/parser.rs`:
- Around line 8472-8562: The new parser logic is sensitive to newlines but the
tests don't cover newline disambiguation; add explicit tests that assert
behavior when a closing `>` is followed by a newline then a token—specifically
add cases like `foo<int>\n.bar` (should still produce GENERIC_ARGS, update or
add a test near bare_generic_instantiation_followed_by_member_access),
`foo<int>\n;` (should produce GENERIC_ARGS, add near
bare_generic_instantiation_in_value_position), and multiline comparison forms
like `a < b\n> c` or `a < b\n` (should NOT produce GENERIC_ARGS, add near
comparison_chain_is_not_generic_args/simple_comparison_is_not_generic_args); in
each new test call parse_source, assert_no_errors, and assert
has_generic_args(...) or !has_generic_args(...) as appropriate to lock
newline-sensitive behavior.
In `@baml_language/crates/baml_compiler2_mir/src/optimize.rs`:
- Around line 1356-1358: The comment in the match arm for
crate::Rvalue::LoadType(_) | crate::Rvalue::MakeGenericFunction { .. } only
mentions LoadType even though both variants are handled; update the comment to
reflect both patterns (e.g., "No local operands — nothing to check.") next to
that match arm so it accurately documents that neither LoadType nor
MakeGenericFunction require local-operand checks.
- Around line 392-394: The comment for the match arm handling
crate::Rvalue::LoadType(_) | crate::Rvalue::MakeGenericFunction { .. } is
inaccurate — it only mentions LoadType; change it to a generic note such as "No
local operands to scan" (or similar) so it correctly describes both
Rvalue::LoadType and Rvalue::MakeGenericFunction; update the comment adjacent to
that match arm in optimize.rs accordingly.
In `@baml_language/crates/bex_heap/src/gc.rs`:
- Around line 396-405: Add a unit test that constructs and roots an
Object::GenericFunction, runs a GC collection, and asserts the GenericFunction
instance survives and that no outgoing references are reported; specifically
create a GenericFunction value (using the same constructor/allocator used
elsewhere in tests), root it via the heap/rooting APIs used in gc.rs, call the
GC entrypoint (e.g. the collect/mark-sweep function exercised by other tests),
then assert the root still points to the same GenericFunction and that
tracing/edge-count utilities show no outgoing pointers; add the test near other
gc tests in the same crate, include cases covering the other mentioned sites
(lines ~545 and ~806) if applicable, and run cargo test --lib before committing.
In `@baml_language/crates/bex_vm_types/src/bytecode.rs`:
- Around line 2393-2618: The test suite misses coverage for the new
MakeGenericFunction encoding; add a unit test in the compact_tests module using
the existing helper make_bytecode that creates an
Instruction::MakeGenericFunction { function: GlobalIndex::from_raw(42),
ntypeargs: 3 }, call lower_to_compact(), and assert the compact.code length
equals 7, compact.code[0] == OpCode::MakeGenericFunction as u8, the 4 bytes at
compact.code[1..5] decode to 42 (u32 little-endian), and the 2 bytes at
compact.code[5..7] decode to 3 (u16 little-endian) to verify the opcode + u32
function + u16 ntypeargs encoding and round-trip consistency.
In `@baml_language/crates/tools_onionskin/src/compiler.rs`:
- Line 5251: Replace the terse "<generic_function>" display for
Object::GenericFunction(_) with a more informative string like the
Object::Function(f) arm: extract the function index/name from the
GenericFunction variant (e.g., use its tuple/field (like .0 or .index) or look
up the function name in the program's function table) and format it as
"<generic_fn {index}>" or "<generic_fn {name}>" for debugging; if no index/name
is available, include at least the raw variant payload in the formatted string
to avoid the uninformative constant.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 99d69b1e-0e29-4ec9-8b9b-af40a0c0409f
⛔ Files ignored due to path filters (8)
baml_language/crates/baml_tests/snapshots/baml_src/_root.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/instantiation_expr.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____03_hir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_tir.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____06_codegen.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded_unoptimized.snapis excluded by!**/*.snap
📒 Files selected for processing (31)
baml_language/crates/baml_compiler2_ast/src/ast.rsbaml_language/crates/baml_compiler2_ast/src/lower_expr_body.rsbaml_language/crates/baml_compiler2_emit/src/analysis.rsbaml_language/crates/baml_compiler2_emit/src/emit.rsbaml_language/crates/baml_compiler2_emit/src/pull_semantics.rsbaml_language/crates/baml_compiler2_emit/src/stack_carry.rsbaml_language/crates/baml_compiler2_hir/src/builder.rsbaml_language/crates/baml_compiler2_mir/src/ir.rsbaml_language/crates/baml_compiler2_mir/src/lower.rsbaml_language/crates/baml_compiler2_mir/src/optimize.rsbaml_language/crates/baml_compiler2_mir/src/pretty.rsbaml_language/crates/baml_compiler2_tir/src/builder.rsbaml_language/crates/baml_compiler2_tir/src/throws_analysis.rsbaml_language/crates/baml_compiler_parser/src/parser.rsbaml_language/crates/baml_tests/baml_src/ns_instantiation_expr/instantiation_expr.bamlbaml_language/crates/baml_tests/src/compiler2_tir/explicit_type_args.rsbaml_language/crates/baml_tests/src/compiler2_tir/mod.rsbaml_language/crates/baml_tests/tests/interfaces.rsbaml_language/crates/bex_engine/src/conversion.rsbaml_language/crates/bex_heap/src/accessor.rsbaml_language/crates/bex_heap/src/gc.rsbaml_language/crates/bex_heap/src/heap_debugger/real.rsbaml_language/crates/bex_vm/src/debug.rsbaml_language/crates/bex_vm/src/package_baml/json.rsbaml_language/crates/bex_vm/src/package_baml/root.rsbaml_language/crates/bex_vm/src/package_baml/unstable.rsbaml_language/crates/bex_vm/src/vm.rsbaml_language/crates/bex_vm_types/src/bytecode.rsbaml_language/crates/bex_vm_types/src/lib.rsbaml_language/crates/bex_vm_types/src/types.rsbaml_language/crates/tools_onionskin/src/compiler.rs
Binary size checks passed✅ 7 passed
Generated by |
- ast.rs: render Expr::GenericApply in display_expr_inner (was "...") so diagnostics show the actual foo<int> shape. - lower_expr_body.rs: a call now marks ALL GENERIC_ARGS in the callee subtree as consumed, not just the one find_callee_generic_args returns. Fixes Container<int>.method<U>(x) wrongly wrapping the receiver <int> into an Expr::GenericApply (which surfaced as a spurious "not generic" error); restores the pre-existing class-type-arg behavior. - builder.rs: infer_generic_apply short-circuits on an Unknown/Error base instead of cascading a second TypeIsNotGeneric diagnostic. - vm.rs: normalize a GenericFunction entry point to its concrete inner function + stored type args before dispatch, so the native/sysop trampoline and bytecode frame never carry a GenericFunction pointer.
Addresses two more CodeRabbit comments on generic instantiation exprs: #3 (lower.rs): a `GenericApply` whose base is not a compile-time function reference (a local/captured generic function value, e.g. `let g = foo; let h = g<int>`) previously fell back to lowering the base with its type args ERASED — reintroducing the type/runtime mismatch the feature removes. Now the runtime value is specialized: a new `Rvalue::MakeGenericFunctionFromValue` + `MakeGenericFunctionFromValue` opcode wraps the evaluated callable in a Closure carrying the (frame-resolved) type args, reusing the closure call path that already seeds `frame.type_args`. So `g<int>` runs end-to-end and a type-reifying body (`reflect.type_of<T>`) resolves T correctly, including the param-dependent `g<T>` case. No new GC-traced object: the only reachable non-ItemRef base is a local holding a bare function (static methods take the pooled ItemRef path, lambdas/GenericFunction bases are rejected by TIR). #6 (instantiation_interning.rs): `baml.deep_equals` is structural, so the fixture couldn't prove the pooled, pointer-stable identity (`foo<int> === foo<int>`); BAML has no in-language identity operator. Added a Rust harness that inspects the compiled object pool: two `identity<Marker>` references intern to one `Object::GenericFunction`, distinct from `identity<Other>`. Full workspace suite green (5006 passed).
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
baml_language/crates/baml_tests/tests/instantiation_interning.rs (1)
40-89: 💤 Low valueConsider placing this as a unit test rather than an integration test.
baml_tests::engine::compile_sourceis a public helper, so this coverage could live as a#[cfg(test)]module inside thebaml_testscratesrc/rather than undertests/. As per coding guidelines: "Prefer writing Rust unit tests over integration tests where possible".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/baml_tests/tests/instantiation_interning.rs` around lines 40 - 89, The test concrete_instantiation_values_are_interned_and_distinct should be converted from an integration test into a unit test inside the baml_tests crate by moving its body into a #[cfg(test)] mod in the crate's src (e.g., create a tests module in src/lib.rs or src/main.rs), keep the #[test] on concrete_instantiation_values_are_interned_and_distinct and continue to call compile_source and generic_function_indices_mentioning as before, ensuring any helpers used are public (compile_source is already public) or made accessible to the crate test module; remove the file under tests/ and run cargo test to verify visibility and compilation.baml_language/crates/baml_compiler2_emit/src/stack_carry.rs (1)
1283-1291: ⚡ Quick winAdd a unit test for the new stack effect.
make_generic_function_from_valueis now part of the simulator contract, but the test module does not pin thentypeargs + callable -> 1 resultbehavior yet. A small unit test here would catch emitter/simulator drift early. Please also runcargo test --liblocally after adding it.Suggested unit test
+ #[test] + fn make_generic_function_from_value_stack_effect_pops_callable_and_type_args() { + let mut sim = StackCarrySim { + depth: Some(3), + used: false, + }; + let classifications = HashMap::new(); + let def_use = HashMap::new(); + let mut sink = StackCarryPullSink { + sim: &mut sim, + carried_local: Local(0), + classifications: &classifications, + def_use: &def_use, + }; + + sink.make_generic_function_from_value(2).unwrap(); + + assert_eq!(sim.depth, Some(1)); + }As per coding guidelines,
**/*.rs: Prefer writing Rust unit tests over integration tests where possible; Always runcargo test --libif you changed any Rust code.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/baml_compiler2_emit/src/stack_carry.rs` around lines 1283 - 1291, Add a unit test that exercises make_generic_function_from_value to assert the simulator stack effect: given a simulated stack with a callable plus N type-arg values, calling make_generic_function_from_value(ntypeargs) consumes ntypeargs+1 items (verify sim.pop_n was successful or stack shrunk) and results in a single pushed result (verify stack size increased by 1 after push or top is present); place the test in the same Rust module's #[cfg(test)] mod tests so it is a unit test, invoke make_generic_function_from_value with a few ntypeargs values (e.g., 0 and >0) and run cargo test --lib to confirm it passes.baml_language/crates/bex_vm_types/src/bytecode.rs (1)
698-709: ⚡ Quick winAdd a focused compact-opcode test for
MakeGenericFunctionFromValue.This opcode now threads through size calculation, decoding, display, and byte emission, but
compact_testsstill doesn't lock that round-trip down. One unit test here would make future opcode-table edits much safer. Please also runcargo test --liblocally after adding it.Suggested unit test
+ #[test] + fn encode_make_generic_function_from_value() { + let bc = make_bytecode( + vec![Instruction::MakeGenericFunctionFromValue { ntypeargs: 2 }], + vec![], + ); + let compact = bc.lower_to_compact(); + + assert_eq!(compact.code.len(), 3); + assert_eq!(compact.code[0], OpCode::MakeGenericFunctionFromValue as u8); + assert_eq!(u16::from_le_bytes([compact.code[1], compact.code[2]]), 2); + assert_eq!( + OpCode::try_from(compact.code[0]), + Ok(OpCode::MakeGenericFunctionFromValue) + ); + assert_eq!( + Instruction::MakeGenericFunctionFromValue { ntypeargs: 2 }.to_string(), + "MAKE_GENERIC_FUNCTION_FROM_VALUE ntypeargs=2" + ); + }As per coding guidelines,
**/*.rs: Prefer writing Rust unit tests over integration tests where possible; Always runcargo test --libif you changed any Rust code.Also applies to: 915-916, 1039-1040, 1174-1176, 1300-1300, 1556-1558, 2094-2097, 2410-2412
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/bex_vm_types/src/bytecode.rs` around lines 698 - 709, Add a focused unit test that covers the compact-opcode round-trip for the MakeGenericFunctionFromValue opcode: construct an instruction with a representative ntypeargs (e.g. 0 and a non-zero like 3), assert its size/encoding, run it through the existing encode/decode/display/emit helpers used by compact_tests to ensure the decoded opcode equals the original and that display/byte-emission are stable; place the test as a Rust #[cfg(test)] unit test near the other compact_tests in the same module (bex_vm_types::bytecode) and run cargo test --lib to verify.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@baml_language/crates/bex_vm/src/vm.rs`:
- Around line 5088-5108: When specializing a closure value, preserve any
existing captured_type_args from the original Closure (the c.captured_type_args
when matching Object::Closure(c)) instead of discarding them; create a combined
vector by taking c.captured_type_args, converting to a Vec, appending the
incoming type_args in the same order used by the normal call path, then set
Closure { ..., captured_type_args: combined.into_boxed_slice() } so the new
closure wrapper retains outer/class generic context.
---
Nitpick comments:
In `@baml_language/crates/baml_compiler2_emit/src/stack_carry.rs`:
- Around line 1283-1291: Add a unit test that exercises
make_generic_function_from_value to assert the simulator stack effect: given a
simulated stack with a callable plus N type-arg values, calling
make_generic_function_from_value(ntypeargs) consumes ntypeargs+1 items (verify
sim.pop_n was successful or stack shrunk) and results in a single pushed result
(verify stack size increased by 1 after push or top is present); place the test
in the same Rust module's #[cfg(test)] mod tests so it is a unit test, invoke
make_generic_function_from_value with a few ntypeargs values (e.g., 0 and >0)
and run cargo test --lib to confirm it passes.
In `@baml_language/crates/baml_tests/tests/instantiation_interning.rs`:
- Around line 40-89: The test
concrete_instantiation_values_are_interned_and_distinct should be converted from
an integration test into a unit test inside the baml_tests crate by moving its
body into a #[cfg(test)] mod in the crate's src (e.g., create a tests module in
src/lib.rs or src/main.rs), keep the #[test] on
concrete_instantiation_values_are_interned_and_distinct and continue to call
compile_source and generic_function_indices_mentioning as before, ensuring any
helpers used are public (compile_source is already public) or made accessible to
the crate test module; remove the file under tests/ and run cargo test to verify
visibility and compilation.
In `@baml_language/crates/bex_vm_types/src/bytecode.rs`:
- Around line 698-709: Add a focused unit test that covers the compact-opcode
round-trip for the MakeGenericFunctionFromValue opcode: construct an instruction
with a representative ntypeargs (e.g. 0 and a non-zero like 3), assert its
size/encoding, run it through the existing encode/decode/display/emit helpers
used by compact_tests to ensure the decoded opcode equals the original and that
display/byte-emission are stable; place the test as a Rust #[cfg(test)] unit
test near the other compact_tests in the same module (bex_vm_types::bytecode)
and run cargo test --lib to verify.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 75fd2cf5-886d-4efb-8d8b-99f0d3b4a3e1
⛔ Files ignored due to path filters (1)
baml_language/crates/baml_tests/snapshots/baml_src/instantiation_expr.snapis excluded by!**/*.snap
📒 Files selected for processing (13)
baml_language/crates/baml_compiler2_emit/src/analysis.rsbaml_language/crates/baml_compiler2_emit/src/emit.rsbaml_language/crates/baml_compiler2_emit/src/pull_semantics.rsbaml_language/crates/baml_compiler2_emit/src/stack_carry.rsbaml_language/crates/baml_compiler2_mir/src/ir.rsbaml_language/crates/baml_compiler2_mir/src/lower.rsbaml_language/crates/baml_compiler2_mir/src/optimize.rsbaml_language/crates/baml_compiler2_mir/src/pretty.rsbaml_language/crates/baml_tests/baml_src/ns_instantiation_expr/instantiation_expr.bamlbaml_language/crates/baml_tests/tests/instantiation_interning.rsbaml_language/crates/bex_vm/src/debug.rsbaml_language/crates/bex_vm/src/vm.rsbaml_language/crates/bex_vm_types/src/bytecode.rs
🚧 Files skipped from review as they are similar to previous changes (5)
- baml_language/crates/bex_vm/src/debug.rs
- baml_language/crates/baml_compiler2_emit/src/analysis.rs
- baml_language/crates/baml_compiler2_mir/src/pretty.rs
- baml_language/crates/baml_compiler2_mir/src/lower.rs
- baml_language/crates/baml_compiler2_emit/src/emit.rs
…#5) A param-dependent instantiation value (`let f = foo<T>; f(1)` inside a generic body) has type `(T) -> T` where `T` is the enclosing scope's rigid type param. Calling it with a mismatched concrete argument was silently accepted: the call checker treated the `T` in the value's signature as an inference variable and bound `T = int`, collapsing `foo<T>` to `foo<int>`. Fix: when the callee is a function *value* (a bare local/param holding a function, not a direct reference to a function/method declaration), restrict call-site inference to the value's own inferable type vars — its declared `generic_params` plus synthetic effect-polymorphism params — and treat everything else (rigid ambient vars) as opaque, matched structurally. So `f(1)` now errors (`int` is not a subtype of a rigid `T`), while: - valid forwarding `f(y)` with `y : T` still type-checks, - an uninstantiated value `let g = foo; g(5)` still infers `T = int`, - higher-order effect inference (`let run = fwd; run(cb)`) still binds the callback's `throws` effect param. This is gated on value calls specifically: a *declaration* callee (`arr.map`, `wrap(42)`, `StreamCache.new`) reaches the checker with its `generic_params` cleared by receiver/class substitution yet its own params still inferable, so the same restriction would wrongly freeze them — those keep the existing behavior untouched. Adds TIR regression tests pinning both the rejection and the preserved inference. Full workspace suite green (5008 passed).
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@baml_language/crates/baml_compiler2_tir/src/generics.rs`:
- Around line 419-454: contains_inferable_typevar is missing a Ty::Future arm so
type variables inside Future<value, error, ...> are ignored; add a
Ty::Future(value, error, ..) match arm that recurses into the value and error
types (calling contains_inferable_typevar on both) and returns true if either
contains an inferable typevar; also update the sibling contains_typevar
analogously so Future-wrapped typevars are consistently visited during
still_a_hole/value-call validation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 10101439-e0a3-42c8-95d2-cde891f578e1
📒 Files selected for processing (3)
baml_language/crates/baml_compiler2_tir/src/builder.rsbaml_language/crates/baml_compiler2_tir/src/generics.rsbaml_language/crates/baml_tests/src/compiler2_tir/explicit_type_args.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- baml_language/crates/baml_compiler2_tir/src/builder.rs
… (PR review) Two follow-ups from CodeRabbit on the #5 / #3 fixes: - generics.rs: `contains_typevar` and `contains_inferable_typevar` had no `Ty::Future(value, error, _)` arm, so type vars nested under `Future<...>` were invisible — inconsistent with `substitute_ty`, which already recurses into both. On a value call that made a `Future`-wrapped type var look rigid; add the arm to both walkers. - vm.rs (MakeGenericFunctionFromValue): when specializing a callable that is already a Closure, carry over its existing `captured_type_args` (the outer/class generic environment) and append the new instantiation args in call order, instead of dropping them — otherwise a later indirect call would seed an incomplete `frame.type_args`. Full workspace suite green (5008 passed).
`baml_fmt` errored on `(expr)<args>` — e.g. an inline generic-lambda
instantiation `(<T>(x: T) -> T { x })<int>` or `(foo)<int>` — failing the
whole file with `UnexpectedAdditionalElement`. The parser wraps any postfix
`<...>` in a `PATH_EXPR`, but the formatter's `PathExpr` only models a
word/dotted-path base, so a parenthesized (or otherwise non-path) base hit the
catch-all error arm.
Add an `Expression::GenericApply { base, generic_args }` variant for that case.
`Expression::from_cst` routes a `PATH_EXPR` to `PathExpr` when its first child
is a word/path and to `GenericApply` otherwise; printing emits the base
followed by the generic args. Path/qualified-path bases (`foo<int>`,
`a.b.foo<int>`) are unchanged.
All forms format and are idempotent (test added). baml_fmt suite + stdlib
formatter stage green.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@baml_language/crates/baml_fmt/tests/generic_instantiation.rs`:
- Around line 1-42: The tests in generic_instantiation.rs are integration tests
(functions generic_instantiation_forms_format_and_are_idempotent and
paren_base_keeps_generic_args) and must be run with cargo test --test
generic_instantiation rather than cargo test --lib; update any test invocation
(CI config, README, or scripts) that calls cargo test --lib to use cargo test
--test generic_instantiation (or simply cargo test) so these integration tests
are executed correctly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: bdc7256f-8baf-4aae-94e9-3f689db1480d
📒 Files selected for processing (2)
baml_language/crates/baml_fmt/src/ast/expressions.rsbaml_language/crates/baml_fmt/tests/generic_instantiation.rs
CodeRabbit verified with `cargo test --lib`, which skips `tests/` integration tests. baml_fmt's tests are conventionally inline unit tests in lib.rs; move the generic-instantiation formatting tests into the existing `lambda_format_tests` module so they run under `cargo test --lib` (and nextest). No behavior change.
…-expr # Conflicts: # baml_language/crates/baml_compiler2_tir/src/builder.rs # baml_language/crates/baml_compiler2_tir/src/generics.rs
There was a problem hiding this comment.
♻️ Duplicate comments (1)
baml_language/crates/baml_compiler2_tir/src/generics.rs (1)
497-556:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDescend into
Ty::Futureincontains_typevar_where.
contains_typevar_whereis now the value-call deferral/check gate, but unlikecontains_typevarit still treatsFuture<value, error>as a leaf. That makesbuilder.rsLine 2893 miss rigid or still-uninferred vars hidden under either branch and can wrongly treatFuture<T>as fully determined.Suggested fix
pub fn contains_typevar_where(ty: &Ty, pred: &dyn Fn(&Name) -> bool) -> bool { match ty { Ty::TypeVar(name, _) => pred(name), Ty::List(inner, _) | Ty::Optional(inner, _) | Ty::EvolvingList(inner, _) => { contains_typevar_where(inner, pred) } Ty::Map(k, v, _) | Ty::EvolvingMap(k, v, _) => { contains_typevar_where(k, pred) || contains_typevar_where(v, pred) } Ty::Union(tys, _) => tys.iter().any(|t| contains_typevar_where(t, pred)), + Ty::Future(value, error, _) => { + contains_typevar_where(value, pred) || contains_typevar_where(error, pred) + } Ty::Function { generic_params, generic_param_bounds, params, ret,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/baml_compiler2_tir/src/generics.rs` around lines 497 - 556, contains_typevar_where currently treats Ty::Future as a leaf so it misses typevars hidden in the future's value or error branches; update the match in contains_typevar_where to handle Ty::Future(value, error, _) (or whatever tuple form it uses) and recurse into both contained types (i.e., call contains_typevar_where on the value and on the error) so rigid/uninferred vars inside Future<value, error> are detected just like for List/Map/Function/AssociatedTypeProjection.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@baml_language/crates/baml_compiler2_tir/src/generics.rs`:
- Around line 497-556: contains_typevar_where currently treats Ty::Future as a
leaf so it misses typevars hidden in the future's value or error branches;
update the match in contains_typevar_where to handle Ty::Future(value, error, _)
(or whatever tuple form it uses) and recurse into both contained types (i.e.,
call contains_typevar_where on the value and on the error) so rigid/uninferred
vars inside Future<value, error> are detected just like for
List/Map/Function/AssociatedTypeProjection.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 0458bdfb-acb4-4ab6-a8f6-f733c90bde86
⛔ Files ignored due to path filters (2)
baml_language/crates/baml_tests/snapshots/baml_src/_root.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snapis excluded by!**/*.snap
📒 Files selected for processing (5)
baml_language/crates/baml_compiler2_mir/src/lower.rsbaml_language/crates/baml_compiler2_tir/src/builder.rsbaml_language/crates/baml_compiler2_tir/src/generics.rsbaml_language/crates/baml_fmt/src/lib.rsbaml_language/crates/baml_tests/tests/interfaces.rs
🚧 Files skipped from review as they are similar to previous changes (3)
- baml_language/crates/baml_tests/tests/interfaces.rs
- baml_language/crates/baml_compiler2_mir/src/lower.rs
- baml_language/crates/baml_compiler2_tir/src/builder.rs
Summary
Adds generic instantiation expressions — referencing a generic callable with explicit type arguments as a value (not a call):
Previously
foo<int>in value position silently dropped the type args (parsed as a<comparison or lowered to a fully-generic ref), solet f = foo<int>; f("s")was wrongly accepted.Behavior
Plus enforcement and lowering fixes that fall out of this:
PATHreceivers ((b).foo<int>,arr[0].foo<int>,self.subs[i].m<T>(...)) previously lowered to<missing>; now lowered correctly (this fixed two latent std-lib<missing>bugs).Implementation
Front-end
canFollowTypeArgumentsInExpressionto disambiguate<…>in value position vs. a<comparison (reuses the existing balanced->scan).Expr::GenericApply { base, type_args }; lowering capturesGENERIC_ARGS(with a consumed-args marker sofoo<int>(x)doesn't double-count).generic_params, so a later call checks args against the concrete params.Runtime — a generic-function value carries its type args so an indirect call behaves exactly like the direct
foo<int>(...):Object::GenericFunction { function: GlobalIndex, type_args }. Stored by global slot (notHeapPtr), so it's a compile-time-poolable, GC-leaf value.Constant::GenericFunction(dedup by(function, type_args)→ one object → pointer-stable identity).foo<T>whereTis the enclosing frame's type param) → a newMakeGenericFunctionopcode that resolves the type-arg templates against the current frame'stype_argsat runtime.frame.type_args(forreflect.type_of<T>/ bytecode bodies) andpending_call_type_args(for type-reifying natives).Testing
f("s")rejection, arity, bounds, multi-arg).ns_instantiation_exprcovering: end-to-end call, pointer identity, json-native-through-value, reflect-through-value (concrete + param-dependent), bound violation, deep-copy equality.Notes
This is forward-compatible with function mocking: a generic-callable value carries its
(function, type_args)structurally, and the VM seeds those at the call — exactly the key mock dispatch needs.Summary by CodeRabbit
New Features
let f = identity<int>;) so specialized callables can be stored, passed, and invoked later.<...>generic-argument lists from comparison operators in value position.Improvements
Tests