@tilua/parser 2.1.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/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
  }
@@ -4670,14 +4713,17 @@ function classArguments(t, name) {
4670
4713
  if (t.kind === "object") return t.class?.typeArguments?.get(name);
4671
4714
  return void 0;
4672
4715
  }
4673
- function unify(param, arg, vars, out) {
4716
+ function unify(param, arg, vars, out, how = "union") {
4674
4717
  if (param.kind === "typeParam" && param.constraint && !vars.has(param.name)) {
4675
- unify(param.constraint, arg, vars, out);
4718
+ unify(param.constraint, arg, vars, out, how);
4676
4719
  return;
4677
4720
  }
4678
4721
  if (param.kind === "typeParam" && vars.has(param.name)) {
4679
4722
  const prev = out.get(param.name);
4680
- out.set(param.name, prev ? union([prev, arg]) : arg);
4723
+ if (!prev) out.set(param.name, arg);
4724
+ else if (isAssignable(arg, prev)) {
4725
+ } else if (isAssignable(prev, arg)) out.set(param.name, arg);
4726
+ else if (how === "union") out.set(param.name, union([prev, arg]));
4681
4727
  return;
4682
4728
  }
4683
4729
  if (arg.kind === "any" || arg.kind === "never") return;
@@ -5445,7 +5491,7 @@ function moduleExports(program, scopes, types, resolveModule) {
5445
5491
  params: declaration.typeParams.map((g) => g.name)
5446
5492
  });
5447
5493
  }
5448
- } else for (const target of declaration.names) exportPattern(target);
5494
+ } else exportPattern(declaration.name);
5449
5495
  } else if (stmt.type === "ExportTypeAliasStatement") {
5450
5496
  const name = stmt.alias.name.name;
5451
5497
  const type = types.aliases.get(name);
@@ -5684,12 +5730,10 @@ function assignedFields(block) {
5684
5730
  const names = /* @__PURE__ */ new Set();
5685
5731
  walkNodes(block, (node) => {
5686
5732
  const record = node;
5687
- const targets = record.type === "AssignmentStatement" ? record.targets : record.type === "CompoundAssignmentStatement" ? [record.target] : void 0;
5688
- for (const target of targets ?? []) {
5689
- const member = target;
5690
- if (member.type === "MemberExpression" && member.object?.type === "Identifier" && member.object.name === "this" && member.property?.name) {
5691
- names.add(member.property.name);
5692
- }
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);
5693
5737
  }
5694
5738
  });
5695
5739
  return names;
@@ -6165,12 +6209,10 @@ var TypeAnalyzer = class {
6165
6209
  classDisplayNames = /* @__PURE__ */ new WeakMap();
6166
6210
  /** `const Name = class ... end` — the name the class will be known by. */
6167
6211
  nameClassExpressions(stmt) {
6168
- stmt.names.forEach((target, i) => {
6169
- const value = stmt.init[i];
6170
- if (target.type === "IdentifierPattern" && value?.type === "ClassExpression" && !value.name) {
6171
- this.classDisplayNames.set(value, target.name);
6172
- }
6173
- });
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
+ }
6174
6216
  }
6175
6217
  classTypeParams(stmt) {
6176
6218
  return stmt.type === "ClassDeclaration" ? stmt.typeParams : [];
@@ -7453,35 +7495,30 @@ var TypeAnalyzer = class {
7453
7495
  switch (stmt.type) {
7454
7496
  case "VariableDeclaration": {
7455
7497
  this.nameClassExpressions(stmt);
7456
- stmt.names.forEach((target, i) => {
7457
- if (target.type === "IdentifierPattern" && target.typeAnnotation && stmt.init[i]) {
7458
- this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
7459
- }
7460
- });
7461
- const { types: valueTypes, sources } = this.valueList(stmt.init, env);
7462
- this.checkValueCount(stmt, stmt.names.length, stmt.init, valueTypes);
7463
- stmt.names.forEach((target, i) => {
7464
- const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
7465
- const source = sources[i];
7466
- if (this.emitDiagnostics && target.type === "IdentifierPattern" && target.typeAnnotation && source) {
7467
- const declared = this.resolveType(target.typeAnnotation);
7468
- if (declared.kind !== "any" && !this.namesNothing(declared) && !this.fitsAnnotation(source, declared, inferred, env)) {
7469
- this.diagnostics.push({
7470
- node: stmt,
7471
- message: `Type '${briefType(inferred)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(inferred, declared)
7472
- });
7473
- } else if (declared.kind !== "any") {
7474
- this.reportExcessProperties(source, declared);
7475
- }
7476
- }
7477
- const mode = this.initIsAsConst(source) ? "asconst" : !isFreshLiteralExpr(source) ? "keep" : stmt.kind === "const" ? "const" : "widen";
7478
- this.bindPattern(target, inferred, env, mode);
7479
- if (stmt.kind === "const") {
7480
- this.correlateDestructuring(target, inferred, env);
7481
- this.correlateIndexed(target, source, env);
7482
- 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);
7483
7513
  }
7484
- });
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
+ }
7485
7522
  return;
7486
7523
  }
7487
7524
  case "ClassDeclaration": {
@@ -7538,48 +7575,41 @@ var TypeAnalyzer = class {
7538
7575
  return;
7539
7576
  }
7540
7577
  case "AssignmentStatement": {
7541
- stmt.targets.forEach((target, i) => {
7542
- const value = stmt.values[i];
7543
- if (!value) return;
7544
- if (target.type === "MemberExpression" || target.type === "IndexExpression") {
7545
- this.applyContext(value, this.infer(target, env));
7546
- } else if (target.type === "Identifier") {
7547
- const id = this.bindingIdOf(target);
7548
- if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
7549
- }
7550
- });
7551
- const { types: valueTypes, sources } = this.valueList(stmt.values, env);
7552
- this.checkValueCount(stmt, stmt.targets.length, stmt.values, valueTypes);
7553
- stmt.targets.forEach((target, i) => {
7554
- const vt = valueTypes[i] ?? unknownType;
7555
- const source = sources[i];
7556
- if (target.type === "Identifier") {
7557
- const id = this.bindingIdOf(target);
7558
- if (id !== void 0) {
7559
- this.uncorrelate(id);
7560
- const next = isFreshLiteralExpr(source) ? widen(vt) : vt;
7561
- if (this.annotated.has(id)) {
7562
- const declared = this.bindingType.get(id);
7563
- if (this.emitDiagnostics && !isAssignable(next, declared) && declared.kind !== "any") {
7564
- this.diagnostics.push({
7565
- node: stmt,
7566
- message: `Type '${briefType(next)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(next, declared)
7567
- });
7568
- }
7569
- this.setBinding(env, id, narrowTo(declared, next));
7570
- } else {
7571
- this.setBinding(env, id, next);
7572
- 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
+ });
7573
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]));
7574
7604
  }
7575
- } else if (target.type === "MemberExpression" || target.type === "IndexExpression") {
7576
- this.infer(target, env);
7577
- this.checkReadonlyAssign(target, env);
7578
- this.assignToRef(target, isFreshLiteralExpr(source) ? widen(vt) : vt, env);
7579
- } else if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
7580
- this.reassignPattern(target, vt, env);
7581
7605
  }
7582
- });
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
+ }
7583
7613
  return;
7584
7614
  }
7585
7615
  case "CompoundAssignmentStatement":
@@ -7743,55 +7773,6 @@ var TypeAnalyzer = class {
7743
7773
  return;
7744
7774
  }
7745
7775
  }
7746
- /** Flatten an expression list into the values it actually produces.
7747
- *
7748
- * Lua's adjustment rule: every expression but the last contributes one
7749
- * value, and the last contributes all of its values if it is a call or
7750
- * `...`. That is what makes `local ok, err = pcall(f)` work, and it is
7751
- * why `local x = f()` takes only the first value.
7752
- *
7753
- * `sources` maps each produced value back to the expression it came from
7754
- * (undefined for the 2nd and later values of a multi-value call), so the
7755
- * caller can still do contextual typing against the written expression. */
7756
- valueList(exprs, env) {
7757
- const types = [];
7758
- const sources = [];
7759
- exprs.forEach((e, i) => {
7760
- const t = this.infer(e, env);
7761
- const last = i === exprs.length - 1;
7762
- if (e.type === "SpreadElement") {
7763
- const held = this.typeOf.get(e.argument);
7764
- const expanded = held && this.expand(held);
7765
- if (expanded?.kind === "tuple") {
7766
- for (const element of expanded.elements) {
7767
- types.push(element);
7768
- sources.push(e);
7769
- }
7770
- return;
7771
- }
7772
- types.push(t);
7773
- sources.push(e);
7774
- return;
7775
- }
7776
- void last;
7777
- types.push(t);
7778
- sources.push(e);
7779
- });
7780
- return { types, sources };
7781
- }
7782
- /** `const a, b = x, y` pairs names with values, one each. A call is one
7783
- * value — several come back as an array — so `const a, b = f()` is a
7784
- * name short of a value, and says how to take them apart. */
7785
- checkValueCount(node, names, values, types) {
7786
- if (!this.emitDiagnostics || !values.length || types.length >= names) return;
7787
- const last = values[values.length - 1];
7788
- const lastType = types[types.length - 1] && this.expand(types[types.length - 1]);
7789
- const call = last.type === "CallExpression" || last.type === "MethodCallExpression";
7790
- this.diagnostics.push({
7791
- node,
7792
- 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`
7793
- });
7794
- }
7795
7776
  /** Run `visit` with a fresh place to collect `break` states, and return
7796
7777
  * what it collected. */
7797
7778
  withBreakScope(visit) {
@@ -7862,8 +7843,10 @@ var TypeAnalyzer = class {
7862
7843
  // --------------------------------------------------------
7863
7844
  // Functions
7864
7845
  // --------------------------------------------------------
7865
- visitFunctionBody(func, outerEnv) {
7866
- this.withTypeParams(func.generics, () => this.visitFunctionBodyInner(func, outerEnv));
7846
+ /** `returns`, when given, collects the type of each `return` the walk
7847
+ * reads — see `inferFunctionBody`. */
7848
+ visitFunctionBody(func, outerEnv, returns) {
7849
+ this.withTypeParams(func.generics, () => this.visitFunctionBodyInner(func, outerEnv, returns));
7867
7850
  }
7868
7851
  /** A parameter's type: annotation, else a shape synthesized from a
7869
7852
  * destructuring pattern, else inferred from its default, else `any`. */
@@ -7912,6 +7895,53 @@ var TypeAnalyzer = class {
7912
7895
  * it, as in TypeScript: `signal:Connect(function(player) ... end)` knows
7913
7896
  * `player` from `Connect`'s callback type. Anything else is inferred as
7914
7897
  * usual. */
7898
+ /** The arguments of a call, in two passes where one is needed.
7899
+ *
7900
+ * A function written at the call site with a parameter nothing annotates
7901
+ * reads that parameter from the signature — and where the signature says
7902
+ * `(v: T) => U`, `T` is only known once the other arguments have been
7903
+ * read. So such an argument is read once quietly, to get a shape to pick
7904
+ * a signature with, and then again with `T` in hand: `map(xs, v => v + 1)`
7905
+ * has its `v` a number by then, and `U` follows from what the body
7906
+ * answers. Everything else is read once, as it always was. */
7907
+ inferArguments(written, fns, selfOf, extra, env) {
7908
+ const expected = this.expectedArguments(written, fns, selfOf);
7909
+ written.forEach((a, i) => this.applyContext(a, expected[i]));
7910
+ const waits = written.map((a, i) => this.waitsOnInference(a, expected[i]));
7911
+ if (!waits.some(Boolean)) return written.map((a) => this.infer(a, env));
7912
+ const argTypes = written.map((a, i) => waits[i] ? this.quietly(() => this.infer(a, env)) : this.infer(a, env));
7913
+ const picked = this.pickOverload(fns, argTypes, (f) => extra(f, argTypes), this.spreadOf(written, selfOf(fns[0])));
7914
+ const subst = picked?.typeParams?.length ? this.inferTypeArgs(picked, extra(picked, argTypes)) : void 0;
7915
+ written.forEach((a, i) => {
7916
+ if (!waits[i]) return;
7917
+ if (picked && subst) {
7918
+ const self = selfOf(picked);
7919
+ const declared = i + self < picked.params.length ? picked.params[i + self].type : picked.varargs;
7920
+ if (declared) this.applyContext(a, this.reduceType(substitute(declared, subst)));
7921
+ }
7922
+ argTypes[i] = this.infer(a, env);
7923
+ });
7924
+ return argTypes;
7925
+ }
7926
+ /** Is this argument a function whose own type waits on the call's? */
7927
+ waitsOnInference(arg, expected) {
7928
+ if (!expected || !containsTypeParam(expected)) return false;
7929
+ let e = arg;
7930
+ while (e.type === "ParenthesizedExpression") e = e.expression;
7931
+ if (e.type !== "FunctionExpression") return false;
7932
+ return e.func.params.some((p) => !p.typeAnnotation && !p.default && !p.pattern && p.name !== "self" && p.name !== "this");
7933
+ }
7934
+ /** Read something without saying anything about it: a first pass whose
7935
+ * only purpose is a shape to go on. */
7936
+ quietly(read) {
7937
+ const was = this.emitDiagnostics;
7938
+ this.emitDiagnostics = false;
7939
+ try {
7940
+ return read();
7941
+ } finally {
7942
+ this.emitDiagnostics = was;
7943
+ }
7944
+ }
7915
7945
  applyContext(expr, expected) {
7916
7946
  let e = expr;
7917
7947
  while (e.type === "ParenthesizedExpression") e = e.expression;
@@ -8060,7 +8090,7 @@ var TypeAnalyzer = class {
8060
8090
  this.contextualReturns.pop();
8061
8091
  }
8062
8092
  }
8063
- visitFunctionBodyInner(func, outerEnv) {
8093
+ visitFunctionBodyInner(func, outerEnv, returns) {
8064
8094
  const env = forkEnv(outerEnv);
8065
8095
  for (const p of func.params) {
8066
8096
  if (p.pattern) {
@@ -8077,7 +8107,7 @@ var TypeAnalyzer = class {
8077
8107
  if (p.typeAnnotation) this.annotated.add(id);
8078
8108
  }
8079
8109
  }
8080
- this.withVarargs(func, () => this.collectReturns(void 0, () => {
8110
+ this.withVarargs(func, () => this.collectReturns(returns, () => {
8081
8111
  this.visitBlock(func.body, env);
8082
8112
  this.checkReturnsAtAll(func, this.declaredReturns[this.declaredReturns.length - 1]);
8083
8113
  }));
@@ -8131,7 +8161,7 @@ var TypeAnalyzer = class {
8131
8161
  if (arg === void 0) return;
8132
8162
  const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
8133
8163
  const preserve = keepsLiterals(param) || param.kind !== "typeParam" && containsTypeParam(param);
8134
- unify(p.type, preserve ? arg : widen(arg), vars, subst);
8164
+ unify(p.type, preserve ? arg : widen(arg), vars, subst, "first");
8135
8165
  });
8136
8166
  if (f.varargs && f.restIsWhole) {
8137
8167
  const rest = argTypes.slice(f.params.length).map((a) => a === void 0 ? unknownType : widen(a));
@@ -8482,7 +8512,12 @@ var TypeAnalyzer = class {
8482
8512
  asserts: node.asserts
8483
8513
  };
8484
8514
  }
8485
- inferFunctionBody(func, env) {
8515
+ /** `visited`, when given, is what the real walk of the body just
8516
+ * collected from its `return`s: a function expression is walked before
8517
+ * its type is asked for, so there is nothing left to guess, and walking
8518
+ * the body a second time only to read them made every callback nested in
8519
+ * another cost twice its parent's. */
8520
+ inferFunctionBody(func, env, visited) {
8486
8521
  const names = func.generics.map((g) => g.name);
8487
8522
  return this.withTypeParams(func.generics, () => {
8488
8523
  const params = func.params.flatMap((p) => {
@@ -8511,6 +8546,8 @@ var TypeAnalyzer = class {
8511
8546
  returns = this.resolveType(func.returnType);
8512
8547
  } else if (func.predicate) {
8513
8548
  returns = booleanType;
8549
+ } else if (visited) {
8550
+ returns = visited.length ? union([...visited]) : this.withVarargs(func, () => this.silently(() => this.inferReturnType(func.body, bodyEnv)));
8514
8551
  } else {
8515
8552
  const collected = [];
8516
8553
  returns = this.withVarargs(func, () => this.silently(() => {
@@ -9383,10 +9420,12 @@ var TypeAnalyzer = class {
9383
9420
  return this.inferArray(expr, env, false);
9384
9421
  case "TableExpression":
9385
9422
  return this.inferObject(expr, env, false);
9386
- case "FunctionExpression":
9423
+ case "FunctionExpression": {
9387
9424
  this.checkParamOrder(expr.func.params, expr);
9388
- this.visitFunctionBody(expr.func, env);
9389
- return this.inferFunctionBody(expr.func, env);
9425
+ const returned = [];
9426
+ this.visitFunctionBody(expr.func, env, returned);
9427
+ return this.inferFunctionBody(expr.func, env, returned);
9428
+ }
9390
9429
  case "ParenthesizedExpression":
9391
9430
  return this.infer(expr.expression, env);
9392
9431
  case "TypeAssertionExpression": {
@@ -9590,9 +9629,7 @@ var TypeAnalyzer = class {
9590
9629
  const united = this.unionSignatures(callee);
9591
9630
  const fns = united ?? this.overloadsOf(callee);
9592
9631
  const explicit = this.explicitTypeArguments(expr, fns);
9593
- const expected = this.expectedArguments(expr.arguments, fns, () => 0);
9594
- expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
9595
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
9632
+ const argTypes = this.inferArguments(expr.arguments, fns, () => 0, (_, args) => args, env);
9596
9633
  if (fns.length) {
9597
9634
  this.recordExpected(expr.arguments, fns, () => 0, () => argTypes);
9598
9635
  const spread = this.spreadOf(expr.arguments);
@@ -9657,9 +9694,13 @@ var TypeAnalyzer = class {
9657
9694
  const united = this.unionSignatures(method);
9658
9695
  const fns = united ?? this.overloadsOf(method);
9659
9696
  const explicit = this.explicitTypeArguments(expr, fns);
9660
- const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
9661
- expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
9662
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
9697
+ const argTypes = this.inferArguments(
9698
+ expr.arguments,
9699
+ fns,
9700
+ (f) => this.takesSelf(f) ? 1 : 0,
9701
+ (f, args) => this.takesSelf(f) ? [objType, ...args] : args,
9702
+ env
9703
+ );
9663
9704
  if (fns.length) {
9664
9705
  const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
9665
9706
  const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
@@ -9715,7 +9756,35 @@ var TypeAnalyzer = class {
9715
9756
  }
9716
9757
  const read = this.expand(type);
9717
9758
  if (read.kind === "unknown" && read.declared) this.reportUnknownAccess(object);
9718
- 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
+ }
9719
9788
  }
9720
9789
  /** Objects already reported as possibly nil: a loop body is visited more
9721
9790
  * than once. */
@@ -10637,13 +10706,12 @@ var TypeAnalyzer = class {
10637
10706
  computingAhead = /* @__PURE__ */ new Set();
10638
10707
  declaredAhead(id) {
10639
10708
  this.aheadDeclarations ??= this.indexAheadDeclarations();
10640
- const found = this.aheadDeclarations.get(id);
10641
- 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;
10642
10711
  this.computingAhead.add(id);
10643
10712
  const wasEmitting = this.emitDiagnostics;
10644
10713
  this.emitDiagnostics = false;
10645
10714
  try {
10646
- const { statement, index } = found;
10647
10715
  let type;
10648
10716
  if (statement.type === "DeclareStatement") {
10649
10717
  type = this.resolveType(statement.valueType);
@@ -10652,11 +10720,11 @@ var TypeAnalyzer = class {
10652
10720
  } else if (statement.type === "ClassDeclaration") {
10653
10721
  type = this.classValueType(statement);
10654
10722
  } else if (statement.type === "VariableDeclaration") {
10655
- const target = statement.names[index];
10723
+ const target = statement.name;
10656
10724
  if (target.type === "IdentifierPattern" && target.typeAnnotation) {
10657
10725
  type = this.resolveType(target.typeAnnotation);
10658
- } else if (statement.init[index]) {
10659
- 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());
10660
10728
  type = statement.kind === "const" ? value : widen(value);
10661
10729
  }
10662
10730
  }
@@ -10674,11 +10742,10 @@ var TypeAnalyzer = class {
10674
10742
  for (const statement of this.program.body.statements) {
10675
10743
  const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
10676
10744
  if (declaration.type !== "VariableDeclaration") continue;
10677
- declaration.names.forEach((target, index) => {
10678
- if (target.type !== "IdentifierPattern") return;
10679
- const id = this.bindingIdByName(target.name, target);
10680
- if (id !== void 0) out.set(id, { statement: declaration, index });
10681
- });
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);
10682
10749
  }
10683
10750
  const visit = (node) => {
10684
10751
  if (!node || typeof node !== "object") return;
@@ -10689,11 +10756,11 @@ var TypeAnalyzer = class {
10689
10756
  const record = node;
10690
10757
  if (record.type === "ClassDeclaration" && record.name) {
10691
10758
  const id = this.bindingIdByName(record.name.name, record.name);
10692
- if (id !== void 0) out.set(id, { statement: node, index: 0 });
10759
+ if (id !== void 0) out.set(id, node);
10693
10760
  }
10694
10761
  if (record.type === "FunctionDeclaration" && record.name) {
10695
10762
  const id = this.bindingIdByName(record.name.name, record.name);
10696
- if (id !== void 0) out.set(id, { statement: node, index: 0 });
10763
+ if (id !== void 0) out.set(id, node);
10697
10764
  }
10698
10765
  for (const [key, value] of Object.entries(node)) {
10699
10766
  if (key !== "line" && key !== "column" && value && typeof value === "object") visit(value);
@@ -10702,7 +10769,7 @@ var TypeAnalyzer = class {
10702
10769
  visit(this.program.body);
10703
10770
  for (const [name, statement] of this.deferredDeclares) {
10704
10771
  const id = this.scopes.globalsByName.get(name);
10705
- if (id !== void 0) out.set(id, { statement, index: 0 });
10772
+ if (id !== void 0) out.set(id, statement);
10706
10773
  }
10707
10774
  return out;
10708
10775
  }