Skip to content

TS Conformance: propagate TSnnnn codes through the recovery path (unlocks negative-test matching) #125

@nickna

Description

@nickna

Part of #80. Surfaced while implementing #122.

Problem

The TS conformance errors-baseline runner matches on (line, TSnnnn) tuples. But the runner type-checks via TypeChecker.CheckWithRecovery, and that path drops the TsCode: RecordTypeError maps the caught exception to an internal DiagnosticCode enum and calls DiagnosticCollector.AddError(code, message, location) — which constructs a Diagnostic with TsCode = null. The canonical TSnnnn carried on the exception (ex.Diagnostic.TsCode, e.g. "TS2322") is never propagated.

Consequence: only tests that expect zero errors can currently Pass. Any negative test produces diagnostics with null TsCode, which are excluded from baseline matching (by design, see Diagnostics/Diagnostic.cs), so the actual set is effectively empty and never matches a non-empty expected set.

This is why #122 (model call/construct signatures on object types) produced no baseline movement despite being correct — verified directly: with TsCode propagated, assignmentCompatWithConstructSignatures becomes a perfect match and flips to Pass, and assignmentCompatBetweenTupleAndArray also flips.

The catch — it unmasks pre-existing false positives

Simply propagating TsCode (one-line change in RecordTypeError + an optional tsCode param on AddError/AddWarning) is correct but regresses the committed baseline, because it surfaces latent false positives that were previously hidden behind null codes. Observed in the current subset (each a test expecting zero errors that then fails):

  • nullAssignableToEveryType, undefinedAssignableToEveryType — SharpTS flags var b: number = null etc.; TS treats null/undefined as assignable to everything under non-strict (strictNullChecks off), which is what these baselines assume.
  • assignmentCompatWithObjectMembers2, assignmentCompatWithObjectMembers3s = t between two identically-shaped classes; SharpTS uses nominal class typing (per CLAUDE.md) while TS compares structurally here.
  • intersectionIncludingPropFromGlobalAugmentation — TS2698/TS2339 false positives (separate gap).

Net with naive propagation in the current subset: +2 passes, −5 (unmasked) ⇒ baseline goes down. Hence it must not ship alone.

Scope (do together to keep the baseline green)

  1. Propagate TsCode through the recovery path (RecordTypeErrorAddError/AddWarningDiagnostic.TsCode).
  2. Fix (or scope/skip) the unmasked false positives so the baseline is net-positive:
    • non-strict null/undefined assignability (likely the biggest lever; gate on a strict-null-checks flag that defaults off to match the conformance baselines),
    • structural compatibility for identically-shaped classes where TS expects it,
    • the intersection-augmentation case.
  3. Regenerate the baseline; confirm net new passes and zero regressions.

Payoff

Unlocks measurable conformance for every negative test — the entire point of the errors-baseline runner. #122's modeling (and #123's, once landed) becomes visible the moment this lands. Without it, correct type-checking work shows as no movement.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions