Skip to content

Commit 30a327f

Browse files
olwangclaude
andcommitted
feat(selfhost): AST producer reaches 619/619 byte-exact — full corpus parity
Closes the final long-tail mismatches; the self-hosted streaming AST dump now matches the Rust oracle byte-for-byte across the entire corpus (0 run-failures): - scoped-view desugar: `view v = expr` + rest-of-block → `with v { … }` handled in the block statement loop (collect_statements), capturing trailing stmts. - match arms: skip `,`/`;` separators so a trailing comma after an arm's `{ }` block isn't miscounted as a malformed (no-`=>`) arm. - effect-annotated closure `read || { … }`: special-cased BEFORE the binary split (mirrors parse_expr) so the `||` isn't torn apart as logical-or; the general `read <expr>` effect stays AFTER the split (effect binds tighter than any operator — `read r * read r` = `(read r) * (read r)`). Floor 609 -> 619. Corpus samples added for the fixed constructs so they stay guarded by the fast ast_parity_samples gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 427cf06 commit 30a327f

8 files changed

Lines changed: 222 additions & 9 deletions

File tree

crates/rsscript/src/selfhost_parity.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1522,7 +1522,7 @@ const AST_SAMPLE_MIN: usize = 6;
15221522
/// Floor for `ast_parity_corpus` — the number of corpus files whose rss AST dump
15231523
/// already matches the oracle byte-for-byte. Ratchets up as the producer's
15241524
/// coverage grows; a drop signals a regression. (Full parity = files.len().)
1525-
const AST_CORPUS_PARITY_FLOOR: usize = 609;
1525+
const AST_CORPUS_PARITY_FLOOR: usize = 619;
15261526

15271527
/// Step-2 measurement gate (ignored by default): how many corpus files the rss
15281528
/// producer reproduces byte-for-byte. Not full parity yet — this ratchets a floor

selfhost/astdump.rss

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,17 @@ fn emit_expr(toks: read List<Tok>, start: read Int, end: read Int, d: read Int)
277277
emit_explicit_fn(toks: read toks, start: read s, end: read e, d: read d)
278278
return Unit
279279
}
280+
// Effect-annotated closure `read || { … }` / `mut |x| …`: handled BEFORE the
281+
// binary split so the closure `|` pipes aren't read as logical-or / bit-or.
282+
// (parse_expr special-cases ONLY effect+closure here; a general `read <expr>`
283+
// like `read r * read r` still splits at the operator first — effect binds
284+
// tighter than any binary op.)
285+
let effClosure = effect_kw(toks: read toks, i: read s)
286+
if String.len(value: read effClosure) > 0 && at_symbol(toks: read toks, i: read (s + 1), code: read SYM_PIPE) {
287+
emit(depth: read d, s: read String.concat(left: read "effect kind=", right: read effClosure))
288+
emit_closure(toks: read toks, start: read (s + 1), end: read e, d: read (d + 1))
289+
return Unit
290+
}
280291
// Binary split (lowest precedence, last top-level occurrence).
281292
let opIdx = find_binop(toks: read toks, start: read s, end: read e)
282293
if opIdx >= 0 {
@@ -315,10 +326,10 @@ fn emit_expr(toks: read List<Tok>, start: read Int, end: read Int, d: read Int)
315326
return Unit
316327
}
317328
}
318-
// Effect prefix: read/mut/take <value>. The receiver-call shorthand
319-
// `<effect> recv.method(args)` (lowercase receiver) parses as a ReceiverCall,
320-
// NOT an Effect wrapper; an uppercase namespace (`read Cache.put()`) stays a
321-
// qualified call under the effect.
329+
// General effect prefix: read/mut/take <value>, handled AFTER the binary split
330+
// (effect binds tighter than any operator). The receiver-call shorthand
331+
// `<effect> recv.method(args)` (lowercase receiver) parses as a ReceiverCall;
332+
// an uppercase namespace (`read Cache.put()`) stays a qualified call under it.
322333
let effWord = effect_kw(toks: read toks, i: read s)
323334
if String.len(value: read effWord) > 0 {
324335
let recvStart = s + 1
@@ -1694,13 +1705,39 @@ fn find_block_open(toks: read List<Tok>, start: read Int, limit: read Int) -> In
16941705
// Emit a `block` node and its statements for the braces [open, close].
16951706
fn emit_block(toks: read List<Tok>, open: read Int, close: read Int, d: read Int) -> Unit {
16961707
emit(depth: read d, s: read "block")
1697-
let mut i = open + 1
1708+
emit_stmts(toks: read toks, from: read (open + 1), close: read close, d: read (d + 1))
1709+
return Unit
1710+
}
1711+
1712+
// True if a scoped-view binding `view <ident> = …` begins at i (is_view_binding).
1713+
fn is_view_binding(toks: read List<Tok>, i: read Int, close: read Int) -> Bool {
1714+
return is_kw_text(toks: read toks, i: read i, word: read "view") && is_ident_tok(toks: read toks, i: read (i + 1)) && at_symbol(toks: read toks, i: read (i + 2), code: read SYM_EQ) && (i + 2) < close
1715+
}
1716+
1717+
// Emit statements in [from, close) at depth d. A `view name = expr` desugars to a
1718+
// `with expr as name { <rest of block> }` that captures the remaining statements
1719+
// (collect_statements' scoped-view desugar).
1720+
fn emit_stmts(toks: read List<Tok>, from: read Int, close: read Int, d: read Int) -> Unit {
1721+
let mut i = from
16981722
while i < close {
1699-
// Skip stray semicolons/commas that are statement trivia.
17001723
if at_symbol(toks: read toks, i: read i, code: read SYM_SEMI) {
17011724
i = i + 1
1725+
} else if is_view_binding(toks: read toks, i: read i, close: read close) {
1726+
let name = tk_text(toks: read toks, i: read (i + 1))
1727+
let stEnd = stmt_end(toks: read toks, start: read i, limit: read close)
1728+
let eqPos = first_eq(toks: read toks, start: read (i + 2), end: read stEnd)
1729+
emit(depth: read d, s: read String.concat(left: read "with binding=", right: read name))
1730+
emit(depth: read (d + 1), s: read "resource")
1731+
if eqPos >= 0 {
1732+
emit_expr(toks: read toks, start: read (eqPos + 1), end: read stEnd, d: read (d + 2))
1733+
} else {
1734+
emit(depth: read (d + 2), s: read "unknown-expr")
1735+
}
1736+
emit(depth: read (d + 1), s: read "block")
1737+
emit_stmts(toks: read toks, from: read stEnd, close: read close, d: read (d + 2))
1738+
i = close
17021739
} else {
1703-
let next = emit_stmt(toks: read toks, start: read i, limit: read close, d: read (d + 1))
1740+
let next = emit_stmt(toks: read toks, start: read i, limit: read close, d: read d)
17041741
if next > i { i = next } else { i = i + 1 }
17051742
}
17061743
}
@@ -2495,7 +2532,9 @@ fn emit_match_arms(toks: read List<Tok>, start: read Int, end: read Int, d: read
24952532
let mut i = start
24962533
let mut mal = 0
24972534
while i < end {
2498-
if at_symbol(toks: read toks, i: read i, code: read SYM_SEMI) {
2535+
// Skip separators between arms (`,`/`;`) so a trailing comma after an arm's
2536+
// `{ }` block isn't misread as an arm with no `=>`.
2537+
if at_symbol(toks: read toks, i: read i, code: read SYM_SEMI) || at_symbol(toks: read toks, i: read i, code: read SYM_COMMA) {
24992538
i = i + 1
25002539
} else {
25012540
let lineEnd = next_line_or_block_end(toks: read toks, start: read i, end: read end)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
features: async
2+
3+
async fn nested_if() -> Result<Unit, TimerError> {
4+
if true {
5+
await Timer.sleep(ms: 1)?
6+
}
7+
return Ok(Unit)
8+
}
9+
10+
async fn nested_loop() -> Result<Unit, TimerError> {
11+
let mut count = 0
12+
while count < 1 {
13+
await Timer.sleep(ms: 1)?
14+
count = count + 1
15+
}
16+
return Ok(Unit)
17+
}
18+
19+
async fn nested_match(value: read Option<Int>) -> Result<Unit, TimerError> {
20+
match value {
21+
Some(_) => {
22+
await Timer.sleep(ms: 1)?
23+
},
24+
None => {
25+
await Timer.sleep(ms: 1)?
26+
},
27+
}
28+
return Ok(Unit)
29+
}
30+
31+
async fn main() -> Result<Unit, TimerError> {
32+
await nested_if()?
33+
await nested_loop()?
34+
await nested_match(value: read Some(1))?
35+
await nested_match(value: read None)?
36+
return Ok(Unit)
37+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
struct Image
2+
struct Callback
3+
4+
fn store(callback: read Callback) -> Unit
5+
effects(retains(callback))
6+
7+
fn schedule_image(image: read Image) -> Unit {
8+
store(callback: read || {
9+
Image.inspect(image: read image)
10+
})
11+
12+
return Unit
13+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// expect: RS0801
2+
features: local
3+
4+
class Scheduler {
5+
callbacks: List<Callback>
6+
}
7+
8+
struct Image {
9+
pixels: Buffer
10+
}
11+
12+
fn schedule(scheduler: mut Scheduler, callback: read Callback) -> Unit
13+
effects(retains(callback))
14+
{
15+
}
16+
17+
fn bad_schedule(scheduler: mut Scheduler, path: read Path) -> Unit {
18+
local image = Image.load(path: read path)
19+
schedule(
20+
scheduler: mut scheduler,
21+
callback: read || {
22+
Image.inspect(image: read image)
23+
},
24+
)
25+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// JIT hot path: native parent calls a compiled child with a Bool parameter and
2+
// Bool return, then consumes that result in native control flow. This pins the
3+
// staged native-call ABI for Bool values instead of relying on the Int encoding
4+
// accidentally working.
5+
6+
fn bench_size(default: Int) -> Int {
7+
let raw = Args.get_or_default(index: 0, default: read String.from_int(value: default))
8+
match String.parse_int(value: read raw) {
9+
Some(value) => {
10+
return value
11+
}
12+
None => {
13+
return default
14+
}
15+
}
16+
}
17+
18+
fn matches(flag: Bool, value: Int) -> Bool {
19+
let over = value > 10
20+
return flag == over
21+
}
22+
23+
fn hot(limit: Int) -> Int {
24+
let mut i = 0
25+
let mut total = 0
26+
while i < limit {
27+
let ok = matches(flag: read true, value: read i % 23)
28+
if ok {
29+
total = total + i
30+
} else {
31+
total = total - 1
32+
}
33+
i = i + 1
34+
}
35+
return total
36+
}
37+
38+
fn main() -> Unit {
39+
let limit = bench_size(default: 750000)
40+
let result = hot(limit: limit)
41+
Log.write(message: read String.from_int(value: result))
42+
return Unit
43+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
sum Shape {
2+
Circle(radius: Int)
3+
Rectangle(width: Int, height: Int)
4+
Prism(width: Int, height: Int, depth: Int)
5+
}
6+
7+
// Two-field positional binding.
8+
fn area(shape: read Shape) -> Int {
9+
match read shape {
10+
Circle(r) => { return read r * read r * 3 }
11+
Rectangle(w, h) => { return read w * read h }
12+
Prism(w, h, d) => { return read w * read h * read d }
13+
}
14+
}
15+
16+
// Three-field positional binding with an ignored middle position.
17+
fn width_times_depth(shape: read Shape) -> Int {
18+
match read shape {
19+
Circle(r) => { return read r }
20+
Rectangle(w, _) => { return read w }
21+
Prism(w, _, d) => { return read w * read d }
22+
}
23+
}
24+
25+
// Named field patterns produce the identical result as positional binding.
26+
fn area_named(shape: read Shape) -> Int {
27+
match read shape {
28+
Circle { radius } => { return read radius * read radius * 3 }
29+
Rectangle { width, height } => { return read width * read height }
30+
Prism { width, height, depth } => { return read width * read height * read depth }
31+
}
32+
}
33+
34+
fn main() -> Unit {
35+
let r = Rectangle(width: 3, height: 4)
36+
let a = area(shape: read r)
37+
let b = area_named(shape: read r)
38+
let p = Prism(width: 2, height: 3, depth: 5)
39+
let c = area(shape: read p)
40+
let d = width_times_depth(shape: read p)
41+
return Unit
42+
}

selfhost/samples/ast/x_view.rss

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// expect: RS0702
2+
// A scoped-view binding (`view name = expr`, spec §20.2-1) desugars to a
3+
// `with`-lease whose scope ends at the enclosing block, so it cannot escape via
4+
// return — same rule as any `with` resource.
5+
features: local
6+
7+
fn bad(data: read Bytes) -> BytesView {
8+
view v = Bytes.view(value: read data, start: 0, len: 1)
9+
return v
10+
}
11+
12+
fn main() -> Unit {
13+
return Unit
14+
}

0 commit comments

Comments
 (0)