@tilua/parser 3.0.0 → 3.1.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.js CHANGED
@@ -603,7 +603,7 @@ function bindThis(func, at) {
603
603
  func.isMethod = true;
604
604
  }
605
605
  function bindThisParam(params, at) {
606
- params.unshift({ type: "FunctionParameter", name: "this", ...spanFrom(at, at) });
606
+ params.unshift({ type: "FunctionParameter", name: "this", implicit: true, ...spanFrom(at, at) });
607
607
  }
608
608
  var ParseError = class extends Error {
609
609
  constructor(message, line, column) {
@@ -1660,7 +1660,7 @@ var Parser = class {
1660
1660
  };
1661
1661
  }
1662
1662
  if (isMethod) {
1663
- func.params.unshift({ type: "FunctionParameter", name: "self", ...spanFrom(target, target) });
1663
+ func.params.unshift({ type: "FunctionParameter", name: "self", implicit: true, ...spanFrom(target, target) });
1664
1664
  func.isMethod = true;
1665
1665
  }
1666
1666
  return {
@@ -1854,47 +1854,7 @@ var Parser = class {
1854
1854
  const isStatic = modifiers.has("static");
1855
1855
  if (this.checkKeyword("function")) {
1856
1856
  this.advance();
1857
- const memberName = this.parseIdentifier();
1858
- if (modifiers.has("abstract")) {
1859
- const head = this.parseFunctionHead();
1860
- const func = {
1861
- type: "FunctionBody",
1862
- generics: head.generics,
1863
- params: head.params,
1864
- hasVarargs: head.hasVarargs,
1865
- returnType: head.returnType,
1866
- predicate: head.predicate,
1867
- body: { type: "Block", statements: [], ...spanFrom(head.start, this.previous()) },
1868
- ...spanFrom(head.start, this.previous())
1869
- };
1870
- if (!isStatic) bindThis(func, start);
1871
- if (!head.returnType) this.problem("An abstract method has no body to infer from: write its return type");
1872
- if (this.checkPunctuator("{")) this.problem("An abstract method has no body");
1873
- return { type: "ClassMethod", name: memberName, isStatic, func, isAbstract: true, ...spanFrom(start, this.previous()) };
1874
- }
1875
- const signatures = [];
1876
- let written = memberName;
1877
- while (true) {
1878
- const head = this.parseFunctionHead();
1879
- if (this.isClassOverloadContinuation(memberName.name, isStatic)) {
1880
- if (!isStatic) bindThisParam(head.params, start);
1881
- signatures.push({ ...this.headToSignature(head), name: written });
1882
- if (isStatic) this.advance();
1883
- this.expectKeyword("function");
1884
- written = this.parseIdentifier();
1885
- continue;
1886
- }
1887
- const func = this.headToBody(head, start);
1888
- if (!isStatic) bindThis(func, start);
1889
- return {
1890
- type: "ClassMethod",
1891
- name: memberName,
1892
- isStatic,
1893
- func,
1894
- signatures: signatures.length ? signatures : void 0,
1895
- ...spanFrom(start, this.previous())
1896
- };
1897
- }
1857
+ return this.parseClassMethod(start, modifiers);
1898
1858
  }
1899
1859
  if (!isStatic && this.checkIdentifierValue("constructor") && this.punctuatorAt(1, "(")) {
1900
1860
  this.advance();
@@ -1904,6 +1864,9 @@ var Parser = class {
1904
1864
  bindThis(func, start);
1905
1865
  return { type: "ClassConstructor", func, ...spanFrom(start, this.previous()) };
1906
1866
  }
1867
+ if (this.startsMethodShorthand(0)) {
1868
+ return this.parseClassMethod(start, modifiers);
1869
+ }
1907
1870
  if ((this.checkIdentifierValue("get") || this.checkIdentifierValue("set")) && this.peek(1).type === "Identifier" && this.punctuatorAt(2, "(")) {
1908
1871
  const kind = this.advance().value;
1909
1872
  const memberName = this.parseIdentifier();
@@ -1933,25 +1896,90 @@ var Parser = class {
1933
1896
  return { type: "ClassField", name: memberName, isStatic, typeAnnotation, init, ...spanFrom(start, this.previous()) };
1934
1897
  }
1935
1898
  if (this.recover) {
1936
- this.softError("Expected a class member: a field, 'function', 'constructor', 'get' or 'set'");
1899
+ this.softError("Expected a class member: a field, a method, 'constructor', 'get' or 'set'");
1937
1900
  this.advance();
1938
1901
  return void 0;
1939
1902
  }
1940
- this.error("Expected a class member: a field, 'function', 'constructor', 'get' or 'set'");
1903
+ this.error("Expected a class member: a field, a method, 'constructor', 'get' or 'set'");
1904
+ }
1905
+ /** A name at `offset` opening a method without `function`: `name(` or
1906
+ * `name<T>(`. A field's name is followed by `:` or `=` instead. */
1907
+ startsMethodShorthand(offset) {
1908
+ if (this.peek(offset).type !== "Identifier") return false;
1909
+ const next = this.peek(offset + 1);
1910
+ return (next.type === "Punctuator" || next.type === "Operator") && (next.value === "(" || next.value === "<");
1911
+ }
1912
+ /** A method, from its name on: `function` (if written) and the modifiers
1913
+ * are behind. */
1914
+ parseClassMethod(start, modifiers) {
1915
+ const isStatic = modifiers.has("static");
1916
+ const memberName = this.parseIdentifier();
1917
+ if (modifiers.has("abstract")) {
1918
+ const head = this.parseFunctionHead();
1919
+ const func = {
1920
+ type: "FunctionBody",
1921
+ generics: head.generics,
1922
+ params: head.params,
1923
+ hasVarargs: head.hasVarargs,
1924
+ returnType: head.returnType,
1925
+ predicate: head.predicate,
1926
+ body: { type: "Block", statements: [], ...spanFrom(head.start, this.previous()) },
1927
+ ...spanFrom(head.start, this.previous())
1928
+ };
1929
+ if (!isStatic) bindThis(func, start);
1930
+ if (!head.returnType) this.problem("An abstract method has no body to infer from: write its return type");
1931
+ if (this.checkPunctuator("{")) this.problem("An abstract method has no body");
1932
+ return { type: "ClassMethod", name: memberName, isStatic, func, isAbstract: true, ...spanFrom(start, this.previous()) };
1933
+ }
1934
+ const signatures = [];
1935
+ let written = memberName;
1936
+ while (true) {
1937
+ const head = this.parseFunctionHead();
1938
+ const skip = this.classOverloadContinuation(memberName.name, isStatic);
1939
+ if (skip !== void 0) {
1940
+ if (!isStatic) bindThisParam(head.params, start);
1941
+ signatures.push({ ...this.headToSignature(head), name: written });
1942
+ for (let i = 0; i < skip; i++) this.advance();
1943
+ written = this.parseIdentifier();
1944
+ continue;
1945
+ }
1946
+ const func = this.headToBody(head, start);
1947
+ if (!isStatic) bindThis(func, start);
1948
+ return {
1949
+ type: "ClassMethod",
1950
+ name: memberName,
1951
+ isStatic,
1952
+ func,
1953
+ signatures: signatures.length ? signatures : void 0,
1954
+ ...spanFrom(start, this.previous())
1955
+ };
1956
+ }
1941
1957
  }
1942
- /** Give a class method its `this`: a real first parameter, the way
1943
- * `function T:m()` gets a real `self`. Every later pass — scopes, types,
1944
- * arity, lowering — then sees an ordinary parameter and needs to know
1945
- * nothing about classes. */
1946
1958
  /** After a bodyless head inside a class body, does another declaration of
1947
- * the same member follow? Then the head was an overload signature. */
1948
- isClassOverloadContinuation(name, isStatic) {
1949
- const offset = isStatic ? 1 : 0;
1950
- if (isStatic && !this.checkIdentifierValue("static")) return false;
1959
+ * the same member follow? Then the head was an overload signature, and
1960
+ * this is how many tokens stand before the next one's name: a `;` that
1961
+ * ends the head, as TypeScript writes it, the modifiers, and `function`
1962
+ * when it is written. `undefined` when no overload follows. Each head may
1963
+ * be written either way, with `function` or without. */
1964
+ classOverloadContinuation(name, isStatic) {
1965
+ let offset = 0;
1966
+ if (this.punctuatorAt(0, ";")) offset++;
1967
+ let sawStatic = false;
1968
+ while (true) {
1969
+ const t = this.peek(offset);
1970
+ const word = t.type === "Identifier" ? t.value : void 0;
1971
+ if (word === void 0 || !CLASS_MODIFIERS.has(word) || this.startsMethodShorthand(offset)) break;
1972
+ if (word === "static") sawStatic = true;
1973
+ offset++;
1974
+ }
1975
+ if (sawStatic !== isStatic) return void 0;
1951
1976
  const keyword = this.peek(offset);
1952
- if (!(keyword.type === "Keyword" && keyword.value === "function")) return false;
1953
- const named = this.peek(offset + 1);
1954
- return named.type === "Identifier" && named.value === name;
1977
+ const hasKeyword = keyword.type === "Keyword" && keyword.value === "function";
1978
+ if (hasKeyword) offset++;
1979
+ const named = this.peek(offset);
1980
+ if (!(named.type === "Identifier" && named.value === name)) return void 0;
1981
+ if (!hasKeyword && !this.startsMethodShorthand(offset)) return void 0;
1982
+ return offset;
1955
1983
  }
1956
1984
  operatorAt(ahead, value) {
1957
1985
  const token = this.peek(ahead);
@@ -3788,6 +3816,10 @@ var Analyzer = class {
3788
3816
  moduleScope = this.globalScope;
3789
3817
  /** How many function bodies enclose the walk. */
3790
3818
  functionDepth = 0;
3819
+ /** How many loops enclose the walk inside the nearest function body. A
3820
+ * function starts again at 0: `continue` in `xs:map(x => ...)` has no
3821
+ * loop to go on with, whatever loop the call is written in. */
3822
+ loopDepth = 0;
3791
3823
  /** Function declarations already declared by their block's hoisting, with
3792
3824
  * the function depth of that block. */
3793
3825
  hoisted = /* @__PURE__ */ new Map();
@@ -4140,11 +4172,11 @@ var Analyzer = class {
4140
4172
  return;
4141
4173
  case "WhileStatement":
4142
4174
  this.visitExpression(stmt.condition, scope);
4143
- this.visitBlockInNewScope(stmt.body, scope);
4175
+ this.inLoop(() => this.visitBlockInNewScope(stmt.body, scope));
4144
4176
  return;
4145
4177
  case "RepeatStatement": {
4146
4178
  const bodyScope = childScope(scope);
4147
- this.visitBlock(stmt.body, bodyScope);
4179
+ this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
4148
4180
  this.visitExpression(stmt.condition, bodyScope);
4149
4181
  return;
4150
4182
  }
@@ -4162,14 +4194,14 @@ var Analyzer = class {
4162
4194
  if (stmt.step) this.visitExpression(stmt.step, scope);
4163
4195
  const bodyScope = childScope(scope);
4164
4196
  this.declare(bodyScope, stmt.variable.name, "for-numeric", stmt.variable);
4165
- this.visitBlock(stmt.body, bodyScope);
4197
+ this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
4166
4198
  return;
4167
4199
  }
4168
4200
  case "GenericForStatement": {
4169
4201
  this.visitExpression(stmt.iterator, scope);
4170
4202
  const bodyScope = childScope(scope);
4171
4203
  this.declarePattern(bodyScope, stmt.variable, "for-generic", scope, stmt.kind === "const");
4172
- this.visitBlock(stmt.body, bodyScope);
4204
+ this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
4173
4205
  return;
4174
4206
  }
4175
4207
  case "ReturnStatement":
@@ -4177,6 +4209,15 @@ var Analyzer = class {
4177
4209
  return;
4178
4210
  case "BreakStatement":
4179
4211
  case "ContinueStatement":
4212
+ if (this.loopDepth === 0) {
4213
+ const word = stmt.type === "BreakStatement" ? "break" : "continue";
4214
+ this.diagnostics.push({
4215
+ node: stmt,
4216
+ message: this.functionDepth > 0 ? `'${word}' is not inside a loop of this function; to skip a value in a callback, 'return' from it` : `'${word}' is not inside a loop`,
4217
+ kind: "outside-loop"
4218
+ });
4219
+ }
4220
+ return;
4180
4221
  case "ErrorStatement":
4181
4222
  return;
4182
4223
  case "DeclareStatement":
@@ -4232,6 +4273,15 @@ var Analyzer = class {
4232
4273
  return;
4233
4274
  }
4234
4275
  }
4276
+ /** Visit a loop's body, where `break` and `continue` have a loop. */
4277
+ inLoop(visit) {
4278
+ this.loopDepth++;
4279
+ try {
4280
+ visit();
4281
+ } finally {
4282
+ this.loopDepth--;
4283
+ }
4284
+ }
4235
4285
  // ---------------- functions ----------------
4236
4286
  visitFunctionBody(func, outerScope, isMethod = false) {
4237
4287
  const fnScope = childScope(outerScope);
@@ -4247,11 +4297,14 @@ var Analyzer = class {
4247
4297
  }
4248
4298
  });
4249
4299
  this.visitType(func.returnType, fnScope);
4300
+ const loops = this.loopDepth;
4250
4301
  this.functionDepth++;
4302
+ this.loopDepth = 0;
4251
4303
  try {
4252
4304
  this.visitBlock(func.body, fnScope);
4253
4305
  } finally {
4254
4306
  this.functionDepth--;
4307
+ this.loopDepth = loops;
4255
4308
  }
4256
4309
  }
4257
4310
  /** An overload signature: no body and no bindings, but its types can hold
@@ -4505,11 +4558,14 @@ declare metatable string: {
4505
4558
  -- answer \`nil\` rather than JavaScript's -1. \`sort\` takes Lua's comparator
4506
4559
  -- (true when \`a\` comes first). \`push\`, \`pop\`, \`shift\`, \`unshift\`,
4507
4560
  -- \`sort\` and \`reverse\` change the array; everything else returns a new one.
4561
+ -- A \`filter\` whose test is a guard \u2014 \`v is S\`, or \`v => v ~= nil\` \u2014
4562
+ -- answers an array of what it let through.
4508
4563
  declare metatable<T> T[]: {
4509
4564
  __index: {
4510
4565
  find: (self: T[], test: (value: T, index: number) => boolean) => T | nil,
4511
4566
  findIndex: (self: T[], test: (value: T, index: number) => boolean) => number | nil,
4512
- filter: (self: T[], test: (value: T, index: number) => boolean) => T[],
4567
+ filter: (<S extends T>(self: T[], test: (value: T, index: number) => value is S) => S[])
4568
+ & ((self: T[], test: (value: T, index: number) => boolean) => T[]),
4513
4569
  map: <U>(self: T[], transform: (value: T, index: number) => U) => U[],
4514
4570
  forEach: (self: T[], visit: (value: T, index: number) => nil) => nil,
4515
4571
  some: (self: T[], test: (value: T, index: number) => boolean) => boolean,
@@ -4543,11 +4599,34 @@ declare metatable<T> T[]: {
4543
4599
  -- \`nil\` when it is missing; an indexer's keys follow the named ones. A
4544
4600
  -- member of the table's own with one of these names is what a call reaches,
4545
4601
  -- and a class instance, which has a metatable of its own, has none of them.
4602
+ --
4603
+ -- The rest are JavaScript's \`Object\` functions, written as methods of the
4604
+ -- table they take first:
4605
+ --
4606
+ -- point:hasOwn("x") -- Object.hasOwn(point, "x")
4607
+ -- point:assign({ z: 3 }) -- Object.assign(point, { z: 3 })
4608
+ -- point:freeze() -- Object.freeze(point)
4609
+ --
4610
+ -- \`hasOwn\` asks the table itself, not what its metatable's \`__index\`
4611
+ -- lends it. \`assign\` copies each source's members onto the table, later ones
4612
+ -- over earlier ones, skipping a \`nil\` source, and answers the table. \`freeze\`
4613
+ -- is Luau's \`table.freeze\`; under Lua 5.1, which cannot freeze a table, the
4614
+ -- table is answered as it is and \`isFrozen\` is false.
4546
4615
  declare metatable<T extends {}> T: {
4547
4616
  __index: {
4548
4617
  keys: (self: T) => ObjectKeys<T>,
4549
4618
  values: (self: T) => ObjectValues<T>,
4550
4619
  entries: (self: T) => ObjectEntries<T>,
4620
+
4621
+ hasOwn: (self: T, key: unknown) => boolean,
4622
+ hasOwnProperty: (self: T, key: unknown) => boolean,
4623
+ assign: (<U>(self: T, source: U) => T & U)
4624
+ & (<U, V>(self: T, a: U, b: V) => T & U & V)
4625
+ & (<U, V, W>(self: T, a: U, b: V, c: W) => T & U & V & W)
4626
+ -- Generic like the rest: a plain signature would be tried first.
4627
+ & (<S extends {}>(self: T, ...sources: S[]) => T),
4628
+ freeze: (self: T) => Readonly<T>,
4629
+ isFrozen: (self: T) => boolean,
4551
4630
  },
4552
4631
  }
4553
4632
  `;
@@ -4648,8 +4727,14 @@ function substitute(t, subst) {
4648
4727
  if (subst.size === 0) return t;
4649
4728
  if (!containsTypeParam(t)) return t;
4650
4729
  switch (t.kind) {
4651
- case "typeParam":
4652
- return subst.get(t.name) ?? t;
4730
+ case "typeParam": {
4731
+ const bound = subst.get(t.name);
4732
+ if (bound) return bound;
4733
+ if (t.constraint && containsTypeParam(t.constraint)) {
4734
+ return { ...t, constraint: substitute(t.constraint, new Map([...subst, [t.name, t]])) };
4735
+ }
4736
+ return t;
4737
+ }
4653
4738
  case "array":
4654
4739
  return { ...arrayOf(substitute(t.element, subst)), readonly: t.readonly };
4655
4740
  case "tuple":
@@ -4794,6 +4879,10 @@ function unify(param, arg, vars, out, how = "union") {
4794
4879
  if (arg.kind === "function") {
4795
4880
  param.params.forEach((p, i) => arg.params[i] && unify(p.type, arg.params[i].type, vars, out));
4796
4881
  unify(param.returns, arg.returns, vars, out);
4882
+ const wanted = param.predicate, given = arg.predicate;
4883
+ if (wanted?.type && given?.type && wanted.param === given.param && !wanted.asserts && !given.asserts) {
4884
+ unify(wanted.type, given.type, vars, out);
4885
+ }
4797
4886
  }
4798
4887
  return;
4799
4888
  case "object":
@@ -5049,6 +5138,11 @@ function isAssignableInner(a, b) {
5049
5138
  for (let i = 0; i < n; i++) {
5050
5139
  if (!isAssignable(a.params[i].type, b.params[i].type) && !isAssignable(b.params[i].type, a.params[i].type)) return false;
5051
5140
  }
5141
+ const wanted = b.predicate;
5142
+ if (wanted?.type && !wanted.asserts) {
5143
+ const given = a.predicate;
5144
+ if (!given?.type || given.asserts || given.param !== wanted.param || !isAssignable(given.type, wanted.type)) return false;
5145
+ }
5052
5146
  return isAssignable(a.returns, b.returns);
5053
5147
  }
5054
5148
  if (b.kind === "typeParam") return false;
@@ -5183,7 +5277,7 @@ function containsFreeTypeParam(t, seen, bound) {
5183
5277
  return [...t.properties.values()].some((v) => containsTypeParam(v.type, seen, bound)) || !!t.indexer && (containsTypeParam(t.indexer.key, seen, bound) || containsTypeParam(t.indexer.value, seen, bound));
5184
5278
  case "function": {
5185
5279
  const inner = t.typeParams?.length ? /* @__PURE__ */ new Set([...bound, ...t.typeParams]) : bound;
5186
- return t.params.some((p) => containsTypeParam(p.type, seen, inner)) || !!t.varargs && containsTypeParam(t.varargs, seen, inner) || containsTypeParam(t.returns, seen, inner);
5280
+ return t.params.some((p) => containsTypeParam(p.type, seen, inner)) || !!t.varargs && containsTypeParam(t.varargs, seen, inner) || containsTypeParam(t.returns, seen, inner) || !!t.predicate?.type && containsTypeParam(t.predicate.type, seen, inner);
5187
5281
  }
5188
5282
  case "genericRef":
5189
5283
  return t.typeArguments.some((a) => containsTypeParam(a, seen, bound));
@@ -5608,9 +5702,14 @@ function isFreshLiteralExpr(e) {
5608
5702
  case "StringLiteral":
5609
5703
  case "BooleanLiteral":
5610
5704
  case "InterpolatedStringExpression":
5705
+ return true;
5706
+ // A table or array literal widened the literals written in it as it
5707
+ // was read (`widenUnlessAsked`), and keeps what it read from
5708
+ // elsewhere — `{ key: name }`, `[...names]` — so there is nothing
5709
+ // left for the binding to widen.
5611
5710
  case "TableExpression":
5612
5711
  case "ArrayExpression":
5613
- return true;
5712
+ return false;
5614
5713
  case "ParenthesizedExpression":
5615
5714
  return isFreshLiteralExpr(e.expression);
5616
5715
  case "UnaryExpression":
@@ -5625,6 +5724,30 @@ function isFreshLiteralExpr(e) {
5625
5724
  return false;
5626
5725
  }
5627
5726
  }
5727
+ function overloadTier(f) {
5728
+ const takesGuard = f.params.some((p) => p.type.kind === "function" && p.type.predicate?.type && !p.type.predicate.asserts);
5729
+ if (takesGuard) return 0;
5730
+ return (f.typeParams?.length ?? 0) > 0 ? 2 : 1;
5731
+ }
5732
+ function writesLiteral(e) {
5733
+ switch (e.type) {
5734
+ case "NumberLiteral":
5735
+ case "StringLiteral":
5736
+ case "BooleanLiteral":
5737
+ case "InterpolatedStringExpression":
5738
+ return true;
5739
+ case "ParenthesizedExpression":
5740
+ return writesLiteral(e.expression);
5741
+ case "UnaryExpression":
5742
+ return writesLiteral(e.argument);
5743
+ case "IfElseExpression":
5744
+ return e.clauses.some((c) => writesLiteral(c.body)) || writesLiteral(e.alternate);
5745
+ case "BinaryExpression":
5746
+ return (e.operator === "or" || e.operator === "and") && (writesLiteral(e.left) || writesLiteral(e.right));
5747
+ default:
5748
+ return false;
5749
+ }
5750
+ }
5628
5751
  function collectInferNames(node) {
5629
5752
  const out = [];
5630
5753
  const walk = (n) => {
@@ -8007,7 +8130,9 @@ var TypeAnalyzer = class {
8007
8130
  const waits = written.map((a, i) => this.waitsOnInference(a, expected[i]));
8008
8131
  if (!waits.some(Boolean)) return written.map((a) => this.infer(a, env));
8009
8132
  const argTypes = written.map((a, i) => waits[i] ? this.quietly(() => this.infer(a, env)) : this.infer(a, env));
8010
- const picked = this.pickOverload(fns, argTypes, (f) => extra(f, argTypes), this.spreadOf(written, selfOf(fns[0])));
8133
+ const spread = this.spreadOf(written, selfOf(fns[0]));
8134
+ const provisional = argTypes.map((t, i) => waits[i] ? anyType : t);
8135
+ const picked = this.pickOverload(fns, argTypes, (f) => extra(f, argTypes), spread) ?? this.pickOverload(fns, provisional, (f) => extra(f, provisional), spread);
8011
8136
  const subst = picked?.typeParams?.length ? this.inferTypeArgs(picked, extra(picked, argTypes)) : void 0;
8012
8137
  written.forEach((a, i) => {
8013
8138
  if (!waits[i]) return;
@@ -8198,6 +8323,16 @@ var TypeAnalyzer = class {
8198
8323
  }
8199
8324
  const id = this.bindingIdByName(p.name, p);
8200
8325
  const t = this.paramType(p, env);
8326
+ if (p.default && p.typeAnnotation) {
8327
+ const given = this.infer(p.default, env);
8328
+ const declared = this.resolveType(p.typeAnnotation);
8329
+ if (this.emitDiagnostics && !isAssignable(given, declared)) {
8330
+ this.diagnostics.push({
8331
+ node: p.default,
8332
+ message: `Type '${briefType(given)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(given, declared)
8333
+ });
8334
+ }
8335
+ }
8201
8336
  if (id !== void 0) {
8202
8337
  this.bindingType.set(id, t);
8203
8338
  this.setBinding(env, id, t);
@@ -8295,9 +8430,9 @@ var TypeAnalyzer = class {
8295
8430
  * lets `typeof` declare `(v: number) -> "number"` alongside a trailing
8296
8431
  * `<T>(v: T) -> string` and still pick the precise one. */
8297
8432
  pickOverload(fns, argTypes, argsFor, spread) {
8298
- for (const generic of [false, true]) {
8433
+ for (const tier of [0, 1, 2]) {
8299
8434
  for (const f of fns) {
8300
- if ((f.typeParams?.length ?? 0) > 0 !== generic) continue;
8435
+ if (overloadTier(f) !== tier) continue;
8301
8436
  if (this.overloadAccepts(f, argsFor ? argsFor(f) : argTypes, spread)) return f;
8302
8437
  }
8303
8438
  }
@@ -8323,7 +8458,7 @@ var TypeAnalyzer = class {
8323
8458
  if (position < 0) return void 0;
8324
8459
  const members = this.expand(argTypes[position]).types;
8325
8460
  if (members.length > 32) return void 0;
8326
- const rank = (f) => ((f.typeParams?.length ?? 0) > 0 ? fns.length : 0) + fns.indexOf(f);
8461
+ const rank = (f) => overloadTier(f) * fns.length + fns.indexOf(f);
8327
8462
  const limit = picked ? rank(picked) : Infinity;
8328
8463
  const results = [];
8329
8464
  for (const member of members) {
@@ -8716,10 +8851,38 @@ var TypeAnalyzer = class {
8716
8851
  returns,
8717
8852
  void 0,
8718
8853
  names,
8719
- this.resolvePredicate(func.predicate, params)
8854
+ func.predicate || func.returnType ? this.resolvePredicate(func.predicate, params) : this.inferPredicate(func, params, returns, bodyEnv)
8720
8855
  ), func.params);
8721
8856
  });
8722
8857
  }
8858
+ /** `v => v ~= nil` is a guard without saying so, as TypeScript reads it:
8859
+ * a body that is one `return` of a boolean which, true, narrows a
8860
+ * parameter — and, false, leaves it exactly what that excludes. Then
8861
+ * `list:filter(v => v ~= nil)` knows what it kept. */
8862
+ inferPredicate(func, params, returns, env) {
8863
+ const only = func.body.statements.length === 1 ? func.body.statements[0] : void 0;
8864
+ if (only?.type !== "ReturnStatement" || !only.argument) return void 0;
8865
+ if (returns.kind === "any" || returns.kind === "never" || !isAssignable(returns, booleanType)) return void 0;
8866
+ const cond = only.argument;
8867
+ for (let i = 0; i < params.length; i++) {
8868
+ const p = func.params[i];
8869
+ if (p.pattern || p.rest) continue;
8870
+ const id = this.bindingIdByName(p.name, p);
8871
+ if (id === void 0) continue;
8872
+ const declared = params[i].type;
8873
+ const key = bindKey(id);
8874
+ const t = forkEnv(env);
8875
+ const f = forkEnv(env);
8876
+ this.silently(() => this.applyNarrowing(cond, env, t, f));
8877
+ const yes = t.get(key);
8878
+ if (!yes || equalTypes(yes, declared) || !isAssignable(yes, declared)) continue;
8879
+ const no = this.expand(f.get(key) ?? declared);
8880
+ const rest = no.kind === "union" ? no.types : [no];
8881
+ if (rest.some((m) => m.kind !== "never" && isAssignable(m, yes))) continue;
8882
+ return { param: i, type: yes, asserts: false };
8883
+ }
8884
+ return void 0;
8885
+ }
8723
8886
  /** Where the return types of the function being walked are collected, so
8724
8887
  * each is read where it is written — inside the branch that narrowed it —
8725
8888
  * rather than in whatever state the body ends in. */
@@ -9841,6 +10004,27 @@ var TypeAnalyzer = class {
9841
10004
  elseEnv = whenFalse;
9842
10005
  }
9843
10006
  branches.push(this.infer(expr.alternate, elseEnv));
10007
+ const bodies = [...expr.clauses.map((c) => c.body), expr.alternate].map(unwrapParens);
10008
+ const isEmpty = (e) => e.type === "TableExpression" && e.fields.length === 0 || e.type === "ArrayExpression" && e.elements.length === 0;
10009
+ const others = union(branches.filter((_, i) => !isEmpty(bodies[i])));
10010
+ const parts = (others.kind === "union" ? others.types : [others]).map((m) => this.expand(m)).filter((m) => !(m.kind === "primitive" && m.name === "nil"));
10011
+ const fits = (e) => {
10012
+ if (!parts.length) return void 0;
10013
+ if (e.type === "ArrayExpression") {
10014
+ if (parts.every((m) => m.kind === "array")) return others;
10015
+ return parts.every((m) => m.kind === "array" || m.kind === "tuple") ? tuple([]) : void 0;
10016
+ }
10017
+ return parts.every((m) => m.kind === "object" && !m.class && [...m.properties.values()].every((prop) => prop.optional)) ? others : void 0;
10018
+ };
10019
+ if (bodies.some(isEmpty) && others.kind !== "never" && others.kind !== "any" && bodies.every((body) => !isEmpty(body) || fits(body))) {
10020
+ const typed = bodies.map((body, i) => {
10021
+ if (!isEmpty(body)) return branches[i];
10022
+ const t = fits(body);
10023
+ this.typeOf.set(body, t);
10024
+ return t;
10025
+ });
10026
+ return union(typed);
10027
+ }
9844
10028
  return union(branches);
9845
10029
  }
9846
10030
  }
@@ -10147,23 +10331,87 @@ var TypeAnalyzer = class {
10147
10331
  if (wantedTuple && !asConst) return this.inferTupleLiteral(expr, env, wantedTuple);
10148
10332
  const elems = [];
10149
10333
  let hadSpread = false;
10334
+ const written = [];
10150
10335
  for (const el of expr.elements) {
10151
10336
  if (el.type === "SpreadElement") {
10152
10337
  hadSpread = true;
10153
- const s = this.infer(el.argument, env);
10154
- if (s.kind === "array") elems.push(s.element);
10155
- else if (s.kind === "tuple") elems.push(...s.elements);
10156
- else elems.push(unknownType);
10338
+ const spread = this.infer(el.argument, env);
10339
+ written.push({ spread: true, type: spread });
10340
+ elems.push(...this.spreadElements(spread));
10157
10341
  } else {
10158
- elems.push(asConst ? this.inferAsConst(el, env) : this.infer(el, env));
10342
+ const t = asConst ? this.inferAsConst(el, env) : this.infer(el, env);
10343
+ written.push({ spread: false, type: asConst ? t : this.widenUnlessAsked(t, el) });
10344
+ elems.push(t);
10159
10345
  }
10160
10346
  }
10161
10347
  if (asConst && !hadSpread) return tuple(elems);
10348
+ if (hadSpread) {
10349
+ const tuples = this.spreadTuples(written);
10350
+ if (tuples) return union(tuples.map((elements) => tuple(elements)));
10351
+ }
10162
10352
  return arrayOf(elems.length ? union(elems.map((t, i) => {
10163
10353
  const element = expr.elements[i];
10164
10354
  return asConst || !element || element.type === "SpreadElement" ? t : this.widenUnlessAsked(t, element);
10165
10355
  })) : unknownType);
10166
10356
  }
10357
+ /** The tuples an array literal with spreads is, when every spread is a
10358
+ * tuple of known length or a union of them: one per way the unions go.
10359
+ * `undefined` when a spread's length is not known (an array, a tuple
10360
+ * with a rest), or the ways multiply past what is worth listing. */
10361
+ spreadTuples(written) {
10362
+ const MAX = 32;
10363
+ let variants = [[]];
10364
+ for (const { spread, type } of written) {
10365
+ if (!spread) {
10366
+ variants = variants.map((v) => [...v, type]);
10367
+ continue;
10368
+ }
10369
+ const options = this.fixedTuples(type);
10370
+ if (!options) return void 0;
10371
+ const next = [];
10372
+ for (const v of variants) for (const option of options) next.push([...v, ...option]);
10373
+ if (next.length > MAX) return void 0;
10374
+ variants = next;
10375
+ }
10376
+ return variants;
10377
+ }
10378
+ /** A tuple of known length's members, or a union of such tuples' — each
10379
+ * a way the value can be. `nil` in a union spreads nothing. */
10380
+ fixedTuples(t, depth = 0) {
10381
+ if (depth > 8) return void 0;
10382
+ const held = this.expand(t);
10383
+ if (held.kind === "tuple") return held.rest ? void 0 : [held.elements];
10384
+ if (held.kind !== "union") return void 0;
10385
+ const out = [];
10386
+ for (const member of held.types) {
10387
+ if (member.kind === "primitive" && member.name === "nil") continue;
10388
+ const options = this.fixedTuples(member, depth + 1);
10389
+ if (!options) return void 0;
10390
+ out.push(...options);
10391
+ }
10392
+ return out.length ? out : void 0;
10393
+ }
10394
+ /** What `...xs` puts into an array: an array's element, a tuple's members,
10395
+ * and for a union each member's — `...(host ? extra : [])` adds `extra`'s
10396
+ * elements and the empty tuple nothing. Anything else, `unknown`. */
10397
+ spreadElements(t, depth = 0) {
10398
+ const held = this.expand(t);
10399
+ if (depth > 8) return [unknownType];
10400
+ switch (held.kind) {
10401
+ case "array":
10402
+ return [held.element];
10403
+ case "tuple":
10404
+ return tupleMembers(held);
10405
+ case "union":
10406
+ return held.types.filter((m) => !(m.kind === "primitive" && m.name === "nil")).flatMap((m) => this.spreadElements(m, depth + 1));
10407
+ case "typeParam":
10408
+ return held.constraint ? this.spreadElements(held.constraint, depth + 1) : [unknownType];
10409
+ case "any":
10410
+ return [anyType];
10411
+ default:
10412
+ return [unknownType];
10413
+ }
10414
+ }
10167
10415
  /** `[a, b, ...more]` where a tuple is wanted: the places it names are the
10168
10416
  * tuple's, and what comes after them — more elements, or a spread — is
10169
10417
  * its rest. */
@@ -10174,10 +10422,7 @@ var TypeAnalyzer = class {
10174
10422
  expr.elements.forEach((element, i) => {
10175
10423
  if (element.type === "SpreadElement") {
10176
10424
  spread = true;
10177
- const held = this.expand(this.infer(element.argument, env));
10178
- if (held.kind === "array") tail.push(held.element);
10179
- else if (held.kind === "tuple") tail.push(...tupleMembers(held));
10180
- else tail.push(unknownType);
10425
+ tail.push(...this.spreadElements(this.infer(element.argument, env)));
10181
10426
  return;
10182
10427
  }
10183
10428
  const t = this.widenUnlessAsked(this.infer(element, env), element);
@@ -10194,6 +10439,7 @@ var TypeAnalyzer = class {
10194
10439
  * The context was recorded by `applyContext` before the value was
10195
10440
  * inferred, so this is a lookup rather than a second pass. */
10196
10441
  widenUnlessAsked(value, at) {
10442
+ if (!writesLiteral(at)) return value;
10197
10443
  const wanted = this.expectedTypeOf.get(at);
10198
10444
  if (value.kind === "literal") {
10199
10445
  const arms = this.contextArms.get(at);
@@ -11566,7 +11812,53 @@ function aliasTargets(config, specifier) {
11566
11812
 
11567
11813
  // src/project/sourcemap.ts
11568
11814
  import { dirname as dirname4, extname, resolve as resolve4 } from "path";
11815
+
11816
+ // src/luau.ts
11817
+ var LUAU_KEYWORDS = /* @__PURE__ */ new Set([
11818
+ "and",
11819
+ "break",
11820
+ "do",
11821
+ "else",
11822
+ "elseif",
11823
+ "end",
11824
+ "false",
11825
+ "for",
11826
+ "function",
11827
+ "if",
11828
+ "in",
11829
+ "local",
11830
+ "nil",
11831
+ "not",
11832
+ "or",
11833
+ "repeat",
11834
+ "return",
11835
+ "then",
11836
+ "true",
11837
+ "until",
11838
+ "while"
11839
+ ]);
11569
11840
  var IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
11841
+ function isIdentifier(name) {
11842
+ return IDENTIFIER.test(name);
11843
+ }
11844
+ function isLuauName(name) {
11845
+ return IDENTIFIER.test(name) && !LUAU_KEYWORDS.has(name);
11846
+ }
11847
+ var NAMED_ESCAPES = {
11848
+ "\\": "\\\\",
11849
+ '"': '\\"',
11850
+ "\n": "\\n",
11851
+ "\r": "\\r",
11852
+ " ": "\\t"
11853
+ };
11854
+ function escapeLuauString(text) {
11855
+ return text.replace(/[\\"\x00-\x1f\x7f]/g, (c) => NAMED_ESCAPES[c] ?? `\\${c.charCodeAt(0).toString().padStart(3, "0")}`);
11856
+ }
11857
+ function luauString(text) {
11858
+ return `"${escapeLuauString(text)}"`;
11859
+ }
11860
+
11861
+ // src/project/sourcemap.ts
11570
11862
  var INSTANCE_MEMBERS = /* @__PURE__ */ new Set(["Name", "ClassName", "Parent", "Archivable"]);
11571
11863
  var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".tilua", ".luau", ".lua"]);
11572
11864
  function sourceMapTypes(text, path, options) {
@@ -11593,7 +11885,7 @@ function sourceMapTypes(text, path, options) {
11593
11885
  const alias = aliasFor(segments);
11594
11886
  aliasOfNode.set(node, alias);
11595
11887
  for (const filePath of node.filePaths ?? []) aliasOfFile.set(fileKey(resolve4(directory, filePath)), alias);
11596
- const className = IDENTIFIER.test(node.className) && options.classes.has(node.className) ? node.className : "Instance";
11888
+ const className = isIdentifier(node.className) && options.classes.has(node.className) ? node.className : "Instance";
11597
11889
  const taken = options.membersOf?.(className) ?? INSTANCE_MEMBERS;
11598
11890
  const members = [];
11599
11891
  if (parent) members.push(`Parent: ${parent}`);
@@ -11602,7 +11894,7 @@ function sourceMapTypes(text, path, options) {
11602
11894
  if (!isNode(child)) continue;
11603
11895
  const childAlias = visit(child, [...segments, child.name], alias);
11604
11896
  const service = child.name === child.className && options.classes.has(child.className);
11605
- if (!IDENTIFIER.test(child.name) || taken.has(child.name) && !service || named.has(child.name)) continue;
11897
+ if (!isIdentifier(child.name) || taken.has(child.name) && !service || named.has(child.name)) continue;
11606
11898
  named.add(child.name);
11607
11899
  members.push(`${child.name}: ${childAlias}`);
11608
11900
  }
@@ -11644,6 +11936,34 @@ function fileKey(path) {
11644
11936
  return process.platform === "win32" ? normalized.toLowerCase() : normalized;
11645
11937
  }
11646
11938
 
11939
+ // src/loweringHelpers.ts
11940
+ function withoutNil2(type) {
11941
+ if (!type) return [];
11942
+ const members = type.kind === "union" ? type.types : [type];
11943
+ return members.filter((m) => !(m.kind === "primitive" && m.name === "nil"));
11944
+ }
11945
+ function isFunctionType(type) {
11946
+ const members = withoutNil2(type);
11947
+ return members.length > 0 && members.every((m) => m.kind === "function" || m.kind === "intersection" && m.types.every(isFunctionType));
11948
+ }
11949
+ function everyObject(type, test) {
11950
+ const members = withoutNil2(type);
11951
+ return members.length > 0 && members.every((m) => m.kind === "object" && test(m));
11952
+ }
11953
+ function isInstanceOf(type, className) {
11954
+ return everyObject(type, (m) => m.class !== void 0 && m.class.ancestors.includes(className));
11955
+ }
11956
+ function hasMembers(type, names) {
11957
+ return everyObject(type, (m) => names.every((name) => m.properties.has(name)));
11958
+ }
11959
+ function returnsTuple(receiver, method) {
11960
+ return everyObject(receiver, (m) => {
11961
+ const found = m.properties.get(method)?.type;
11962
+ const signatures = !found ? [] : found.kind === "intersection" ? found.types : [found];
11963
+ return signatures.length > 0 && signatures.every((f) => f.kind === "function" && f.returns.kind === "tuple");
11964
+ });
11965
+ }
11966
+
11647
11967
  // src/index.ts
11648
11968
  var tilua = {
11649
11969
  tokenize,
@@ -11663,6 +11983,7 @@ export {
11663
11983
  CONFIG_FILE_NAMES,
11664
11984
  Keywords,
11665
11985
  LANGUAGE_GLOBALS,
11986
+ LUAU_KEYWORDS,
11666
11987
  LexError,
11667
11988
  Operators,
11668
11989
  PRELUDE_SOURCE,
@@ -11684,20 +12005,27 @@ export {
11684
12005
  difference,
11685
12006
  directivesOf,
11686
12007
  equalTypes,
12008
+ escapeLuauString,
11687
12009
  falsyType,
11688
12010
  findConfig,
11689
12011
  fn,
11690
12012
  formatType,
11691
12013
  getBinding,
12014
+ hasMembers,
11692
12015
  intersection,
11693
12016
  isAssignable,
11694
12017
  isClassType,
12018
+ isFunctionType,
11695
12019
  isGlobal,
12020
+ isIdentifier,
12021
+ isInstanceOf,
12022
+ isLuauName,
11696
12023
  isPossiblyFalsy,
11697
12024
  isPossiblyTruthy,
11698
12025
  isUnassignedGlobal,
11699
12026
  literal,
11700
12027
  loadConfig,
12028
+ luauString,
11701
12029
  matchInfer,
11702
12030
  moduleCandidates,
11703
12031
  moduleExports,
@@ -11720,6 +12048,7 @@ export {
11720
12048
  readDirectives,
11721
12049
  resolveModulePath,
11722
12050
  resolveTypeLibraries,
12051
+ returnsTuple,
11723
12052
  setAliasExpander,
11724
12053
  setDeferredBound,
11725
12054
  sourceMapTypes,
@@ -11738,5 +12067,6 @@ export {
11738
12067
  unknownType,
11739
12068
  widen,
11740
12069
  withAliasName,
11741
- withoutAliasName
12070
+ withoutAliasName,
12071
+ withoutNil2 as withoutNil
11742
12072
  };