Repository navigation
Make tuple types usable with array literals, add array destructuring - #116
Merged
Merged
Conversation
Tuples parsed and had a full Type implementation, but no array literal ever produced one, so `pair: [string, number] = ['a', 1]` failed with (string | number)[]. Array literals now infer to ArrayLiteralType, which remembers its elements by position and satisfies a tuple target through isCompatibleWith — covering declarations, arguments, returns, object properties, arrays of tuples, aliases and nesting at once, with no expected type plumbed through the visitor (at call sites the arguments are inferred before the callee's parameters resolve, so that would have meant restructuring visit order). widenFreshness() strips the positional view at variable bindings and inferred return types, so `x = ['a', 1]` stays a (string | number)[] and `as const` remains what makes a tuple type stick, as in TypeScript. Mismatch diagnostics now name the positions instead of the widened union: Cannot assign [number, string] to [string, number]. Adds array destructuring (`[a, b] = pair`), previously absent from the grammar and failing with a backend scope error. Names bind per position, annotated individually or as a whole pattern. Spreading a tuple keeps its length known, so `[...pair, true]` builds a longer tuple, and spread tuples now contribute their element types — `[...pair]` used to infer any[]. One test asserted that f([1, 2]) must fail for `type Pair = [1, 2]`; that encoded the old limitation (TypeScript accepts it) and now covers what `as const` actually adds. tupleTypes.test.blop was written around the gap and now uses real annotations. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Tuple types were documented as done and had a complete
TupleTypeimplementation with thorough unit tests — but no array literal ever produced one, so tuples were unusable in practice. Every ordinary use failed:The only way to get a tuple value was
['a', 1] as const. The existing tests encoded the workaround rather than the gap —tupleTypes.test.blopsaid "Functions WITHOUT typed parameters so array literals are accepted freely" and left// process([42, 'hello'])commented out.This PR makes tuples work, and adds the array destructuring they need to be useful.
1. Contextual typing of array literals
At a call site, arguments are inferred before the callee's parameter types are resolved (
handleFuncCallAccessreadsfuncCallNode.inference). Plumbing an expected type down through the visitor would have meant restructuring visit order across five sites. Instead the literal carries its own positional view:ArrayLiteralType(src/inference/Type.js) — anArrayTypesubclass that also remembers each element by position. It prints as the array it widens to ((string | number)[], so hover is unchanged) and overridesisCompatibleWithto match a tuple target position by position. One production point inliterals.jscovers declarations, arguments, returns, object properties, arrays of tuples, aliases and nesting at once.widenFreshness()strips the positional view wherever a value stops being an expression: variable bindings (visitor.js,statements.js), inferred return types (functions.js), and destructuring bindings. Sox = ['a', 1]is a(string | number)[]and nothing more, andas constremains what makes a tuple type stick — matching TypeScript.Diagnostics were actively misleading on the most common failure, since the widened union hides which position is wrong.
describeType()renders a literal positionally against a tuple target:Cannot assign (string | number)[] to [string, number]Cannot assign [number, string] to [string, number]Cannot assign (string | number)[] to [string, number]Cannot assign [string, number, number] to [string, number]expected [string, number] … but got (string | number)[]expected [string, number] … but got [number, string]Cannot assign (string | (boolean | number)[])[] to …Cannot assign [string, [boolean, number]] to [string, [number, boolean]]Spreading a tuple now keeps its length known, so
[...pair, true]satisfies[string, number, boolean]. This also fixes a latent bug:[...pair]used to inferany[], because aTupleTypeis not anArrayTypeand fell through to the "unknown spread" branch.2. Array destructuring
[a, b] = pairdid not exist in the grammar — onlyobject_destructuring— and failed with a confusing backend error (Token "x" is undefined in the current scope) rather than a parse error. Added across grammar, codegen and inference:Each name binds to its own element type — the element type of the tuple position, or the element type for a plain array. Out-of-range reports
Tuple [string, number] has no element at index 2; destructuring a clearly non-iterable value reportsCannot destructure 42 as an array(strings are left alone, being iterable in JS). It reuses the existing scope machinery, so redefinition errors and unused-variable warnings behave exactly as they do for object destructuring.The new
assignalternative is ordered beforeexp:path, which would otherwise read the left-hand side as an array literal. Statements that legitimately start with[(e.g.[a, b].forEach(…)) still parse as expression statements, and error messages for incomplete statements are byte-identical to the existing{ a, b } =behaviour.Tests
1502 → 1591 tests (137 → 140 files), all passing.
src/tests/typeSystem/tupleContextualTyping.test.js—ArrayLiteralTypeandwidenFreshnessunits, every tuple position, spread behaviour, positional mismatches, message assertions, and the two freshness-must-not-leak cases.src/tests/typeSystem/arrayDestructuring.test.js— element types, annotations, diagnostics.src/tests/languageFeatures/arrayDestructuring.test.blop— runtime behaviour.src/tests/typeSystem/tupleTypes.test.blop— rewritten to use real tuple annotations instead of working around their absence.src/tests/formatter/expressions.test.js— formatting and idempotency for the new syntax.One existing test changed meaning rather than breaking.
constAssertion.test.jsasserted thatf([1, 2])must fail fortype Pair = [1, 2]; TypeScript accepts that, so the test was encoding the missing feature. It is replaced by tests for whatas constactually adds — making the tuple survive a variable binding.Docs
docs/SYNTAX_REFERENCE.md; array destructuring added to the variables and arrays sections. Every example in both was executed against the compiler and produces the stated output.docs/TYPESCRIPT_GAP_ANALYSIS.mdcorrected: tuples updated, array destructuring added, and generic constraints moved out of "missing" —<T extends string>andK extends keyof Tare implemented and enforced, but were listed as Tier 3 not-yet-implemented.CHANGELOG.mdentries under 1.3.0.Verification
Notes for the reviewer
src/parser.jsis regenerated fromsrc/grammar.js; the real grammar change is 14 lines.vscode/blop-linter/.../server/src) is not synced in this PR — it is regenerated separately bynpm run linter, so the extension will report stale tuple warnings until that is run.[a, ...rest]) or hole ([, b]) patterns in array destructuring.(string | null)[]is rejected; andnpx eslintcannot run at all because.eslintrc.jsusesmodule.exportswhilepackage.jsonsets"type": "module".🤖 Generated with Claude Code