Skip to content

Excess property checking lost through a generic mapped-type parameter (regression in 6.0) #64006

Description

🔎 Search Terms

excess property check, freshness, generic mapped type, constraint, regression, 5.9, 6.0, Prisma, SelectSubset

🕗 Version & Regression Information

  • This changed between versions 5.9.3 and 6.0.0.
  • Bisected to a single nightly: good on 6.0.0-dev.20251205, broken on 6.0.0-dev.20251206.
  • Still reproduces on 7.0.2 (latest) and 7.1.0-dev.20260825.1 (nightly).

⏯ Playground Link

Repro is dependency-free and included below (no playground link — filed from a local bisect).

💻 Code

type Subset<T, U> = { [K in keyof T]: K extends keyof U ? T[K] : never }

type Inner = { id?: boolean }
type Args = { select?: Inner }

declare function f<T extends Args>(args: Subset<T, Args>): void

// A: unknown key ALONE — errors on every version tested
f({ select: { bogus: true } })

// B: unknown key BESIDE a valid one — errors on 5.9 ONLY.  <-- the regression
f({ select: { id: true, bogus: true } })

// C: control, no generic — errors on every version tested
const c: Inner = { id: true, bogus: true }

🙁 Actual behavior

tsc --strict --noEmit repro.ts

5.9.3                    ->  errors on lines 9, 12, 15
6.0.0-dev.20251205       ->  errors on lines 9, 12, 15
6.0.0-dev.20251206       ->  errors on lines 9, 15      <-- line 12 lost here
6.0.0-beta / 7.0.2       ->  errors on lines 9, 15
7.1.0-dev.20260825.1     ->  errors on lines 9, 15

From 6.0.0-dev.20251206 onward, line 12 reports nothing. The nested object literal is no longer excess-property-checked against Inner once it is routed through the generic parameter T.

Line 9 still errors on every version, but only via the weak-type check — the literal shares zero keys with Inner. Adding any one valid key silences it. That is what makes the loss easy to miss in real code, where argument objects almost always contain at least one real key.

Line 15 (the same literal, assigned directly to Inner with no generic in the way) still errors everywhere, which is what isolates this to the generic path rather than to excess-property checking in general.

🙂 Expected behavior

Line 12 should error, as it did through 6.0.0-dev.20251205:

error TS2353: Object literal may only specify known properties, and 'bogus' does not exist in type 'Inner'.

Additional information about the issue

This is the shape of Prisma Client's query API — its SelectSubset<T, U> is the same mapped type with an extra conditional intersection, which the reduction above shows is not required to trigger the behavior.

The practical consequence is that on 6.0+ a Prisma select, where or data naming a column that does not exist compiles cleanly and throws at runtime (PrismaClientValidationError). In our codebase that reached production once already, and the failure looks intermittent in a way that took a while to pin down, because the weak-type check still catches the single-key case:

where:  { bogusColumn: 'x' }            // caught
where:  { id: 'x', bogusColumn: 'y' }   // silent

Things ruled out by A/B before filing, in case they save anyone time: the Prisma version (7.2.0 and 7.9.1 behave identically, and both are fully checked under TS 5.9), Prisma preview features, skipLibCheck, and the size of the generated types — a synthetic 272-model schema producing a 34 MB .d.ts still catches everything under 5.9. The compiler version is the only variable that moves this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions