Skip to content

Commit e30b99f

Browse files
olwangclaude
andcommitted
feat(selfhost): AST producer — Fn types + type prefixes + fresh-return → 405/580
emit_type_ref was rendering only a bare `name` with fresh from a leading `fresh` and hardcoded noescape=false owned=false, and treated a leading `owned`/`noescape` as the type name. Rewrote it to mirror parse_type_ref: - Prefix flags fresh/noescape/owned read from the FIRST token; the name is the first non-prefix token (skipping read/mut/take/fresh/handle/weak/noescape/owned). - `Fn(<params>) -> <ret>` types now emit `fn-param` (with optional read/mut/take effect) + `fn-return` children — e.g. `owned Fn(Int) -> Int`. - Fixed `-> fresh T` return types: `fresh` becomes the fn's returns-fresh and the return TypeRef begins at T (fresh=false), matching parse_function_decl. Corpus reach: 339 -> 405 / 580 byte-exact (+66, ~70%), still 0 run-failures. Floor -> 405. New sample fntypes.rss; 25 samples green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 454c50a commit e30b99f

3 files changed

Lines changed: 57 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 = 339;
1525+
const AST_CORPUS_PARITY_FLOOR: usize = 405;
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: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -955,13 +955,25 @@ fn emit_object_or_map(toks: read List<Tok>, start: read Int, close: read Int, d:
955955
// emit_type_ref renders one type starting at token i and returns the next index.
956956
// -------------------------------------------------------------------------
957957

958+
// True if token j is a type-level prefix keyword skipped when locating the name
959+
// (read/mut/take effects + fresh/handle/weak/noescape/owned), matching name_index
960+
// in parse_type_ref.
961+
fn is_type_prefix(toks: read List<Tok>, j: read Int) -> Bool {
962+
return is_kw_text(toks: read toks, i: read j, word: read "read") || is_kw_text(toks: read toks, i: read j, word: read "mut") || is_kw_text(toks: read toks, i: read j, word: read "take") || is_kw_text(toks: read toks, i: read j, word: read "fresh") || is_kw_text(toks: read toks, i: read j, word: read "handle") || is_kw_text(toks: read toks, i: read j, word: read "weak") || is_kw_text(toks: read toks, i: read j, word: read "noescape") || is_kw_text(toks: read toks, i: read j, word: read "owned")
963+
}
964+
958965
fn emit_type_ref(toks: read List<Tok>, i: read Int, tag: read String, eff: read String, d: read Int) -> Int {
959-
let mut j = i
966+
// Prefix flags reflect ONLY the token at `i` (matching parse_type_ref, which
967+
// reads is_fresh/is_noescape/is_owned from tokens[start]).
960968
let mut isFresh = false
961-
if at_ident(toks: read toks, i: read j, wid: read WORD_FRESH) {
962-
isFresh = true
963-
j = j + 1
964-
}
969+
let mut isNoescape = false
970+
let mut isOwned = false
971+
if at_ident(toks: read toks, i: read i, wid: read WORD_FRESH) { isFresh = true }
972+
else if is_kw_text(toks: read toks, i: read i, word: read "noescape") { isNoescape = true }
973+
else if is_kw_text(toks: read toks, i: read i, word: read "owned") { isOwned = true }
974+
// Name is the first non-prefix token.
975+
let mut j = i
976+
while is_type_prefix(toks: read toks, j: read j) { j = j + 1 }
965977
let name = tk_text(toks: read toks, i: read j)
966978
j = j + 1
967979
local sb = StringBuilder.new()
@@ -974,9 +986,12 @@ fn emit_type_ref(toks: read List<Tok>, i: read Int, tag: read String, eff: read
974986
StringBuilder.push(builder: mut sb, value: read name)
975987
StringBuilder.push(builder: mut sb, value: read " fresh=")
976988
StringBuilder.push(builder: mut sb, value: read boolstr(b: read isFresh))
977-
StringBuilder.push(builder: mut sb, value: read " noescape=false owned=false")
989+
StringBuilder.push(builder: mut sb, value: read " noescape=")
990+
StringBuilder.push(builder: mut sb, value: read boolstr(b: read isNoescape))
991+
StringBuilder.push(builder: mut sb, value: read " owned=")
992+
StringBuilder.push(builder: mut sb, value: read boolstr(b: read isOwned))
978993
emit(depth: read d, s: read StringBuilder.finish(builder: take sb))
979-
// Generic args.
994+
// Generic args `<...>`.
980995
if at_symbol(toks: read toks, i: read j, code: read SYM_LANGLE) {
981996
let close = find_matching(toks: read toks, open: read j, openSym: read SYM_LANGLE, closeSym: read SYM_RANGLE)
982997
if close > j {
@@ -988,6 +1003,23 @@ fn emit_type_ref(toks: read List<Tok>, i: read Int, tag: read String, eff: read
9881003
j = close + 1
9891004
}
9901005
}
1006+
// Fn types: `Fn(<params>) -> <ret>` → fn-param (with optional effect) + fn-return.
1007+
if name == "Fn" && at_symbol(toks: read toks, i: read j, code: read SYM_LPAREN) {
1008+
let pclose = find_matching(toks: read toks, open: read j, openSym: read SYM_LPAREN, closeSym: read SYM_RPAREN)
1009+
let mut k = j + 1
1010+
while k < pclose {
1011+
let comma = split_comma(toks: read toks, start: read k, end: read pclose)
1012+
let pEff = effect_kw(toks: read toks, i: read k)
1013+
let mut pStart = k
1014+
if String.len(value: read pEff) > 0 { pStart = k + 1 }
1015+
emit_type_ref(toks: read toks, i: read pStart, tag: read "fn-param", eff: read pEff, d: read (d + 1))
1016+
if comma < 0 { k = pclose } else { k = comma + 1 }
1017+
}
1018+
j = pclose + 1
1019+
if at_symbol(toks: read toks, i: read j, code: read SYM_ARROW) {
1020+
j = emit_type_ref(toks: read toks, i: read (j + 1), tag: read "fn-return", eff: read "", d: read (d + 1))
1021+
}
1022+
}
9911023
return j
9921024
}
9931025

@@ -1429,7 +1461,12 @@ fn emit_function(toks: read List<Tok>, start: read Int, d: read Int) -> Int {
14291461
if at_symbol(toks: read toks, i: read j, code: read SYM_ARROW) {
14301462
hasRet = true
14311463
retStart = j + 1
1432-
if at_ident(toks: read toks, i: read (j + 1), wid: read WORD_FRESH) { retFresh = true }
1464+
// `-> fresh T` sets the fn's returns-fresh and the type ref begins AT T
1465+
// (the return TypeRef itself is fresh=false — matches parse_function_decl).
1466+
if at_ident(toks: read toks, i: read (j + 1), wid: read WORD_FRESH) {
1467+
retFresh = true
1468+
retStart = j + 2
1469+
}
14331470
}
14341471
// body
14351472
let bodyOpen = find_block_open(toks: read toks, start: read j, limit: read List.len(list: read toks))

selfhost/samples/ast/fntypes.rss

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
struct Op derives(Clone) {
2+
apply: owned Fn(Int) -> Int
3+
}
4+
5+
fn make() -> fresh String {
6+
return "x"
7+
}
8+
9+
fn take_fn(f: read Fn(read Int, Int) -> Int) -> Int {
10+
return 0
11+
}

0 commit comments

Comments
 (0)