Skip to content

bigint#3526

Open
2kai2kai2 wants to merge 28 commits into
canaryfrom
kai/bigint
Open

bigint#3526
2kai2kai2 wants to merge 28 commits into
canaryfrom
kai/bigint

Conversation

@2kai2kai2
Copy link
Copy Markdown
Collaborator

@2kai2kai2 2kai2kai2 commented May 14, 2026

Adds the bigint primitive type, which represents an arbitrary-precision signed integer with sign-extended two's complement bitwise semantics. Like string, it is an immutable heap-allocated object.

  • Includes bigint literals: like other popular languages with built-in arbitrary-size integers, we use the n suffix as in 123n or 9999999999999999999999999n
  • int can always be safely widened into bigint, so we allow implicit casting for this case. This includes in binary ops, so 1n + 2 == 3n
  • Operations that can produce very large bigints (such as left shift) may throw an AllocFailure panic if the size of the bigint would exceed 1 << 28 bits (about 33.6MB). For example: 1n << (1n << 29n) will panic. This allows programs to recover in BAML instead of crashing from an out-of-memory error or other allocation problems.
  • Helpful diagnostics for malformed bigint literals like 123N or 1.23n

Summary by CodeRabbit

  • New Features

    • Added Bigint primitive with literal syntax (e.g. 42n), int→bigint widening, full arithmetic/bitwise/shift ops, comparisons, and typed bytecode/VM support.
    • Bigint standard library: parse, to_json, abs/min/max/clamp/isqrt/pow/ilog/random.
  • Runtime / VM

    • Heap/object representation, allocation paths, serializers, and new panics (negative-bit-shift, alloc failure) for bigint.
  • FFI / SDK / Protobuf

    • End-to-end bigint round‑trip support in bridges and SDKs (Python, Node, protobufs).
  • Tests

    • Extensive new and updated tests covering syntax, coercion, ops, serialization, edge cases.
  • Chores

    • Workspace dependencies and codegen/type mappings added for bigint support.

Review Change Stack

2kai2kai2 added 22 commits May 13, 2026 16:57
- Also bigint literals
`int` can always be widened to `bigint` automatically.
Arithmetic and bitwise operators similar to `int`.
These automatically will insert widening an `int` into a `bigint` before the operator instruction.
- Added variant to `BexExternalValue`
- JSON serialization as decimal string
- ffi serialization as hex string
For operations that produce Very Large `bigint` (around `2**(2**28)` or bigger) we throw a baml panic instead of doing it. So `bigint` in baml has an effective limit of being an `i268435456` (as opposed to `int` as `i64`)
These include:
- `bigint.pow`
- `<<` for `bigint`

Also fixed `>>` to saturate when shifting more than `usize::MAX` bits instead of erroring
- Extract `bigint` for builtin codegen
- Python literals don't have `n` suffix
- Assignment statement
- Assignment with optional chaining
- Correctly handle type aliases
- bigint constants should be emitted through the `Object` constants path
- Add `to_json` for `bigint`
- `bigint` x `float` operations are not permitted
- `123N` will tell you to use lowercase
- `12.3n` will tell you that bigints must be integers
We can also widen `int` to `bigint?` or other union-like things that include `bigint` (no widening if the union contains `int`)
@vercel
Copy link
Copy Markdown

vercel Bot commented May 14, 2026

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

Project Deployment Actions Updated (UTC)
beps Ready Ready Preview, Comment May 15, 2026 0:30am
promptfiddle Ready Ready Preview, Comment May 15, 2026 0:30am
promptfiddle2 Error Error May 15, 2026 0:30am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds engine-side BigInt conversion/coercion helpers and a full package_baml Bigint runtime implementation exposing methods (parse, pow, ilog, isqrt, random, etc.), with allocation/bit-size guards and error mapping into VM panics/errors.

Changes

Bigint Engine & Builtin

Layer / File(s) Summary
Engine external↔VM conversion + coercion
crates/bex_engine/src/conversion.rs
Adds VM↔external Bigint conversion arms, union/type matching for external Bigint, sys-op arg handling, and coerce_arg_to_declared_type / coerce_return_to_declared_type helpers implementing Int→Bigint widening and union-aware coercion.
Built-in Bigint runtime (package_baml)
crates/bex_vm/src/package_baml/bigint.rs
Implements BamlClassBigint methods: to_json, abs, min, max, clamp, isqrt, pow, ilog, parse, random, plus alloc_failure_panic and re-export of MAX_BIGINT_BITS. Includes allocation-size preflight checks and host-entropy handling.
Glue & allocation semantics
crates/bex_vm/src/package_baml/bigint.rs (alloc/bit checks) / crates/bex_engine/src/conversion.rs (alloc on TLAB)
Builtin methods allocate/clamp BigInt objects via the VM heap path; engine conversion uses the VM/holder TLAB allocation helpers and maps allocation failures to engine/VM error types.

Sequence Diagram

sequenceDiagram
  participant Host
  participant BexEngine
  participant BexVm
  participant BigintBuiltin
  Host->>BexEngine: call function with external value (int/bigint)
  BexEngine->>BexEngine: coerce_arg_to_declared_type(...)
  BexEngine->>BexVm: convert_external_to_vm_value(coerced)
  BexVm->>BigintBuiltin: invoke builtin method (pow/parse/...)
  BigintBuiltin->>BexVm: alloc_bigint / possible VmPanic
  BexVm->>BexEngine: convert_heap_ptr_to_external_with_type(Object::Bigint)
  BexEngine->>Host: return coerced external Bigint
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • BoundaryML/baml#3455 — VM dispatch and opcode/compact-dispatch changes that overlap VM execution paths used by Bigint op handling.
  • BoundaryML/baml#3457 — Compact-bytecode / OpCode pipeline edits related to adding new instruction variants used for Bigint.
  • BoundaryML/baml#3287 — SAP/type-simplification work that intersects the int→bigint widening and simplify_sap logic.

I'm a rabbit with a tiny radix hat,
I hopped 42n and carried it back.
From host to VM and back I prance,
decimals for logs, hex for the dance.
Bigints now hop — a roundtrip glance! 🐇✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch kai/bigint

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 14, 2026

Binary size checks failed

2 violations · ✅ 5 passed

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

Artifact Platform Gzip Baseline Delta Status
bridge_cffi Linux 6.7 MB 6.4 MB +230.3 KB (+3.6%) FAIL
bridge_cffi-stripped Linux 5.7 MB 5.7 MB +26.7 KB (+0.5%) OK
bridge_cffi macOS 5.5 MB 5.1 MB +392.7 KB (+7.7%) OK
bridge_cffi-stripped macOS 4.7 MB 4.7 MB +21.8 KB (+0.5%) OK
bridge_cffi Windows 5.5 MB 5.1 MB +357.8 KB (+7.0%) OK
bridge_cffi-stripped Windows 4.8 MB 4.7 MB +93.8 KB (+2.0%) OK
bridge_wasm WASM 3.6 MB 3.5 MB +106.3 KB (+3.0%) FAIL
Details & how to fix

Violations:

  • bridge_cffi (Linux) gzip_bytes: 6.7 MB exceeds limit of 6.5 MB (exceeded by +166.1 KB, policy: max_gzip_bytes)
  • bridge_wasm (WASM) gzip_bytes: 3.6 MB exceeds limit of 3.6 MB (exceeded by +38.2 KB, policy: max_gzip_bytes)

Add/update baselines:

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

[artifacts.bridge_wasm]
file_bytes = 12691923
gzip_bytes = 3638234

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

[artifacts.bridge_cffi]
file_bytes = 17864952
stripped_bytes = 17864944
gzip_bytes = 6666116

Generated by cargo size-gate · workflow run

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

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/bridge_ctypes/src/value_decode.rs (1)

65-77: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Include bigint in the default scalar union type.

Line [32] now decodes InboundValueVariant::BigintValue to BexExternalValue::Bigint, but default_scalar_union_ty() (Line [69]–Line [76]) doesn’t include bigint. That can mislabel inferred list/map value types and break downstream type handling for untyped inbound containers.

🤖 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/bridge_ctypes/src/value_decode.rs` around lines 65 - 77,
default_scalar_union_ty() omits the bigint variant, but inbound decoding
produces InboundValueVariant::BigintValue -> BexExternalValue::Bigint; update
default_scalar_union_ty() to include a Ty::Bigint { attr: d.clone() } entry in
the Vec so bigint is treated as part of the "any scalar" union (preserve using
the same TyAttr `d` and follow the pattern used for
Int/Float/String/Bool/Uint8Array/Null).
baml_language/sdks/python/rust/codegen_python/src/translate_ty.rs (1)

273-298: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add test cases for the new bigint type and literal.

The test suite translate_ty_covers_phase_g3_matrix is meant to comprehensively cover all Ty variants, but it's missing cases for the newly added Ty::Bigint and Ty::Literal(Literal::Bigint(_)).

📋 Suggested test cases to add
             Case {
+                label: "bigint",
+                ty: Ty::Bigint,
+                ctx: ctx(&["lorem"]),
+                expected: "int",
+            },
+            Case {
+                label: "literal bigint",
+                ty: Ty::Literal(Literal::Bigint("123456789012345678901234567890".to_string())),
+                ctx: ctx(&["lorem"]),
+                expected: "typing.Literal[123456789012345678901234567890]",
+            },
+            Case {
                 label: "int",
                 ty: Ty::Int,

As per coding guidelines, prefer writing Rust unit tests where possible and always run cargo test --lib after changing 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/sdks/python/rust/codegen_python/src/translate_ty.rs` around
lines 273 - 298, The test suite translate_ty_covers_phase_g3_matrix is missing
coverage for the new AST variants Ty::Bigint and literal bigint forms
(Literal::Bigint(_)); add unit test cases that construct Ty::Bigint and
Ty::Literal(Literal::Bigint(...)) (or the codepath that produces those Ty
variants) and include them in the phase G3 matrix so check_exhaustive (in
translate_ty.rs) sees these variants; ensure tests assert translation/roundtrip
behavior consistent with other entries and run cargo test --lib to verify.
🧹 Nitpick comments (8)
baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs (1)

251-251: ⚡ Quick win

Add a direct unit test for TypeExpr::Bigint lowering.

This new primitive branch is small but easy to regress; a focused local test asserting TypeExpr::Bigint lowers to Ty::Primitive(PrimitiveType::Bigint, ...) would lock behavior in.

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_compiler2_tir/src/lower_type_expr.rs` at line 251,
Add a focused unit test that constructs a TypeExpr::Bigint and passes it to the
existing lowering function (e.g., lower_type_expr or the Lower::lower_type_expr
API used in this module) and assert the result equals
Ty::Primitive(PrimitiveType::Bigint, TyAttr::default()); place the test in this
crate’s unit tests (same module or tests mod) so it exercises the exact branch
added for TypeExpr::Bigint and prevents regressions of the PrimitiveType::Bigint
lowering behavior.
baml_language/crates/baml_compiler2_tir/src/normalize.rs (1)

234-235: ⚡ Quick win

Add bigint-focused subtype regression tests in this module.

Please add direct tests for int <: bigint, bigint !<: int, Literal(Int) <: bigint, and Literal(Bigint) <: bigint to keep the new widening rules stable.

As per coding guidelines "Prefer writing Rust unit tests over integration tests where possible".

Also applies to: 241-243, 407-407

🤖 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/normalize.rs` around lines 234 -
235, Add unit tests in this module that assert the new numeric-widening subtype
relations for StructuralTy: create tests verifying (1) StructuralTy::Int is a
subtype of StructuralTy::Bigint, (2) StructuralTy::Bigint is NOT a subtype of
StructuralTy::Int, (3) Literal(Int) is a subtype of StructuralTy::Bigint, and
(4) Literal(Bigint) is a subtype of StructuralTy::Bigint; call the same internal
subtype-checking function used elsewhere in this file (the normalize/rules
function that handles StructuralTy variants) and use assert!(is_subtype(...)) or
assert!(!is_subtype(...)) accordingly, giving each test a descriptive name
(e.g., int_is_subtype_of_bigint, bigint_not_subtype_of_int,
literal_int_subtype_of_bigint, literal_bigint_subtype_of_bigint) so they run as
unit tests in this module.
baml_language/crates/baml_compiler_lexer/src/tokens.rs (1)

1005-1039: 💤 Low value

Comprehensive test coverage for bigint literals.

The test thoroughly covers valid bigint literals and important edge cases. One optional enhancement: consider adding tests that document how malformed bigint literals (e.g., 123N, 1.23n) are tokenized, even though diagnostics are emitted by the parser.

📋 Optional: Document malformed bigint tokenization
// Malformed bigint literals (uppercase N) — lexer emits separate tokens,
// parser diagnoses the error
assert_eq!(
    lex_no_whitespace("123N"),
    vec![TokenKind::IntegerLiteral, TokenKind::Word]
);

// Malformed bigint literals (float with n suffix) — lexer emits separate tokens
assert_eq!(
    lex_no_whitespace("1.23n"),
    vec![TokenKind::FloatLiteral, TokenKind::Word]
);
🤖 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_lexer/src/tokens.rs` around lines 1005 -
1039, Add assertions covering malformed bigint tokenization in the bigint tests:
inside or alongside the existing lex_bigint_literal test, add cases that call
lex_no_whitespace("123N") and assert it returns [TokenKind::IntegerLiteral,
TokenKind::Word], and call lex_no_whitespace("1.23n") and assert it returns
[TokenKind::FloatLiteral, TokenKind::Word]; reference the existing
lex_no_whitespace helper and TokenKind variants (IntegerLiteral, FloatLiteral,
Word) so the lexer behavior is documented without changing lexer logic.
baml_language/crates/bex_heap/src/tlab.rs (1)

202-207: ⚡ Quick win

Add a unit test for alloc_bigint to lock in object-shape behavior.

This new allocator is straightforward, but adding a local unit test (matching other alloc_* tests in this file) would prevent regressions in variant wrapping and heap write/read flow.

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/bex_heap/src/tlab.rs` around lines 202 - 207, Add a
local unit test for alloc_bigint mirroring the existing alloc_* tests: construct
a num_bigint::BigInt (e.g., BigInt::from(123u32)), call
tlab.alloc_bigint(value.clone()), then read the allocated object back from the
heap (using the same helper/read method used by other tests) and assert it
matches Object::Bigint(Arc::new(value)) or otherwise compare the inner BigInt
for equality; ensure the test lives in the same module/tests block as the other
alloc_* tests so it exercises the allocator path and the Arc-wrapping/read
semantics for alloc_bigint.
baml_language/crates/bex_sap/src/deserializer/coercer/coerce_primitive.rs (1)

274-513: ⚡ Quick win

Add focused unit tests for the new bigint coercion helpers.

parse_bigint_from_number_text, bigint_from_finite_f64, and BigintTy::{coerce,try_cast} introduce nuanced numeric behavior; unit tests here would materially improve confidence on boundary cases (huge ints, exponent/decimal rejection in try_cast, float rounding paths, and string parsing variants).

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/bex_sap/src/deserializer/coercer/coerce_primitive.rs`
around lines 274 - 513, Add focused unit tests covering
parse_bigint_from_number_text, bigint_from_finite_f64, BigintTy::coerce, and
BigintTy::try_cast: test parse_bigint_from_number_text rejects numbers with '.'
or 'e'/'E' and parses very large integer strings; test bigint_from_finite_f64
for round-half-away-from-zero behavior on positive/negative .5 boundaries, for
finite huge floats beyond i128 that must be parsed via decimal formatting, and
for NaN/infinity returning None; for BigintTy::coerce add tests for Number
variants (i64/u64 path, arbitrary-precision via Number text, float-to-bigint
with flags and error on non-finite), String inputs (integer strings,
comma-trim/decimal/fraction variants that round, in-progress CompletionState
handling), Array->singular coercion, and for try_cast verify it accepts exact
integer Numbers only (rejects decimals/exponents and floats) and respects
CompletionState/AttrLiteral branches. Use the existing helpers and type
constructors from the module to build minimal ParsingContext/value fixtures.
baml_language/crates/bridge_ctypes/src/handle_table.rs (1)

296-302: ⚡ Quick win

Add explicit bigint rejection coverage in try_from_rejects_primitives.

Since BexExternalValue::Bigint(_) was newly added to the rejection list (Line [96]), add an assertion for it in this test to lock in behavior and prevent accidental re-allowing.

🤖 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/bridge_ctypes/src/handle_table.rs` around lines 296 -
302, The test try_from_rejects_primitives should also assert that
CffiHandleTableEntry::try_from rejects BexExternalValue::Bigint; add an
assertion in that function like
assert!(CffiHandleTableEntry::try_from(BexExternalValue::Bigint(...)).is_err())
so the new rejection of Bigint (added near the match on BexExternalValue) is
covered and cannot be accidentally reverted.
baml_language/crates/sys_llm/src/types/output_format.rs (1)

695-695: ⚡ Quick win

Add direct unit coverage for bigint rendering paths.

Please add tests for Ty::Bigint auto rendering and LiteralValue::Bigint rendering (123n, negative values) to lock this behavior down.

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/sys_llm/src/types/output_format.rs` at line 695, Add
unit tests in the same crate (inside a #[cfg(test)] mod tests block) that assert
the rendering behavior for both Ty::Bigint auto-rendering and
LiteralValue::Bigint formatting; specifically create tests that construct a
Ty::Bigint and verify its rendered string matches the expected auto-render
output, and create tests that construct LiteralValue::Bigint for positive (e.g.,
123) and negative values and assert the renderer produces "123n" and "-123n"
respectively. Place the tests near the code in output_format.rs (or in its test
module), use the existing render/formatting functions used by the file
(referencing LiteralValue::Bigint and Ty::Bigint) and use assert_eq! to lock the
behavior. Ensure tests are unit tests (not integration) per guidelines.
baml_language/crates/baml_tests/projects/compiles/bigint_arith/bigint_arith.baml (1)

5-43: ⚡ Quick win

Add mixed bigint/int arithmetic cases to lock in implicit widening opcode coverage.

Current tests only exercise bigint-bigint operands. Adding mixed operands (e.g., 1n + 2, 1n << 4) helps prevent regressions in the int -> bigint widening path.

Suggested additions
 function AddBigints() -> bigint {
     2n + 3n
 }
 
+function AddBigintIntMixed() -> bigint {
+    2n + 3
+}
+
 ...
 
 function ShlBigints() -> bigint {
     1n << 4n
 }
 
+function ShlBigintIntMixed() -> bigint {
+    1n << 4
+}
🤖 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/projects/compiles/bigint_arith/bigint_arith.baml`
around lines 5 - 43, Add test cases that mix bigint and int operands to cover
implicit widening paths: create functions like AddBigintInt() -> bigint (e.g.,
1n + 2), AddIntBigint() -> bigint (e.g., 1 + 2n), ShlBigintInt() -> bigint
(e.g., 1n << 4), ShrBigintInt() -> bigint (e.g., 256n >> 4), and other mixed
variants for Sub, Mul, Div, Mod, BitAnd, BitOr, BitXor using existing function
naming pattern (e.g., SubBigintInt, MulIntBigint) so the compiler exercises int
-> bigint widening and opcode coverage for mixed operands.
🤖 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_compiler_parser/src/parser.rs`:
- Around line 1988-1992: parse_type_primary accepts BigintLiteral but several
upstream guards/disambiguators still exclude TokenKind::BigintLiteral so valid
big-int inputs never reach that branch; update the predicate/guard functions
that test token starts to include BigintLiteral (e.g. the functions or checks
that currently gate field/param type start, pattern atom start, generic-arg
disambiguation and config-expression detection) instead of excluding it — find
the checks that call self.at(TokenKind::...) or use helper predicates like
is_start_of_type / is_start_of_pattern_atom / is_start_of_generic_arg /
is_start_of_config_expr and add TokenKind::BigintLiteral as an allowed starter
(or remove the explicit exclusion) so the existing parse_type_primary branch for
BigintLiteral is reachable.

In `@baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs`:
- Around line 397-400: The "is" token fallback branch that currently handles
bare INTEGER_LITERAL and FLOAT_LITERAL must also handle BIGINT_LITERAL so
scrutinees like "1n is ..." don't lower to Expr::Missing; add a case matching
SyntaxKind::BIGINT_LITERAL (the same branch where INTEGER_LITERAL/FLOAT_LITERAL
are handled around the "is" fallback) and call
crate::parse_bigint_literal_token(token.text()) and wrap with
self.alloc_expr(Expr::Literal(Literal::Bigint(value)), span) mirroring the
existing INTEGER/FLOAT handling (use the same span and allocation pattern used
in lower_expr_body.rs).

In `@baml_language/crates/baml_compiler2_mir/src/lower.rs`:
- Around line 3372-3383: The code in lower_call_arg_operands currently only
populates param_bigint_flags when callee_func_loc_for_call sees an
AstExpr::Call, so OptionalCall forms (handled via lower_optional_call →
lower_call) never get bigint param flags and miss injecting IntToBigint
coercions; update the resolution logic so that callee_func_loc_for_call (or an
added helper used by lower_call_arg_operands) recognizes AstExpr::OptionalCall
and extracts the inner call/callee FunctionLoc the same way as AstExpr::Call,
then build param_bigint_flags from that FunctionLoc so lower_call_arg_operands
will insert IntToBigint conversions for optional-call arguments as well (apply
same fix at the other occurrence around lines 4486-4495).
- Around line 3145-3167: The current widening branch uses
self.value_is_natural_int(value) which returns false for captured single-segment
paths so captured int RHS in closures bypasses bigint widening; update the
condition that sets rhs to also treat captured single-segment path expressions
as natural ints by checking the AST form (e.g., match Expr::Path with a single
segment and a captured/local flag) or otherwise querying the capture info for
`value` before falling back to self.lower_to_operand; adjust the logic near
dest_widens_int_to_bigint, the use of self.value_is_natural_int(value), and the
code that calls self.lower_expr/lower_to_operand (symbols:
dest_widens_int_to_bigint, value_is_natural_int, lower_expr, lower_to_operand,
Place::local) so that captured single-segment paths are coerced via the
Int->Bigint temp sequence just like explicit natural-int literals/exprs.
- Around line 3383-3406: The code currently re-lowers each signature param via
lower_type_expr_in_ns with an empty generic_params list, losing call-site
instantiations (so T instantiated as bigint isn't seen); instead use the
already-instantiated parameter type for each sig.params entry coming from the
call-site (i.e., apply the call's generic substitutions/instantiation rather
than generic_params = &[]) before passing the type through
resolved_aliases.convert and into Self::dest_widens_int_to_bigint; in practice
replace the re-lowering step that calls lower_type_expr_in_ns with logic that
obtains the instantiated param type (or passes the call's generic substitutions
into lower_type_expr_in_ns) so the widening decision uses the concrete
instantiated type.

In `@baml_language/crates/baml_type/src/lib.rs`:
- Around line 116-118: The new Ty::Bigint variant was added but
Ty::is_primitive() was not updated, so add Bigint to the primitive
classification: update the match/if in Ty::is_primitive() to return true for
Ty::Bigint alongside the other primitive variants (refer to the Ty::Bigint enum
variant and the Ty::is_primitive() function to locate the change), ensuring
bigint follows the same primitive-gated code paths as other primitive types.

In `@baml_language/crates/bex_engine/src/conversion.rs`:
- Around line 681-682: The match arm incorrectly treats Object::Bigint(_) as
non-participating in union-member resolution; update the match in conversion
logic in conversion.rs so Object::Bigint(_) is handled alongside other
participating numeric/string types (i.e., move Bigint into the branch that
attempts union-member resolution) and also add corresponding bigint handling in
the VM→external type matching code path (the external type matcher used by the
VM-to-external union conversion) so bigint values map to the correct external
union member instead of triggering TypeMismatch.

In `@baml_language/crates/bex_vm_types/src/bytecode.rs`:
- Around line 302-310: The comment for ShlBigint is out of date: instead of
saying the VM raises VmBamlError::InvalidArgument "until Phase 12 wires up a
dedicated AllocFailure panic", update the doc to reflect the current runtime
contract—state that if the right operand (shift count) does not fit in a usize
the VM will raise/propagate an AllocFailure panic (or whatever exact
AllocFailure mechanism is now implemented) rather than InvalidArgument; update
both occurrences in the ShlBigint comment text and remove the temporary Phase 12
wording so the docs match the implemented behavior.

In `@baml_language/crates/bex_vm/src/vm.rs`:
- Around line 3468-3490: The right-shift handler currently treats any
non-`usize` RHS as a saturation case (so negative counts like `-1n` saturate
instead of erroring); change the logic that computes `shift_opt` to explicitly
reject negative BigInt RHS values (check `rb.sign() == num_bigint::Sign::Minus`
and return an error/VM trap like the left-shift path does) before attempting
`usize::try_from(...)`, update the analogous right-shift code path (and the
similar handler referenced at lines ~7626-7647) to mirror `ShlBigint`’s
negative-count rejection, and add a unit test asserting that `>> -1n` produces
the expected failure (write a Rust unit test, not an integration test) to verify
the behavior; use the existing helpers `get_object`, `alloc_bigint`, and the VM
error/trap mechanism when implementing the rejection.

In `@baml_language/crates/bridge_ctypes/src/value_decode.rs`:
- Around line 291-294: The test currently builds a local string `message` and
asserts its length, but it should assert the actual error text to detect payload
leakage; replace the existing length check with an assertion on
`err.to_string()` (e.g., assert!(err.to_string().len() < 200)) so the test
verifies the real error produced by the code path that uses `blob_len` rather
than the locally constructed `message`. Ensure `err` is in scope and remove or
ignore the unused `message` variable if no longer needed.

In `@baml_language/crates/bridge_ctypes/src/value_encode.rs`:
- Around line 30-33: Runtime bigint serialization (BexExternalValue::Bigint ->
BamlValueVariant::BigintValue) currently emits hex (format!("{bi:x}")) while
metadata uses decimal (n.to_string()), causing an encoding inconsistency; make
them consistent by changing the runtime path in value_encode.rs to emit a
decimal string (use bi.to_string()) and add/adjust the adjacent comment to note
the temporary inconsistency and link to the Phase 10 bigint plan/TODO so future
coordinated changes handle both BexExternalValue::Bigint and the metadata path
that uses n.to_string().

In `@baml_language/crates/sys_llm/src/types/output_format.rs`:
- Line 429: The Ty::Bigint match arm currently returns a plain "bigint" string
and isn’t handled as a primitive-instruction under RenderSetting::Auto; change
the Ty::Bigint branch in the output formatting code so it follows the same path
as other numeric primitives (mirror the logic used for Ty::Int/Ty::Float) when
RenderSetting::Auto is active—i.e., produce the instruction-style prompt/output
used for primitives rather than the raw "bigint" token; update the Ty::Bigint
arm in the function that maps Ty -> string (the match containing Ty::Bigint) to
reuse the primitive/instruction formatting logic.

---

Outside diff comments:
In `@baml_language/crates/bridge_ctypes/src/value_decode.rs`:
- Around line 65-77: default_scalar_union_ty() omits the bigint variant, but
inbound decoding produces InboundValueVariant::BigintValue ->
BexExternalValue::Bigint; update default_scalar_union_ty() to include a
Ty::Bigint { attr: d.clone() } entry in the Vec so bigint is treated as part of
the "any scalar" union (preserve using the same TyAttr `d` and follow the
pattern used for Int/Float/String/Bool/Uint8Array/Null).

In `@baml_language/sdks/python/rust/codegen_python/src/translate_ty.rs`:
- Around line 273-298: The test suite translate_ty_covers_phase_g3_matrix is
missing coverage for the new AST variants Ty::Bigint and literal bigint forms
(Literal::Bigint(_)); add unit test cases that construct Ty::Bigint and
Ty::Literal(Literal::Bigint(...)) (or the codepath that produces those Ty
variants) and include them in the phase G3 matrix so check_exhaustive (in
translate_ty.rs) sees these variants; ensure tests assert translation/roundtrip
behavior consistent with other entries and run cargo test --lib to verify.

---

Nitpick comments:
In `@baml_language/crates/baml_compiler_lexer/src/tokens.rs`:
- Around line 1005-1039: Add assertions covering malformed bigint tokenization
in the bigint tests: inside or alongside the existing lex_bigint_literal test,
add cases that call lex_no_whitespace("123N") and assert it returns
[TokenKind::IntegerLiteral, TokenKind::Word], and call
lex_no_whitespace("1.23n") and assert it returns [TokenKind::FloatLiteral,
TokenKind::Word]; reference the existing lex_no_whitespace helper and TokenKind
variants (IntegerLiteral, FloatLiteral, Word) so the lexer behavior is
documented without changing lexer logic.

In `@baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs`:
- Line 251: Add a focused unit test that constructs a TypeExpr::Bigint and
passes it to the existing lowering function (e.g., lower_type_expr or the
Lower::lower_type_expr API used in this module) and assert the result equals
Ty::Primitive(PrimitiveType::Bigint, TyAttr::default()); place the test in this
crate’s unit tests (same module or tests mod) so it exercises the exact branch
added for TypeExpr::Bigint and prevents regressions of the PrimitiveType::Bigint
lowering behavior.

In `@baml_language/crates/baml_compiler2_tir/src/normalize.rs`:
- Around line 234-235: Add unit tests in this module that assert the new
numeric-widening subtype relations for StructuralTy: create tests verifying (1)
StructuralTy::Int is a subtype of StructuralTy::Bigint, (2) StructuralTy::Bigint
is NOT a subtype of StructuralTy::Int, (3) Literal(Int) is a subtype of
StructuralTy::Bigint, and (4) Literal(Bigint) is a subtype of
StructuralTy::Bigint; call the same internal subtype-checking function used
elsewhere in this file (the normalize/rules function that handles StructuralTy
variants) and use assert!(is_subtype(...)) or assert!(!is_subtype(...))
accordingly, giving each test a descriptive name (e.g.,
int_is_subtype_of_bigint, bigint_not_subtype_of_int,
literal_int_subtype_of_bigint, literal_bigint_subtype_of_bigint) so they run as
unit tests in this module.

In
`@baml_language/crates/baml_tests/projects/compiles/bigint_arith/bigint_arith.baml`:
- Around line 5-43: Add test cases that mix bigint and int operands to cover
implicit widening paths: create functions like AddBigintInt() -> bigint (e.g.,
1n + 2), AddIntBigint() -> bigint (e.g., 1 + 2n), ShlBigintInt() -> bigint
(e.g., 1n << 4), ShrBigintInt() -> bigint (e.g., 256n >> 4), and other mixed
variants for Sub, Mul, Div, Mod, BitAnd, BitOr, BitXor using existing function
naming pattern (e.g., SubBigintInt, MulIntBigint) so the compiler exercises int
-> bigint widening and opcode coverage for mixed operands.

In `@baml_language/crates/bex_heap/src/tlab.rs`:
- Around line 202-207: Add a local unit test for alloc_bigint mirroring the
existing alloc_* tests: construct a num_bigint::BigInt (e.g.,
BigInt::from(123u32)), call tlab.alloc_bigint(value.clone()), then read the
allocated object back from the heap (using the same helper/read method used by
other tests) and assert it matches Object::Bigint(Arc::new(value)) or otherwise
compare the inner BigInt for equality; ensure the test lives in the same
module/tests block as the other alloc_* tests so it exercises the allocator path
and the Arc-wrapping/read semantics for alloc_bigint.

In `@baml_language/crates/bex_sap/src/deserializer/coercer/coerce_primitive.rs`:
- Around line 274-513: Add focused unit tests covering
parse_bigint_from_number_text, bigint_from_finite_f64, BigintTy::coerce, and
BigintTy::try_cast: test parse_bigint_from_number_text rejects numbers with '.'
or 'e'/'E' and parses very large integer strings; test bigint_from_finite_f64
for round-half-away-from-zero behavior on positive/negative .5 boundaries, for
finite huge floats beyond i128 that must be parsed via decimal formatting, and
for NaN/infinity returning None; for BigintTy::coerce add tests for Number
variants (i64/u64 path, arbitrary-precision via Number text, float-to-bigint
with flags and error on non-finite), String inputs (integer strings,
comma-trim/decimal/fraction variants that round, in-progress CompletionState
handling), Array->singular coercion, and for try_cast verify it accepts exact
integer Numbers only (rejects decimals/exponents and floats) and respects
CompletionState/AttrLiteral branches. Use the existing helpers and type
constructors from the module to build minimal ParsingContext/value fixtures.

In `@baml_language/crates/bridge_ctypes/src/handle_table.rs`:
- Around line 296-302: The test try_from_rejects_primitives should also assert
that CffiHandleTableEntry::try_from rejects BexExternalValue::Bigint; add an
assertion in that function like
assert!(CffiHandleTableEntry::try_from(BexExternalValue::Bigint(...)).is_err())
so the new rejection of Bigint (added near the match on BexExternalValue) is
covered and cannot be accidentally reverted.

In `@baml_language/crates/sys_llm/src/types/output_format.rs`:
- Line 695: Add unit tests in the same crate (inside a #[cfg(test)] mod tests
block) that assert the rendering behavior for both Ty::Bigint auto-rendering and
LiteralValue::Bigint formatting; specifically create tests that construct a
Ty::Bigint and verify its rendered string matches the expected auto-render
output, and create tests that construct LiteralValue::Bigint for positive (e.g.,
123) and negative values and assert the renderer produces "123n" and "-123n"
respectively. Place the tests near the code in output_format.rs (or in its test
module), use the existing render/formatting functions used by the file
(referencing LiteralValue::Bigint and Ty::Bigint) and use assert_eq! to lock the
behavior. Ensure tests are unit tests (not integration) per guidelines.
🪄 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: 613e9e9c-7c65-43c9-b291-e488cee13f27

📥 Commits

Reviewing files that changed from the base of the PR and between 31fbb80 and 14b614b.

⛔ Files ignored due to path filters (102)
  • baml_language/Cargo.lock is excluded by !**/*.lock
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_builtin_package_listing.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__testing_std__/baml_tests__compiles____testing_std____06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_arith/baml_tests__compiles__bigint_arith__01_lexer__bigint_arith.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_arith/baml_tests__compiles__bigint_arith__02_parser__bigint_arith.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_arith/baml_tests__compiles__bigint_arith__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_arith/baml_tests__compiles__bigint_arith__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_arith/baml_tests__compiles__bigint_arith__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_arith/baml_tests__compiles__bigint_arith__05_diagnostics.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_arith/baml_tests__compiles__bigint_arith__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_arith/baml_tests__compiles__bigint_arith__10_formatter__bigint_arith.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_cmp/baml_tests__compiles__bigint_cmp__01_lexer__bigint_cmp.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_cmp/baml_tests__compiles__bigint_cmp__02_parser__bigint_cmp.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_cmp/baml_tests__compiles__bigint_cmp__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_cmp/baml_tests__compiles__bigint_cmp__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_cmp/baml_tests__compiles__bigint_cmp__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_cmp/baml_tests__compiles__bigint_cmp__05_diagnostics.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_cmp/baml_tests__compiles__bigint_cmp__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_cmp/baml_tests__compiles__bigint_cmp__10_formatter__bigint_cmp.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_literal/baml_tests__compiles__bigint_literal__01_lexer__bigint_literal.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_literal/baml_tests__compiles__bigint_literal__02_parser__bigint_literal.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_literal/baml_tests__compiles__bigint_literal__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_literal/baml_tests__compiles__bigint_literal__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_literal/baml_tests__compiles__bigint_literal__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_literal/baml_tests__compiles__bigint_literal__05_diagnostics.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_literal/baml_tests__compiles__bigint_literal__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_literal/baml_tests__compiles__bigint_literal__10_formatter__bigint_literal.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/byte_string_literals/baml_tests__compiles__byte_string_literals__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/closure_loop_variable/baml_tests__compiles__closure_loop_variable__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/closures/baml_tests__compiles__closures__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/event_system/baml_tests__compiles__event_system__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/function_call/baml_tests__compiles__function_call__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/is_operator/baml_tests__compiles__is_operator__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/lambda_advanced/baml_tests__compiles__lambda_advanced__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/lambda_fat_arrow/baml_tests__compiles__lambda_fat_arrow__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/lexical_scoping/baml_tests__compiles__lexical_scoping__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/literal_union_arithmetic/baml_tests__compiles__literal_union_arithmetic__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/parser_expressions/baml_tests__compiles__parser_expressions__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/parser_statements/baml_tests__compiles__parser_statements__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/patterns_class_destructure_namespaces/baml_tests__compiles__patterns_class_destructure_namespaces__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/patterns_new/baml_tests__compiles__patterns_new__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_expr_basic/baml_tests__compiles__test_expr_basic__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_expr_basic/baml_tests__compiles__test_expr_basic__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_expr_basic/baml_tests__compiles__test_expr_basic__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_expr_basic/baml_tests__compiles__test_expr_basic__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_expr_name_concat/baml_tests__compiles__test_expr_name_concat__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_expr_name_concat/baml_tests__compiles__test_expr_name_concat__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_expr_name_concat/baml_tests__compiles__test_expr_name_concat__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_expr_name_concat/baml_tests__compiles__test_expr_name_concat__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_expr_throwing_body/baml_tests__compiles__test_expr_throwing_body__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_expr_throwing_body/baml_tests__compiles__test_expr_throwing_body__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_expr_throwing_body/baml_tests__compiles__test_expr_throwing_body__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_expr_throwing_body/baml_tests__compiles__test_expr_throwing_body__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_old_and_new/baml_tests__compiles__test_old_and_new__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_old_and_new/baml_tests__compiles__test_old_and_new__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_old_and_new/baml_tests__compiles__test_old_and_new__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_old_and_new/baml_tests__compiles__test_old_and_new__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_raw_string_name/baml_tests__compiles__test_raw_string_name__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_raw_string_name/baml_tests__compiles__test_raw_string_name__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_raw_string_name/baml_tests__compiles__test_raw_string_name__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_raw_string_name/baml_tests__compiles__test_raw_string_name__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_with_not_keyword/baml_tests__compiles__test_with_not_keyword__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_with_not_keyword/baml_tests__compiles__test_with_not_keyword__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_with_not_keyword/baml_tests__compiles__test_with_not_keyword__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/test_with_not_keyword/baml_tests__compiles__test_with_not_keyword__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_basic/baml_tests__compiles__testset_basic__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_basic/baml_tests__compiles__testset_basic__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_basic/baml_tests__compiles__testset_basic__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_basic/baml_tests__compiles__testset_basic__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_dynamic/baml_tests__compiles__testset_dynamic__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_dynamic/baml_tests__compiles__testset_dynamic__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_dynamic/baml_tests__compiles__testset_dynamic__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_dynamic/baml_tests__compiles__testset_dynamic__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_nested/baml_tests__compiles__testset_nested__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_nested/baml_tests__compiles__testset_nested__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_nested/baml_tests__compiles__testset_nested__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_nested/baml_tests__compiles__testset_nested__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_vibes_nested/baml_tests__compiles__testset_vibes_nested__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_vibes_nested/baml_tests__compiles__testset_vibes_nested__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_vibes_nested/baml_tests__compiles__testset_vibes_nested__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_vibes_nested/baml_tests__compiles__testset_vibes_nested__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_with_setup/baml_tests__compiles__testset_with_setup__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_with_setup/baml_tests__compiles__testset_with_setup__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_with_setup/baml_tests__compiles__testset_with_setup__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/testset_with_setup/baml_tests__compiles__testset_with_setup__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/test_expr_name_type_error/baml_tests__diagnostic_errors__test_expr_name_type_error__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/test_expr_name_type_error/baml_tests__diagnostic_errors__test_expr_name_type_error__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/test_expr_with_runner/baml_tests__diagnostic_errors__test_expr_with_runner__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/test_expr_with_runner/baml_tests__diagnostic_errors__test_expr_with_runner__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/test_expr_wrong_runner/baml_tests__diagnostic_errors__test_expr_wrong_runner__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/test_expr_wrong_runner/baml_tests__diagnostic_errors__test_expr_wrong_runner__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/test_with_runner_ambiguity/baml_tests__diagnostic_errors__test_with_runner_ambiguity__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/test_with_runner_ambiguity/baml_tests__diagnostic_errors__test_with_runner_ambiguity__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/engine/baml_tests__engine__tests__optional_dropping_adapter_preserves_source_defaults_bytecode.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/src/compiler2_tir/snapshots/baml_tests__compiler2_tir__phase5__snapshot_baml_package_items.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded_unoptimized.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_textual.snap is excluded by !**/*.snap
📒 Files selected for processing (127)
  • baml_language/Cargo.toml
  • baml_language/crates/baml_base/Cargo.toml
  • baml_language/crates/baml_base/src/core_types.rs
  • baml_language/crates/baml_builtins2/baml_std/baml/bigint.baml
  • baml_language/crates/baml_builtins2/src/lib.rs
  • baml_language/crates/baml_builtins2_codegen/src/codegen.rs
  • baml_language/crates/baml_builtins2_codegen/src/codegen_io.rs
  • baml_language/crates/baml_builtins2_codegen/src/extract.rs
  • baml_language/crates/baml_builtins2_codegen/src/types.rs
  • baml_language/crates/baml_codegen_types/src/symbols.rs
  • baml_language/crates/baml_codegen_types/src/ty.rs
  • baml_language/crates/baml_compiler2_ast/Cargo.toml
  • baml_language/crates/baml_compiler2_ast/src/ast.rs
  • baml_language/crates/baml_compiler2_ast/src/lib.rs
  • baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs
  • baml_language/crates/baml_compiler2_ast/src/lower_type_expr.rs
  • baml_language/crates/baml_compiler2_emit/src/analysis.rs
  • baml_language/crates/baml_compiler2_emit/src/emit.rs
  • baml_language/crates/baml_compiler2_emit/src/pull_semantics.rs
  • baml_language/crates/baml_compiler2_emit/src/stack_carry.rs
  • baml_language/crates/baml_compiler2_hir/src/builder.rs
  • baml_language/crates/baml_compiler2_mir/Cargo.toml
  • baml_language/crates/baml_compiler2_mir/src/ir.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler2_mir/src/optimize.rs
  • baml_language/crates/baml_compiler2_mir/src/pretty.rs
  • baml_language/crates/baml_compiler2_ppir/src/expand.rs
  • baml_language/crates/baml_compiler2_ppir/src/ty.rs
  • baml_language/crates/baml_compiler2_tir/src/builder.rs
  • baml_language/crates/baml_compiler2_tir/src/exhaustiveness.rs
  • baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs
  • baml_language/crates/baml_compiler2_tir/src/normalize.rs
  • baml_language/crates/baml_compiler2_tir/src/ty.rs
  • baml_language/crates/baml_compiler2_visualization/src/control_flow/from_ast.rs
  • baml_language/crates/baml_compiler_lexer/src/tokens.rs
  • baml_language/crates/baml_compiler_parser/src/parser.rs
  • baml_language/crates/baml_compiler_syntax/src/ast.rs
  • baml_language/crates/baml_compiler_syntax/src/syntax_kind.rs
  • baml_language/crates/baml_lsp2_actions/src/check.rs
  • baml_language/crates/baml_lsp2_actions/src/utils.rs
  • baml_language/crates/baml_project/src/client_codegen.rs
  • baml_language/crates/baml_tests/Cargo.toml
  • baml_language/crates/baml_tests/projects/compiles/bigint_arith/bigint_arith.baml
  • baml_language/crates/baml_tests/projects/compiles/bigint_cmp/bigint_cmp.baml
  • baml_language/crates/baml_tests/projects/compiles/bigint_literal/bigint_literal.baml
  • baml_language/crates/baml_tests/src/compiler2_tir/inference.rs
  • baml_language/crates/baml_tests/src/compiler2_tir/mod.rs
  • baml_language/crates/baml_tests/src/compiler2_tir/phase3a.rs
  • baml_language/crates/baml_tests/tests/bigints.rs
  • baml_language/crates/baml_tests/tests/classes.rs
  • baml_language/crates/baml_tests/tests/deep_copy.rs
  • baml_language/crates/baml_tests/tests/exceptions.rs
  • baml_language/crates/baml_tests/tests/for_loops.rs
  • baml_language/crates/baml_tests/tests/functions.rs
  • baml_language/crates/baml_tests/tests/if_else.rs
  • baml_language/crates/baml_tests/tests/match_basics.rs
  • baml_language/crates/baml_tests/tests/match_types.rs
  • baml_language/crates/baml_tests/tests/operators.rs
  • baml_language/crates/baml_tests/tests/optimization.rs
  • baml_language/crates/baml_tests/tests/soundness.rs
  • baml_language/crates/baml_tests/tests/watch.rs
  • baml_language/crates/baml_tests/tests/while_loops.rs
  • baml_language/crates/baml_type/Cargo.toml
  • baml_language/crates/baml_type/src/lib.rs
  • baml_language/crates/baml_type/src/simplify_sap.rs
  • baml_language/crates/baml_type/src/typetag.rs
  • baml_language/crates/bex_engine/src/conversion.rs
  • baml_language/crates/bex_events/src/serialize.rs
  • baml_language/crates/bex_external_types/Cargo.toml
  • baml_language/crates/bex_external_types/src/bex_external_value.rs
  • baml_language/crates/bex_heap/Cargo.toml
  • baml_language/crates/bex_heap/src/accessor.rs
  • baml_language/crates/bex_heap/src/gc.rs
  • baml_language/crates/bex_heap/src/heap_debugger/real.rs
  • baml_language/crates/bex_heap/src/tlab.rs
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/mod.rs
  • baml_language/crates/bex_sap/Cargo.toml
  • baml_language/crates/bex_sap/src/baml_value.rs
  • baml_language/crates/bex_sap/src/deserializer/coercer/coerce_literal.rs
  • baml_language/crates/bex_sap/src/deserializer/coercer/coerce_primitive.rs
  • baml_language/crates/bex_sap/src/deserializer/coercer/coerce_ty.rs
  • baml_language/crates/bex_sap/src/deserializer/deserialize_flags.rs
  • baml_language/crates/bex_sap/src/deserializer/score.rs
  • baml_language/crates/bex_sap/src/deserializer/types.rs
  • baml_language/crates/bex_sap/src/jsonish/value.rs
  • baml_language/crates/bex_sap/src/sap_model/convert.rs
  • baml_language/crates/bex_sap/src/sap_model/from_literal.rs
  • baml_language/crates/bex_sap/src/sap_model/mod.rs
  • baml_language/crates/bex_sap/src/sap_model/test_macros.rs
  • baml_language/crates/bex_sap/src/sap_model/type_name.rs
  • baml_language/crates/bex_sap/src/tests/test_basics.rs
  • baml_language/crates/bex_sap/src/to_external.rs
  • baml_language/crates/bex_vm/Cargo.toml
  • baml_language/crates/bex_vm/src/debug.rs
  • baml_language/crates/bex_vm/src/package_baml/bigint.rs
  • baml_language/crates/bex_vm/src/package_baml/json.rs
  • baml_language/crates/bex_vm/src/package_baml/mod.rs
  • baml_language/crates/bex_vm/src/package_baml/root.rs
  • baml_language/crates/bex_vm/src/package_baml/unstable.rs
  • baml_language/crates/bex_vm/src/vm.rs
  • baml_language/crates/bex_vm/tests/deep_equals_bigint.rs
  • baml_language/crates/bex_vm_types/Cargo.toml
  • baml_language/crates/bex_vm_types/src/bytecode.rs
  • baml_language/crates/bex_vm_types/src/types.rs
  • baml_language/crates/bridge_ctypes/Cargo.toml
  • baml_language/crates/bridge_ctypes/src/error.rs
  • baml_language/crates/bridge_ctypes/src/handle_table.rs
  • baml_language/crates/bridge_ctypes/src/value_decode.rs
  • baml_language/crates/bridge_ctypes/src/value_encode.rs
  • baml_language/crates/bridge_ctypes/types/baml_core/cffi/v1/baml_inbound.proto
  • baml_language/crates/bridge_ctypes/types/baml_core/cffi/v1/baml_outbound.proto
  • baml_language/crates/bridge_nodejs/tests/call_function.test.ts
  • baml_language/crates/bridge_nodejs/typescript_src/proto.ts
  • baml_language/crates/sys_jinja_types/src/evaluate_type/stmt.rs
  • baml_language/crates/sys_jinja_types/src/evaluate_type/types.rs
  • baml_language/crates/sys_llm/src/build_request/mod.rs
  • baml_language/crates/sys_llm/src/jinja/value_conversion.rs
  • baml_language/crates/sys_llm/src/types/output_format.rs
  • baml_language/crates/tools_onionskin/src/compiler.rs
  • baml_language/sdks/python/rust/codegen_python/src/leaf.rs
  • baml_language/sdks/python/rust/codegen_python/src/translate_ty.rs
  • baml_language/sdks/python/src/baml_core/cffi/v1/baml_inbound_pb2.py
  • baml_language/sdks/python/src/baml_core/cffi/v1/baml_inbound_pb2.pyi
  • baml_language/sdks/python/src/baml_core/cffi/v1/baml_outbound_pb2.py
  • baml_language/sdks/python/src/baml_core/cffi/v1/baml_outbound_pb2.pyi
  • baml_language/sdks/python/src/baml_core/proto.py
  • baml_language/sdks/python/tests/test_bigint.py
👮 Files not reviewed due to content moderation or server errors (1)
  • baml_language/crates/baml_compiler2_tir/src/builder.rs

Comment thread baml_language/crates/baml_compiler_parser/src/parser.rs
Comment thread baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs
Comment thread baml_language/crates/baml_compiler2_mir/src/lower.rs
Comment thread baml_language/crates/baml_compiler2_mir/src/lower.rs
Comment thread baml_language/crates/baml_compiler2_mir/src/lower.rs
Comment thread baml_language/crates/bex_vm_types/src/bytecode.rs Outdated
Comment thread baml_language/crates/bex_vm/src/vm.rs Outdated
Comment thread baml_language/crates/bridge_ctypes/src/value_decode.rs Outdated
Comment thread baml_language/crates/bridge_ctypes/src/value_encode.rs
Comment thread baml_language/crates/sys_llm/src/types/output_format.rs
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 14, 2026

Merging this PR will improve performance by 21.03%

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 15 improved benchmarks
✅ 4 untouched benchmarks

Performance Changes

Mode Benchmark BASE HEAD Efficiency
WallTime e2e_100_functions 250.8 ms 210.7 ms +19.06%
WallTime vm_loop_500k 37.3 ms 30 ms +24.17%
WallTime e2e_fib_20 161.7 ms 134.4 ms +20.31%
WallTime compile_to_engine 156.7 ms 129.6 ms +20.88%
WallTime vm_field_access_50k 10.8 ms 9 ms +19.76%
WallTime vm_mixed_ops 21.5 ms 12.7 ms +68.93%
WallTime vm_nested_loop 8.4 ms 7.2 ms +16.88%
WallTime e2e_hello_world 157.7 ms 130.4 ms +20.95%
WallTime vm_call_chain_100_x_5k 53.2 ms 46.4 ms +14.67%
WallTime vm_closure_call_50k 13.8 ms 12.4 ms +11.07%
WallTime vm_fib_20 8.8 ms 7.6 ms +16.5%
WallTime e2e_arithmetic 157.6 ms 131 ms +20.3%
WallTime startup_empty_expression 157.3 ms 131.3 ms +19.81%
WallTime vm_class_create_50k 18.3 ms 16.6 ms +10.33%
WallTime e2e_class_and_loop 160.9 ms 133.3 ms +20.7%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing kai/bigint (14b614b) with canary (7a03331)1

Open in CodSpeed

Footnotes

  1. No successful run was found on canary (31fbb80) during the generation of this report, so 7a03331 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

- doc tests
- Generate binding
- Add bigint to matches it was missing in
- Negative shift now correctly throws a BAML panic
- Doc fixes
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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/sys_llm/src/types/output_format.rs (1)

455-478: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add Ty::Bigint to multiline rendering checks for consistency.

Bigint is missing from two primitive pattern matches used to determine whether list/union rendering needs multiple lines. This causes List<bigint> to render multiline ([\n bigint\n]) instead of inline (bigint[]), inconsistent with how List<int> and other numeric primitives render.

📝 Proposed fix
                 let needs_multiline = !is_hoisted
                     && match inner.as_ref() {
                         Ty::String { .. }
                         | Ty::Int { .. }
+                        | Ty::Bigint { .. }
                         | Ty::Float { .. }
                         | Ty::Bool { .. }
                         | Ty::Null { .. } => false,
                         Ty::Enum(tn, _) => {
                             // Inline enums render short; hoisted ones are just a name
                             !hoisted_enums.contains(tn.display_name.as_str())
                                 && inner_str.len() > 15
                         }
                         Ty::Union(items, _) => items.iter().all(|t| {
                             !matches!(
                                 t,
                                 Ty::String { .. }
                                     | Ty::Int { .. }
+                                    | Ty::Bigint { .. }
                                     | Ty::Float { .. }
                                     | Ty::Bool { .. }
                                     | Ty::Null { .. }
                             )
                         }),
                         _ => true,
                     };

Consider adding a test test_render_list_of_bigint (mirroring test_render_list_of_int at line 968) to verify inline rendering.

🤖 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/sys_llm/src/types/output_format.rs` around lines 455 -
478, The multiline-detection match in the needs_multiline calculation omits
Ty::Bigint, causing List<bigint> and unions containing bigint to be treated as
non-primitive and rendered multiline; update both pattern matches that currently
list Ty::String, Ty::Int, Ty::Float, Ty::Bool, Ty::Null to also include
Ty::Bigint so that bigint is treated like other primitive numerics (adjust the
Ty::Enum / Ty::Union arms as needed), ensuring the computation around
needs_multiline, inner_str length checks, and hoisted_enums behavior remains
consistent; add a test mirroring test_render_list_of_int named
test_render_list_of_bigint to assert inline rendering.
🤖 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/sys_llm/src/types/output_format.rs`:
- Around line 455-478: The multiline-detection match in the needs_multiline
calculation omits Ty::Bigint, causing List<bigint> and unions containing bigint
to be treated as non-primitive and rendered multiline; update both pattern
matches that currently list Ty::String, Ty::Int, Ty::Float, Ty::Bool, Ty::Null
to also include Ty::Bigint so that bigint is treated like other primitive
numerics (adjust the Ty::Enum / Ty::Union arms as needed), ensuring the
computation around needs_multiline, inner_str length checks, and hoisted_enums
behavior remains consistent; add a test mirroring test_render_list_of_int named
test_render_list_of_bigint to assert inline rendering.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8ebd9ebd-65ed-4cda-9166-262361164475

📥 Commits

Reviewing files that changed from the base of the PR and between 35421a6 and 740dd4b.

⛔ Files ignored due to path filters (8)
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_builtin_package_listing.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/__baml_std__/baml_tests__compiles____baml_std____06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/src/compiler2_tir/snapshots/baml_tests__compiler2_tir__phase5__snapshot_baml_package_items.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded_unoptimized.snap is excluded by !**/*.snap
📒 Files selected for processing (13)
  • baml_language/crates/baml_builtins2/baml_std/baml/ns_panics/panics.baml
  • baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler_parser/src/parser.rs
  • baml_language/crates/baml_tests/tests/bigints.rs
  • baml_language/crates/baml_tests/tests/exceptions.rs
  • baml_language/crates/baml_type/src/lib.rs
  • baml_language/crates/bex_engine/src/conversion.rs
  • baml_language/crates/bex_vm/src/errors.rs
  • baml_language/crates/bex_vm/src/vm.rs
  • baml_language/crates/bex_vm_types/src/bytecode.rs
  • baml_language/crates/bridge_ctypes/src/value_decode.rs
  • baml_language/crates/sys_llm/src/types/output_format.rs
✅ Files skipped from review due to trivial changes (1)
  • baml_language/crates/baml_builtins2/baml_std/baml/ns_panics/panics.baml
🚧 Files skipped from review as they are similar to previous changes (10)
  • baml_language/crates/bex_engine/src/conversion.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs
  • baml_language/crates/baml_tests/tests/exceptions.rs
  • baml_language/crates/baml_type/src/lib.rs
  • baml_language/crates/bridge_ctypes/src/value_decode.rs
  • baml_language/crates/baml_compiler_parser/src/parser.rs
  • baml_language/crates/bex_vm/src/vm.rs
  • baml_language/crates/bex_vm_types/src/bytecode.rs
  • baml_language/crates/baml_tests/tests/bigints.rs

Passing an `int` into a generic function that explicitly has `<bigint>` generic parameters should widen the `int`
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 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/builder.rs`:
- Around line 7792-7829: The union-reduction in infer_bitwise (inner function
base_ty) rejects mixed int|bigint unions because it only accepts identical
primitives; update base_ty (used by infer_bitwise) to scan union members and
allow widening: if all members reduce to either Int or Bigint and at least one
is Bigint, return PrimitiveType::Bigint; if all are Int return
PrimitiveType::Int; otherwise return None. Ensure literals (Literal::Int /
Literal::Bigint) are treated as Int/Bigint when collecting members so
expressions like int|bigint & 1n correctly reduce to bigint.
- Around line 7605-7619: The code treats comparisons like BinaryOp::Ge as valid
when one side is a union type that may include float and the other is bigint (or
vice versa); extend the float/bigint detection to handle union-shaped types by
either updating is_float_bigint_mix to recurse into Ty::Union (and detect if any
union constituent is float while the other side contains bigint) or add a new
helper (e.g., is_union_float_bigint_mix) invoked where BinaryOp::Ge (and the
other ranges 7766-7786) currently call is_float_bigint_mix; ensure you still
skip Ty::Unknown and Ty::Error branches and call self.context.report_simple with
TirTypeError::InvalidBinaryOp when the union-aware check finds a float↔bigint
possibility.

In `@baml_language/crates/bex_engine/src/conversion.rs`:
- Around line 825-851: coerce_arg_to_declared_type currently only rewrites outer
containers and therefore lets nested Int values in structured types
(list<bigint>, map<string,bigint>, class fields, optional/union children) bypass
the int→bigint widening; update coerce_arg_to_declared_type (and/or
convert_external_to_vm_value) to recurse into declared child types so every
element/entry/field is coerced according to its Ty: when matching
Ty::List(inner), Ty::Map(key, val), Ty::Class(..., fields, ...),
Ty::Optional(inner), Ty::Union(variants), etc., walk the contained values and
call coerce_arg_to_declared_type (or convert_external_to_vm_value where
appropriate) on each child so Int values are widened to BigInt via
coerce_numeric_to_declared_type inside nested structures.
- Around line 898-910: The union-handling branch in the (v, Ty::Union(members,
_)) match only checks for Ty::Int and Ty::Bigint, so numeric literal union
members (e.g., 1 or 1n) are ignored; update the checks and the target-finding
iterator to also recognize the integer and bigint literal type variants (the
literal enum variants used in Ty, e.g., Ty::IntLiteral / Ty::BigintLiteral or
whatever the crate uses) so has_int/has_bigint and the .find(...) consider both
declared and literal numeric members, then pass that found target into
coerce_numeric_to_declared_type as before.

In `@baml_language/crates/bex_vm/src/package_baml/bigint.rs`:
- Around line 162-189: The parse function currently constructs a BigInt from
arbitrary-length decimal text (in parse) without enforcing MAX_BIGINT_BITS; add
a conservative preflight that rejects inputs whose decimal magnitude could
exceed MAX_BIGINT_BITS before calling BigInt::parse_bytes. Specifically: in
parse (the function), after stripping sign (sign_str/digits) trim leading zeros
from digits, count remaining decimal digits, compute an upper-bound on bits
using digits * LOG2_10 (use a constant like LOG2_10 = 3.321928094887362) or
convert MAX_BIGINT_BITS to a maximum allowed decimal digits by
floor(MAX_BIGINT_BITS / LOG2_10) (+1 safety margin), and if the digit count
exceeds that threshold return Err(VmBamlError::AllocFailure { message:
format!("bigint.parse: allocation would exceed MAX_BIGINT_BITS") }.into())
instead of proceeding to BigInt::parse_bytes; only call BigInt::parse_bytes if
the preflight passes.
- Around line 136-159: The ilog binary search currently computes candidate =
base.pow(mid) which can allocate past MAX_BIGINT_BITS; before calling
base.pow(mid) in ilog, estimate the bit-size of b^mid (e.g., bits(candidate) ≤
base.bits().saturating_mul(u64::from(mid))) and if that estimated bits >
MAX_BIGINT_BITS treat the candidate as "too large" (i.e., set hi = mid - 1) so
you never call base.pow for an oversized exponent; use the existing symbols
ilog, base.bits(), MAX_BIGINT_BITS, and bigint.bits() and keep the existing loop
invariant (handle mid == 0 specially so no underflow) so allocations are avoided
and AllocFailure path is preserved.
- Around line 90-106: The current pre-flight uses base_bits * exp which is an
overly loose upper bound and rejects valid exponents (e.g. powers of two);
change the estimate to the provable lower bound bits(base^exp) ≥
exp*(bits(base)-1)+1 so we only fail when the real result must exceed
MAX_BIGINT_BITS. In the bigint.pow pre-flight (where base_bits and exp_u32 are
computed) replace the estimated_bits computation with an integer-safe
lower-bound: compute estimated_bits =
(u64::from(exp_u32)).saturating_mul(base_bits.saturating_sub(1)).saturating_add(1)
and compare that to MAX_BIGINT_BITS; keep the later actual
multiplication/alloc-failure check in place as the final safety net.
- Around line 48-57: The isqrt function uses bigint.sqrt() but the Roots trait
from num_integer is not imported; add an import for the trait (e.g. include use
num_integer::Roots;) in the module where isqrt (function name isqrt and type
BigInt) is defined so BigInt::sqrt() is available, then rebuild to ensure the
compile error is resolved.
🪄 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: 1001a7b3-55f9-4fde-88ff-acfd1d7cd3b0

📥 Commits

Reviewing files that changed from the base of the PR and between 740dd4b and 1d9346c.

⛔ Files ignored due to path filters (10)
  • baml_language/Cargo.lock is excluded by !**/*.lock
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_arith/baml_tests__compiles__bigint_arith__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_arith/baml_tests__compiles__bigint_arith__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_arith/baml_tests__compiles__bigint_arith__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_cmp/baml_tests__compiles__bigint_cmp__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_cmp/baml_tests__compiles__bigint_cmp__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_cmp/baml_tests__compiles__bigint_cmp__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/bridge_nodejs/proto.d.ts.map is excluded by !**/*.map
  • baml_language/crates/bridge_nodejs/proto.js.map is excluded by !**/*.map
  • baml_language/sdks/python/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (20)
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler2_tir/Cargo.toml
  • baml_language/crates/baml_compiler2_tir/src/builder.rs
  • baml_language/crates/baml_tests/tests/bigints.rs
  • baml_language/crates/baml_type/src/lib.rs
  • baml_language/crates/bex_engine/Cargo.toml
  • baml_language/crates/bex_engine/src/conversion.rs
  • baml_language/crates/bex_engine/src/lib.rs
  • baml_language/crates/bex_project/src/bex.rs
  • baml_language/crates/bex_vm/src/package_baml/bigint.rs
  • baml_language/crates/bridge_ctypes/src/value_encode.rs
  • baml_language/crates/bridge_ctypes/types/baml_core/cffi/v1/baml_outbound.proto
  • baml_language/crates/bridge_nodejs/proto.js
  • baml_language/crates/bridge_nodejs/typescript_src/proto.ts
  • baml_language/crates/bridge_nodejs/typescript_src/proto/baml_cffi.d.ts
  • baml_language/crates/bridge_nodejs/typescript_src/proto/baml_cffi.js
  • baml_language/sdks/python/src/baml_core/cffi/v1/baml_outbound_pb2.py
  • baml_language/sdks/python/src/baml_core/cffi/v1/baml_outbound_pb2.pyi
  • baml_language/sdks/python/src/baml_core/proto.py
  • baml_language/sdks/python/tests/test_bigint.py
✅ Files skipped from review due to trivial changes (4)
  • baml_language/crates/baml_compiler2_tir/Cargo.toml
  • baml_language/crates/bex_engine/Cargo.toml
  • baml_language/sdks/python/src/baml_core/cffi/v1/baml_outbound_pb2.py
  • baml_language/crates/bridge_nodejs/typescript_src/proto/baml_cffi.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • baml_language/crates/bridge_nodejs/typescript_src/proto.ts
  • baml_language/crates/baml_type/src/lib.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs

Comment on lines +7605 to +7619
| BinaryOp::Ge => {
if Self::is_float_bigint_mix(lhs, rhs)
&& !matches!(lhs, Ty::Unknown { .. } | Ty::Error { .. })
&& !matches!(rhs, Ty::Unknown { .. } | Ty::Error { .. })
{
self.context.report_simple(
TirTypeError::InvalidBinaryOp {
op,
lhs: lhs.clone(),
rhs: rhs.clone(),
},
at,
);
}
Ty::Primitive(PrimitiveType::Bool, TyAttr::default())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle union-shaped float/bigint comparisons.

is_float_bigint_mix only recognizes direct float/bigint primitives and literals, so something like float | int compared against bigint falls through here as a valid bool expression even though one runtime branch is the forbidden float↔bigint case.

Also applies to: 7766-7786

🤖 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 7605 -
7619, The code treats comparisons like BinaryOp::Ge as valid when one side is a
union type that may include float and the other is bigint (or vice versa);
extend the float/bigint detection to handle union-shaped types by either
updating is_float_bigint_mix to recurse into Ty::Union (and detect if any union
constituent is float while the other side contains bigint) or add a new helper
(e.g., is_union_float_bigint_mix) invoked where BinaryOp::Ge (and the other
ranges 7766-7786) currently call is_float_bigint_mix; ensure you still skip
Ty::Unknown and Ty::Error branches and call self.context.report_simple with
TirTypeError::InvalidBinaryOp when the union-aware check finds a float↔bigint
possibility.

Comment on lines +7792 to +7829
fn infer_bitwise(lhs: &Ty, rhs: &Ty) -> Ty {
fn base_ty(ty: &Ty) -> Option<PrimitiveType> {
// Bitwise only accepts Int and Bigint. Float / String / Bool
// return `None` so the outer match falls through to `Unknown`
// (which surfaces as `InvalidBinaryOp`).
match ty {
Ty::Primitive(PrimitiveType::Int, _) => Some(PrimitiveType::Int),
Ty::Primitive(PrimitiveType::Bigint, _) => Some(PrimitiveType::Bigint),
Ty::Literal(baml_base::Literal::Int(_), _, _) => Some(PrimitiveType::Int),
Ty::Literal(baml_base::Literal::Bigint(_), _, _) => Some(PrimitiveType::Bigint),
Ty::Union(members, _) => {
let mut result: Option<PrimitiveType> = None;
for m in members {
let p = base_ty(m)?;
result = Some(match (result, p) {
(None, p) => p,
(Some(a), b) if a == b => a,
_ => return None,
});
}
result
}
_ => None,
}
}

match (base_ty(lhs), base_ty(rhs)) {
(Some(PrimitiveType::Int), Some(PrimitiveType::Int)) => {
Ty::Primitive(PrimitiveType::Int, TyAttr::default())
}
(Some(PrimitiveType::Bigint | PrimitiveType::Int), Some(PrimitiveType::Bigint))
| (Some(PrimitiveType::Bigint), Some(PrimitiveType::Int)) => {
Ty::Primitive(PrimitiveType::Bigint, TyAttr::default())
}
_ => Ty::Unknown {
attr: TyAttr::default(),
},
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep intbigint widening when reducing union operands for bitwise ops.

The union reducer in infer_bitwise only succeeds when every union member is the exact same primitive, so int | bigint operands get rejected as Unknown. That makes valid cases like let x: int | bigint; x & 1n fail type-checking even though every branch is legal under the new binary widening rules and should produce bigint.

🤖 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 7792 -
7829, The union-reduction in infer_bitwise (inner function base_ty) rejects
mixed int|bigint unions because it only accepts identical primitives; update
base_ty (used by infer_bitwise) to scan union members and allow widening: if all
members reduce to either Int or Bigint and at least one is Bigint, return
PrimitiveType::Bigint; if all are Int return PrimitiveType::Int; otherwise
return None. Ensure literals (Literal::Int / Literal::Bigint) are treated as
Int/Bigint when collecting members so expressions like int|bigint & 1n correctly
reduce to bigint.

Comment on lines +825 to +851
pub(crate) fn coerce_arg_to_declared_type(
value: BexExternalValue,
ty: &Ty,
) -> Result<BexExternalValue, EngineError> {
match (value, ty) {
// ── Class / enum naming (incoming only) ──────────────────────────
(BexExternalValue::Map { entries, .. }, Ty::Class(type_name, _, _)) => {
Ok(BexExternalValue::Instance {
class_name: type_name.to_string(),
fields: entries,
})
}
(BexExternalValue::Instance { fields, .. }, Ty::Class(type_name, _, _)) => {
Ok(BexExternalValue::Instance {
class_name: type_name.to_string(),
fields,
})
}
(BexExternalValue::Variant { variant_name, .. }, Ty::Enum(type_name, _)) => {
Ok(BexExternalValue::Variant {
enum_name: type_name.to_string(),
variant_name,
})
}

// ── Numeric / optional / union ───────────────────────────────────
(v, ty) => coerce_numeric_to_declared_type(v, ty),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Structured bigint slots still bypass widening.

This only rewrites the outer value shape. list<bigint>, map<string, bigint>, and bigint-typed class fields can still carry nested Int values into the VM because neither this helper nor convert_external_to_vm_value walks declared child types. That makes the new int→bigint widening rule false for structured inputs and lets those values round-trip back out as int.

🤖 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_engine/src/conversion.rs` around lines 825 - 851,
coerce_arg_to_declared_type currently only rewrites outer containers and
therefore lets nested Int values in structured types (list<bigint>,
map<string,bigint>, class fields, optional/union children) bypass the int→bigint
widening; update coerce_arg_to_declared_type (and/or
convert_external_to_vm_value) to recurse into declared child types so every
element/entry/field is coerced according to its Ty: when matching
Ty::List(inner), Ty::Map(key, val), Ty::Class(..., fields, ...),
Ty::Optional(inner), Ty::Union(variants), etc., walk the contained values and
call coerce_arg_to_declared_type (or convert_external_to_vm_value where
appropriate) on each child so Int values are widened to BigInt via
coerce_numeric_to_declared_type inside nested structures.

Comment thread baml_language/crates/bex_engine/src/conversion.rs
Comment thread baml_language/crates/bex_vm/src/package_baml/bigint.rs
Comment thread baml_language/crates/bex_vm/src/package_baml/bigint.rs
Comment thread baml_language/crates/bex_vm/src/package_baml/bigint.rs
Comment thread baml_language/crates/bex_vm/src/package_baml/bigint.rs
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (5)
baml_language/crates/bex_vm/src/package_baml/bigint.rs (3)

146-160: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

ilog's binary search can materialise candidates larger than MAX_BIGINT_BITS.

base.pow(mid) is called for every midpoint with no allocation guard. For inputs near the cap, intermediate candidates can be substantially larger than bigint itself (e.g., bits(base^mid) ≈ mid · bits(base), which for base = 3 and mid ≈ k_max ≈ bits(bigint) is ~1.58× the input), so this path can bypass the recoverable AllocFailure and OOM the process internally.

Either bound the candidate's estimated bits before calling base.pow(mid) (e.g., if mid.saturating_mul(base_bits) > MAX_BIGINT_BITS treat it as "too large" and set hi = mid - 1), or compare against bigint.bits() to short-circuit. Keep the special-case mid == 0 to avoid underflow.

🤖 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/src/package_baml/bigint.rs` around lines 146 -
160, The binary search in ilog calls base.pow(mid) unguarded and can allocate
values exceeding MAX_BIGINT_BITS; modify the loop around base.pow(mid)
(referencing ilog, k_max, base.pow(mid), and bigint) to first estimate candidate
bit-size (e.g., compute base_bits = base.bits() or similar and check
mid.saturating_mul(base_bits) > MAX_BIGINT_BITS) or compare mid * base_bits
against bigint.bits(), and if that estimate shows the candidate would be too
large treat it as "too large" by setting hi = mid - 1 (keeping the special-case
for mid == 0 to avoid underflow); only call base.pow(mid) when the estimated
bits are within limits so AllocFailure/OOM is avoided.

162-190: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

bigint.parse ignores MAX_BIGINT_BITS.

This path allocates arbitrary-length decimal text straight into BigInt, so a multi-megabyte digit string can produce a bigint that literals and arithmetic operators are supposed to reject with AllocFailure. A conservative preflight on the trimmed digit count (e.g., reject when digits.len() * log2(10) > MAX_BIGINT_BITS, with a small safety margin) closes the bypass before parse_bytes.

🤖 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/src/package_baml/bigint.rs` around lines 162 -
190, The parse function currently converts arbitrary-length decimal strings into
BigInt without checking MAX_BIGINT_BITS; add a preflight size check in the
parse(text: &str) function after extracting `digits` that computes an
upper-bound bit length (e.g., estimated_bits = digits.len() as f64 * LOG2_10
where LOG2_10 ≈ 3.321928094887362) and rejects input when estimated_bits exceeds
MAX_BIGINT_BITS minus a small safety margin, returning the appropriate
allocation failure error (use the same error variant your codebase uses for
oversized bigints, e.g., VmBamlError::AllocFailure or similar) instead of
calling BigInt::parse_bytes for those cases so huge digit strings cannot bypass
the allocator limits.

90-108: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

pow pre-flight still overestimates by a factor of ~bits(base)/(bits(base)-1).

estimated_bits = base_bits * exp is a loose upper bound, not a lower bound on the result size, so valid inputs near MAX_BIGINT_BITS get rejected with AllocFailure. The exact lower bound is (bits(base) - 1) * exp + 1 — using that only rejects when the result truly must exceed the cap.

For example, with MAX_BIGINT_BITS = 1 << 28, a base of 3 (bits = 2) and exp ≈ 168M produces a result of ~266M bits (under the cap), but the current check sees 2 * 168M = 336M > cap and rejects.

♻️ Proposed fix
-        let estimated_bits = base_bits.saturating_mul(u64::from(exp_u32));
+        // bits(b^e) ≥ (bits(b) - 1) * e + 1 for |b| ≥ 2 — the tightest
+        // lower bound, so we only reject results that *must* exceed the cap.
+        let estimated_bits = u64::from(exp_u32)
+            .saturating_mul(base_bits.saturating_sub(1))
+            .saturating_add(1);
         if estimated_bits > MAX_BIGINT_BITS {
🤖 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/src/package_baml/bigint.rs` around lines 90 -
108, The pre-flight estimate uses estimated_bits = base_bits * exp which
overestimates and rejects valid inputs; replace it with the tight lower bound
(bits(base) - 1) * exp + 1. Concretely, after computing base_bits and exp_u32,
compute estimated_bits = if base_bits == 0 { 0 } else {
base_bits.saturating_sub(1).saturating_mul(u64::from(exp_u32)).saturating_add(1)
} and then compare that against MAX_BIGINT_BITS; keep the existing
alloc_failure_panic paths and final Ok(Arc::new(bigint.pow(exp_u32))). Use the
symbols base_bits, exp_u32, estimated_bits, MAX_BIGINT_BITS, bigint.pow and
alloc_failure_panic to locate and change the logic.
baml_language/crates/bex_engine/src/conversion.rs (2)

887-900: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Union widening still ignores numeric literal members.

has_int/has_bigint and the find target only consider Ty::Int { .. } / Ty::Bigint { .. }, not Ty::Literal(Literal::Int(_), _) / Ty::Literal(Literal::Bigint(_), _). For unions like 1n | string or 1 | bigint, the widening path is never entered even though value_matches_type (line 558/559) and find_matching_union_member (line 619/684) already accept those literal arms, so an incoming Int headed for a bigint-literal union slot survives as the wrong numeric kind and fails downstream selection.

♻️ Proposed fix
         (v, Ty::Union(members, _)) => {
-            let has_int = members.iter().any(|m| matches!(m, Ty::Int { .. }));
-            let has_bigint = members.iter().any(|m| matches!(m, Ty::Bigint { .. }));
+            let has_int = members
+                .iter()
+                .any(|m| matches!(m, Ty::Int { .. } | Ty::Literal(Literal::Int(_), _)));
+            let has_bigint = members.iter().any(|m| {
+                matches!(m, Ty::Bigint { .. } | Ty::Literal(Literal::Bigint(_), _))
+            });
             if has_int == has_bigint {
                 Ok(v)
             } else if let Some(target) = members
                 .iter()
-                .find(|m| matches!(m, Ty::Int { .. } | Ty::Bigint { .. }))
+                .find(|m| {
+                    matches!(
+                        m,
+                        Ty::Int { .. }
+                            | Ty::Bigint { .. }
+                            | Ty::Literal(Literal::Int(_), _)
+                            | Ty::Literal(Literal::Bigint(_), _)
+                    )
+                })
             {
                 coerce_numeric_to_declared_type(v, target)
             } else {
                 Ok(v)
             }
         }
🤖 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_engine/src/conversion.rs` around lines 887 - 900,
The union-widening branch in conversion.rs only checks for Ty::Int and
Ty::Bigint and therefore ignores numeric literal members; update the (v,
Ty::Union(...)) arm to treat Ty::Literal(Literal::Int(_), _) as an Int member
and Ty::Literal(Literal::Bigint(_), _) as a Bigint member when computing
has_int/has_bigint and when locating the target in the .find(...) so that
coerce_numeric_to_declared_type(v, target) is invoked for unions like `1 |
bigint` or `1n | string`; adjust the matches! patterns used in the has_*
booleans and the .find closure to include these Ty::Literal cases (leaving the
call to coerce_numeric_to_declared_type unchanged).

825-853: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Nested bigint slots still bypass Int → Bigint widening.

coerce_arg_to_declared_type only rewrites the outermost shape; the Map→Instance and Instance/Variant arms move entries/fields through untouched, and convert_external_to_vm_value doesn't consult declared child types either. So Int values nested inside list<bigint>, map<string, bigint>, or bigint-typed class/optional fields are still allocated as VM Int rather than widened, breaking the documented int→bigint widening rule for structured inputs.

Recursing into declared child types (Ty::List/Ty::Map/Ty::Class field-by-field/Ty::Optional) before/while calling convert_external_to_vm_value would close this.

🤖 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_engine/src/conversion.rs` around lines 825 - 853,
coerce_arg_to_declared_type currently only rewrites the top-level shape
(Map→Instance, Instance, Variant) and leaves nested values unchanged so Ints
inside structured types don't get widened to Bigint; fix by recursing into
declared child types before converting: in coerce_arg_to_declared_type, when
matching Ty::Class, Ty::List, Ty::Map and Ty::Optional, iterate the
corresponding child type(s) and call convert_external_to_vm_value (or the
existing conversion path) on each entry/field/element using the declared child
Ty so nested Int→Bigint widening occurs; specifically update the Map { entries
}→Instance and Instance { fields } arms to map each entry/field through
conversion using the class field types from Ty::Class, handle
Ty::List/Ty::Map/Ty::Optional by converting their inner values with the inner
Ty, and ensure Variant handling similarly consults enum variant payload types if
present.
🧹 Nitpick comments (1)
baml_language/crates/bex_vm/src/package_baml/bigint.rs (1)

207-226: ⚡ Quick win

Switch bigint.random to bit-granular rejection sampling to improve worst-case acceptance rate.

The current byte-granular implementation allocates range.to_bytes_be().len() bytes for sampling, which wastes entropy when range is just above a byte boundary. For example, with range = 257, sampling 2 bytes gives an acceptance rate of only 257/65536 ≈ 0.39%, requiring ~256 getrandom syscalls per call on average. Similar pathological cases occur for any range = 2^k + 1.

Switching to bit-granular sampling—compute range.bits(), draw ceil(bits / 8) bytes, then mask the high byte to the top bits % 8 bits—keeps acceptance at ≥ 50% and bounds expected iterations to ≤ 2. This approach aligns with standard implementations like num-bigint's gen_biguint_below.

🤖 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/src/package_baml/bigint.rs` around lines 207 -
226, The current byte-granular rejection sampling in bigint.random wastes
entropy for ranges just above byte boundaries; change it to bit-granular
sampling by computing let bits = range.bits(), let byte_len = (bits + 7) / 8,
draw byte_len bytes, then mask the top byte to keep only bits % 8 high bits (no
mask if bits % 8 == 0) before constructing BigUint; keep the same rejection
condition (sample < range) and return BigInt::from(sample) + lower.as_ref()
wrapped in Arc as before; update the buffer size and masking logic in the
function (look for bigint.random / the loop using range.to_bytes_be(), buf,
getrandom::getrandom, BigUint::from_bytes_be, and the return constructing
BigInt) so acceptance rate is >=50% and iterations are bounded.
🤖 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/bex_engine/src/conversion.rs`:
- Around line 887-900: The union-widening branch in conversion.rs only checks
for Ty::Int and Ty::Bigint and therefore ignores numeric literal members; update
the (v, Ty::Union(...)) arm to treat Ty::Literal(Literal::Int(_), _) as an Int
member and Ty::Literal(Literal::Bigint(_), _) as a Bigint member when computing
has_int/has_bigint and when locating the target in the .find(...) so that
coerce_numeric_to_declared_type(v, target) is invoked for unions like `1 |
bigint` or `1n | string`; adjust the matches! patterns used in the has_*
booleans and the .find closure to include these Ty::Literal cases (leaving the
call to coerce_numeric_to_declared_type unchanged).
- Around line 825-853: coerce_arg_to_declared_type currently only rewrites the
top-level shape (Map→Instance, Instance, Variant) and leaves nested values
unchanged so Ints inside structured types don't get widened to Bigint; fix by
recursing into declared child types before converting: in
coerce_arg_to_declared_type, when matching Ty::Class, Ty::List, Ty::Map and
Ty::Optional, iterate the corresponding child type(s) and call
convert_external_to_vm_value (or the existing conversion path) on each
entry/field/element using the declared child Ty so nested Int→Bigint widening
occurs; specifically update the Map { entries }→Instance and Instance { fields }
arms to map each entry/field through conversion using the class field types from
Ty::Class, handle Ty::List/Ty::Map/Ty::Optional by converting their inner values
with the inner Ty, and ensure Variant handling similarly consults enum variant
payload types if present.

In `@baml_language/crates/bex_vm/src/package_baml/bigint.rs`:
- Around line 146-160: The binary search in ilog calls base.pow(mid) unguarded
and can allocate values exceeding MAX_BIGINT_BITS; modify the loop around
base.pow(mid) (referencing ilog, k_max, base.pow(mid), and bigint) to first
estimate candidate bit-size (e.g., compute base_bits = base.bits() or similar
and check mid.saturating_mul(base_bits) > MAX_BIGINT_BITS) or compare mid *
base_bits against bigint.bits(), and if that estimate shows the candidate would
be too large treat it as "too large" by setting hi = mid - 1 (keeping the
special-case for mid == 0 to avoid underflow); only call base.pow(mid) when the
estimated bits are within limits so AllocFailure/OOM is avoided.
- Around line 162-190: The parse function currently converts arbitrary-length
decimal strings into BigInt without checking MAX_BIGINT_BITS; add a preflight
size check in the parse(text: &str) function after extracting `digits` that
computes an upper-bound bit length (e.g., estimated_bits = digits.len() as f64 *
LOG2_10 where LOG2_10 ≈ 3.321928094887362) and rejects input when estimated_bits
exceeds MAX_BIGINT_BITS minus a small safety margin, returning the appropriate
allocation failure error (use the same error variant your codebase uses for
oversized bigints, e.g., VmBamlError::AllocFailure or similar) instead of
calling BigInt::parse_bytes for those cases so huge digit strings cannot bypass
the allocator limits.
- Around line 90-108: The pre-flight estimate uses estimated_bits = base_bits *
exp which overestimates and rejects valid inputs; replace it with the tight
lower bound (bits(base) - 1) * exp + 1. Concretely, after computing base_bits
and exp_u32, compute estimated_bits = if base_bits == 0 { 0 } else {
base_bits.saturating_sub(1).saturating_mul(u64::from(exp_u32)).saturating_add(1)
} and then compare that against MAX_BIGINT_BITS; keep the existing
alloc_failure_panic paths and final Ok(Arc::new(bigint.pow(exp_u32))). Use the
symbols base_bits, exp_u32, estimated_bits, MAX_BIGINT_BITS, bigint.pow and
alloc_failure_panic to locate and change the logic.

---

Nitpick comments:
In `@baml_language/crates/bex_vm/src/package_baml/bigint.rs`:
- Around line 207-226: The current byte-granular rejection sampling in
bigint.random wastes entropy for ranges just above byte boundaries; change it to
bit-granular sampling by computing let bits = range.bits(), let byte_len = (bits
+ 7) / 8, draw byte_len bytes, then mask the top byte to keep only bits % 8 high
bits (no mask if bits % 8 == 0) before constructing BigUint; keep the same
rejection condition (sample < range) and return BigInt::from(sample) +
lower.as_ref() wrapped in Arc as before; update the buffer size and masking
logic in the function (look for bigint.random / the loop using
range.to_bytes_be(), buf, getrandom::getrandom, BigUint::from_bytes_be, and the
return constructing BigInt) so acceptance rate is >=50% and iterations are
bounded.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b24a104f-fb91-4826-8a85-4b464a7d0ee7

📥 Commits

Reviewing files that changed from the base of the PR and between 1d9346c and 303ea0c.

⛔ Files ignored due to path filters (8)
  • baml_language/Cargo.lock is excluded by !**/*.lock
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_arith/baml_tests__compiles__bigint_arith__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_arith/baml_tests__compiles__bigint_arith__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_arith/baml_tests__compiles__bigint_arith__06_codegen.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_cmp/baml_tests__compiles__bigint_cmp__04_5_mir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_cmp/baml_tests__compiles__bigint_cmp__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/bigint_cmp/baml_tests__compiles__bigint_cmp__06_codegen.snap is excluded by !**/*.snap
  • baml_language/sdks/python/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler2_tir/Cargo.toml
  • baml_language/crates/baml_compiler2_tir/src/builder.rs
  • baml_language/crates/baml_tests/tests/bigints.rs
  • baml_language/crates/baml_type/src/lib.rs
  • baml_language/crates/bex_engine/Cargo.toml
  • baml_language/crates/bex_engine/src/conversion.rs
  • baml_language/crates/bex_engine/src/lib.rs
  • baml_language/crates/bex_project/src/bex.rs
  • baml_language/crates/bex_vm/src/package_baml/bigint.rs
  • baml_language/sdks/python/tests/test_bigint.py
✅ Files skipped from review due to trivial changes (2)
  • baml_language/crates/bex_engine/Cargo.toml
  • baml_language/crates/baml_compiler2_tir/Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (7)
  • baml_language/crates/bex_engine/src/lib.rs
  • baml_language/crates/bex_project/src/bex.rs
  • baml_language/sdks/python/tests/test_bigint.py
  • baml_language/crates/baml_type/src/lib.rs
  • baml_language/crates/baml_compiler2_tir/src/builder.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_tests/tests/bigints.rs

2kai2kai2 added 3 commits May 14, 2026 17:11
While we had just a bigint, this adds the literal version, similar to `BamlLiteralInt` and others.
While somewhat unlikely, we should not attempt to allocate a Very Large bigint literal.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant