Repository navigation
feat(compiler2): mathematical baml.ops operator interfaces (B-289) - #4042
Conversation
Currently just normal interfaces with no magic operator overloading behavior
- Primitives are special-pathed to bytecode - Other types are run via a driver (TODO: same-concrete-type can emit direct function call). This includes unions: `A | B + C | D` will be valid if all pairs implement `baml.ops.Add` and the output type is the union of their outputs.
Conflict resolution beyond textual merges: - bex_vm ops.rs driver: value_concrete_runtime_ty is gone; use value_concrete_ty + RealizedTy resolver API, baked method fqn pointers, and fallible realize_frame - TIR builder.rs: drop kai's dual-package implements fix (canary's implements_interface already searches all involved packages); route operator Output projection through TypeContext::normalize/project - check.rs: take canary's side (implements validation moved to TIR impl_rules phase-5 conformance) - math.baml: interfaces now 'requires root.Concrete' so the symbolic Self default for Output discharges its bound; associated types are referenced as Self.Output per current syntax
Conflict-resolved to canary's side during the merge; regenerated against
the merged stdlib (baml.ops.{Add..Negate,__union_*} + baml.Concrete)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds interface-based arithmetic dispatch for user-defined types and unions, compiler type inference and lowering support, runtime implementation resolution, native numeric operators, and compile-time/runtime tests. ChangesArithmetic Interface Dispatch
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant BAMLSource
participant TIRBuilder
participant MIRLowerer
participant PackageBamlOps
participant ImplementsResolver
participant NativeOperator
BAMLSource->>TIRBuilder: infer operator interface and Output
TIRBuilder->>MIRLowerer: lower non-primitive operator
MIRLowerer->>PackageBamlOps: call __union_add or __union_neg
PackageBamlOps->>ImplementsResolver: resolve concrete implementation
ImplementsResolver->>NativeOperator: invoke resolved operator
NativeOperator-->>PackageBamlOps: return arithmetic result
PackageBamlOps-->>BAMLSource: forward operator result
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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 |
⏭️ 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):
|
Binary size checks passed✅ 7 passed
Generated by |
…r plumbing - math.baml: delete the commented-out string Add/Multiply impls; string concat is deliberately fast-path only and string + <non-string> stays a type error (user-decided) - bex_vm ops.rs: drop ForwardResult, reuse the existing PassThroughContinuation - MIR lower.rs: extract lower_via_ops_driver as the shared shape of equals_equals/__union_* driver calls; merge the two is_primitive closures into arith_primitive(ty, include_string). Emitted MIR is unchanged (bytecode snapshots pass untouched)
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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_builtins2/baml_std/baml/ns_ops/math.baml`:
- Around line 52-55: Update the Negate interface’s neg method to constrain its
self type to root.Concrete, matching the requirements of the other arithmetic
operator interfaces while keeping neg available only for concrete types.
In `@baml_language/crates/bex_vm/src/package_baml/ops_math.rs`:
- Around line 85-92: Adjust checked_bigint_mul so its size estimate does not
reject products that actually fit within MAX_BIGINT_BITS, including multiplying
a boundary-sized bigint by 1. Preserve rejection for products exceeding the
limit, and add a regression test covering the boundary case.
In `@baml_language/crates/bex_vm/src/package_baml/ops.rs`:
- Around line 223-227: Update the dispatch flow around resolve_implements_rule
to require an exact match between the requested RHS interface arguments and the
selected implementation before invoking it. Prevent fallback to the first
applicable specialization for non-empty iface_args; if no exact match exists,
return unresolved_op(iface, method) so the result is UnresolvedVirtualCall.
🪄 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: b1ea49a3-7dc8-4de9-a9a1-bcb0686c591e
⛔ Files ignored due to path filters (9)
baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_builtin_package_listing.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/operators.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/src/compiler2_tir/snapshots/baml_tests__compiler2_tir__phase5__snapshot_baml_package_items.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 (11)
baml_language/crates/baml_builtins2/baml_std/baml/core.bamlbaml_language/crates/baml_builtins2/baml_std/baml/ns_ops/math.bamlbaml_language/crates/baml_builtins2/src/lib.rsbaml_language/crates/baml_builtins2_codegen/src/extract.rsbaml_language/crates/baml_compiler2_mir/src/lower.rsbaml_language/crates/baml_compiler2_tir/src/builder.rsbaml_language/crates/baml_tests/baml_src/ns_operators/operators.bamlbaml_language/crates/baml_tests/tests/interfaces.rsbaml_language/crates/bex_vm/src/package_baml/mod.rsbaml_language/crates/bex_vm/src/package_baml/ops.rsbaml_language/crates/bex_vm/src/package_baml/ops_math.rs
- operator_operand_members: dedup alternatives by equivalence after literal widening so (1 | 2) + x resolves the int pair once - ops_qtn(name) replaces the two inline baml.ops QualifiedTypeName constructions
- Negate requires root.Concrete, matching the binary op interfaces - operator dispatch resolves via resolve_implements_rule_exact: on a proof/registry desync a near-miss specialization must surface as UnresolvedVirtualCall, not be silently called with a mismatched rhs (the shared resolve_implements_rule keeps its defensive fallback for empty-request callers) checked_bigint_mul's one-bit over-estimate is deliberate: it mirrors the Mul opcode preflight in vm.rs so operator and interface dispatch agree
…n and existential soundness Four bugs from the B-289 test audit, each with its regression test: - compound assignment: TIR now requires the operator result to be assignable back to the target (Counter += 1 with Output = int was accepted, leaving an int in a Counter slot), and MIR routes OP= on non-primitive operands through the __union_* drivers via a shared emit_assign_op (v += w on a user class compiled to a raw opcode and died with a VM internal error) - opcode parity: the ops_math natives were written against pre-B-266 wrapping semantics; int add/sub/mul/div/neg now throw catchable IntegerOverflow (incl. INT_MIN / -1 and -INT_MIN) and float / by zero throws DivisionByZero, exactly like the opcodes; stale docs rewritten - primitive fast path: infer_arithmetic's wildcard arms claimed user_class + float/string for the fast path (typing it by the primitive operand alone), and mixed-kind primitive unions promoted statically while the runtime value could be the other kind (UB in the specialized opcodes via unreachable_unchecked); both TIR and MIR now keep an operand on the fast path only when it is single-kind (builtin wrapper classes like baml.Float count as their primitive; null union members from chain-narrowed compound assigns stay transparent) - unpinned Output: an operand whose <l as Op<r>>::Output realizes to the = Self default existential (bare baml.ops.Add<int> param, or a typevar bound without an Output pin) is rejected instead of unsoundly typing (x + 1) + 1 Coverage backfill from the audit: user-type Sub/Mul/Div/Rem dispatch, multi-instantiation selection by rhs runtime type (Add<int> + Add<float>), Output != Self at runtime, driver-vs-opcode parity grid (values and all catchable panics via bounded-generic dispatch), mixed-union runtime test. string + uint8array/media now errors statically (it was already broken at runtime on this branch: MIR routed it to a driver with no impl)
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
baml_language/crates/baml_compiler2_tir/src/builder.rs (2)
13735-13758: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick winUnsound primitive filtering permits crashing VM operations.
The
is_non_object_primitivehelper fails to matchTy::Union,Ty::Class(e.g.,baml.Int), andTy::TypeAlias. As a result, it returnsfalsefor these types.When
lhsisTy::String(whereis_non_object_primitivecorrectly yieldsfalse) andrhsisTy::Union(Int, Int(1)), the union also yieldsfalse. Both!is_non_object_primitivechecks evaluate totrue, mistakenly allowingstring + (int | 1)to compile. The VM will then abort when attempting to invokeas_stringon the tagged integer.We can completely remove
is_non_object_primitiveand cleanly close this loophole by strictly matching(Some(PrimitiveType::String), Some(PrimitiveType::String))on the already-resolved base types.🛡️ Proposed fix to ensure type safety
- (Some(PrimitiveType::String), _) | (_, Some(PrimitiveType::String)) => { - // String concatenation, Add only, and only string + string: - // both operands are known primitive-based here (the None arm - // above already sent everything else to the interface path), - // so the non-object-primitive check rejects exactly `string + - // int/float/bigint` — the VM concatenates objects via - // `as_string`, and a tagged primitive has no object - // representation. `string` deliberately has no `baml.ops` - // impls (see math.baml), so no interface fallback applies. - if matches!(op, baml_compiler2_ast::BinaryOp::Add) - && !Self::is_non_object_primitive(lhs) - && !Self::is_non_object_primitive(rhs) - { - Ty::String { - attr: TyAttr::default(), - } - } else { - Ty::Unknown { - attr: TyAttr::default(), - } - } - } + (Some(PrimitiveType::String), Some(PrimitiveType::String)) => { + // String concatenation, Add only, and only string + string. + // `string` deliberately has no `baml.ops` impls (see math.baml), + // so no interface fallback applies. + if matches!(op, baml_compiler2_ast::BinaryOp::Add) { + Ty::String { + attr: TyAttr::default(), + } + } else { + Ty::Unknown { + attr: TyAttr::default(), + } + } + } + (Some(PrimitiveType::String), _) | (_, Some(PrimitiveType::String)) => { + Ty::Unknown { + attr: TyAttr::default(), + } + }(You can also completely remove the now-unused
is_non_object_primitivehelper defined at lines 13630–13647).🤖 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/builder.rs` around lines 13735 - 13758, Replace the `is_non_object_primitive` checks in the string-concatenation branch with a strict match requiring both already-resolved operand base types to be `Some(PrimitiveType::String)`, returning `Ty::String` only for string-plus-string. Remove the now-unused `is_non_object_primitive` helper and preserve `Ty::Unknown` for all other operand types, including unions, classes, and type aliases.
7365-7388: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winUpdate the tracked local type in
AssignOp.AssignOpshould mirror plainAssignfor simple local paths and callassign_local(...); otherwise a flow-typed local likelet x = 1; x += 2;stays recorded asLiteral(1)and can mislead later inference/narrowing.🤖 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/builder.rs` around lines 7365 - 7388, The AssignOp handling around the result-type compatibility check must update tracked local types like plain Assign. For simple local assignment targets, call assign_local(...) with the computed result type; preserve the existing compatibility validation and avoid applying local tracking for non-local or complex paths.Source: Coding guidelines
🧹 Nitpick comments (2)
baml_language/crates/baml_tests/baml_src/ns_operators/operators.baml (2)
368-374: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExercise specialization selection through a union-typed RHS.
These monomorphic wrappers do not verify runtime RHS selection. Route both values through one
int | floatparameter so a broken union double-dispatch path cannot pass.Proposed regression coverage
-function ops_poly_add_int(p: Poly, n: int) -> Poly { p + n } -function ops_poly_add_float(p: Poly, f: float) -> Poly { p + f } +function ops_poly_add(p: Poly, rhs: int | float) -> Poly { p + rhs } test "operator_multi_instantiation_selects_by_rhs_type" { - assert.is_true(baml.deep_equals(ops_poly_add_int(Poly { v: 1 }, 5), Poly { v: 6 })) - assert.is_true(baml.deep_equals(ops_poly_add_float(Poly { v: 1 }, 0.5), Poly { v: 1001 })) + assert.is_true(baml.deep_equals(ops_poly_add(Poly { v: 1 }, 5), Poly { v: 6 })) + assert.is_true(baml.deep_equals(ops_poly_add(Poly { v: 1 }, 0.5), Poly { v: 1001 })) }The PR objectives explicitly call out runtime RHS selection and mixed-union behavior.
🤖 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/baml_src/ns_operators/operators.baml` around lines 368 - 374, Update the operator specialization test around ops_poly_add_int and ops_poly_add_float to add a single wrapper accepting an int | float RHS, then invoke it with both integer and float values. Assert the expected Poly results through this union-typed path so runtime RHS specialization and mixed-union dispatch are exercised rather than only monomorphic wrappers.
432-446: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winComplete the primitive driver parity matrix.
Float omits
-,*, and%; bigint omits-,*,%, and unary-; mixed widening only tests+. Add driver assertions for these paths, especially float% 0.0, whose required result isNaNrather than a panic.The PR objectives claim native numeric parity across the arithmetic interfaces.
🤖 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/baml_src/ns_operators/operators.baml` around lines 432 - 446, Expand the arithmetic parity tests in driver_float_arithmetic_matches_opcodes, driver_bigint_arithmetic_matches_opcodes, and driver_mixed_widening_matches_opcodes to cover subtraction, multiplication, modulo, bigint unary negation, and mixed widening for each supported operator. Include the float modulo-by-zero case and assert that drv_float_mod returns NaN without panicking, while preserving the existing deep-equality checks and operand types.
🤖 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.
Outside diff comments:
In `@baml_language/crates/baml_compiler2_tir/src/builder.rs`:
- Around line 13735-13758: Replace the `is_non_object_primitive` checks in the
string-concatenation branch with a strict match requiring both already-resolved
operand base types to be `Some(PrimitiveType::String)`, returning `Ty::String`
only for string-plus-string. Remove the now-unused `is_non_object_primitive`
helper and preserve `Ty::Unknown` for all other operand types, including unions,
classes, and type aliases.
- Around line 7365-7388: The AssignOp handling around the result-type
compatibility check must update tracked local types like plain Assign. For
simple local assignment targets, call assign_local(...) with the computed result
type; preserve the existing compatibility validation and avoid applying local
tracking for non-local or complex paths.
---
Nitpick comments:
In `@baml_language/crates/baml_tests/baml_src/ns_operators/operators.baml`:
- Around line 368-374: Update the operator specialization test around
ops_poly_add_int and ops_poly_add_float to add a single wrapper accepting an int
| float RHS, then invoke it with both integer and float values. Assert the
expected Poly results through this union-typed path so runtime RHS
specialization and mixed-union dispatch are exercised rather than only
monomorphic wrappers.
- Around line 432-446: Expand the arithmetic parity tests in
driver_float_arithmetic_matches_opcodes,
driver_bigint_arithmetic_matches_opcodes, and
driver_mixed_widening_matches_opcodes to cover subtraction, multiplication,
modulo, bigint unary negation, and mixed widening for each supported operator.
Include the float modulo-by-zero case and assert that drv_float_mod returns NaN
without panicking, while preserving the existing deep-equality checks and
operand types.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 69b335cc-f3a9-4b14-a5bb-19aea64de372
⛔ Files ignored due to path filters (3)
baml_language/crates/baml_tests/snapshots/baml_src/operators.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____06_codegen.snapis excluded by!**/*.snap
📒 Files selected for processing (6)
baml_language/crates/baml_builtins2/baml_std/baml/ns_ops/math.bamlbaml_language/crates/baml_compiler2_mir/src/lower.rsbaml_language/crates/baml_compiler2_tir/src/builder.rsbaml_language/crates/baml_tests/baml_src/ns_operators/operators.bamlbaml_language/crates/baml_tests/tests/interfaces.rsbaml_language/crates/bex_vm/src/package_baml/ops_math.rs
🚧 Files skipped from review as they are similar to previous changes (3)
- baml_language/crates/baml_compiler2_mir/src/lower.rs
- baml_language/crates/baml_builtins2/baml_std/baml/ns_ops/math.baml
- baml_language/crates/baml_tests/tests/interfaces.rs
…ncat typing - builtin package listing: line numbers shifted by the math.baml edits - builtin_io corpus: uint8array + string now errors at compile time (E0004) instead of compiling to a doomed driver call; on canary it concatenated via the string wildcard arm, but B-289's MIR routing had already broken it at runtime, and the string decision (no baml.ops impls for string-likes) makes the static error the intended behavior - interfaces_associated_types fixture: render the int | string catch results via to_string() - a mixed union is not a valid concat operand
An unreachable catch arm's binding is never-typed; 'prefix' + other in such an arm previously slipped through the string wildcard arm and must not become E0004 under the stricter fast path - bottom propagates with no diagnostic
Negate now mirrors Rust's Neg and the binary op interfaces:
type Output extends root.Concrete = Self, so -x can change type
(class Debt { implements Negate { type Output = int ... } }). TIR
negate inference resolves each operand alternative's Output projection
and unions the results instead of returning the operand type; the
unpinned-Output rejection now applies to Negate too (a bare
T extends baml.ops.Negate bound must pin Output to use -x).
Existing impls are unaffected - the = Self default covers them
There was a problem hiding this comment.
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/baml_compiler2_tir/src/builder.rs (1)
13979-14011: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winHandle
Ty::Neverin unaryNeg
infer_unary_opsends aneveroperand throughinfer_negate_via_interface, which falls back toInvalidUnaryOp.Expr::ThrowproducesTy::Never, so-(throw e)gets a spurious diagnostic even though the path is unreachable. Mirror the binary arithmetic short-circuit here and returnTy::Neverbefore interface dispatch.🤖 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/builder.rs` around lines 13979 - 14011, Update infer_unary_op’s UnaryOp::Neg handling to match Ty::Never before infer_negate_via_interface dispatch, returning Ty::Never directly with its existing attributes. Preserve the current primitive, unknown/error, interface-dispatch, and invalid-operation behavior for all other operand types.
🧹 Nitpick comments (1)
baml_language/crates/baml_compiler2_tir/src/builder.rs (1)
13874-13924: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueCartesian-product resolution for
infer_arithmetic_via_interface/infer_negate_via_interface: correct, minor duplication.Both helpers follow the same "resolve each alternative's
Output, bail on any failure via?, join successes" shape — differing only in whetherrhsis threaded through. This is a small, understandable duplication; extracting a shared "join outputs across alternatives" helper would be a nice-to-have but isn't warranted given the modest size and the differing arity (binary vs. unary) of the two call sites.🤖 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/builder.rs` around lines 13874 - 13924, No code change is required: the duplication between infer_arithmetic_via_interface and infer_negate_via_interface is intentional and acceptable. Preserve their current Cartesian-product and fail-fast resolution 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.
Outside diff comments:
In `@baml_language/crates/baml_compiler2_tir/src/builder.rs`:
- Around line 13979-14011: Update infer_unary_op’s UnaryOp::Neg handling to
match Ty::Never before infer_negate_via_interface dispatch, returning Ty::Never
directly with its existing attributes. Preserve the current primitive,
unknown/error, interface-dispatch, and invalid-operation behavior for all other
operand types.
---
Nitpick comments:
In `@baml_language/crates/baml_compiler2_tir/src/builder.rs`:
- Around line 13874-13924: No code change is required: the duplication between
infer_arithmetic_via_interface and infer_negate_via_interface is intentional and
acceptable. Preserve their current Cartesian-product and fail-fast resolution
behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 58ac5311-46c2-4120-b429-4054616030e3
⛔ Files ignored due to path filters (2)
baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_builtin_package_listing.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/snapshots/baml_src/operators.snapis excluded by!**/*.snap
📒 Files selected for processing (4)
baml_language/crates/baml_builtins2/baml_std/baml/ns_ops/math.bamlbaml_language/crates/baml_compiler2_tir/src/builder.rsbaml_language/crates/baml_tests/baml_src/ns_operators/operators.bamlbaml_language/crates/baml_tests/tests/interfaces.rs
🚧 Files skipped from review as they are similar to previous changes (3)
- baml_language/crates/baml_tests/baml_src/ns_operators/operators.baml
- baml_language/crates/baml_tests/tests/interfaces.rs
- baml_language/crates/baml_builtins2/baml_std/baml/ns_ops/math.baml
Implements B-289:
+ - * / %and unary-dispatch through newbaml.ops.{Add,Subtract,Multiply,Divide,Remainder,Negate}interfaces, so user types can overload arithmetic operators. Takes over kai/math-ops and merges current canary into it.baml.opsinterfaces withRhs extends root.Concreteandtype Output extends root.Concrete = Self, plus the int/float/bigint impl matrix (mixed pairs widen; int wraps at i63; integer/ %by zero panicsDivisionByZero; float stays IEEE-754). Newbaml.Concretemarker interface with a blanket impl.infer_arithmeticclassifier; anything else resolves<lhs as Op<rhs>>::Outputvia associated-type projection. Union operands take the cartesian product of alternatives (all pairs must be implemented; result is the union of their Outputs).baml.ops.__union_*runtime drivers.drive_binary_opdouble-dispatches on both operands' runtime concrete types throughresolve_implements_ruleand tail-calls the impl method.Merge adaptations vs kai/math-ops: ops driver ported to the
RealizedTyresolver API; kai's dual-package implements fix dropped (canary'simplements_interfacealready searches all involved packages); interfaces declarerequires root.Concreteso the symbolicSelfdefault forOutputdischarges its bound; associated types referenced asSelf.Output.Deferred:
string + T/string * intimpls (commented out), compound-assignment coverage for user types.Closes B-289
Summary by CodeRabbit
baml.opsinterfaces and native implementations for+ - * / %and unary-acrossint,float, andbigint, including union-aware runtime dispatch.