@@ -1480,12 +1480,14 @@ fn emit_function(toks: read List<Tok>, start: read Int, d: read Int) -> Int {
14801480 retStart = j + 2
14811481 }
14821482 }
1483- // body
1484- let bodyOpen = find_block_open(toks: read toks, start: read j, limit: read List.len(list: read toks))
1485- let hasBody = bodyOpen >= 0 && (isNative == false || same_stmt_line(toks: read toks, a: read start, b: read bodyOpen))
14861483 // Signature region [pclose+1, sigEnd): return type, effects(...), and the
1487- // default-impl marker `= _`.
1484+ // default-impl marker `= _`. function_signature_end stops at the body `{` or
1485+ // the next top-level item — so a body exists iff sigEnd lands on a `{`. (A raw
1486+ // find_block_open would wrongly borrow a LATER fn's `{` for a body-less decl.)
14881487 let sigEnd = function_signature_end(toks: read toks, start: read (pclose + 1))
1488+ let mut bodyOpen = -1
1489+ if at_symbol(toks: read toks, i: read sigEnd, code: read SYM_LBRACE) { bodyOpen = sigEnd }
1490+ let hasBody = bodyOpen >= 0 && (isNative == false || same_stmt_line(toks: read toks, a: read start, b: read bodyOpen))
14891491 let effPos = find_effects_kw(toks: read toks, start: read (pclose + 1), end: read sigEnd)
14901492 let defaultImpl = has_default_impl(toks: read toks, start: read (pclose + 1), end: read sigEnd)
14911493 // A `fresh` anywhere in the return-type region (e.g. `-> Result<fresh String, E>`)
@@ -1541,8 +1543,9 @@ fn emit_function(toks: read List<Tok>, start: read Int, d: read Int) -> Int {
15411543 return bclose + 1
15421544 }
15431545 emit(depth: read (d + 2), s: read "block")
1544- // native no-body: consume to end of declaration line.
1545- return declaration_line_end(toks: read toks, start: read start)
1546+ // No body: the declaration ends exactly at sigEnd (next top-level item / EOF),
1547+ // which is correct even for a signature spanning multiple lines.
1548+ return sigEnd
15461549}
15471550
15481551fn same_stmt_line(toks: read List<Tok >, a: read Int, b: read Int) -> Bool {
@@ -2355,6 +2358,10 @@ fn main() -> Unit {
23552358 let toks = tokenize(source: read source)
23562359 let ntok = List.len(list: read toks)
23572360 emit(depth: read 0, s: read "program")
2361+ // Section 1: features are dumped FIRST regardless of source position (the
2362+ // oracle emits by struct-field order, not source order), so a pre-pass emits
2363+ // every `feature <name >` before any item; the item pass then skips headers.
2364+ emit_all_features(toks: read toks, ntok: read ntok)
23582365 let mut i = 0
23592366 let mut running = true
23602367 while running {
@@ -2367,16 +2374,8 @@ fn main() -> Unit {
23672374 } else {
23682375 let cls = classify_top(toks: read toks, i: read i)
23692376 if cls.isFeatures {
2370- // features line: emit `feature <name >` per known feature word.
2371- let fend = declaration_line_end(toks: read toks, start: read (i + 2))
2372- let mut k = i + 2
2373- while k < fend {
2374- if is_feature_word(toks: read toks, i: read k) {
2375- emit(depth: read 1, s: read String.concat(left: read "feature ", right: read tk_text(toks: read toks, i: read k)))
2376- }
2377- k = k + 1
2378- }
2379- i = fend
2377+ // Already emitted in the pre-pass; skip the header line.
2378+ i = declaration_line_end(toks: read toks, start: read (i + 2))
23802379 } else if cls.isProfile {
23812380 i = declaration_line_end(toks: read toks, start: read i)
23822381 } else if starts_type_decl(toks: read toks, i: read i, isPub: read cls.isPub) {
@@ -2402,6 +2401,97 @@ fn main() -> Unit {
24022401 }
24032402 }
24042403 }
2404+ // Section 5: feature diagnostics, emitted AFTER all items (unknown-feature
2405+ // then duplicate-feature), matching ast_oracle_dump's ordering.
2406+ emit_feature_diags(toks: read toks, ntok: read ntok)
2407+ return Unit
2408+ }
2409+
2410+ // True if token i begins a top-level `features:` header (depth checked by caller).
2411+ fn is_features_header(toks: read List<Tok >, i: read Int) -> Bool {
2412+ return is_kw_text(toks: read toks, i: read i, word: read "features") && at_symbol(toks: read toks, i: read (i + 1), code: read SYM_COLON)
2413+ }
2414+
2415+ // Section 1 pre-pass: emit `feature <name >` for every known feature across all
2416+ // top-level `features:` headers, in source order.
2417+ fn emit_all_features(toks: read List<Tok >, ntok: read Int) -> Unit {
2418+ let mut depth = 0
2419+ let mut i = 0
2420+ while i < ntok {
2421+ if at_symbol(toks: read toks, i: read i, code: read SYM_LPAREN) || at_symbol(toks: read toks, i: read i, code: read SYM_LBRACE) || at_symbol(toks: read toks, i: read i, code: read SYM_LBRACKET) {
2422+ depth = depth + 1
2423+ } else if at_symbol(toks: read toks, i: read i, code: read SYM_RPAREN) || at_symbol(toks: read toks, i: read i, code: read SYM_RBRACE) || at_symbol(toks: read toks, i: read i, code: read SYM_RBRACKET) {
2424+ if depth > 0 { depth = depth - 1 }
2425+ } else if depth == 0 && is_features_header(toks: read toks, i: read i) {
2426+ let fend = declaration_line_end(toks: read toks, start: read (i + 2))
2427+ let mut k = i + 2
2428+ while k < fend {
2429+ if is_feature_word(toks: read toks, i: read k) {
2430+ emit(depth: read 1, s: read String.concat(left: read "feature ", right: read tk_text(toks: read toks, i: read k)))
2431+ }
2432+ k = k + 1
2433+ }
2434+ }
2435+ i = i + 1
2436+ }
2437+ return Unit
2438+ }
2439+
2440+ // True if a known-feature token with the same text as token k appears earlier in
2441+ // the same header [hStart, k) (per-header duplicate, mirroring parse_features).
2442+ fn feat_dup_before(toks: read List<Tok >, hStart: read Int, k: read Int) -> Bool {
2443+ let target = tk_text(toks: read toks, i: read k)
2444+ let mut m = hStart
2445+ while m < k {
2446+ if is_feature_word(toks: read toks, i: read m) && tk_text(toks: read toks, i: read m) == target {
2447+ return true
2448+ }
2449+ m = m + 1
2450+ }
2451+ return false
2452+ }
2453+
2454+ // Section 5 post-pass: unknown-feature markers (all headers) then duplicate-feature
2455+ // markers (all headers), matching ast_oracle_dump.
2456+ fn emit_feature_diags(toks: read List<Tok >, ntok: read Int) -> Unit {
2457+ let mut depth = 0
2458+ let mut i = 0
2459+ while i < ntok {
2460+ if at_symbol(toks: read toks, i: read i, code: read SYM_LPAREN) || at_symbol(toks: read toks, i: read i, code: read SYM_LBRACE) || at_symbol(toks: read toks, i: read i, code: read SYM_LBRACKET) {
2461+ depth = depth + 1
2462+ } else if at_symbol(toks: read toks, i: read i, code: read SYM_RPAREN) || at_symbol(toks: read toks, i: read i, code: read SYM_RBRACE) || at_symbol(toks: read toks, i: read i, code: read SYM_RBRACKET) {
2463+ if depth > 0 { depth = depth - 1 }
2464+ } else if depth == 0 && is_features_header(toks: read toks, i: read i) {
2465+ let fend = declaration_line_end(toks: read toks, start: read (i + 2))
2466+ let mut k = i + 2
2467+ while k < fend {
2468+ if at_symbol(toks: read toks, i: read k, code: read SYM_COMMA) == false && tk_kind(toks: read toks, i: read k) != TOK_EOF && is_feature_word(toks: read toks, i: read k) == false {
2469+ emit(depth: read 1, s: read String.concat(left: read "unknown-feature ", right: read escape(s: read tk_text(toks: read toks, i: read k))))
2470+ }
2471+ k = k + 1
2472+ }
2473+ }
2474+ i = i + 1
2475+ }
2476+ depth = 0
2477+ i = 0
2478+ while i < ntok {
2479+ if at_symbol(toks: read toks, i: read i, code: read SYM_LPAREN) || at_symbol(toks: read toks, i: read i, code: read SYM_LBRACE) || at_symbol(toks: read toks, i: read i, code: read SYM_LBRACKET) {
2480+ depth = depth + 1
2481+ } else if at_symbol(toks: read toks, i: read i, code: read SYM_RPAREN) || at_symbol(toks: read toks, i: read i, code: read SYM_RBRACE) || at_symbol(toks: read toks, i: read i, code: read SYM_RBRACKET) {
2482+ if depth > 0 { depth = depth - 1 }
2483+ } else if depth == 0 && is_features_header(toks: read toks, i: read i) {
2484+ let fend = declaration_line_end(toks: read toks, start: read (i + 2))
2485+ let mut k = i + 2
2486+ while k < fend {
2487+ if is_feature_word(toks: read toks, i: read k) && feat_dup_before(toks: read toks, hStart: read (i + 2), k: read k) {
2488+ emit(depth: read 1, s: read String.concat(left: read "duplicate-feature ", right: read tk_text(toks: read toks, i: read k)))
2489+ }
2490+ k = k + 1
2491+ }
2492+ }
2493+ i = i + 1
2494+ }
24052495 return Unit
24062496}
24072497
0 commit comments