Skip to content

fix(parse): split a bare alias at a top-level space - #341

Merged
tiagolauer merged 1 commit into
masterfrom
fix/276-multiarg-call-alias
Aug 2, 2026
Merged

fix(parse): split a bare alias at a top-level space#341
tiagolauer merged 1 commit into
masterfrom
fix/276-multiarg-call-alias

Conversation

@tiagolauer

Copy link
Copy Markdown
Owner

Fixes #276.

The bug

Query<DB, 'select power(age, 2) from users'>            // { '2)': unknown }[]
StrictRow<DB, 'select power(age, 2) from users'>        // QueryTypeError<'unknown column: power(age,'>
Query<DB, 'select cast(id as text) from users'>         // { 'as text)': unknown }[]
Query<DB, 'select extract(epoch from created_at) from users'> // { 'from created_at)': unknown }[]
Query<DB, "select coalesce(name, 'anon') from users">   // { "'')": unknown }[]

Adding an explicit alias made it work, but select power(age, 2) is everyday SQL and should give a power column like other unaliased calls do.

The fix

The bare-alias fallback matched `${infer Expression} ${infer Alias}`, which splits at the first space with no paren-balance check guarding the branch.

The split is now depth-tracked, reusing the ApplyParenDelta the sibling scans (FindTopLevelAsKeyword, ColumnsBeforeFrom) already use. A space inside a call is no longer a boundary, so an entry that is one call from end to end has no alias to find and falls through to OutputName — which is how count(*) has always been named.

Verification

tests/multiarg-call-alias.test-d.ts, eleven cases. Six red on master:

tests/multiarg-call-alias.test-d.ts(20,3): error TS2344: Type 'false' does not satisfy the constraint 'true'.
tests/multiarg-call-alias.test-d.ts(24,3): ...  (28,3), (32,3), (36,3), (41,3)
  • power(age, 2) in both modes, cast(id as text), extract(epoch from created_at), coalesce(name, 'anon'), and one among ordinary columns
  • an alias after the call is still an alias, bare or with AS

Controls: lower(name), count(*) and a plain bare alias are unchanged.

tsc --noEmit clean, 192 runtime tests pass. Budget 191,482 — 1,004 below master: the naive match was building an alias candidate for entries that went on to discard it.

    Query<DB, 'select power(age, 2) from users'>      // was { '2)': unknown }[]
    StrictRow<DB, 'select power(age, 2) from users'>  // was unknown column: power(age,

The bare-alias fallback matched `${infer Expression} ${infer Alias}`, which
splits at the first space in the entry with nothing checking whether that
space is inside a call. An unaliased call carrying one in its arguments was
cut in half: the tail became a fake alias and the head a fake column name.
`cast(id as text)`, `extract(epoch from created_at)` and
`coalesce(name, 'anon')` all failed the same way.

The split is now depth-tracked, using the same ApplyParenDelta the sibling
scans already use, so a space inside a call is not a boundary and an entry
that is one call from end to end simply has no alias. An alias written after
the call still is one, with or without AS.

Costs nothing: 191,482 instantiations against master's 192,486. The naive
match was building an alias candidate for entries that then discarded it.

Fixes #276

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tiagolauer
tiagolauer force-pushed the fix/276-multiarg-call-alias branch from 51583aa to 5932912 Compare August 2, 2026 13:04
@tiagolauer
tiagolauer merged commit 408d231 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.

Unaliased function call with a space in its arguments produces garbage keys and false strict errors

1 participant