Skip to content

feat(literals): hex, octal, and binary int/bigint literals with underscore separators - #4065

Merged
codeshaunted merged 2 commits into
canaryfrom
avery/int-literal-bases
Jul 16, 2026
Merged

codeshaunted merged 2 commits into
canaryfrom
avery/int-literal-bases

Conversation

@codeshaunted

@codeshaunted codeshaunted commented Jul 16, 2026 •

Copy link
Copy Markdown
Contributor

What

Adds `0xFF`, `0o755`, `0b1010` integer and bigint (`0xFFn`) literals, plus `_` digit separators in all numeric literals (`1_000_000`, `1_000.5`, `1e1_0`, `1_000n`), following Rust's semantics.

How

Studied rustc's implementation and ported its architecture (no code copied — ours is logos-based):

  • Lexer over-accepts so malformed literals stay one token with a good span: `0b`/`0o` consume any decimal digits (`0b123` is a single token), prefixes also match uppercase (`0X1F`), and a bare prefix (`0x`) still lexes as a literal.
  • `baml_base::num_lit` is the single validator/parser for all literal token text. Every compile-time consumer (expr lowering, type-level literals, config items, test args) goes through it. Float text is underscore-normalized at AST lowering so downstream `f64` parsing is untouched.
  • New E0152 (HIR phase): uppercase base prefix (suggests the lowercase spelling), no valid digits, invalid digit for the base (one diagnostic per offending digit with a one-character span, rustc-style), and integer literals above `i64::MAX`.
  • `diags` are threaded through `lower_type_expr` so type-level literals (`0x1 | 0x2`) validate too; the CST literal accessors now return the raw signed token instead of parsed values.
  • Config/test-arg duck-typing contexts parse the new forms silently and still fall back to string.

Bug fixed along the way

`let x = 99999999999999999999` previously lowered silently to 0 (`parse::().unwrap_or(0)`). It now errors with E0152 "integer literal is too large". The i63 `int` range check is unchanged (TIR E0150), so the two layers compose: > 2^62-1 → E0150, > i64::MAX → E0152.

Deliberate divergence from Rust

A float exponent must start with a digit: `1e_1` is not a float (`1e1_` is fine). logos miscompiles a leading `_*` loop in the exponent pattern and breaks backtracking for plain `1e10`/`1e`. Documented on the `FloatLiteral` regex.

Tests

Ported rustc's numeric-literal test corpus:

  • lexer unit tests (token shapes, munch boundaries like `0xFF.to_string()` and `1._5`)
  • `num_lit` unit tests (all four error classes, per-base overflow, bigints beyond i64)
  • new `diagnostic_errors/invalid_numeric_literals` project (mirrors `lex-bad-numeric-literals.rs`, `lex-bad-binary-literal.rs`, `lex-bad-octal-literal.rs`, incl. type-level literals)
  • run-pass fixtures in `ns_ints`/`ns_bigints`/`ns_floats`: hex bitwise ops, `int.max_value()` written as `0x3FFF_FFFF_FFFF_FFFF`, a 2^80-1 hex bigint, underscore floats

Three pre-existing lexer snapshots changed benignly: string interiors like `"05_diagnostics"` now tokenize as `05_` + `diagnostics` instead of `05` + `_diagnostics`; parser reassembly is identical (no parser/diagnostics snapshots moved).

Summary by CodeRabbit

  • New Features

    • Added support for hexadecimal, octal, and binary integer and bigint literals.
    • Added underscore separators in integer, bigint, and floating-point literals.
    • Added negative prefixed numeric literals and improved large bigint handling.
    • Added precise compiler errors for invalid numeric formats, digits, prefixes, and overflow.
  • Documentation

    • Documented supported numeric literal formats and separators.
  • Tests

    • Added comprehensive coverage for valid literals, invalid syntax, diagnostics, and numeric edge cases.

…score separators

Add 0x/0o/0b integer and bigint literals plus _ digit separators in all
numeric literals, following Rust's semantics and rustc's validation
architecture:

- The lexer deliberately over-accepts (0b/0o eat any decimal digits,
  prefixes also match uppercase, a bare 0x is one token) so malformed
  literals stay single tokens with good spans.
- baml_base::num_lit is the single validator/parser for all literal
  token text; underscores are stripped there, and float text is
  normalized during AST lowering so downstream f64 parsing is unchanged.
- New E0152 covers uppercase prefixes (with the fixed spelling), no
  digits, invalid digits (one diagnostic per digit, rustc-style), and
  integer literals above i64::MAX, which previously lowered silently
  to 0 via unwrap_or(0). The i63 int range check stays in TIR (E0150).
- diags are threaded through lower_type_expr so type-level literals
  (0x1 | 0x2) validate too; the syntax literal accessors now return the
  raw token instead of parsed values.
- Config and test-arg duck-typing contexts parse the new forms silently
  and still fall back to string.

One deliberate divergence from Rust: a float exponent must start with a
digit (1e_1 is not a float) because logos miscompiles a leading _* loop
in the exponent, breaking backtracking for plain 1e10.

Tests port rustc's numeric-literal corpus: lexer and num_lit unit
tests, a diagnostic_errors/invalid_numeric_literals project, and
run-pass fixtures in ns_ints/ns_bigints/ns_floats (hex bitwise ops,
0xFF.to_string() munch boundary, int.max_value() in hex, 2^80-1
bigint).
@vercel

vercel Bot commented Jul 16, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
beps Ready Ready Preview, Comment Jul 16, 2026 9:32pm
promptfiddle2 Ready Ready Preview, Comment Jul 16, 2026 9:32pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
promptfiddle Skipped Skipped Jul 16, 2026 9:32pm

Request Review

@vercel
vercel Bot temporarily deployed to Preview – beps July 16, 2026 20:59 Inactive
@github-actions

Copy link
Copy Markdown

⏭️ Performance benchmarks were skipped

Perf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to canary/main.

To run them on this PR, do any of the following, then push a commit (or re-run CI):

  • Add RUN_CODSPEED=1 to the PR description, or
  • Include run-perf or /perf in the PR title or any commit message.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • baml_language/crates/baml_cli/src/snapshots/baml_cli__describe_command_tests__render_builtin_package_listing.snap is excluded by !**/*.snap

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5f57e513-efd4-43ce-9955-283b74fab747

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Numeric literal handling now supports base prefixes and underscore separators across lexing, shared parsing, AST lowering, type lowering, diagnostics, documentation, and regression tests. Invalid literals produce structured E0152 diagnostics with targeted spans.

Changes

Numeric literal support

Layer / File(s) Summary
Token and syntax literal contracts
baml_language/crates/baml_compiler_lexer/src/tokens.rs, baml_language/crates/baml_compiler_syntax/src/ast.rs
Lexer patterns accept prefixed and underscore-separated numeric forms, while syntax accessors preserve signed literal tokens for downstream parsing.
Shared numeric parsing utilities
baml_language/crates/baml_base/src/num_lit.rs, baml_language/crates/baml_base/src/lib.rs, baml_language/crates/baml_builtins2/baml_std/baml/*
Shared utilities validate integer and bigint literals, parse supported bases, detect invalid digits and overflow, and normalize float separators.
Diagnostic-aware literal lowering
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_config_item.rs, baml_language/crates/baml_compiler2_ast/src/lower_type_expr.rs, baml_language/crates/baml_compiler2_ast/src/lowering_diagnostic.rs, baml_language/crates/baml_compiler_diagnostics/src/diagnostic.rs
Expression, configuration, and numeric type literals use centralized parsing and emit structured invalid-literal diagnostics.
Type lowering diagnostic propagation
baml_language/crates/baml_compiler2_ast/src/lower_cst.rs, baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs, baml_language/crates/baml_compiler2_ast/src/lower_type_expr.rs
Diagnostic sinks are threaded through generic bounds, type expressions, projections, patterns, aliases, signatures, and generic arguments.
Language and diagnostic regression coverage
baml_language/crates/baml_tests/baml_src/ns_bigints/bigints.baml, baml_language/crates/baml_tests/baml_src/ns_floats/floats.baml, baml_language/crates/baml_tests/baml_src/ns_ints/ints.baml, baml_language/crates/baml_tests/projects/diagnostic_errors/invalid_numeric_literals/*
Tests cover valid numeric formats, separators, bigint values, float normalization, token boundaries, invalid digits, missing digits, uppercase prefixes, and overflow.

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

Sequence Diagram(s)

sequenceDiagram
  participant Source
  participant Lexer
  participant ASTLowering
  participant Diagnostics
  Source->>Lexer: tokenize numeric literal
  Lexer->>ASTLowering: preserve literal token text
  ASTLowering->>ASTLowering: parse and normalize numeric value
  ASTLowering->>Diagnostics: emit E0152 for invalid literal
Loading

Possibly related PRs

Suggested reviewers: sxlijin

Poem

I’m a rabbit counting digits bright,
In hex and binary moonlit night.
Underscores hop, floats gleam,
Bigints stretch beyond the stream.
Bad little tokens earn a sign—
E0152 keeps the burrow fine.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding hex, octal, and binary numeric literals with underscore separators.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch avery/int-literal-bases

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 July 16, 2026 21:06 Inactive

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
baml_language/crates/baml_base/src/num_lit.rs (1)

133-134: 🎯 Functional Correctness | 🔵 Trivial

Run cargo test --lib from baml_language/

The repository root has no Cargo.toml; use baml_language/Cargo.toml (or --manifest-path) so the unit tests actually run.

🤖 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_base/src/num_lit.rs` around lines 133 - 134, Update
the test workflow for the tests module in num_lit.rs to run cargo test --lib
from the baml_language manifest, using baml_language/Cargo.toml or
--manifest-path instead of the repository root.

Source: Coding guidelines

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

Nitpick comments:
In `@baml_language/crates/baml_base/src/num_lit.rs`:
- Around line 133-134: Update the test workflow for the tests module in
num_lit.rs to run cargo test --lib from the baml_language manifest, using
baml_language/Cargo.toml or --manifest-path instead of the repository root.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4c4ec820-b9d1-43ca-9004-837563fff1d1

📥 Commits

Reviewing files that changed from the base of the PR and between 06f82ed and e9cb4df.

⛔ Files ignored due to path filters (15)
  • baml_language/crates/baml_tests/snapshots/baml_src/bigints.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/floats.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/baml_src/ints.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/catch_arm_return/baml_tests__compiles__catch_arm_return__01_lexer__catch_arm_return.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/compiles/numeric_literal_method_call/baml_tests__compiles__numeric_literal_method_call__01_lexer__numeric_literal_method_call.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/invalid_numeric_literals/baml_tests__diagnostic_errors__invalid_numeric_literals__01_lexer__negative.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/invalid_numeric_literals/baml_tests__diagnostic_errors__invalid_numeric_literals__01_lexer__positive.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/invalid_numeric_literals/baml_tests__diagnostic_errors__invalid_numeric_literals__02_parser__negative.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/invalid_numeric_literals/baml_tests__diagnostic_errors__invalid_numeric_literals__02_parser__positive.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/invalid_numeric_literals/baml_tests__diagnostic_errors__invalid_numeric_literals__03_hir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/invalid_numeric_literals/baml_tests__diagnostic_errors__invalid_numeric_literals__04_tir.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/invalid_numeric_literals/baml_tests__diagnostic_errors__invalid_numeric_literals__05_diagnostics.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/invalid_numeric_literals/baml_tests__diagnostic_errors__invalid_numeric_literals__10_formatter__negative.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/invalid_numeric_literals/baml_tests__diagnostic_errors__invalid_numeric_literals__10_formatter__positive.snap is excluded by !**/*.snap
  • baml_language/crates/baml_tests/snapshots/diagnostic_errors/json_static_method_arity/baml_tests__diagnostic_errors__json_static_method_arity__01_lexer__json_static_method_arity.snap is excluded by !**/*.snap
📒 Files selected for processing (19)
  • baml_language/crates/baml_base/src/lib.rs
  • baml_language/crates/baml_base/src/num_lit.rs
  • baml_language/crates/baml_builtins2/baml_std/baml/bigint.baml
  • baml_language/crates/baml_builtins2/baml_std/baml/int.baml
  • baml_language/crates/baml_compiler2_ast/src/lib.rs
  • baml_language/crates/baml_compiler2_ast/src/lower_config_item.rs
  • baml_language/crates/baml_compiler2_ast/src/lower_cst.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_ast/src/lowering_diagnostic.rs
  • baml_language/crates/baml_compiler_diagnostics/src/diagnostic.rs
  • baml_language/crates/baml_compiler_lexer/src/tokens.rs
  • baml_language/crates/baml_compiler_syntax/src/ast.rs
  • baml_language/crates/baml_compiler_syntax/src/tests.rs
  • baml_language/crates/baml_tests/baml_src/ns_bigints/bigints.baml
  • baml_language/crates/baml_tests/baml_src/ns_floats/floats.baml
  • baml_language/crates/baml_tests/baml_src/ns_ints/ints.baml
  • baml_language/crates/baml_tests/projects/diagnostic_errors/invalid_numeric_literals/negative.baml
  • baml_language/crates/baml_tests/projects/diagnostic_errors/invalid_numeric_literals/positive.baml

@github-actions

github-actions Bot commented Jul 16, 2026 •

Copy link
Copy Markdown

Binary size checks passed

✅ 7 passed

Artifact Platform File Gzip Gated on Baseline Delta Status
✅ baml-cli Linux 🔒 23.1 MB 9.8 MB file 22.8 MB +308.6 KB (+1.4%) OK
✅ packed-program Linux 🔒 16.4 MB 6.9 MB file 16.3 MB +158.1 KB (+1.0%) OK
✅ baml-cli macOS 🔒 17.8 MB 8.6 MB file 17.6 MB +252.5 KB (+1.4%) OK
✅ packed-program macOS 🔒 12.8 MB 6.0 MB file 12.6 MB +116.3 KB (+0.9%) OK
✅ baml-cli Windows 🔒 19.3 MB 8.7 MB file 19.2 MB +115.7 KB (+0.6%) OK
✅ packed-program Windows 🔒 13.7 MB 6.1 MB file 13.5 MB +136.6 KB (+1.0%) OK
✅ bridge_wasm WASM 15.3 MB 🔒 4.3 MB gzip 4.3 MB +29.9 KB (+0.7%) OK

🔒 = the size this artifact is GATED on (ceiling + delta). Binaries gate on file size (installed binary); WASM gates on gzip (download size). The other size is shown for information only.


Generated by cargo size-gate · workflow run

@vercel
vercel Bot temporarily deployed to Preview – promptfiddle July 16, 2026 21:21 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle July 16, 2026 21:24 Inactive
@vercel
vercel Bot temporarily deployed to Preview – beps July 16, 2026 21:25 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 July 16, 2026 21:32 Inactive
@codeshaunted
codeshaunted added this pull request to the merge queue Jul 16, 2026
Merged via the queue into canary with commit c67c61e Jul 16, 2026
51 checks passed
@codeshaunted
codeshaunted deleted the avery/int-literal-bases branch July 16, 2026 22:22

This branch was previously deployed

3 inactive deployments
Preview – promptfiddle2 — 002a47c9 Deployed Jul 16, 2026 by vercel[bot]
Preview – beps — 002a47c9 Deployed Jul 16, 2026 by vercel[bot]
Preview – promptfiddle — 002a47c9 Deployed Jul 16, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant