@tilua/parser 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -593,9 +593,17 @@ An array is a table, at runtime as much as in the types: `return [a, b]` is
593
593
  a Roblox method such as `FindPartOnRay` — the type library puts a function of
594
594
  its own in place of that global or method, which answers one value, as its
595
595
  type says. A tuple may end in a rest,
596
- `[string, ...number[]]`: that many, then any number more. `const a, b = 1, 2`
597
- still pairs names with values one each; `const a, b = f()` is a name short,
598
- and says to write `const [a, b] = f()`.
596
+ `[string, ...number[]]`: that many, then any number more.
597
+
598
+ A declaration and an assignment take one value, and a call is one value
599
+ wherever it is written — an argument, a `return`. Luau's `local a, b = 1, 2`
600
+ is an error that says to write `const [a, b] = [1, 2]`. Where a Luau function
601
+ answers several values and nothing gathers them, ask for all of them by making
602
+ the call an array's last element:
603
+
604
+ ```luau
605
+ const [a, b] = [require(module)] -- { require(module) }: every value
606
+ ```
599
607
 
600
608
  **A loop takes one value too** — `for (const item in source)`, and several are
601
609
  an array the pattern takes apart:
package/dist/index.cjs CHANGED
@@ -969,10 +969,10 @@ var Parser = class {
969
969
  expressionOr(stop) {
970
970
  return this.attempt(() => this.parseExpression(), stop, (start, from) => this.errorExpression(start, from));
971
971
  }
972
- /** A comma-separated list of values: a `return`'s, a declaration's, an
973
- * assignment's. Each is one value; a spread belongs in a call's
974
- * arguments or an array, and is reported here — then read as one, so
975
- * the rest still means something. */
972
+ /** A comma-separated list of values where one belongs — a `return`'s, a
973
+ * declaration's, an assignment's — read whole, so that writing several
974
+ * is reported once and the rest still means something. A spread belongs
975
+ * in a call's arguments or an array, and is reported here too. */
976
976
  expressionListOr(stop) {
977
977
  const until = () => stop() || this.checkPunctuator(",");
978
978
  const item = () => {
@@ -1564,7 +1564,7 @@ var Parser = class {
1564
1564
  }
1565
1565
  this.error("Expected 'const', 'let', 'function', 'class', 'type', 'default', '{' or '*' after 'export'");
1566
1566
  }
1567
- // `const x = ...` / `let x, y = ...`.
1567
+ // `const x = ...` / `let [a, b] = ...`.
1568
1568
  // tilua has no `local` — `const` bindings are immutable, `let` mutable.
1569
1569
  /** `kind` reads the leading word as that keyword (recovery's `local`). */
1570
1570
  parseVariableDeclaration(as) {
@@ -1578,14 +1578,34 @@ var Parser = class {
1578
1578
  while (this.matchPunctuator(",")) {
1579
1579
  names.push(this.parseBindingTarget(true));
1580
1580
  }
1581
- let init = [];
1581
+ let values = [];
1582
1582
  if (this.matchOperator("=")) {
1583
- init = this.expressionListOr(() => false);
1583
+ values = this.expressionListOr(() => false);
1584
1584
  } else if (kind === "const") {
1585
1585
  if (!this.recover) this.error("'const' declaration requires an initializer");
1586
1586
  this.softError("'const' declaration requires an initializer");
1587
1587
  }
1588
- return { type: "VariableDeclaration", kind, names, init, ...spanFrom(start, this.previous()) };
1588
+ let name = names[0];
1589
+ let init = values[0];
1590
+ if (names.length > 1 || values.length > 1) {
1591
+ this.problemAt(start, `A declaration takes one value: take several from an array, '${kind} [a, b] = [x, y]'`);
1592
+ if (names.length > 1) name = this.namesAsPattern(names);
1593
+ if (values.length > 1) init = this.valuesAsArray(values);
1594
+ }
1595
+ return { type: "VariableDeclaration", kind, name, init, ...spanFrom(start, this.previous()) };
1596
+ }
1597
+ /** `a, b` written where one name goes, read as `[a, b]`. */
1598
+ namesAsPattern(names) {
1599
+ const elements = names.map((value) => ({
1600
+ type: "ArrayPatternElement",
1601
+ value,
1602
+ ...spanFrom(value, value)
1603
+ }));
1604
+ return { type: "ArrayPattern", elements, ...spanFrom(names[0], names[names.length - 1]) };
1605
+ }
1606
+ /** `x, y` written where one value goes, read as `[x, y]`. */
1607
+ valuesAsArray(values) {
1608
+ return { type: "ArrayExpression", elements: values, ...spanFrom(values[0], values[values.length - 1]) };
1589
1609
  }
1590
1610
  parseIfStatement() {
1591
1611
  const start = this.current();
@@ -2066,7 +2086,7 @@ var Parser = class {
2066
2086
  argument = values[0];
2067
2087
  if (values.length > 1) {
2068
2088
  this.problemAt(start, "A function returns one value: return several as an array, 'return [a, b]'");
2069
- argument = { type: "ArrayExpression", elements: values, ...spanFrom(values[0], values[values.length - 1]) };
2089
+ argument = this.valuesAsArray(values);
2070
2090
  }
2071
2091
  }
2072
2092
  return { type: "ReturnStatement", argument, ...spanFrom(start, this.previous()) };
@@ -2092,28 +2112,15 @@ var Parser = class {
2092
2112
  parseExpressionStatement() {
2093
2113
  const start = this.current();
2094
2114
  if (this.checkPunctuator("{") || this.checkPunctuator("[")) {
2095
- const targets = [
2096
- this.checkPunctuator("{") ? this.parseObjectPattern() : this.parseArrayPattern()
2097
- ];
2098
- while (this.matchPunctuator(",")) {
2099
- targets.push(this.parseAssignTarget());
2100
- }
2101
- this.expectOperator("=");
2102
- const values = this.expressionListOr(() => false);
2103
- return { type: "AssignmentStatement", targets, values, ...spanFrom(start, this.previous()) };
2115
+ const target = this.checkPunctuator("{") ? this.parseObjectPattern() : this.parseArrayPattern();
2116
+ return this.parseAssignmentRest(start, target);
2104
2117
  }
2105
2118
  const statementStart = this.cursor;
2106
2119
  const errorsAtStart = this.errors.length;
2107
2120
  const first = this.parsePrefixExpression();
2108
2121
  if (this.checkOperator("=") || this.checkPunctuator(",")) {
2109
- const targets = [first];
2110
- while (this.matchPunctuator(",")) {
2111
- targets.push(this.parseAssignTarget());
2112
- }
2113
- for (const target of targets) this.rejectOptionalTarget(target);
2114
- this.expectOperator("=");
2115
- const values = this.expressionListOr(() => false);
2116
- return { type: "AssignmentStatement", targets, values, ...spanFrom(start, this.previous()) };
2122
+ this.rejectOptionalTarget(first);
2123
+ return this.parseAssignmentRest(start, first);
2117
2124
  }
2118
2125
  const t = this.current();
2119
2126
  if (t.type === "Operator" && COMPOUND_ASSIGN_OPS.has(t.value)) {
@@ -2139,6 +2146,21 @@ var Parser = class {
2139
2146
  }
2140
2147
  return { type: "ExpressionStatement", expression, ...spanFrom(start, this.previous()) };
2141
2148
  }
2149
+ /** From after an assignment's target: `= value`. Luau's `a, b = x, y`
2150
+ * is reported, and read as its first target taking its first value. */
2151
+ parseAssignmentRest(start, target) {
2152
+ let several = false;
2153
+ while (this.matchPunctuator(",")) {
2154
+ this.parseAssignTarget();
2155
+ several = true;
2156
+ }
2157
+ this.expectOperator("=");
2158
+ const values = this.expressionListOr(() => false);
2159
+ if (several || values.length > 1) {
2160
+ this.problemAt(start, "An assignment takes one value: take several from an array, '[a, b] = [x, y]'");
2161
+ }
2162
+ return { type: "AssignmentStatement", target, value: values[0], ...spanFrom(start, this.previous()) };
2163
+ }
2142
2164
  // ============================================================
2143
2165
  // Expressions
2144
2166
  // ============================================================
@@ -2622,8 +2644,8 @@ var Parser = class {
2622
2644
  startsSuperUse() {
2623
2645
  return this.punctuatorAt(1, "(") || this.punctuatorAt(1, ".") && this.peek(2).type === "Identifier";
2624
2646
  }
2625
- /** After `...`, is there something to spread? Nothing following it means
2626
- * the vararg pack, which is what `f(...)` has always passed on. */
2647
+ /** After `...`, is there something to spread? tilua has no bare `...`:
2648
+ * a varying number of arguments is a rest parameter, `...args: T[]`. */
2627
2649
  startsSpread() {
2628
2650
  const next = this.peek(1);
2629
2651
  if (next.type === "Punctuator") {
@@ -2642,8 +2664,8 @@ var Parser = class {
2642
2664
  const token = this.peek(ahead);
2643
2665
  return token.type === "Punctuator" && token.value === value;
2644
2666
  }
2645
- /** An assignment target after the first: a prefix expression (`a.b`,
2646
- * `a[i]`, `a`) or a nested destructuring pattern. */
2667
+ /** An assignment target after the first — Luau's `a, b = ...`, read only
2668
+ * to be reported: a prefix expression or a destructuring pattern. */
2647
2669
  parseAssignTarget() {
2648
2670
  if (this.checkPunctuator("{")) return this.parseObjectPattern();
2649
2671
  if (this.checkPunctuator("[")) return this.parseArrayPattern();
@@ -3134,7 +3156,7 @@ var Parser = class {
3134
3156
  };
3135
3157
  })() : this.parseFunctionHead();
3136
3158
  this.expectPunctuator("=>");
3137
- const body = this.checkPunctuator("{") ? this.parseBraceBlock() : this.returnOf(this.parseExpression(0));
3159
+ const body = this.checkPunctuator("{") ? this.parseBraceBlock() : this.parseArrowExpressionBody();
3138
3160
  const func = {
3139
3161
  type: "FunctionBody",
3140
3162
  generics: head.generics,
@@ -3147,6 +3169,29 @@ var Parser = class {
3147
3169
  };
3148
3170
  return { type: "FunctionExpression", func, ...spanFrom(start, this.previous()) };
3149
3171
  }
3172
+ /** `() => value` returns the value. `() => a = 1` and `() => n += 1` are
3173
+ * an assignment, which is a statement: the body is that
3174
+ * statement, and the function returns nothing. */
3175
+ parseArrowExpressionBody() {
3176
+ const start = this.cursor;
3177
+ const errors = this.errors.length;
3178
+ const expression = this.parseExpression(0);
3179
+ const t = this.current();
3180
+ const assigns = t.type === "Operator" && (t.value === "=" || COMPOUND_ASSIGN_OPS.has(t.value));
3181
+ if (!assigns) return this.returnOf(expression);
3182
+ this.cursor = start;
3183
+ this.errors.length = errors;
3184
+ const first = this.current();
3185
+ const target = this.parsePrefixExpression();
3186
+ this.rejectOptionalTarget(target);
3187
+ const opToken = this.current();
3188
+ const op = opToken.value;
3189
+ if (opToken.type !== "Operator" || op !== "=" && !COMPOUND_ASSIGN_OPS.has(op)) this.error("Expected '='");
3190
+ this.advance();
3191
+ const value = this.expressionOr(() => false);
3192
+ const statement = op === "=" ? { type: "AssignmentStatement", target, value, ...spanFrom(first, this.previous()) } : { type: "CompoundAssignmentStatement", operator: op, target, value, ...spanFrom(first, this.previous()) };
3193
+ return { type: "Block", statements: [statement], ...spanFrom(statement, statement) };
3194
+ }
3150
3195
  /** A one-expression body: the value is what the function returns. */
3151
3196
  returnOf(expression) {
3152
3197
  const statement = {
@@ -4078,10 +4123,9 @@ var Analyzer = class {
4078
4123
  visitStatement(stmt, scope) {
4079
4124
  switch (stmt.type) {
4080
4125
  case "VariableDeclaration": {
4081
- for (const init of stmt.init) this.visitExpression(init, scope);
4082
- for (const name of stmt.names) this.visitType(name.typeAnnotation, scope);
4083
- const isConst = stmt.kind === "const";
4084
- for (const name of stmt.names) this.declarePattern(scope, name, "local", scope, isConst);
4126
+ if (stmt.init) this.visitExpression(stmt.init, scope);
4127
+ this.visitType(stmt.name.typeAnnotation, scope);
4128
+ this.declarePattern(scope, stmt.name, "local", scope, stmt.kind === "const");
4085
4129
  return;
4086
4130
  }
4087
4131
  case "FunctionDeclaration": {
@@ -4115,16 +4159,15 @@ var Analyzer = class {
4115
4159
  return;
4116
4160
  }
4117
4161
  case "AssignmentStatement": {
4118
- for (const value of stmt.values) this.visitExpression(value, scope);
4119
- for (const target of stmt.targets) {
4120
- if (target.type === "Identifier") {
4121
- this.referenceAsAssignmentTarget(scope, target);
4122
- } else if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
4123
- this.assignPattern(scope, target);
4124
- } else {
4125
- this.visitExpression(target, scope);
4126
- this.checkModuleWrite(target);
4127
- }
4162
+ this.visitExpression(stmt.value, scope);
4163
+ const target = stmt.target;
4164
+ if (target.type === "Identifier") {
4165
+ this.referenceAsAssignmentTarget(scope, target);
4166
+ } else if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
4167
+ this.assignPattern(scope, target);
4168
+ } else {
4169
+ this.visitExpression(target, scope);
4170
+ this.checkModuleWrite(target);
4128
4171
  }
4129
4172
  return;
4130
4173
  }
@@ -5448,7 +5491,7 @@ function moduleExports(program, scopes, types, resolveModule) {
5448
5491
  params: declaration.typeParams.map((g) => g.name)
5449
5492
  });
5450
5493
  }
5451
- } else for (const target of declaration.names) exportPattern(target);
5494
+ } else exportPattern(declaration.name);
5452
5495
  } else if (stmt.type === "ExportTypeAliasStatement") {
5453
5496
  const name = stmt.alias.name.name;
5454
5497
  const type = types.aliases.get(name);
@@ -5687,12 +5730,10 @@ function assignedFields(block) {
5687
5730
  const names = /* @__PURE__ */ new Set();
5688
5731
  walkNodes(block, (node) => {
5689
5732
  const record = node;
5690
- const targets = record.type === "AssignmentStatement" ? record.targets : record.type === "CompoundAssignmentStatement" ? [record.target] : void 0;
5691
- for (const target of targets ?? []) {
5692
- const member = target;
5693
- if (member.type === "MemberExpression" && member.object?.type === "Identifier" && member.object.name === "this" && member.property?.name) {
5694
- names.add(member.property.name);
5695
- }
5733
+ if (record.type !== "AssignmentStatement" && record.type !== "CompoundAssignmentStatement") return;
5734
+ const member = record.target;
5735
+ if (member.type === "MemberExpression" && member.object?.type === "Identifier" && member.object.name === "this" && member.property?.name) {
5736
+ names.add(member.property.name);
5696
5737
  }
5697
5738
  });
5698
5739
  return names;
@@ -6168,12 +6209,10 @@ var TypeAnalyzer = class {
6168
6209
  classDisplayNames = /* @__PURE__ */ new WeakMap();
6169
6210
  /** `const Name = class ... end` — the name the class will be known by. */
6170
6211
  nameClassExpressions(stmt) {
6171
- stmt.names.forEach((target, i) => {
6172
- const value = stmt.init[i];
6173
- if (target.type === "IdentifierPattern" && value?.type === "ClassExpression" && !value.name) {
6174
- this.classDisplayNames.set(value, target.name);
6175
- }
6176
- });
6212
+ const value = stmt.init;
6213
+ if (stmt.name.type === "IdentifierPattern" && value?.type === "ClassExpression" && !value.name) {
6214
+ this.classDisplayNames.set(value, stmt.name.name);
6215
+ }
6177
6216
  }
6178
6217
  classTypeParams(stmt) {
6179
6218
  return stmt.type === "ClassDeclaration" ? stmt.typeParams : [];
@@ -7456,35 +7495,30 @@ var TypeAnalyzer = class {
7456
7495
  switch (stmt.type) {
7457
7496
  case "VariableDeclaration": {
7458
7497
  this.nameClassExpressions(stmt);
7459
- stmt.names.forEach((target, i) => {
7460
- if (target.type === "IdentifierPattern" && target.typeAnnotation && stmt.init[i]) {
7461
- this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
7462
- }
7463
- });
7464
- const { types: valueTypes, sources } = this.valueList(stmt.init, env);
7465
- this.checkValueCount(stmt, stmt.names.length, stmt.init, valueTypes);
7466
- stmt.names.forEach((target, i) => {
7467
- const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
7468
- const source = sources[i];
7469
- if (this.emitDiagnostics && target.type === "IdentifierPattern" && target.typeAnnotation && source) {
7470
- const declared = this.resolveType(target.typeAnnotation);
7471
- if (declared.kind !== "any" && !this.namesNothing(declared) && !this.fitsAnnotation(source, declared, inferred, env)) {
7472
- this.diagnostics.push({
7473
- node: stmt,
7474
- message: `Type '${briefType(inferred)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(inferred, declared)
7475
- });
7476
- } else if (declared.kind !== "any") {
7477
- this.reportExcessProperties(source, declared);
7478
- }
7479
- }
7480
- const mode = this.initIsAsConst(source) ? "asconst" : !isFreshLiteralExpr(source) ? "keep" : stmt.kind === "const" ? "const" : "widen";
7481
- this.bindPattern(target, inferred, env, mode);
7482
- if (stmt.kind === "const") {
7483
- this.correlateDestructuring(target, inferred, env);
7484
- this.correlateIndexed(target, source, env);
7485
- this.aliasReference(target, source);
7498
+ const target = stmt.name;
7499
+ const source = stmt.init;
7500
+ if (target.type === "IdentifierPattern" && target.typeAnnotation && source) {
7501
+ this.applyContext(source, this.resolveType(target.typeAnnotation));
7502
+ }
7503
+ const inferred = source ? this.infer(source, env) : nilType;
7504
+ if (this.emitDiagnostics && target.type === "IdentifierPattern" && target.typeAnnotation && source) {
7505
+ const declared = this.resolveType(target.typeAnnotation);
7506
+ if (declared.kind !== "any" && !this.namesNothing(declared) && !this.fitsAnnotation(source, declared, inferred, env)) {
7507
+ this.diagnostics.push({
7508
+ node: stmt,
7509
+ message: `Type '${briefType(inferred)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(inferred, declared)
7510
+ });
7511
+ } else if (declared.kind !== "any") {
7512
+ this.reportExcessProperties(source, declared);
7486
7513
  }
7487
- });
7514
+ }
7515
+ const mode = this.initIsAsConst(source) ? "asconst" : !isFreshLiteralExpr(source) ? "keep" : stmt.kind === "const" ? "const" : "widen";
7516
+ this.bindPattern(target, inferred, env, mode);
7517
+ if (stmt.kind === "const") {
7518
+ this.correlateDestructuring(target, inferred, env);
7519
+ this.correlateIndexed(target, source, env);
7520
+ this.aliasReference(target, source);
7521
+ }
7488
7522
  return;
7489
7523
  }
7490
7524
  case "ClassDeclaration": {
@@ -7541,48 +7575,41 @@ var TypeAnalyzer = class {
7541
7575
  return;
7542
7576
  }
7543
7577
  case "AssignmentStatement": {
7544
- stmt.targets.forEach((target, i) => {
7545
- const value = stmt.values[i];
7546
- if (!value) return;
7547
- if (target.type === "MemberExpression" || target.type === "IndexExpression") {
7548
- this.applyContext(value, this.infer(target, env));
7549
- } else if (target.type === "Identifier") {
7550
- const id = this.bindingIdOf(target);
7551
- if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
7552
- }
7553
- });
7554
- const { types: valueTypes, sources } = this.valueList(stmt.values, env);
7555
- this.checkValueCount(stmt, stmt.targets.length, stmt.values, valueTypes);
7556
- stmt.targets.forEach((target, i) => {
7557
- const vt = valueTypes[i] ?? unknownType;
7558
- const source = sources[i];
7559
- if (target.type === "Identifier") {
7560
- const id = this.bindingIdOf(target);
7561
- if (id !== void 0) {
7562
- this.uncorrelate(id);
7563
- const next = isFreshLiteralExpr(source) ? widen(vt) : vt;
7564
- if (this.annotated.has(id)) {
7565
- const declared = this.bindingType.get(id);
7566
- if (this.emitDiagnostics && !isAssignable(next, declared) && declared.kind !== "any") {
7567
- this.diagnostics.push({
7568
- node: stmt,
7569
- message: `Type '${briefType(next)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(next, declared)
7570
- });
7571
- }
7572
- this.setBinding(env, id, narrowTo(declared, next));
7573
- } else {
7574
- this.setBinding(env, id, next);
7575
- this.bindingType.set(id, union([this.bindingType.get(id) ?? next, next]));
7578
+ const target = stmt.target;
7579
+ const source = stmt.value;
7580
+ if (target.type === "MemberExpression" || target.type === "IndexExpression") {
7581
+ this.applyContext(source, this.infer(target, env));
7582
+ } else if (target.type === "Identifier") {
7583
+ const id = this.bindingIdOf(target);
7584
+ if (id !== void 0 && this.annotated.has(id)) this.applyContext(source, this.bindingType.get(id));
7585
+ }
7586
+ const vt = this.infer(source, env);
7587
+ if (target.type === "Identifier") {
7588
+ const id = this.bindingIdOf(target);
7589
+ if (id !== void 0) {
7590
+ this.uncorrelate(id);
7591
+ const next = isFreshLiteralExpr(source) ? widen(vt) : vt;
7592
+ if (this.annotated.has(id)) {
7593
+ const declared = this.bindingType.get(id);
7594
+ if (this.emitDiagnostics && !isAssignable(next, declared) && declared.kind !== "any") {
7595
+ this.diagnostics.push({
7596
+ node: stmt,
7597
+ message: `Type '${briefType(next)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(next, declared)
7598
+ });
7576
7599
  }
7600
+ this.setBinding(env, id, narrowTo(declared, next));
7601
+ } else {
7602
+ this.setBinding(env, id, next);
7603
+ this.bindingType.set(id, union([this.bindingType.get(id) ?? next, next]));
7577
7604
  }
7578
- } else if (target.type === "MemberExpression" || target.type === "IndexExpression") {
7579
- this.infer(target, env);
7580
- this.checkReadonlyAssign(target, env);
7581
- this.assignToRef(target, isFreshLiteralExpr(source) ? widen(vt) : vt, env);
7582
- } else if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
7583
- this.reassignPattern(target, vt, env);
7584
7605
  }
7585
- });
7606
+ } else if (target.type === "MemberExpression" || target.type === "IndexExpression") {
7607
+ this.infer(target, env);
7608
+ this.checkReadonlyAssign(target, env);
7609
+ this.assignToRef(target, isFreshLiteralExpr(source) ? widen(vt) : vt, env);
7610
+ } else if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
7611
+ this.reassignPattern(target, vt, env);
7612
+ }
7586
7613
  return;
7587
7614
  }
7588
7615
  case "CompoundAssignmentStatement":
@@ -7746,55 +7773,6 @@ var TypeAnalyzer = class {
7746
7773
  return;
7747
7774
  }
7748
7775
  }
7749
- /** Flatten an expression list into the values it actually produces.
7750
- *
7751
- * Lua's adjustment rule: every expression but the last contributes one
7752
- * value, and the last contributes all of its values if it is a call or
7753
- * `...`. That is what makes `local ok, err = pcall(f)` work, and it is
7754
- * why `local x = f()` takes only the first value.
7755
- *
7756
- * `sources` maps each produced value back to the expression it came from
7757
- * (undefined for the 2nd and later values of a multi-value call), so the
7758
- * caller can still do contextual typing against the written expression. */
7759
- valueList(exprs, env) {
7760
- const types = [];
7761
- const sources = [];
7762
- exprs.forEach((e, i) => {
7763
- const t = this.infer(e, env);
7764
- const last = i === exprs.length - 1;
7765
- if (e.type === "SpreadElement") {
7766
- const held = this.typeOf.get(e.argument);
7767
- const expanded = held && this.expand(held);
7768
- if (expanded?.kind === "tuple") {
7769
- for (const element of expanded.elements) {
7770
- types.push(element);
7771
- sources.push(e);
7772
- }
7773
- return;
7774
- }
7775
- types.push(t);
7776
- sources.push(e);
7777
- return;
7778
- }
7779
- void last;
7780
- types.push(t);
7781
- sources.push(e);
7782
- });
7783
- return { types, sources };
7784
- }
7785
- /** `const a, b = x, y` pairs names with values, one each. A call is one
7786
- * value — several come back as an array — so `const a, b = f()` is a
7787
- * name short of a value, and says how to take them apart. */
7788
- checkValueCount(node, names, values, types) {
7789
- if (!this.emitDiagnostics || !values.length || types.length >= names) return;
7790
- const last = values[values.length - 1];
7791
- const lastType = types[types.length - 1] && this.expand(types[types.length - 1]);
7792
- const call = last.type === "CallExpression" || last.type === "MethodCallExpression";
7793
- this.diagnostics.push({
7794
- node,
7795
- message: call && (lastType?.kind === "tuple" || lastType?.kind === "array") ? `A call is one value, here an array: take its parts with '[${Array.from({ length: names }, (_, i) => String.fromCharCode(97 + i)).join(", ")}] = ...'` : `${names} names, but ${types.length} ${types.length === 1 ? "value" : "values"}: each name needs one`
7796
- });
7797
- }
7798
7776
  /** Run `visit` with a fresh place to collect `break` states, and return
7799
7777
  * what it collected. */
7800
7778
  withBreakScope(visit) {
@@ -9778,7 +9756,35 @@ var TypeAnalyzer = class {
9778
9756
  }
9779
9757
  const read = this.expand(type);
9780
9758
  if (read.kind === "unknown" && read.declared) this.reportUnknownAccess(object);
9781
- return { type, shortCircuits: inChain !== void 0 || link.optional === true };
9759
+ const optionalNil = link.optional === true && this.mayBeNil(inChain ?? full);
9760
+ return { type, shortCircuits: inChain !== void 0 || optionalNil };
9761
+ }
9762
+ /** Whether a value of `raw` can be nil — `includesNil`, and also the types
9763
+ * that do not say: `any`, `unknown`, an unconstrained type parameter. */
9764
+ mayBeNil(raw) {
9765
+ const t = this.expand(raw);
9766
+ switch (t.kind) {
9767
+ case "primitive":
9768
+ return t.name === "nil";
9769
+ case "any":
9770
+ case "unknown":
9771
+ return true;
9772
+ case "typeParam":
9773
+ return t.constraint === void 0 || this.mayBeNil(t.constraint);
9774
+ case "union":
9775
+ return t.types.some((m) => this.mayBeNil(m));
9776
+ case "never":
9777
+ case "literal":
9778
+ case "array":
9779
+ case "tuple":
9780
+ case "object":
9781
+ case "function":
9782
+ case "intersection":
9783
+ case "templateLiteral":
9784
+ return false;
9785
+ default:
9786
+ return true;
9787
+ }
9782
9788
  }
9783
9789
  /** Objects already reported as possibly nil: a loop body is visited more
9784
9790
  * than once. */
@@ -10700,13 +10706,12 @@ var TypeAnalyzer = class {
10700
10706
  computingAhead = /* @__PURE__ */ new Set();
10701
10707
  declaredAhead(id) {
10702
10708
  this.aheadDeclarations ??= this.indexAheadDeclarations();
10703
- const found = this.aheadDeclarations.get(id);
10704
- if (!found || this.computingAhead.has(id)) return void 0;
10709
+ const statement = this.aheadDeclarations.get(id);
10710
+ if (!statement || this.computingAhead.has(id)) return void 0;
10705
10711
  this.computingAhead.add(id);
10706
10712
  const wasEmitting = this.emitDiagnostics;
10707
10713
  this.emitDiagnostics = false;
10708
10714
  try {
10709
- const { statement, index } = found;
10710
10715
  let type;
10711
10716
  if (statement.type === "DeclareStatement") {
10712
10717
  type = this.resolveType(statement.valueType);
@@ -10715,11 +10720,11 @@ var TypeAnalyzer = class {
10715
10720
  } else if (statement.type === "ClassDeclaration") {
10716
10721
  type = this.classValueType(statement);
10717
10722
  } else if (statement.type === "VariableDeclaration") {
10718
- const target = statement.names[index];
10723
+ const target = statement.name;
10719
10724
  if (target.type === "IdentifierPattern" && target.typeAnnotation) {
10720
10725
  type = this.resolveType(target.typeAnnotation);
10721
- } else if (statement.init[index]) {
10722
- const value = this.infer(statement.init[index], /* @__PURE__ */ new Map());
10726
+ } else if (statement.init) {
10727
+ const value = this.infer(statement.init, /* @__PURE__ */ new Map());
10723
10728
  type = statement.kind === "const" ? value : widen(value);
10724
10729
  }
10725
10730
  }
@@ -10737,11 +10742,10 @@ var TypeAnalyzer = class {
10737
10742
  for (const statement of this.program.body.statements) {
10738
10743
  const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
10739
10744
  if (declaration.type !== "VariableDeclaration") continue;
10740
- declaration.names.forEach((target, index) => {
10741
- if (target.type !== "IdentifierPattern") return;
10742
- const id = this.bindingIdByName(target.name, target);
10743
- if (id !== void 0) out.set(id, { statement: declaration, index });
10744
- });
10745
+ const target = declaration.name;
10746
+ if (target.type !== "IdentifierPattern") continue;
10747
+ const id = this.bindingIdByName(target.name, target);
10748
+ if (id !== void 0) out.set(id, declaration);
10745
10749
  }
10746
10750
  const visit = (node) => {
10747
10751
  if (!node || typeof node !== "object") return;
@@ -10752,11 +10756,11 @@ var TypeAnalyzer = class {
10752
10756
  const record = node;
10753
10757
  if (record.type === "ClassDeclaration" && record.name) {
10754
10758
  const id = this.bindingIdByName(record.name.name, record.name);
10755
- if (id !== void 0) out.set(id, { statement: node, index: 0 });
10759
+ if (id !== void 0) out.set(id, node);
10756
10760
  }
10757
10761
  if (record.type === "FunctionDeclaration" && record.name) {
10758
10762
  const id = this.bindingIdByName(record.name.name, record.name);
10759
- if (id !== void 0) out.set(id, { statement: node, index: 0 });
10763
+ if (id !== void 0) out.set(id, node);
10760
10764
  }
10761
10765
  for (const [key, value] of Object.entries(node)) {
10762
10766
  if (key !== "line" && key !== "column" && value && typeof value === "object") visit(value);
@@ -10765,7 +10769,7 @@ var TypeAnalyzer = class {
10765
10769
  visit(this.program.body);
10766
10770
  for (const [name, statement] of this.deferredDeclares) {
10767
10771
  const id = this.scopes.globalsByName.get(name);
10768
- if (id !== void 0) out.set(id, { statement, index: 0 });
10772
+ if (id !== void 0) out.set(id, statement);
10769
10773
  }
10770
10774
  return out;
10771
10775
  }
package/dist/index.d.cts CHANGED
@@ -242,14 +242,17 @@ interface DeclareClassStatement extends BaseNode {
242
242
  interface ErrorStatement extends BaseNode {
243
243
  type: "ErrorStatement";
244
244
  }
245
- /** `const x = 1` / `let x, y = a, b` — the only variable-binding form in tilua
246
- * (Luau's `local` is gone). `const` bindings are immutable and infer literal
247
- * types (`const n = 1` → `1`); `let` bindings are mutable and widen. */
245
+ /** `const x = 1` / `let [a, b] = pair` — the only variable-binding form in
246
+ * tilua (Luau's `local` is gone). One name, or one pattern, and one value:
247
+ * several are taken from an array with a destructuring. `const` bindings are
248
+ * immutable and infer literal types (`const n = 1` → `1`); `let` bindings
249
+ * are mutable and widen. */
248
250
  interface VariableDeclaration extends BaseNode {
249
251
  type: "VariableDeclaration";
250
252
  kind: "const" | "let";
251
- names: BindingTarget[];
252
- init: Expression[];
253
+ name: BindingTarget;
254
+ /** Absent only on `let x`. */
255
+ init?: Expression;
253
256
  }
254
257
  type BindingTarget = IdentifierPattern | ObjectPattern | ArrayPattern;
255
258
  interface IdentifierPattern extends BaseNode {
@@ -448,10 +451,11 @@ interface ClassConstructor extends BaseNode {
448
451
  interface SuperExpression extends BaseNode {
449
452
  type: "SuperExpression";
450
453
  }
454
+ /** `x = v` / `[a, b] = [b, a]` — one target, one value. */
451
455
  interface AssignmentStatement extends BaseNode {
452
456
  type: "AssignmentStatement";
453
- targets: (Expression | ObjectPattern | ArrayPattern)[];
454
- values: Expression[];
457
+ target: Expression | ObjectPattern | ArrayPattern;
458
+ value: Expression;
455
459
  }
456
460
  interface CompoundAssignmentStatement extends BaseNode {
457
461
  type: "CompoundAssignmentStatement";
@@ -672,9 +676,7 @@ interface ArrayExpression extends BaseNode {
672
676
  * only produces one in those two places.
673
677
  *
674
678
  * Lua spreads with `table.unpack`, which only yields every value when it is
675
- * written last; anywhere else the compiler builds the whole list first. Bare
676
- * `...` is unaffected — that is the vararg pack, and `f(...)` passes it on
677
- * as it always did. */
679
+ * written last; anywhere else the compiler builds the whole list first. */
678
680
  interface SpreadElement extends BaseNode {
679
681
  type: "SpreadElement";
680
682
  argument: Expression;
package/dist/index.d.ts CHANGED
@@ -242,14 +242,17 @@ interface DeclareClassStatement extends BaseNode {
242
242
  interface ErrorStatement extends BaseNode {
243
243
  type: "ErrorStatement";
244
244
  }
245
- /** `const x = 1` / `let x, y = a, b` — the only variable-binding form in tilua
246
- * (Luau's `local` is gone). `const` bindings are immutable and infer literal
247
- * types (`const n = 1` → `1`); `let` bindings are mutable and widen. */
245
+ /** `const x = 1` / `let [a, b] = pair` — the only variable-binding form in
246
+ * tilua (Luau's `local` is gone). One name, or one pattern, and one value:
247
+ * several are taken from an array with a destructuring. `const` bindings are
248
+ * immutable and infer literal types (`const n = 1` → `1`); `let` bindings
249
+ * are mutable and widen. */
248
250
  interface VariableDeclaration extends BaseNode {
249
251
  type: "VariableDeclaration";
250
252
  kind: "const" | "let";
251
- names: BindingTarget[];
252
- init: Expression[];
253
+ name: BindingTarget;
254
+ /** Absent only on `let x`. */
255
+ init?: Expression;
253
256
  }
254
257
  type BindingTarget = IdentifierPattern | ObjectPattern | ArrayPattern;
255
258
  interface IdentifierPattern extends BaseNode {
@@ -448,10 +451,11 @@ interface ClassConstructor extends BaseNode {
448
451
  interface SuperExpression extends BaseNode {
449
452
  type: "SuperExpression";
450
453
  }
454
+ /** `x = v` / `[a, b] = [b, a]` — one target, one value. */
451
455
  interface AssignmentStatement extends BaseNode {
452
456
  type: "AssignmentStatement";
453
- targets: (Expression | ObjectPattern | ArrayPattern)[];
454
- values: Expression[];
457
+ target: Expression | ObjectPattern | ArrayPattern;
458
+ value: Expression;
455
459
  }
456
460
  interface CompoundAssignmentStatement extends BaseNode {
457
461
  type: "CompoundAssignmentStatement";
@@ -672,9 +676,7 @@ interface ArrayExpression extends BaseNode {
672
676
  * only produces one in those two places.
673
677
  *
674
678
  * Lua spreads with `table.unpack`, which only yields every value when it is
675
- * written last; anywhere else the compiler builds the whole list first. Bare
676
- * `...` is unaffected — that is the vararg pack, and `f(...)` passes it on
677
- * as it always did. */
679
+ * written last; anywhere else the compiler builds the whole list first. */
678
680
  interface SpreadElement extends BaseNode {
679
681
  type: "SpreadElement";
680
682
  argument: Expression;
package/dist/index.js CHANGED
@@ -864,10 +864,10 @@ var Parser = class {
864
864
  expressionOr(stop) {
865
865
  return this.attempt(() => this.parseExpression(), stop, (start, from) => this.errorExpression(start, from));
866
866
  }
867
- /** A comma-separated list of values: a `return`'s, a declaration's, an
868
- * assignment's. Each is one value; a spread belongs in a call's
869
- * arguments or an array, and is reported here — then read as one, so
870
- * the rest still means something. */
867
+ /** A comma-separated list of values where one belongs — a `return`'s, a
868
+ * declaration's, an assignment's — read whole, so that writing several
869
+ * is reported once and the rest still means something. A spread belongs
870
+ * in a call's arguments or an array, and is reported here too. */
871
871
  expressionListOr(stop) {
872
872
  const until = () => stop() || this.checkPunctuator(",");
873
873
  const item = () => {
@@ -1459,7 +1459,7 @@ var Parser = class {
1459
1459
  }
1460
1460
  this.error("Expected 'const', 'let', 'function', 'class', 'type', 'default', '{' or '*' after 'export'");
1461
1461
  }
1462
- // `const x = ...` / `let x, y = ...`.
1462
+ // `const x = ...` / `let [a, b] = ...`.
1463
1463
  // tilua has no `local` — `const` bindings are immutable, `let` mutable.
1464
1464
  /** `kind` reads the leading word as that keyword (recovery's `local`). */
1465
1465
  parseVariableDeclaration(as) {
@@ -1473,14 +1473,34 @@ var Parser = class {
1473
1473
  while (this.matchPunctuator(",")) {
1474
1474
  names.push(this.parseBindingTarget(true));
1475
1475
  }
1476
- let init = [];
1476
+ let values = [];
1477
1477
  if (this.matchOperator("=")) {
1478
- init = this.expressionListOr(() => false);
1478
+ values = this.expressionListOr(() => false);
1479
1479
  } else if (kind === "const") {
1480
1480
  if (!this.recover) this.error("'const' declaration requires an initializer");
1481
1481
  this.softError("'const' declaration requires an initializer");
1482
1482
  }
1483
- return { type: "VariableDeclaration", kind, names, init, ...spanFrom(start, this.previous()) };
1483
+ let name = names[0];
1484
+ let init = values[0];
1485
+ if (names.length > 1 || values.length > 1) {
1486
+ this.problemAt(start, `A declaration takes one value: take several from an array, '${kind} [a, b] = [x, y]'`);
1487
+ if (names.length > 1) name = this.namesAsPattern(names);
1488
+ if (values.length > 1) init = this.valuesAsArray(values);
1489
+ }
1490
+ return { type: "VariableDeclaration", kind, name, init, ...spanFrom(start, this.previous()) };
1491
+ }
1492
+ /** `a, b` written where one name goes, read as `[a, b]`. */
1493
+ namesAsPattern(names) {
1494
+ const elements = names.map((value) => ({
1495
+ type: "ArrayPatternElement",
1496
+ value,
1497
+ ...spanFrom(value, value)
1498
+ }));
1499
+ return { type: "ArrayPattern", elements, ...spanFrom(names[0], names[names.length - 1]) };
1500
+ }
1501
+ /** `x, y` written where one value goes, read as `[x, y]`. */
1502
+ valuesAsArray(values) {
1503
+ return { type: "ArrayExpression", elements: values, ...spanFrom(values[0], values[values.length - 1]) };
1484
1504
  }
1485
1505
  parseIfStatement() {
1486
1506
  const start = this.current();
@@ -1961,7 +1981,7 @@ var Parser = class {
1961
1981
  argument = values[0];
1962
1982
  if (values.length > 1) {
1963
1983
  this.problemAt(start, "A function returns one value: return several as an array, 'return [a, b]'");
1964
- argument = { type: "ArrayExpression", elements: values, ...spanFrom(values[0], values[values.length - 1]) };
1984
+ argument = this.valuesAsArray(values);
1965
1985
  }
1966
1986
  }
1967
1987
  return { type: "ReturnStatement", argument, ...spanFrom(start, this.previous()) };
@@ -1987,28 +2007,15 @@ var Parser = class {
1987
2007
  parseExpressionStatement() {
1988
2008
  const start = this.current();
1989
2009
  if (this.checkPunctuator("{") || this.checkPunctuator("[")) {
1990
- const targets = [
1991
- this.checkPunctuator("{") ? this.parseObjectPattern() : this.parseArrayPattern()
1992
- ];
1993
- while (this.matchPunctuator(",")) {
1994
- targets.push(this.parseAssignTarget());
1995
- }
1996
- this.expectOperator("=");
1997
- const values = this.expressionListOr(() => false);
1998
- return { type: "AssignmentStatement", targets, values, ...spanFrom(start, this.previous()) };
2010
+ const target = this.checkPunctuator("{") ? this.parseObjectPattern() : this.parseArrayPattern();
2011
+ return this.parseAssignmentRest(start, target);
1999
2012
  }
2000
2013
  const statementStart = this.cursor;
2001
2014
  const errorsAtStart = this.errors.length;
2002
2015
  const first = this.parsePrefixExpression();
2003
2016
  if (this.checkOperator("=") || this.checkPunctuator(",")) {
2004
- const targets = [first];
2005
- while (this.matchPunctuator(",")) {
2006
- targets.push(this.parseAssignTarget());
2007
- }
2008
- for (const target of targets) this.rejectOptionalTarget(target);
2009
- this.expectOperator("=");
2010
- const values = this.expressionListOr(() => false);
2011
- return { type: "AssignmentStatement", targets, values, ...spanFrom(start, this.previous()) };
2017
+ this.rejectOptionalTarget(first);
2018
+ return this.parseAssignmentRest(start, first);
2012
2019
  }
2013
2020
  const t = this.current();
2014
2021
  if (t.type === "Operator" && COMPOUND_ASSIGN_OPS.has(t.value)) {
@@ -2034,6 +2041,21 @@ var Parser = class {
2034
2041
  }
2035
2042
  return { type: "ExpressionStatement", expression, ...spanFrom(start, this.previous()) };
2036
2043
  }
2044
+ /** From after an assignment's target: `= value`. Luau's `a, b = x, y`
2045
+ * is reported, and read as its first target taking its first value. */
2046
+ parseAssignmentRest(start, target) {
2047
+ let several = false;
2048
+ while (this.matchPunctuator(",")) {
2049
+ this.parseAssignTarget();
2050
+ several = true;
2051
+ }
2052
+ this.expectOperator("=");
2053
+ const values = this.expressionListOr(() => false);
2054
+ if (several || values.length > 1) {
2055
+ this.problemAt(start, "An assignment takes one value: take several from an array, '[a, b] = [x, y]'");
2056
+ }
2057
+ return { type: "AssignmentStatement", target, value: values[0], ...spanFrom(start, this.previous()) };
2058
+ }
2037
2059
  // ============================================================
2038
2060
  // Expressions
2039
2061
  // ============================================================
@@ -2517,8 +2539,8 @@ var Parser = class {
2517
2539
  startsSuperUse() {
2518
2540
  return this.punctuatorAt(1, "(") || this.punctuatorAt(1, ".") && this.peek(2).type === "Identifier";
2519
2541
  }
2520
- /** After `...`, is there something to spread? Nothing following it means
2521
- * the vararg pack, which is what `f(...)` has always passed on. */
2542
+ /** After `...`, is there something to spread? tilua has no bare `...`:
2543
+ * a varying number of arguments is a rest parameter, `...args: T[]`. */
2522
2544
  startsSpread() {
2523
2545
  const next = this.peek(1);
2524
2546
  if (next.type === "Punctuator") {
@@ -2537,8 +2559,8 @@ var Parser = class {
2537
2559
  const token = this.peek(ahead);
2538
2560
  return token.type === "Punctuator" && token.value === value;
2539
2561
  }
2540
- /** An assignment target after the first: a prefix expression (`a.b`,
2541
- * `a[i]`, `a`) or a nested destructuring pattern. */
2562
+ /** An assignment target after the first — Luau's `a, b = ...`, read only
2563
+ * to be reported: a prefix expression or a destructuring pattern. */
2542
2564
  parseAssignTarget() {
2543
2565
  if (this.checkPunctuator("{")) return this.parseObjectPattern();
2544
2566
  if (this.checkPunctuator("[")) return this.parseArrayPattern();
@@ -3029,7 +3051,7 @@ var Parser = class {
3029
3051
  };
3030
3052
  })() : this.parseFunctionHead();
3031
3053
  this.expectPunctuator("=>");
3032
- const body = this.checkPunctuator("{") ? this.parseBraceBlock() : this.returnOf(this.parseExpression(0));
3054
+ const body = this.checkPunctuator("{") ? this.parseBraceBlock() : this.parseArrowExpressionBody();
3033
3055
  const func = {
3034
3056
  type: "FunctionBody",
3035
3057
  generics: head.generics,
@@ -3042,6 +3064,29 @@ var Parser = class {
3042
3064
  };
3043
3065
  return { type: "FunctionExpression", func, ...spanFrom(start, this.previous()) };
3044
3066
  }
3067
+ /** `() => value` returns the value. `() => a = 1` and `() => n += 1` are
3068
+ * an assignment, which is a statement: the body is that
3069
+ * statement, and the function returns nothing. */
3070
+ parseArrowExpressionBody() {
3071
+ const start = this.cursor;
3072
+ const errors = this.errors.length;
3073
+ const expression = this.parseExpression(0);
3074
+ const t = this.current();
3075
+ const assigns = t.type === "Operator" && (t.value === "=" || COMPOUND_ASSIGN_OPS.has(t.value));
3076
+ if (!assigns) return this.returnOf(expression);
3077
+ this.cursor = start;
3078
+ this.errors.length = errors;
3079
+ const first = this.current();
3080
+ const target = this.parsePrefixExpression();
3081
+ this.rejectOptionalTarget(target);
3082
+ const opToken = this.current();
3083
+ const op = opToken.value;
3084
+ if (opToken.type !== "Operator" || op !== "=" && !COMPOUND_ASSIGN_OPS.has(op)) this.error("Expected '='");
3085
+ this.advance();
3086
+ const value = this.expressionOr(() => false);
3087
+ const statement = op === "=" ? { type: "AssignmentStatement", target, value, ...spanFrom(first, this.previous()) } : { type: "CompoundAssignmentStatement", operator: op, target, value, ...spanFrom(first, this.previous()) };
3088
+ return { type: "Block", statements: [statement], ...spanFrom(statement, statement) };
3089
+ }
3045
3090
  /** A one-expression body: the value is what the function returns. */
3046
3091
  returnOf(expression) {
3047
3092
  const statement = {
@@ -3973,10 +4018,9 @@ var Analyzer = class {
3973
4018
  visitStatement(stmt, scope) {
3974
4019
  switch (stmt.type) {
3975
4020
  case "VariableDeclaration": {
3976
- for (const init of stmt.init) this.visitExpression(init, scope);
3977
- for (const name of stmt.names) this.visitType(name.typeAnnotation, scope);
3978
- const isConst = stmt.kind === "const";
3979
- for (const name of stmt.names) this.declarePattern(scope, name, "local", scope, isConst);
4021
+ if (stmt.init) this.visitExpression(stmt.init, scope);
4022
+ this.visitType(stmt.name.typeAnnotation, scope);
4023
+ this.declarePattern(scope, stmt.name, "local", scope, stmt.kind === "const");
3980
4024
  return;
3981
4025
  }
3982
4026
  case "FunctionDeclaration": {
@@ -4010,16 +4054,15 @@ var Analyzer = class {
4010
4054
  return;
4011
4055
  }
4012
4056
  case "AssignmentStatement": {
4013
- for (const value of stmt.values) this.visitExpression(value, scope);
4014
- for (const target of stmt.targets) {
4015
- if (target.type === "Identifier") {
4016
- this.referenceAsAssignmentTarget(scope, target);
4017
- } else if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
4018
- this.assignPattern(scope, target);
4019
- } else {
4020
- this.visitExpression(target, scope);
4021
- this.checkModuleWrite(target);
4022
- }
4057
+ this.visitExpression(stmt.value, scope);
4058
+ const target = stmt.target;
4059
+ if (target.type === "Identifier") {
4060
+ this.referenceAsAssignmentTarget(scope, target);
4061
+ } else if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
4062
+ this.assignPattern(scope, target);
4063
+ } else {
4064
+ this.visitExpression(target, scope);
4065
+ this.checkModuleWrite(target);
4023
4066
  }
4024
4067
  return;
4025
4068
  }
@@ -5343,7 +5386,7 @@ function moduleExports(program, scopes, types, resolveModule) {
5343
5386
  params: declaration.typeParams.map((g) => g.name)
5344
5387
  });
5345
5388
  }
5346
- } else for (const target of declaration.names) exportPattern(target);
5389
+ } else exportPattern(declaration.name);
5347
5390
  } else if (stmt.type === "ExportTypeAliasStatement") {
5348
5391
  const name = stmt.alias.name.name;
5349
5392
  const type = types.aliases.get(name);
@@ -5582,12 +5625,10 @@ function assignedFields(block) {
5582
5625
  const names = /* @__PURE__ */ new Set();
5583
5626
  walkNodes(block, (node) => {
5584
5627
  const record = node;
5585
- const targets = record.type === "AssignmentStatement" ? record.targets : record.type === "CompoundAssignmentStatement" ? [record.target] : void 0;
5586
- for (const target of targets ?? []) {
5587
- const member = target;
5588
- if (member.type === "MemberExpression" && member.object?.type === "Identifier" && member.object.name === "this" && member.property?.name) {
5589
- names.add(member.property.name);
5590
- }
5628
+ if (record.type !== "AssignmentStatement" && record.type !== "CompoundAssignmentStatement") return;
5629
+ const member = record.target;
5630
+ if (member.type === "MemberExpression" && member.object?.type === "Identifier" && member.object.name === "this" && member.property?.name) {
5631
+ names.add(member.property.name);
5591
5632
  }
5592
5633
  });
5593
5634
  return names;
@@ -6063,12 +6104,10 @@ var TypeAnalyzer = class {
6063
6104
  classDisplayNames = /* @__PURE__ */ new WeakMap();
6064
6105
  /** `const Name = class ... end` — the name the class will be known by. */
6065
6106
  nameClassExpressions(stmt) {
6066
- stmt.names.forEach((target, i) => {
6067
- const value = stmt.init[i];
6068
- if (target.type === "IdentifierPattern" && value?.type === "ClassExpression" && !value.name) {
6069
- this.classDisplayNames.set(value, target.name);
6070
- }
6071
- });
6107
+ const value = stmt.init;
6108
+ if (stmt.name.type === "IdentifierPattern" && value?.type === "ClassExpression" && !value.name) {
6109
+ this.classDisplayNames.set(value, stmt.name.name);
6110
+ }
6072
6111
  }
6073
6112
  classTypeParams(stmt) {
6074
6113
  return stmt.type === "ClassDeclaration" ? stmt.typeParams : [];
@@ -7351,35 +7390,30 @@ var TypeAnalyzer = class {
7351
7390
  switch (stmt.type) {
7352
7391
  case "VariableDeclaration": {
7353
7392
  this.nameClassExpressions(stmt);
7354
- stmt.names.forEach((target, i) => {
7355
- if (target.type === "IdentifierPattern" && target.typeAnnotation && stmt.init[i]) {
7356
- this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
7357
- }
7358
- });
7359
- const { types: valueTypes, sources } = this.valueList(stmt.init, env);
7360
- this.checkValueCount(stmt, stmt.names.length, stmt.init, valueTypes);
7361
- stmt.names.forEach((target, i) => {
7362
- const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
7363
- const source = sources[i];
7364
- if (this.emitDiagnostics && target.type === "IdentifierPattern" && target.typeAnnotation && source) {
7365
- const declared = this.resolveType(target.typeAnnotation);
7366
- if (declared.kind !== "any" && !this.namesNothing(declared) && !this.fitsAnnotation(source, declared, inferred, env)) {
7367
- this.diagnostics.push({
7368
- node: stmt,
7369
- message: `Type '${briefType(inferred)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(inferred, declared)
7370
- });
7371
- } else if (declared.kind !== "any") {
7372
- this.reportExcessProperties(source, declared);
7373
- }
7374
- }
7375
- const mode = this.initIsAsConst(source) ? "asconst" : !isFreshLiteralExpr(source) ? "keep" : stmt.kind === "const" ? "const" : "widen";
7376
- this.bindPattern(target, inferred, env, mode);
7377
- if (stmt.kind === "const") {
7378
- this.correlateDestructuring(target, inferred, env);
7379
- this.correlateIndexed(target, source, env);
7380
- this.aliasReference(target, source);
7393
+ const target = stmt.name;
7394
+ const source = stmt.init;
7395
+ if (target.type === "IdentifierPattern" && target.typeAnnotation && source) {
7396
+ this.applyContext(source, this.resolveType(target.typeAnnotation));
7397
+ }
7398
+ const inferred = source ? this.infer(source, env) : nilType;
7399
+ if (this.emitDiagnostics && target.type === "IdentifierPattern" && target.typeAnnotation && source) {
7400
+ const declared = this.resolveType(target.typeAnnotation);
7401
+ if (declared.kind !== "any" && !this.namesNothing(declared) && !this.fitsAnnotation(source, declared, inferred, env)) {
7402
+ this.diagnostics.push({
7403
+ node: stmt,
7404
+ message: `Type '${briefType(inferred)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(inferred, declared)
7405
+ });
7406
+ } else if (declared.kind !== "any") {
7407
+ this.reportExcessProperties(source, declared);
7381
7408
  }
7382
- });
7409
+ }
7410
+ const mode = this.initIsAsConst(source) ? "asconst" : !isFreshLiteralExpr(source) ? "keep" : stmt.kind === "const" ? "const" : "widen";
7411
+ this.bindPattern(target, inferred, env, mode);
7412
+ if (stmt.kind === "const") {
7413
+ this.correlateDestructuring(target, inferred, env);
7414
+ this.correlateIndexed(target, source, env);
7415
+ this.aliasReference(target, source);
7416
+ }
7383
7417
  return;
7384
7418
  }
7385
7419
  case "ClassDeclaration": {
@@ -7436,48 +7470,41 @@ var TypeAnalyzer = class {
7436
7470
  return;
7437
7471
  }
7438
7472
  case "AssignmentStatement": {
7439
- stmt.targets.forEach((target, i) => {
7440
- const value = stmt.values[i];
7441
- if (!value) return;
7442
- if (target.type === "MemberExpression" || target.type === "IndexExpression") {
7443
- this.applyContext(value, this.infer(target, env));
7444
- } else if (target.type === "Identifier") {
7445
- const id = this.bindingIdOf(target);
7446
- if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
7447
- }
7448
- });
7449
- const { types: valueTypes, sources } = this.valueList(stmt.values, env);
7450
- this.checkValueCount(stmt, stmt.targets.length, stmt.values, valueTypes);
7451
- stmt.targets.forEach((target, i) => {
7452
- const vt = valueTypes[i] ?? unknownType;
7453
- const source = sources[i];
7454
- if (target.type === "Identifier") {
7455
- const id = this.bindingIdOf(target);
7456
- if (id !== void 0) {
7457
- this.uncorrelate(id);
7458
- const next = isFreshLiteralExpr(source) ? widen(vt) : vt;
7459
- if (this.annotated.has(id)) {
7460
- const declared = this.bindingType.get(id);
7461
- if (this.emitDiagnostics && !isAssignable(next, declared) && declared.kind !== "any") {
7462
- this.diagnostics.push({
7463
- node: stmt,
7464
- message: `Type '${briefType(next)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(next, declared)
7465
- });
7466
- }
7467
- this.setBinding(env, id, narrowTo(declared, next));
7468
- } else {
7469
- this.setBinding(env, id, next);
7470
- this.bindingType.set(id, union([this.bindingType.get(id) ?? next, next]));
7473
+ const target = stmt.target;
7474
+ const source = stmt.value;
7475
+ if (target.type === "MemberExpression" || target.type === "IndexExpression") {
7476
+ this.applyContext(source, this.infer(target, env));
7477
+ } else if (target.type === "Identifier") {
7478
+ const id = this.bindingIdOf(target);
7479
+ if (id !== void 0 && this.annotated.has(id)) this.applyContext(source, this.bindingType.get(id));
7480
+ }
7481
+ const vt = this.infer(source, env);
7482
+ if (target.type === "Identifier") {
7483
+ const id = this.bindingIdOf(target);
7484
+ if (id !== void 0) {
7485
+ this.uncorrelate(id);
7486
+ const next = isFreshLiteralExpr(source) ? widen(vt) : vt;
7487
+ if (this.annotated.has(id)) {
7488
+ const declared = this.bindingType.get(id);
7489
+ if (this.emitDiagnostics && !isAssignable(next, declared) && declared.kind !== "any") {
7490
+ this.diagnostics.push({
7491
+ node: stmt,
7492
+ message: `Type '${briefType(next)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(next, declared)
7493
+ });
7471
7494
  }
7495
+ this.setBinding(env, id, narrowTo(declared, next));
7496
+ } else {
7497
+ this.setBinding(env, id, next);
7498
+ this.bindingType.set(id, union([this.bindingType.get(id) ?? next, next]));
7472
7499
  }
7473
- } else if (target.type === "MemberExpression" || target.type === "IndexExpression") {
7474
- this.infer(target, env);
7475
- this.checkReadonlyAssign(target, env);
7476
- this.assignToRef(target, isFreshLiteralExpr(source) ? widen(vt) : vt, env);
7477
- } else if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
7478
- this.reassignPattern(target, vt, env);
7479
7500
  }
7480
- });
7501
+ } else if (target.type === "MemberExpression" || target.type === "IndexExpression") {
7502
+ this.infer(target, env);
7503
+ this.checkReadonlyAssign(target, env);
7504
+ this.assignToRef(target, isFreshLiteralExpr(source) ? widen(vt) : vt, env);
7505
+ } else if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
7506
+ this.reassignPattern(target, vt, env);
7507
+ }
7481
7508
  return;
7482
7509
  }
7483
7510
  case "CompoundAssignmentStatement":
@@ -7641,55 +7668,6 @@ var TypeAnalyzer = class {
7641
7668
  return;
7642
7669
  }
7643
7670
  }
7644
- /** Flatten an expression list into the values it actually produces.
7645
- *
7646
- * Lua's adjustment rule: every expression but the last contributes one
7647
- * value, and the last contributes all of its values if it is a call or
7648
- * `...`. That is what makes `local ok, err = pcall(f)` work, and it is
7649
- * why `local x = f()` takes only the first value.
7650
- *
7651
- * `sources` maps each produced value back to the expression it came from
7652
- * (undefined for the 2nd and later values of a multi-value call), so the
7653
- * caller can still do contextual typing against the written expression. */
7654
- valueList(exprs, env) {
7655
- const types = [];
7656
- const sources = [];
7657
- exprs.forEach((e, i) => {
7658
- const t = this.infer(e, env);
7659
- const last = i === exprs.length - 1;
7660
- if (e.type === "SpreadElement") {
7661
- const held = this.typeOf.get(e.argument);
7662
- const expanded = held && this.expand(held);
7663
- if (expanded?.kind === "tuple") {
7664
- for (const element of expanded.elements) {
7665
- types.push(element);
7666
- sources.push(e);
7667
- }
7668
- return;
7669
- }
7670
- types.push(t);
7671
- sources.push(e);
7672
- return;
7673
- }
7674
- void last;
7675
- types.push(t);
7676
- sources.push(e);
7677
- });
7678
- return { types, sources };
7679
- }
7680
- /** `const a, b = x, y` pairs names with values, one each. A call is one
7681
- * value — several come back as an array — so `const a, b = f()` is a
7682
- * name short of a value, and says how to take them apart. */
7683
- checkValueCount(node, names, values, types) {
7684
- if (!this.emitDiagnostics || !values.length || types.length >= names) return;
7685
- const last = values[values.length - 1];
7686
- const lastType = types[types.length - 1] && this.expand(types[types.length - 1]);
7687
- const call = last.type === "CallExpression" || last.type === "MethodCallExpression";
7688
- this.diagnostics.push({
7689
- node,
7690
- message: call && (lastType?.kind === "tuple" || lastType?.kind === "array") ? `A call is one value, here an array: take its parts with '[${Array.from({ length: names }, (_, i) => String.fromCharCode(97 + i)).join(", ")}] = ...'` : `${names} names, but ${types.length} ${types.length === 1 ? "value" : "values"}: each name needs one`
7691
- });
7692
- }
7693
7671
  /** Run `visit` with a fresh place to collect `break` states, and return
7694
7672
  * what it collected. */
7695
7673
  withBreakScope(visit) {
@@ -9673,7 +9651,35 @@ var TypeAnalyzer = class {
9673
9651
  }
9674
9652
  const read = this.expand(type);
9675
9653
  if (read.kind === "unknown" && read.declared) this.reportUnknownAccess(object);
9676
- return { type, shortCircuits: inChain !== void 0 || link.optional === true };
9654
+ const optionalNil = link.optional === true && this.mayBeNil(inChain ?? full);
9655
+ return { type, shortCircuits: inChain !== void 0 || optionalNil };
9656
+ }
9657
+ /** Whether a value of `raw` can be nil — `includesNil`, and also the types
9658
+ * that do not say: `any`, `unknown`, an unconstrained type parameter. */
9659
+ mayBeNil(raw) {
9660
+ const t = this.expand(raw);
9661
+ switch (t.kind) {
9662
+ case "primitive":
9663
+ return t.name === "nil";
9664
+ case "any":
9665
+ case "unknown":
9666
+ return true;
9667
+ case "typeParam":
9668
+ return t.constraint === void 0 || this.mayBeNil(t.constraint);
9669
+ case "union":
9670
+ return t.types.some((m) => this.mayBeNil(m));
9671
+ case "never":
9672
+ case "literal":
9673
+ case "array":
9674
+ case "tuple":
9675
+ case "object":
9676
+ case "function":
9677
+ case "intersection":
9678
+ case "templateLiteral":
9679
+ return false;
9680
+ default:
9681
+ return true;
9682
+ }
9677
9683
  }
9678
9684
  /** Objects already reported as possibly nil: a loop body is visited more
9679
9685
  * than once. */
@@ -10595,13 +10601,12 @@ var TypeAnalyzer = class {
10595
10601
  computingAhead = /* @__PURE__ */ new Set();
10596
10602
  declaredAhead(id) {
10597
10603
  this.aheadDeclarations ??= this.indexAheadDeclarations();
10598
- const found = this.aheadDeclarations.get(id);
10599
- if (!found || this.computingAhead.has(id)) return void 0;
10604
+ const statement = this.aheadDeclarations.get(id);
10605
+ if (!statement || this.computingAhead.has(id)) return void 0;
10600
10606
  this.computingAhead.add(id);
10601
10607
  const wasEmitting = this.emitDiagnostics;
10602
10608
  this.emitDiagnostics = false;
10603
10609
  try {
10604
- const { statement, index } = found;
10605
10610
  let type;
10606
10611
  if (statement.type === "DeclareStatement") {
10607
10612
  type = this.resolveType(statement.valueType);
@@ -10610,11 +10615,11 @@ var TypeAnalyzer = class {
10610
10615
  } else if (statement.type === "ClassDeclaration") {
10611
10616
  type = this.classValueType(statement);
10612
10617
  } else if (statement.type === "VariableDeclaration") {
10613
- const target = statement.names[index];
10618
+ const target = statement.name;
10614
10619
  if (target.type === "IdentifierPattern" && target.typeAnnotation) {
10615
10620
  type = this.resolveType(target.typeAnnotation);
10616
- } else if (statement.init[index]) {
10617
- const value = this.infer(statement.init[index], /* @__PURE__ */ new Map());
10621
+ } else if (statement.init) {
10622
+ const value = this.infer(statement.init, /* @__PURE__ */ new Map());
10618
10623
  type = statement.kind === "const" ? value : widen(value);
10619
10624
  }
10620
10625
  }
@@ -10632,11 +10637,10 @@ var TypeAnalyzer = class {
10632
10637
  for (const statement of this.program.body.statements) {
10633
10638
  const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
10634
10639
  if (declaration.type !== "VariableDeclaration") continue;
10635
- declaration.names.forEach((target, index) => {
10636
- if (target.type !== "IdentifierPattern") return;
10637
- const id = this.bindingIdByName(target.name, target);
10638
- if (id !== void 0) out.set(id, { statement: declaration, index });
10639
- });
10640
+ const target = declaration.name;
10641
+ if (target.type !== "IdentifierPattern") continue;
10642
+ const id = this.bindingIdByName(target.name, target);
10643
+ if (id !== void 0) out.set(id, declaration);
10640
10644
  }
10641
10645
  const visit = (node) => {
10642
10646
  if (!node || typeof node !== "object") return;
@@ -10647,11 +10651,11 @@ var TypeAnalyzer = class {
10647
10651
  const record = node;
10648
10652
  if (record.type === "ClassDeclaration" && record.name) {
10649
10653
  const id = this.bindingIdByName(record.name.name, record.name);
10650
- if (id !== void 0) out.set(id, { statement: node, index: 0 });
10654
+ if (id !== void 0) out.set(id, node);
10651
10655
  }
10652
10656
  if (record.type === "FunctionDeclaration" && record.name) {
10653
10657
  const id = this.bindingIdByName(record.name.name, record.name);
10654
- if (id !== void 0) out.set(id, { statement: node, index: 0 });
10658
+ if (id !== void 0) out.set(id, node);
10655
10659
  }
10656
10660
  for (const [key, value] of Object.entries(node)) {
10657
10661
  if (key !== "line" && key !== "column" && value && typeof value === "object") visit(value);
@@ -10660,7 +10664,7 @@ var TypeAnalyzer = class {
10660
10664
  visit(this.program.body);
10661
10665
  for (const [name, statement] of this.deferredDeclares) {
10662
10666
  const id = this.scopes.globalsByName.get(name);
10663
- if (id !== void 0) out.set(id, { statement, index: 0 });
10667
+ if (id !== void 0) out.set(id, statement);
10664
10668
  }
10665
10669
  return out;
10666
10670
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tilua/parser",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "tilua parser for roblox",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",