Skip to content

fix(parse): let a shadowing name replace the table it hides - #316

Merged
tiagolauer merged 1 commit into
masterfrom
fix/285-286-shadowing
Aug 2, 2026
Merged

fix(parse): let a shadowing name replace the table it hides#316
tiagolauer merged 1 commit into
masterfrom
fix/285-286-shadowing

Conversation

@tiagolauer

Copy link
Copy Markdown
Owner

Fixes #285. Fixes #286.

Two issues, one root cause and one helper, which is why they are together here rather than in two PRs that would each need half of it.

The bug

Object intersection is additive: DB['users'] & { id: number } still exposes name. Two places relied on it where SQL says the name is hidden.

A CTE was omitted from the schema only for the outer query, so a later CTE body still saw the real table:

StrictQuery<DB, 'with users as (select id from users), t as (select name from users) select name from t'>
// was: { name: string }[]   -- inside `t`, `users` is the CTE, which has no `name`

And a derived table whose alias reuses a real table name was intersected with it:

StrictQuery<DB, 'select name from (select id from users) users'>
// was: { name: string }[]   -- the alias hides the table; only `id` is in scope

Both cases have strict mode accepting a query the database rejects, and typing a column the query cannot produce. The comment at ResolveCteContext already described the first gap.

The fix

ShadowedBy pairs the existing OmitShadowedTables with the intersection, and both sites use it.

It is gated on the overlay being non-empty. That matters: the omit is a mapped type over every table in the schema, and running it for the empty overlay a plain query carries measured +18,744 instantiations, 10% of the whole budget and blew the ceiling. With the gate the budget lands at 192,086, slightly under master's 192,486, because the intersection it replaces used to run on every query.

Versioning

Minor: strict mode reports a mistake it previously let through.

Verification

tests/shadowing.test-d.ts, two cases red on master (one per issue), seven controls green throughout:

  • outer-query CTE shadowing still works (it always did)
  • a CTE referring to an earlier CTE
  • an unshadowed table still visible from a CTE body
  • a derived table with a fresh alias
  • a derived alias still exposing its own columns
  • a plain query

tsc --noEmit clean, 192 unit tests pass.

Found during an audit pass; fix and tests written with Claude Code.

@tiagolauer
tiagolauer force-pushed the fix/285-286-shadowing branch from c1f9b3c to 0aa076d Compare August 1, 2026 15:17
Object intersection is additive, so `DB['users'] & { id: number }` still
exposes `name`. Two places relied on it where SQL says the name is hidden:

- a CTE was omitted from the schema only for the outer query, so a later CTE
  body still saw the real table's other columns (#285)
- a derived table whose alias reuses a real table name was intersected with
  that table instead of hiding it (#286)

Both let strict mode accept a query the database rejects, and type a column
the query cannot produce - `select name from (select id from users) users`
came back as `{ name: string }[]`.

ShadowedBy pairs the existing OmitShadowedTables with the intersection, and
both sites use it. It is gated on the overlay being non-empty: the omit is a
mapped type over every table in the schema, and running it for the empty
overlay a plain query carries measured 10% of the whole type budget. With
the gate the budget lands slightly under master's, since the intersection it
replaces used to run on every query.

Under VERSIONING.md this is a minor: strict mode reports a mistake it
previously let through.

Fixes #285
Fixes #286

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tiagolauer
tiagolauer force-pushed the fix/285-286-shadowing branch from 0aa076d to e1fc1d3 Compare August 2, 2026 13:05
@tiagolauer
tiagolauer merged commit f5265de into master Aug 2, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Derived-table alias shadowing a real table name is ignored CTE shadowing of a real table is not applied inside later CTE bodies

1 participant