Skip to content

Commit 51c2141

Browse files
olwangclaude
andcommitted
feat(selfhost): AST producer — native bodies, unknown-top-level, malformed with/struct → 581/603
- Native fns with a body (body on a line after `effects(native)`) now report has-body=true: the stale same_stmt_line guard is gone (the sigEnd-based detection already handles body-less native decls correctly). - unknown-top-level markers (section 5, before malformed-declaration) for unrecognized top-level items: `namespace …` and the removed `profile …`. - Nameless `struct {` → program-level malformed-declaration (type_decl_malformed). - `with … as {` (no binding) → malformed-with: the binding after `as` must be an ident immediately before the block (mirrors parse_with_stmt). Corpus reach: 565 -> 581 / 603 byte-exact (+16, ~96.4%), still 0 run-failures. Floor -> 581. 48 samples green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8858ae3 commit 51c2141

8 files changed

Lines changed: 89 additions & 5 deletions

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 = 565;
1525+
const AST_CORPUS_PARITY_FLOOR: usize = 581;
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: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1350,6 +1350,12 @@ fn emit_with(toks: read List<Tok>, start: read Int, limit: read Int, d: read Int
13501350
return stmt_end(toks: read toks, start: read start, limit: read limit)
13511351
}
13521352
let close = find_matching(toks: read toks, open: read open, openSym: read SYM_LBRACE, closeSym: read SYM_RBRACE)
1353+
// The binding after `as` must be an ident immediately before the block `{`
1354+
// (`as {` with no binding is malformed) — mirrors parse_with_stmt.
1355+
if is_ident_tok(toks: read toks, i: read (asIdx + 1)) == false || (asIdx + 2) != open {
1356+
emit(depth: read d, s: read "malformed-with")
1357+
return close + 1
1358+
}
13531359
let binding = tk_text(toks: read toks, i: read (asIdx + 1))
13541360
emit(depth: read d, s: read String.concat(left: read "with binding=", right: read binding))
13551361
emit(depth: read (d + 1), s: read "resource")
@@ -1534,7 +1540,10 @@ fn emit_function(toks: read List<Tok>, start: read Int, d: read Int) -> Int {
15341540
let sigEnd = function_signature_end(toks: read toks, start: read (pclose + 1))
15351541
let mut bodyOpen = -1
15361542
if at_symbol(toks: read toks, i: read sigEnd, code: read SYM_LBRACE) { bodyOpen = sigEnd }
1537-
let hasBody = bodyOpen >= 0 && (isNative == false || same_stmt_line(toks: read toks, a: read start, b: read bodyOpen))
1543+
// A body exists iff sigEnd lands on `{` — including native fns, whose body may
1544+
// sit on a line after the `effects(native)` clause (parse_function_decl treats
1545+
// it as a normal body; the earlier same_stmt_line guard wrongly dropped it).
1546+
let hasBody = bodyOpen >= 0
15381547
let effPos = find_effects_kw(toks: read toks, start: read (pclose + 1), end: read sigEnd)
15391548
let defaultImpl = has_default_impl(toks: read toks, start: read (pclose + 1), end: read sigEnd)
15401549
// A `fresh` anywhere in the return-type region (e.g. `-> Result<fresh String, E>`)
@@ -2560,6 +2569,7 @@ fn main() -> Unit {
25602569
emit_all_features(toks: read toks, ntok: read ntok)
25612570
let mut i = 0
25622571
let mut malDecl = 0
2572+
let mut unknownTop = 0
25632573
let mut running = true
25642574
while running {
25652575
if i >= ntok {
@@ -2574,9 +2584,19 @@ fn main() -> Unit {
25742584
// Already emitted in the pre-pass; skip the header line.
25752585
i = declaration_line_end(toks: read toks, start: read (i + 2))
25762586
} else if cls.isProfile {
2587+
// `profile …` is no longer a recognized top-level item → the oracle
2588+
// records it as unknown-top-level (section 5).
2589+
unknownTop = unknownTop + 1
25772590
i = declaration_line_end(toks: read toks, start: read i)
25782591
} else if starts_type_decl(toks: read toks, i: read i, isPub: read cls.isPub) {
2579-
i = advance(toks: read toks, i: read i, r: read emit_type_decl(toks: read toks, start: read i, d: read 1))
2592+
// A struct/class/resource with no name (`struct {`) fails to parse →
2593+
// program-level malformed-declaration.
2594+
if type_decl_malformed(toks: read toks, start: read i) {
2595+
malDecl = malDecl + 1
2596+
i = advance(toks: read toks, i: read i, r: read next_decl_skip(toks: read toks, start: read i))
2597+
} else {
2598+
i = advance(toks: read toks, i: read i, r: read emit_type_decl(toks: read toks, start: read i, d: read 1))
2599+
}
25802600
} else if cls.isSum {
25812601
i = advance(toks: read toks, i: read i, r: read emit_sum(toks: read toks, start: read i, d: read 1))
25822602
} else if cls.isTypeAlias {
@@ -2603,17 +2623,30 @@ fn main() -> Unit {
26032623
i = advance(toks: read toks, i: read i, r: read emit_function(toks: read toks, start: read i, d: read 1))
26042624
}
26052625
} else {
2626+
// Unrecognized top-level token (e.g. `namespace …`) → unknown-top-level.
2627+
unknownTop = unknownTop + 1
26062628
i = advance(toks: read toks, i: read i, r: read next_decl_skip(toks: read toks, start: read i))
26072629
}
26082630
}
26092631
}
2610-
// Section 5: feature diagnostics (unknown-feature then duplicate-feature),
2611-
// then malformed-declaration, emitted AFTER all items — matching ast_oracle_dump.
2632+
// Section 5, in ast_oracle_dump order: unknown-feature, duplicate-feature (both
2633+
// in emit_feature_diags), then unknown-top-level, then malformed-declaration.
26122634
emit_feature_diags(toks: read toks, ntok: read ntok)
2635+
emit_repeat(marker: read "unknown-top-level", n: read unknownTop, d: read 1)
26132636
emit_repeat(marker: read "malformed-declaration", n: read malDecl, d: read 1)
26142637
return Unit
26152638
}
26162639

2640+
// True if a struct/class/resource declaration at `start` has no name (`struct {`),
2641+
// which parse_type_decl rejects → program-level malformed-declaration.
2642+
fn type_decl_malformed(toks: read List<Tok>, start: read Int) -> Bool {
2643+
let mut j = start
2644+
if is_kw_text(toks: read toks, i: read j, word: read "pub") { j = j + 1 }
2645+
if is_kw_text(toks: read toks, i: read j, word: read "opaque") { j = j + 1 }
2646+
j = j + 1
2647+
return is_ident_tok(toks: read toks, i: read j) == false
2648+
}
2649+
26172650
// Position of the function name (just past `fn`) after skipping #attrs and
26182651
// pub/async/native, or -1 if this isn't a fn-like head.
26192652
fn fn_name_start(toks: read List<Tok>, start: read Int) -> Int {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// expect: RS0015
2+
struct {
3+
value: String
4+
}
5+
6+
fn main() -> Unit {
7+
return Unit
8+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// expect: RS0015
2+
3+
fn missing_as(path: read Path) -> Unit {
4+
with File.open(path: read path) {
5+
return Unit
6+
}
7+
}
8+
9+
fn missing_binding(path: read Path) -> Unit {
10+
with File.open(path: read path)? as {
11+
return Unit
12+
}
13+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// expect: RS0015
2+
namespace Json
3+
4+
opaque struct JsonValue
5+
6+
pub fn parse(text: read String) -> Result<fresh JsonValue, JsonError>
7+
8+
fn main() -> Unit {
9+
return Unit
10+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// expect: RS0015
2+
features: native
3+
4+
native fn Host.emit(message: read String) -> Unit
5+
effects(native)
6+
{
7+
return Unit
8+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// expect: RS0101
2+
native fn Host.emit(message: read String) -> Unit
3+
effects(native)
4+
{
5+
return Unit
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// expect: RS0010
2+
profile: review
3+
4+
fn main() -> Unit {
5+
return Unit
6+
}

0 commit comments

Comments
 (0)