Skip to content

t27c: refuse a malformed number literal by name, read 0o as octal (Closes #7319, Closes #2645) - #7421

Closed
gHashTag wants to merge 1 commit into
masterfrom
genc-number-literals
Closed

gHashTag wants to merge 1 commit into
masterfrom
genc-number-literals

Conversation

@gHashTag

@gHashTag gHashTag commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator

Closes #7319
Closes #2645
Part of #5980

What was wrong

#7319. The number lexer takes any run of digits, ., x X b B _ (and hex letters after 0x) as one number, and gen-c wrote what it read. So 010 and 0_10 reached C as the octal constant 8 (a test asserting D == 10 compiled and trapped), 08 and 0b102 did not compile, and 1_, 1__0 and 0x_1F were quietly normalized. All exited 0.

#2645. 0o17 lexed as the number 0 followed by the identifier o17, so const E: u32 = 0o777; was 0. The issue text says this was fixed; on master the probe still writes #define D 0.

What changes

  • The parser refuses a malformed number taken as a value with an error naming the literal, its line and column, and why: a leading zero (C reads it as octal), a digit outside its radix, a _ that is not between two digits, two decimal points, a letter inside a decimal. Example: malformed number literal `010` at line 2:16: a leading zero; C reads it as an octal constant (write `0o` for octal).
  • Only value sites are judged (expressions, const initializers, array dimensions, enum values, switch arm patterns, range bounds). A number inside a hyphenated module name (RUST-04) or a tuple index is not a literal, and the token stream is unchanged, so the lexer conformance table (lex_conform.rs) still holds.
  • A Rust-style typed literal keeps its _ before the suffix: 10_usize, 2.0_f64 and 0x7FFFFFFF_i32 are in the corpus and still pass.
  • A bare 0x before a non-hex letter (0xT27B007, the corpus item The lexer turns 0o777 into 0; and the nested-literal group needs the parser, not the emitter #2645 lists) is unchanged.
  • The lexer reads 0o directly after a lone leading 0 as octal. No two backends spell octal alike (C 017, Zig and Rust 0o17, Verilog none), so a well-formed octal literal is handed on as the decimal it denotes, suffix kept; 0o18, 0o_7, 0o7_ and 0o are refused by name.

The self-hosted core keeps refusing all of these with E_LEX (including 0o); gen-c now refuses them too, or (for octal) writes the right value, so nothing the core accepts changes.

Tests

bootstrap/tests/genc_number_literals.rs, 8 tests: the issue's seven literals refused by name and line; the issue's seven well-formed literals pass and are written as C reads them; fractions and exponents pass; more misplaced separators and foreign digits refused; typed literals and a RUST-04 module segment pass; octal values (0o17 = 15, 0o1_7, 0o0, 0o777 = 511); malformed octal refused; and a generated test that asserts 10, 0o777 == 511 and 1_000 == 1000 is compiled and run.

Lab (t27c-lab): 8 passed on this branch; with compiler.rs and FROZEN_HASH reverted to master, 5 failed (the 3 that pass there are the guards for forms that already worked). core_selfhost passes (fixpoint, fixtures, refusals, corpus differential); the compiler's own unit tests pass (1845).

Corpus

  • t27c gen-c over every tracked spec under specs/, master binary vs this branch: 0 outputs change. No reseal is needed and no seal goes stale.
  • t27c parse over all 1686 tracked .t27 files, master vs branch: 6 go from parsing to refused, all of them bootstrap/tests/fixtures/core_selfhost/refuse/e1_*.t27, the core's own refusal fixtures for exactly these literals (0b102, 1__0, 010, 0_10, 0x_1F, 1_). No spec under specs/ stops parsing. (A first cut that checked every number token also refused prose and module names in 7 specs, 4x4 in a bench description, RUST-04, 1.2.3 inside a mis-escaped string; checking only value sites fixed that, and the typed-suffix rule kept 10_usize and friends.)

Foreign code

This edits Rust (bootstrap/src/compiler.rs + its FROZEN_HASH seal, and a new test file), so it carries owner-approved-foreign and a tools/policy/foreign-exceptions.txt entry naming #7319 and #2645.

🤖 Generated with Claude Code

Closes #7319, Closes #2645)

The number lexer took any run of digits, x X b B _ (and hex letters after
0x) as one number, and gen-c wrote what it read: 010 and 0_10 reached C as
the octal constant 8, 08 and 0b102 did not compile, and 1_, 1__0 and 0x_1F
were quietly normalized. The parser now refuses each by name and line: a
leading zero, a digit outside its radix, a misplaced separator, two
decimal points, a letter inside a decimal.

0o17 lexed as 0 followed by the identifier o17. The lexer now reads an
octal literal after a lone leading 0 and hands it on as the decimal it
denotes, since no two backends spell octal alike.

Part of #5980.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gHashTag

gHashTag commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator Author

Parked: this PR is over the foreign-line budget that #7399 (owner rule 2026-10-07, #7371) put on master while it waited for CI. It is now a draft, with auto-merge off.

check_budget() from master's gen/c/policy/own_language.c, run over git diff --numstat --no-renames origin/master...origin/genc-number-literals, denies it:

  • bootstrap/src/compiler.rs: 159 added lines (limit 40 per file)
  • bootstrap/tests/genc_number_literals.rs: 231 added lines (limit 40 per file)
  • all files: 390 added hand-written Rust lines (limit 80 per diff)

No exception and no label lifts this limit. Splitting the same Rust into 80-line PRs would get around the rule rather than follow it, so I have not done that. The fix itself is still correct and tested on the t27c lab: the regression tests fail on master's compiler and pass with the fix. Landing it needs the literal lowering written in .t27 (gen-rust into bootstrap/gen/rust), or an owner decision.

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-07 10:12:20 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 38
PRs with All Checks Green 12
READY 1
FAILING 38
PENDING 0
NO CHECKS YET 0

These columns do not partition: 1 + 38 + 0 + 0 = 39, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=fa2ac7bac755 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

This was referenced Oct 9, 2026
@gHashTag

Copy link
Copy Markdown
Collaborator Author

A draft since 2026-10-07 (last update 2026-10-07); its base is 1438 commits behind master, and the rules it predates (Only t27 2026-10-05, the foreign-line budget 2026-10-07/09, seal schema v2) would each need a rework before it could merge.

Closed in the PR sweep of 2026-10-19 on the owner's decision (stale drafts from before 2026-10-10); reopen it if the work is still wanted.

🤖 Generated with Claude Code

@gHashTag gHashTag closed this Oct 19, 2026
@gHashTag
gHashTag deleted the genc-number-literals branch October 26, 2026 06:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

owner-approved-foreign Owner-approved exception to the only-t27 rule: hand-written foreign code allowed in this PR

Projects

None yet

1 participant