em 03: extract-first scan + full suggestion report

- ExtractFirstScanner: reads-only scan of non-local/param read expressions
  (member/field/property access, element access, invocation, cast), deduped
  by symbol+text with occurrence counts; invocations flagged (hoisting
  changes eval count); own-class field/property reads marked optional.
  Assignment LHS skipped whole (decision 5) — surfaced as a report note.
- SignatureBuilder: promotes bucket overlaps into one coherent signature
  (declared-inside vars drop from params; declared-inside return candidates
  become the return; outside candidates already covered by their ref
  write-back stay ref params). Trailing composite returns stay void with a
  note suggesting an extract-variable first.
- ExtractionReporter + ReportFormatter: single composition shared by CLI and
  tests; report = header, params (ref), returns, locals, extract-first
  (count + flags), notes, suggested-signature line last.
- DataFlowClassifier: Returns now carry types (ReturnSuggestion) — the
  signature line needs them.
- Displays: compact symbol/type formatting for the report.
- Fixture: RepeatReads (dedupe ×2 + skipped field write), Casts (cast
  candidate) appended at the end so all pinned line numbers stay put.
This commit is contained in:
2026-09-12 18:26:20 +01:00
parent 5d02c1aabf
commit 49a5633a7d
11 changed files with 985 additions and 68 deletions
@@ -92,4 +92,27 @@ public class Demo
return best;
}
/// <summary>
/// Bucket (em 03): the field read <c>_seed</c> appears TWICE — the
/// extract-first scan must dedupe it to one entry with an occurrence
/// count of 2. The field WRITE below (<c>_seed = a;</c>) is a
/// non-variable assignment left-hand side: the reads-only scan skips it
/// (decision 5) and the report notes the limitation.
/// </summary>
public int RepeatReads(int n)
{
int a = _seed + n;
int b = _seed * n;
_seed = a;
return a + b;
}
/// <summary>Bucket (em 03): a cast is an extract-first candidate too
/// (parent spec lists casts); its bound symbol is null — conversions are
/// not symbols — so the entry keys on its text alone.</summary>
public double Casts(int total)
{
return (double)total / _seed;
}
}