@@ -519,6 +519,119 @@ fn seg_is_share(toks: read List<Tok>, segStart: read Int, segEnd: read Int) -> B
519519 return tk_text(toks: read toks, i: read (segStart + 2)) == "share"
520520}
521521
522+ // The 17 Copy scalar type names (crate scalar/primitive set).
523+ fn is_scalar_name(name: read String) -> Bool {
524+ return name == "Bool" || name == "Byte" || name == "Char" || name == "Float" || name == "Float32" || name == "Float64" || name == "Int" || name == "Int8" || name == "Int16" || name == "Int32" || name == "Int64" || name == "UInt" || name == "UInt8" || name == "UInt16" || name == "UInt32" || name == "UInt64" || name == "Unit"
525+ }
526+
527+ // Copy classification of a param type region for RS0007. The type begins after
528+ // `name :` and an optional data-effect; then optional prefixes precede the base.
529+ // Copy iff (no fresh/noescape prefix) AND base ∈ scalars AND next token is not `<`.
530+ fn seg_type_is_copy(toks: read List<Tok >, segStart: read Int, segEnd: read Int) -> Bool {
531+ let mut k = segStart + 2
532+ if is_effect_kw(toks: read toks, i: read k) { k = k + 1 }
533+ let mut sawFreshNoescape = false
534+ let mut scanning = true
535+ while scanning {
536+ let t = tk_text(toks: read toks, i: read k)
537+ if t == "fresh" || t == "noescape" {
538+ sawFreshNoescape = true
539+ k = k + 1
540+ } else if t == "owned" || t == "handle" || t == "weak" {
541+ k = k + 1
542+ } else {
543+ scanning = false
544+ }
545+ }
546+ if sawFreshNoescape { return false }
547+ let base = tk_text(toks: read toks, i: read k)
548+ if is_scalar_name(name: read base) == false { return false }
549+ if at_symbol(toks: read toks, i: read (k + 1), code: read SYM_LANGLE) { return false }
550+ return true
551+ }
552+
553+ // Status of a named param for RS0007: -1 = this segment is not param `pname`,
554+ // 2 = matched and Copy, 1 = matched and non-Copy.
555+ fn one_param_status(toks: read List<Tok >, segStart: read Int, segEnd: read Int, pname: read String) -> Int {
556+ if segStart >= segEnd { return -1 }
557+ if is_ident_tok(toks: read toks, i: read segStart) == false { return -1 }
558+ if at_symbol(toks: read toks, i: read (segStart + 1), code: read SYM_COLON) == false { return -1 }
559+ if tk_text(toks: read toks, i: read segStart) != pname { return -1 }
560+ if seg_type_is_copy(toks: read toks, segStart: read segStart, segEnd: read segEnd) { return 2 }
561+ return 1
562+ }
563+
564+ // Walk the param list [open,close] looking for param `pname`. Returns 0 (not a
565+ // param), 1 (param, non-Copy), or 2 (param, Copy).
566+ fn param_retains_status(toks: read List<Tok >, open: read Int, close: read Int, pname: read String) -> Int {
567+ let mut k = open + 1
568+ let mut depth = 0
569+ let mut angle = 0
570+ let mut segStart = open + 1
571+ let mut status = 0
572+ while k < close {
573+ let opener = at_symbol(toks: read toks, i: read k, code: read SYM_LPAREN) || at_symbol(toks: read toks, i: read k, code: read SYM_LBRACKET) || at_symbol(toks: read toks, i: read k, code: read SYM_LBRACE)
574+ let closer = at_symbol(toks: read toks, i: read k, code: read SYM_RPAREN) || at_symbol(toks: read toks, i: read k, code: read SYM_RBRACKET) || at_symbol(toks: read toks, i: read k, code: read SYM_RBRACE)
575+ if opener {
576+ depth = depth + 1
577+ } else if closer {
578+ if depth > 0 { depth = depth - 1 }
579+ } else if at_symbol(toks: read toks, i: read k, code: read SYM_LANGLE) {
580+ angle = angle + 1
581+ } else if at_symbol(toks: read toks, i: read k, code: read SYM_RANGLE) {
582+ if angle > 0 { angle = angle - 1 }
583+ } else if depth == 0 && angle == 0 && at_symbol(toks: read toks, i: read k, code: read SYM_COMMA) {
584+ let s = one_param_status(toks: read toks, segStart: read segStart, segEnd: read k, pname: read pname)
585+ if s != -1 { status = s }
586+ segStart = k + 1
587+ }
588+ k = k + 1
589+ }
590+ let s = one_param_status(toks: read toks, segStart: read segStart, segEnd: read close, pname: read pname)
591+ if s != -1 { status = s }
592+ return status
593+ }
594+
595+ // One effects-clause item [segStart,segEnd): a `retains(p)` where p is not a
596+ // param, or is a Copy param → RS0007.
597+ fn retains_item_bad(toks: read List<Tok >, segStart: read Int, segEnd: read Int, popen: read Int, pclose: read Int) -> Bool {
598+ if effect_item_kind(toks: read toks, segStart: read segStart, segEnd: read segEnd) != 2 { return false }
599+ let pname = tk_text(toks: read toks, i: read (segStart + 2))
600+ let status = param_retains_status(toks: read toks, open: read popen, close: read pclose, pname: read pname)
601+ return status == 0 || status == 2
602+ }
603+
604+ // RS0007: an `effects(retains(p))` item whose p is not a param, or is a Copy param.
605+ fn fn_retains_bad(toks: read List<Tok >, start: read Int) -> Bool {
606+ let effOpen = find_effects_open(toks: read toks, start: read start)
607+ if effOpen < 0 { return false }
608+ let effClose = find_matching(toks: read toks, open: read effOpen, openSym: read SYM_LPAREN, closeSym: read SYM_RPAREN)
609+ if effClose < 0 { return false }
610+ let ns = fn_name_start(toks: read toks, i: read start)
611+ let sigEnd = function_signature_end(toks: read toks, start: read (ns - 1))
612+ let popen = find_param_open(toks: read toks, from: read ns, end: read sigEnd)
613+ if popen < 0 { return false }
614+ let pclose = find_matching(toks: read toks, open: read popen, openSym: read SYM_LPAREN, closeSym: read SYM_RPAREN)
615+ if pclose < 0 { return false }
616+ let mut k = effOpen + 1
617+ let mut segStart = effOpen + 1
618+ let mut depth = 0
619+ let mut hit = false
620+ while k < effClose {
621+ if at_symbol(toks: read toks, i: read k, code: read SYM_LPAREN) {
622+ depth = depth + 1
623+ } else if at_symbol(toks: read toks, i: read k, code: read SYM_RPAREN) {
624+ if depth > 0 { depth = depth - 1 }
625+ } else if depth == 0 && at_symbol(toks: read toks, i: read k, code: read SYM_COMMA) {
626+ if retains_item_bad(toks: read toks, segStart: read segStart, segEnd: read k, popen: read popen, pclose: read pclose) { hit = true }
627+ segStart = k + 1
628+ }
629+ k = k + 1
630+ }
631+ if retains_item_bad(toks: read toks, segStart: read segStart, segEnd: read effClose, popen: read popen, pclose: read pclose) { hit = true }
632+ return hit
633+ }
634+
522635fn main() -> Unit {
523636 let source = Args.get_or_default(index: 0, default: read "")
524637 let toks = tokenize(source: read source)
@@ -541,6 +654,7 @@ fn main() -> Unit {
541654 let mut unknownEffect = false
542655 let mut removedRuntime = false
543656 let mut invalidSelf = false
657+ let mut retainsBad = false
544658 // RS0033: any decimal integer literal in the file that overflows i64.
545659 let outOfRangeInt = has_out_of_range_int(toks: read toks)
546660 let mut i = 0
@@ -691,6 +805,7 @@ fn main() -> Unit {
691805 if effects_name_probe(toks: read toks, start: read i, mode: read 0) { unknownEffect = true }
692806 if effects_name_probe(toks: read toks, start: read i, mode: read 1) { removedRuntime = true }
693807 if fn_has_invalid_self(toks: read toks, start: read i) { invalidSelf = true }
808+ if fn_retains_bad(toks: read toks, start: read i) { retainsBad = true }
694809 i = r
695810 }
696811 } else {
@@ -726,6 +841,9 @@ fn main() -> Unit {
726841 if invalidSelf {
727842 Log.write(message: read "RS0028")
728843 }
844+ if retainsBad {
845+ Log.write(message: read "RS0007")
846+ }
729847 if outOfRangeInt {
730848 Log.write(message: read "RS0033")
731849 }
@@ -738,7 +856,7 @@ fn main() -> Unit {
738856 if headerCount >= 2 {
739857 Log.write(message: read "RS0006")
740858 }
741- let anyDiag = found || hasUnknownFeat || hasDupFeat || headerCount >= 2 || missingReturn || untypedParam || hasProfile || shareParam || unknownEffect || removedRuntime || invalidSelf || outOfRangeInt
859+ let anyDiag = found || hasUnknownFeat || hasDupFeat || headerCount >= 2 || missingReturn || untypedParam || hasProfile || shareParam || unknownEffect || removedRuntime || invalidSelf || outOfRangeInt || retainsBad
742860 if anyDiag == false {
743861 Log.write(message: read "CLEAN")
744862 }
0 commit comments