Skip to content

fix(params): name the placeholder whose types conflict - #332

Merged
tiagolauer merged 1 commit into
masterfrom
fix/302-conflicting-param-types
Aug 2, 2026
Merged

fix(params): name the placeholder whose types conflict#332
tiagolauer merged 1 commit into
masterfrom
fix/302-conflicting-param-types

Conversation

@tiagolauer

Copy link
Copy Markdown
Owner

Fixes #302.

The bug

Params<DB, 'select id from users where id = $1 or name = $1'>
// [never]

Every call is rejected, including the zero-argument one, behind an opaque "not assignable to never" chain. Rejecting the query is defensible — pg refuses inconsistent deduced parameter types too — but nothing told the user what went wrong or where. #230 treated a never tuple as a bug for exactly this reason: the failure mode is invisible.

The fix

SetSlot intersects on repeat (Head & Type), and number & string is never. The merge step now checks for that and produces QueryTypeError<'conflicting types for $1'>, naming the token as written — $1, @v, :v.

An already-conflicting slot is kept rather than intersected again, so a third occurrence does not rename the error.

Verification

tests/conflicting-params.test-d.ts, eight cases. Four red on master:

tests/conflicting-params.test-d.ts(18,3): error TS2344: Type 'false' does not satisfy the constraint 'true'.
tests/conflicting-params.test-d.ts(25,3): ...
tests/conflicting-params.test-d.ts(32,3): ...
tests/conflicting-params.test-d.ts(40,3): ...
  • the numbered, @name and :name spellings each report their own token
  • the conflicting slot does not swallow the one beside it ([QueryTypeError<...>, string | null])

Four controls stay pinned: a repeat against the same type still collapses to one slot (numbered, named, and across a join), and two distinct placeholders are unaffected.

tsc --noEmit clean. Budget 193,953, up 1,467 from master's 192,486 (97% of 200,000) — the cost of one extra conditional in the merge step.

    Params<DB, 'select id from users where id = $1 or name = $1'>
    // was [never]

A repeated placeholder intersects the types it was read with, and
`number & string` is `never`. A tuple holding never is uncallable with any
argument list at all, the empty one included, and the compiler explains that
as a chain of "not assignable to never" - the failure mode #230 already
treated as a bug in its own right, because it is invisible.

Rejecting the query is the right call: pg refuses inconsistent deduced
parameter types too. So the rejection stays and the diagnosis is added -
QueryTypeError<'conflicting types for $1'>, naming the token as written.
An already-conflicting slot is left alone rather than intersected again, so
a third occurrence does not rename the error.

Costs 1,467 instantiations (192,486 -> 193,953, 97% of the budget), all of
it the extra conditional in the merge step.

Fixes #302

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tiagolauer
tiagolauer force-pushed the fix/302-conflicting-param-types branch from fd91210 to b522f48 Compare August 2, 2026 13:03
@tiagolauer
tiagolauer merged commit 5296c20 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.

Repeated $1 with conflicting column types collapses to [never] with no diagnostic

1 participant