@@ -204,7 +204,10 @@ fn find_param_open(toks: read List<Tok>, from: read Int, end: read Int) -> Int {
204204// function.return_ty.is_none()). True iff no top-level `->` follows the param list.
205205fn fn_missing_return(toks: read List<Tok >, start: read Int) -> Bool {
206206 let ns = fn_name_start(toks: read toks, i: read start)
207- let sigEnd = function_signature_end(toks: read toks, start: read start)
207+ // Signature scan must begin at the `fn` keyword (ns - 1), not the decl start:
208+ // when the decl is attribute-led (`#deprecated(...) fn …`), starting at `#`
209+ // makes function_signature_end stop at the later `fn` (a top-level-item start).
210+ let sigEnd = function_signature_end(toks: read toks, start: read (ns - 1))
208211 let open = find_param_open(toks: read toks, from: read ns, end: read sigEnd)
209212 if open < 0 { return false }
210213 let close = find_matching(toks: read toks, open: read open, openSym: read SYM_LPAREN, closeSym: read SYM_RPAREN)
@@ -234,7 +237,10 @@ fn seg_is_untyped(toks: read List<Tok>, segStart: read Int, segEnd: read Int) ->
234237// ()[]{}<>) and tests each segment with seg_is_untyped.
235238fn fn_has_untyped_param(toks: read List<Tok >, start: read Int) -> Bool {
236239 let ns = fn_name_start(toks: read toks, i: read start)
237- let sigEnd = function_signature_end(toks: read toks, start: read start)
240+ // Signature scan must begin at the `fn` keyword (ns - 1), not the decl start:
241+ // when the decl is attribute-led (`#deprecated(...) fn …`), starting at `#`
242+ // makes function_signature_end stop at the later `fn` (a top-level-item start).
243+ let sigEnd = function_signature_end(toks: read toks, start: read (ns - 1))
238244 let open = find_param_open(toks: read toks, from: read ns, end: read sigEnd)
239245 if open < 0 { return false }
240246 let close = find_matching(toks: read toks, open: read open, openSym: read SYM_LPAREN, closeSym: read SYM_RPAREN)
@@ -265,10 +271,110 @@ fn fn_has_untyped_param(toks: read List<Tok>, start: read Int) -> Bool {
265271 return untyped
266272}
267273
274+ // Valid v0.6 effect names for an `effects(...)` clause (check_function_effects).
275+ fn is_valid_effect_name(name: read String) -> Bool {
276+ return name == "no_panic" || name == "noalloc" || name == "no_block" || name == "pure" || name == "unsafe" || name == "native" || name == "parallel"
277+ }
278+
279+ // Removed runtime effects → RS0012 (removed_runtime_effect_replacement set).
280+ fn is_removed_runtime_effect(name: read String) -> Bool {
281+ return name == "io" || name == "allocates" || name == "may_panic" || name == "may_fail" || name == "async" || name == "suspends"
282+ }
283+
284+ // Index of the `(` of a top-level `effects(...)` clause in a function signature,
285+ // or -1. The clause sits after the param list, before the body.
286+ fn find_effects_open(toks: read List<Tok >, start: read Int) -> Int {
287+ let ns = fn_name_start(toks: read toks, i: read start)
288+ // Signature scan must begin at the `fn` keyword (ns - 1), not the decl start:
289+ // when the decl is attribute-led (`#deprecated(...) fn …`), starting at `#`
290+ // makes function_signature_end stop at the later `fn` (a top-level-item start).
291+ let sigEnd = function_signature_end(toks: read toks, start: read (ns - 1))
292+ let open = find_param_open(toks: read toks, from: read ns, end: read sigEnd)
293+ if open < 0 { return -1 }
294+ let pclose = find_matching(toks: read toks, open: read open, openSym: read SYM_LPAREN, closeSym: read SYM_RPAREN)
295+ if pclose < 0 { return -1 }
296+ let mut k = pclose + 1
297+ let mut depth = 0
298+ let mut r = -1
299+ while k < sigEnd && r < 0 {
300+ if at_symbol(toks: read toks, i: read k, code: read SYM_LPAREN) || at_symbol(toks: read toks, i: read k, code: read SYM_LANGLE) {
301+ depth = depth + 1
302+ } else if at_symbol(toks: read toks, i: read k, code: read SYM_RPAREN) || at_symbol(toks: read toks, i: read k, code: read SYM_RANGLE) {
303+ if depth > 0 { depth = depth - 1 }
304+ } else if depth == 0 && tk_text(toks: read toks, i: read k) == "effects" && at_symbol(toks: read toks, i: read (k + 1), code: read SYM_LPAREN) {
305+ r = k + 1
306+ }
307+ k = k + 1
308+ }
309+ return r
310+ }
311+
312+ // Classify one effects-clause item [segStart,segEnd) exactly like parse_effects:
313+ // 1 = a valid bare `Name` (single-token effect name)
314+ // 2 = a valid `retains(ident)` item
315+ // 0 = malformed (empty, non-ident, `name(args)`, bad retains) → skipped, no check
316+ fn effect_item_kind(toks: read List<Tok >, segStart: read Int, segEnd: read Int) -> Int {
317+ if segStart >= segEnd { return 0 }
318+ if is_ident_tok(toks: read toks, i: read segStart) == false { return 0 }
319+ let name = tk_text(toks: read toks, i: read segStart)
320+ if name == "retains" {
321+ if at_symbol(toks: read toks, i: read (segStart + 1), code: read SYM_LPAREN) {
322+ let close = find_matching(toks: read toks, open: read (segStart + 1), openSym: read SYM_LPAREN, closeSym: read SYM_RPAREN)
323+ if close >= 0 && (close + 1) == segEnd && (segStart + 3) == close && is_ident_tok(toks: read toks, i: read (segStart + 2)) {
324+ return 2
325+ }
326+ }
327+ return 0
328+ }
329+ if (segStart + 1) == segEnd { return 1 }
330+ return 0
331+ }
332+
333+ // Walk a function's `effects(...)` items (only the VALID ones — malformed items
334+ // recover to RS0015 and are skipped). `mode`: 0 = RS0004 (unknown effect: `fresh`
335+ // or an unrecognized name), 1 = RS0012 (removed runtime effect).
336+ fn effects_name_probe(toks: read List<Tok >, start: read Int, mode: read Int) -> Bool {
337+ let effOpen = find_effects_open(toks: read toks, start: read start)
338+ if effOpen < 0 { return false }
339+ let effClose = find_matching(toks: read toks, open: read effOpen, openSym: read SYM_LPAREN, closeSym: read SYM_RPAREN)
340+ if effClose < 0 { return false }
341+ let mut k = effOpen + 1
342+ let mut segStart = effOpen + 1
343+ let mut depth = 0
344+ let mut hit = false
345+ while k < effClose {
346+ if at_symbol(toks: read toks, i: read k, code: read SYM_LPAREN) {
347+ depth = depth + 1
348+ } else if at_symbol(toks: read toks, i: read k, code: read SYM_RPAREN) {
349+ if depth > 0 { depth = depth - 1 }
350+ } else if depth == 0 && at_symbol(toks: read toks, i: read k, code: read SYM_COMMA) {
351+ if effect_name_hits(toks: read toks, segStart: read segStart, segEnd: read k, mode: read mode) { hit = true }
352+ segStart = k + 1
353+ }
354+ k = k + 1
355+ }
356+ if effect_name_hits(toks: read toks, segStart: read segStart, segEnd: read effClose, mode: read mode) { hit = true }
357+ return hit
358+ }
359+
360+ // Does a single VALID bare-name item trigger `mode`? (retains/malformed items
361+ // never trigger RS0004/RS0012.)
362+ fn effect_name_hits(toks: read List<Tok >, segStart: read Int, segEnd: read Int, mode: read Int) -> Bool {
363+ if effect_item_kind(toks: read toks, segStart: read segStart, segEnd: read segEnd) != 1 { return false }
364+ let name = tk_text(toks: read toks, i: read segStart)
365+ if mode == 1 { return is_removed_runtime_effect(name: read name) }
366+ if name == "fresh" { return true }
367+ if is_removed_runtime_effect(name: read name) { return false }
368+ return is_valid_effect_name(name: read name) == false
369+ }
370+
268371// RS0011: a parameter written `name: share …` (no data effect, type name `share`).
269372fn fn_has_share_param(toks: read List<Tok >, start: read Int) -> Bool {
270373 let ns = fn_name_start(toks: read toks, i: read start)
271- let sigEnd = function_signature_end(toks: read toks, start: read start)
374+ // Signature scan must begin at the `fn` keyword (ns - 1), not the decl start:
375+ // when the decl is attribute-led (`#deprecated(...) fn …`), starting at `#`
376+ // makes function_signature_end stop at the later `fn` (a top-level-item start).
377+ let sigEnd = function_signature_end(toks: read toks, start: read (ns - 1))
272378 let open = find_param_open(toks: read toks, from: read ns, end: read sigEnd)
273379 if open < 0 { return false }
274380 let close = find_matching(toks: read toks, open: read open, openSym: read SYM_LPAREN, closeSym: read SYM_RPAREN)
@@ -322,10 +428,12 @@ fn main() -> Unit {
322428 // RS0002 (any fn missing an explicit return type) / RS0003 (any untyped param).
323429 let mut missingReturn = false
324430 let mut untypedParam = false
325- // Signature-family diagnostics: RS0010 (removed profile ), RS0011 (removed
326- // share data effect on a parameter ).
431+ // Signature-family diagnostics: RS0004 (unknown effect ), RS0010 (removed
432+ // profile), RS0011 (removed share data effect), RS0012 (removed runtime effect ).
327433 let mut hasProfile = false
328434 let mut shareParam = false
435+ let mut unknownEffect = false
436+ let mut removedRuntime = false
329437 let mut i = 0
330438 let mut running = true
331439 while running {
@@ -471,6 +579,8 @@ fn main() -> Unit {
471579 if fn_missing_return(toks: read toks, start: read i) { missingReturn = true }
472580 if fn_has_untyped_param(toks: read toks, start: read i) { untypedParam = true }
473581 if fn_has_share_param(toks: read toks, start: read i) { shareParam = true }
582+ if effects_name_probe(toks: read toks, start: read i, mode: read 0) { unknownEffect = true }
583+ if effects_name_probe(toks: read toks, start: read i, mode: read 1) { removedRuntime = true }
474584 i = r
475585 }
476586 } else {
@@ -488,6 +598,9 @@ fn main() -> Unit {
488598 if untypedParam {
489599 Log.write(message: read "RS0003")
490600 }
601+ if unknownEffect {
602+ Log.write(message: read "RS0004")
603+ }
491604 if found {
492605 Log.write(message: read "RS0005")
493606 }
@@ -497,6 +610,9 @@ fn main() -> Unit {
497610 if shareParam {
498611 Log.write(message: read "RS0011")
499612 }
613+ if removedRuntime {
614+ Log.write(message: read "RS0012")
615+ }
500616 if hasUnknownFeat {
501617 Log.write(message: read "RS0016")
502618 }
@@ -506,7 +622,7 @@ fn main() -> Unit {
506622 if headerCount >= 2 {
507623 Log.write(message: read "RS0006")
508624 }
509- let anyDiag = found || hasUnknownFeat || hasDupFeat || headerCount >= 2 || missingReturn || untypedParam || hasProfile || shareParam
625+ let anyDiag = found || hasUnknownFeat || hasDupFeat || headerCount >= 2 || missingReturn || untypedParam || hasProfile || shareParam || unknownEffect || removedRuntime
510626 if anyDiag == false {
511627 Log.write(message: read "CLEAN")
512628 }
0 commit comments