Skip to content

Generator IR maintainability: RawCallSite copy-method divergence drops CTE metadata (bug), AnalyzeChainGroup god-method, decentralized dialect logic with permissive defaults #318

Description

@DJGosnell

Combined finding from the 2026-07-07 multi-agent deep review (generator-architecture perspective, medium severity except the CTE-metadata bug). These are the structural maintainability risks the review flagged as "needing structural fixes before the next feature wave, or the same classes of silent failure keep recurring." Grouped because they all touch the IR core and emitters.

1 (bug, fix first): RawCallSite copy methods have diverged — CTE metadata silently dropped

  • RawCallSite has ~50 constructor parameters and three hand-written With* copy methods that each re-list all arguments. They have already diverged: WithResultTypeName (RawCallSite.cs:388-450) passes cteEntityTypeName/isCteInnerChain/cteInnerArgSpanStart/cteColumns, but WithEntityTypeName (:324-382) and WithOperandEntityTypeName (:259-317) omit all four, resetting them to null/false.
  • CallSiteBinder.cs:90 invokes WithEntityTypeName during context-namespace normalization — triggered when discovery resolved a user-written partial entity class in a different namespace than the context (the exact multi-context scenario the normalization exists for). A CTE site hit by that path loses its CTE metadata, breaking CteDef matching downstream (likely spurious QRY081 or malformed WITH SQL).
  • Fix: repair the two copy methods now; add a test covering a CTE chain through the namespace-normalization path.

2: RawCallSite god-object — structural remediation

  • Every new IR field must be manually threaded through a ~50-arg constructor, three copy methods, Equals, and GetHashCode — the pattern that produced defect 1 guarantees recurrence.
  • Options (review recommendation): convert to a record with with-expressions (auto-generated copy semantics kills the divergence class — requires the mutable enrichment fields to move out or become init, coordinating with the display-class issue); or group cohesive fields into nested immutable sub-models (CteInfo, ScopeInfo, EnrichmentInfo) so copy methods pass whole sub-objects; plus a reflection-based test asserting every With* method preserves all properties except the one it changes (cheap guard even before the refactor).

3: ChainAnalyzer.AnalyzeChainGroup god-method

  • ~1,170 lines (ChainAnalyzer.cs:255-1423), 11 parameters (three optional dictionaries, two bool mode flags), handling terminal selection, Prepare collapse, fork detection, set-op operand splitting (by source-position heuristics, :434-523 — inherently formatting-brittle), join propagation with in-place re-translation, conditional bit assignment, CTE building, projection building, and parameter enrichment in one pass. UsageSiteDiscovery.cs is 4,419 lines; ProjectionAnalyzer.cs 3,576.
  • Every recent feature (set ops, CTEs, Patch, nav aggregates) threaded new parameters/inline phases through the same body — the primary velocity risk in the file.
  • Fix direction: extract per-concern phases with explicit input/output models (the conditional-mask rework in the mask issue is a natural forcing function); replace the positional set-op operand splitter with syntax-tree-based chain structure.

4: Dialect logic decentralized across ~50 sites with permissive defaults

  • ~27 switch statements + ~28 equality checks across SqlAssembler, SqlExprRenderer, TerminalEmitHelpers, CarrierEmitter, ManifestEmitter, ReaderCodeGenerator, CommandBehaviorSelector, PipelineOrchestrator, and Quarry.Shared/Sql/SqlFormatting. No dialect strategy/capability object; SqlDialectConfig carries only the enum + mode flags.
  • Critically, switch defaults are permissive: QuoteIdentifier falls through to double quotes (QuarryGenerator.cs:965-973), FormatParamName to @p{index} (CarrierEmitter.cs:1614-1622), GetDialectLiteral to a numeric cast — a hypothetical 5th dialect silently inherits SQLite-ish behavior at dozens of sites, producing wrong SQL rather than compile errors.
  • Fix direction: introduce a dialect capability/strategy object consulted at all sites (or at minimum change every dialect switch default to throw new UnreachableException/fail-loud so omissions surface immediately); inventory the sites as part of the change.

Non-goals

This issue is refactoring + the one CTE bug; behavioral changes to masks/diagnostics/caching are tracked in their own issues. Sequence this after the conditional-mask fix to avoid churn conflicts in ChainAnalyzer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions