Skip to content

codegen: the emitter's one reflection site (IsClrTypeGenericInNamespace) spells a CLR type by resolving it at emit time against FrameworkOnly while semantic analysis resolved it against the registry — Rule-2 cell, no materialized fact #2320

Closed

Description

@antonsynd

Class

Contract (CLAUDE.md Rule 2): the RoslynEmitter makes no type decision and performs no reflection; every fact it reads is materialized in semantic analysis (Symbol.CodeGenInfo or a node-keyed SemanticInfo carrier). RoslynEmitter.IsClrTypeGenericInNamespace (src/Sharpy.Compiler/CodeGen/RoslynEmitter.CompilationUnit.cs:588 @ 045ce6b) is the emitter's one remaining reflection site: it decides how to spell a type name (Sharpy.IList vs System.Collections.Generic.IList<T>) by resolving {namespace}.{name}`` {1..4}` through reflection at emit time.

Cells

# cell observed @ 045ce6b expected
1 the site itself reflection in CodeGen; its comment says "Rule-2 cell (#2250 findings ledger; no tracker yet)" — a known defect comment with no issue (Rule 8) a materialized fact; no reflection in CodeGen
2 which world it asks (P54 seam divergence, code-read by the class-cure auditor) since 333de07 the site asks AssemblyVisibility.FrameworkOnly (the shared framework + Sharpy.Core — a process constant, so the P54 contract holds); semantic analysis answers the same question from the compilation's registry visibility ({framework} ∪ {this registry's references}). Its caller IsNetFrameworkNamespace admits any System.*/Microsoft.* namespace, so for a referenced non-framework package in such a namespace (e.g. -r System.Reactive.dll, System.Reactive.Linq) the semantic side sees the generic type and the emitter does not — the two halves of one decision read two different sets one decision, made once, in semantic analysis

Lane ice (a mismatched spelling surfaces as SPY0908 behind the C# compiler, if it surfaces at all), reach probe (needs a referenced package in a System.*/Microsoft.* namespace whose generic type is spelled through this arm — no program-level repro recorded; cell 2 is code-read).

Acceptance

  • The spelling decision is recorded on the type symbol's CodeGenInfo (or a node-keyed carrier merged in SemanticInfo.MergeFrom) during semantic analysis, computed from the registry's visibility; IsClrTypeGenericInNamespace and the FrameworkOnly read in CodeGen are deleted.
  • EmitterCarrierOnlyConformanceTests / ProcessWideResolutionScanTests ratchet the site out (the scan's src/Sharpy.Compiler census drops by one; the emitter names no Discovery.AssemblyVisibility).
  • A program with a referenced package generic in a System.* namespace prints the same under run before and after (direction record), and a mutation that re-reads FrameworkOnly in the emitter is red.

Found by the P54 (#2250) plan's own trackers list ("Rule-2 emitter reflection: no open tracker — the lead files ONE class issue") and confirmed by the /verify-implementation class-cure auditor @ 045ce6b.

Activity

  1. added
    lane:iceInternal compiler error: SPY0908/SPY0909, a CSxxxx leak, a crash
    reach:probeNeeds an escaped spelling, a wrong-annotation probe or a python3 message diff
    class-trackerThe one issue per class: carries the contract and the cell table
    on Oct 15, 2026
  2. antonsynd commented on Oct 15, 2026

    @antonsynd
    OwnerAuthor

    Batching 2026-10-15 @ f8c37f9 — charter .claude/plans/remediation-round-2026-09-20-batching.md §3.0, chore (same band as #2261 part 2, #2311, #2313 cell 0; rides P21e's gate). Ruling R-HM (owner, 2026-10-15): (a) a chore — the emitter's one remaining reflection site becomes a materialized fact.

    Internals (read @ f8c37f9): RoslynEmitter.CompilationUnit.cs:588 IsClrTypeGenericInNamespace resolves {ns}.{name}`1..4 against AssemblyVisibility.FrameworkOnly to skip a using alias of an open generic (CS0305); its one caller (:510) sits under IsNetFrameworkNamespace (:546), which admits any System.*/Microsoft.* module name, so a referenced package in such a namespace is visible to the checker (registry visibility) and invisible here. Mechanism: one symbol-keyed fact on the imported type symbol's CodeGenInfo ("is an open generic definition"), computed from the registry's visibility in semantic analysis and frozen at MaterializeCodeGenInfo; the method and the FrameworkOnly read are deleted. Guard: EmitterCarrierOnlyConformanceTests / ProcessWideResolutionScanTests drop one census entry (the emitter names no Discovery.AssemblyVisibility); direction record on a program importing a referenced System.*-namespace generic by name (prints the same before and after); mutation (re-read FrameworkOnly in the emitter → red). Semantic + CodeGen → a whole-solution gate; seam-free against P21e. Rejected: (b) a P26 cell (~20 groups out; no layout change rides on this fact). Two-way. Lane / reach: ice · probe. Class tracker: this issue.

  3. antonsynd commented on Oct 22, 2026

    @antonsynd
    OwnerAuthor

    Landed (chores band 2026-10-22) on dev @ 4d22212, PR #2346 to mainline. Whole-solution gate 81669/0/7 @ 4d22212 (measured), against 81093/0/7 @ be4664e.

    Commit f6cf5c9 (R-HM a). CodeGenInfo.IsOpenGenericDefinition is recorded by CodeGenInfoComputer.ProcessFromImport. It is computed against the registry's own visibility, framework plus references, and freezes at MaterializeCodeGenInfo. The emitter's IsClrTypeGenericInNamespace, its AssemblyVisibility.FrameworkOnly read and the TODO are deleted. SemanticBinding.MergeFrom carries the fact onto as-aliased imports.

    Premise correction. Neither EmitterCarrierOnlyConformanceTests nor ProcessWideResolutionScanTests had a census entry for this site, so there was nothing to ratchet out. The new guard is EmitterCarrierOnlyConformanceTests.CodeGenSources_NameNoClrVisibilitySeam. It denies CodeGen every Discovery type that is or carries AssemblyVisibility, derived by reflection, with a synthetic positive and negative control.

    Matrix: OpenGenericImportAliasTests, visibility {framework, referenced} × spelling {bare, as} × name {generic arity group, non-generic}.

    Mutation: the emitter re-reading FrameworkOnly turned 1 row red.

    Direction: framework imports print the same and the generated C# is byte-identical. A referenced System.Widgets prints the same, and the only C# change is that using Box = …Box; is no longer emitted, because the framework-only read missed Box<T>.

    Every acceptance bullet is met. Closing.

  4. added a commit that references this issue on Oct 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    class-trackerThe one issue per class: carries the contract and the cell tablelane:iceInternal compiler error: SPY0908/SPY0909, a CSxxxx leak, a crashreach:probeNeeds an escaped spelling, a wrong-annotation probe or a python3 message diff

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions