@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.cjs CHANGED
@@ -24,6 +24,7 @@ __export(index_exports, {
24
24
  CONFIG_FILE_NAMES: () => CONFIG_FILE_NAMES,
25
25
  Keywords: () => Keywords,
26
26
  LANGUAGE_GLOBALS: () => LANGUAGE_GLOBALS,
27
+ LUAU_KEYWORDS: () => LUAU_KEYWORDS,
27
28
  LexError: () => LexError,
28
29
  Operators: () => Operators,
29
30
  PRELUDE_SOURCE: () => PRELUDE_SOURCE,
@@ -45,20 +46,27 @@ __export(index_exports, {
45
46
  difference: () => difference,
46
47
  directivesOf: () => directivesOf,
47
48
  equalTypes: () => equalTypes,
49
+ escapeLuauString: () => escapeLuauString,
48
50
  falsyType: () => falsyType,
49
51
  findConfig: () => findConfig,
50
52
  fn: () => fn,
51
53
  formatType: () => formatType,
52
54
  getBinding: () => getBinding,
55
+ hasMembers: () => hasMembers,
53
56
  intersection: () => intersection,
54
57
  isAssignable: () => isAssignable,
55
58
  isClassType: () => isClassType,
59
+ isFunctionType: () => isFunctionType,
56
60
  isGlobal: () => isGlobal,
61
+ isIdentifier: () => isIdentifier,
62
+ isInstanceOf: () => isInstanceOf,
63
+ isLuauName: () => isLuauName,
57
64
  isPossiblyFalsy: () => isPossiblyFalsy,
58
65
  isPossiblyTruthy: () => isPossiblyTruthy,
59
66
  isUnassignedGlobal: () => isUnassignedGlobal,
60
67
  literal: () => literal,
61
68
  loadConfig: () => loadConfig,
69
+ luauString: () => luauString,
62
70
  matchInfer: () => matchInfer,
63
71
  moduleCandidates: () => moduleCandidates,
64
72
  moduleExports: () => moduleExports,
@@ -81,6 +89,7 @@ __export(index_exports, {
81
89
  readDirectives: () => readDirectives,
82
90
  resolveModulePath: () => resolveModulePath,
83
91
  resolveTypeLibraries: () => resolveTypeLibraries,
92
+ returnsTuple: () => returnsTuple,
84
93
  setAliasExpander: () => setAliasExpander,
85
94
  setDeferredBound: () => setDeferredBound,
86
95
  sourceMapTypes: () => sourceMapTypes,
@@ -99,7 +108,8 @@ __export(index_exports, {
99
108
  unknownType: () => unknownType,
100
109
  widen: () => widen,
101
110
  withAliasName: () => withAliasName,
102
- withoutAliasName: () => withoutAliasName
111
+ withoutAliasName: () => withoutAliasName,
112
+ withoutNil: () => withoutNil2
103
113
  });
104
114
  module.exports = __toCommonJS(index_exports);
105
115
 
@@ -708,7 +718,7 @@ function bindThis(func, at) {
708
718
  func.isMethod = true;
709
719
  }
710
720
  function bindThisParam(params, at) {
711
- params.unshift({ type: "FunctionParameter", name: "this", ...spanFrom(at, at) });
721
+ params.unshift({ type: "FunctionParameter", name: "this", implicit: true, ...spanFrom(at, at) });
712
722
  }
713
723
  var ParseError = class extends Error {
714
724
  constructor(message, line, column) {
@@ -1765,7 +1775,7 @@ var Parser = class {
1765
1775
  };
1766
1776
  }
1767
1777
  if (isMethod) {
1768
- func.params.unshift({ type: "FunctionParameter", name: "self", ...spanFrom(target, target) });
1778
+ func.params.unshift({ type: "FunctionParameter", name: "self", implicit: true, ...spanFrom(target, target) });
1769
1779
  func.isMethod = true;
1770
1780
  }
1771
1781
  return {
@@ -1959,47 +1969,7 @@ var Parser = class {
1959
1969
  const isStatic = modifiers.has("static");
1960
1970
  if (this.checkKeyword("function")) {
1961
1971
  this.advance();
1962
- const memberName = this.parseIdentifier();
1963
- if (modifiers.has("abstract")) {
1964
- const head = this.parseFunctionHead();
1965
- const func = {
1966
- type: "FunctionBody",
1967
- generics: head.generics,
1968
- params: head.params,
1969
- hasVarargs: head.hasVarargs,
1970
- returnType: head.returnType,
1971
- predicate: head.predicate,
1972
- body: { type: "Block", statements: [], ...spanFrom(head.start, this.previous()) },
1973
- ...spanFrom(head.start, this.previous())
1974
- };
1975
- if (!isStatic) bindThis(func, start);
1976
- if (!head.returnType) this.problem("An abstract method has no body to infer from: write its return type");
1977
- if (this.checkPunctuator("{")) this.problem("An abstract method has no body");
1978
- return { type: "ClassMethod", name: memberName, isStatic, func, isAbstract: true, ...spanFrom(start, this.previous()) };
1979
- }
1980
- const signatures = [];
1981
- let written = memberName;
1982
- while (true) {
1983
- const head = this.parseFunctionHead();
1984
- if (this.isClassOverloadContinuation(memberName.name, isStatic)) {
1985
- if (!isStatic) bindThisParam(head.params, start);
1986
- signatures.push({ ...this.headToSignature(head), name: written });
1987
- if (isStatic) this.advance();
1988
- this.expectKeyword("function");
1989
- written = this.parseIdentifier();
1990
- continue;
1991
- }
1992
- const func = this.headToBody(head, start);
1993
- if (!isStatic) bindThis(func, start);
1994
- return {
1995
- type: "ClassMethod",
1996
- name: memberName,
1997
- isStatic,
1998
- func,
1999
- signatures: signatures.length ? signatures : void 0,
2000
- ...spanFrom(start, this.previous())
2001
- };
2002
- }
1972
+ return this.parseClassMethod(start, modifiers);
2003
1973
  }
2004
1974
  if (!isStatic && this.checkIdentifierValue("constructor") && this.punctuatorAt(1, "(")) {
2005
1975
  this.advance();
@@ -2009,6 +1979,9 @@ var Parser = class {
2009
1979
  bindThis(func, start);
2010
1980
  return { type: "ClassConstructor", func, ...spanFrom(start, this.previous()) };
2011
1981
  }
1982
+ if (this.startsMethodShorthand(0)) {
1983
+ return this.parseClassMethod(start, modifiers);
1984
+ }
2012
1985
  if ((this.checkIdentifierValue("get") || this.checkIdentifierValue("set")) && this.peek(1).type === "Identifier" && this.punctuatorAt(2, "(")) {
2013
1986
  const kind = this.advance().value;
2014
1987
  const memberName = this.parseIdentifier();
@@ -2038,25 +2011,90 @@ var Parser = class {
2038
2011
  return { type: "ClassField", name: memberName, isStatic, typeAnnotation, init, ...spanFrom(start, this.previous()) };
2039
2012
  }
2040
2013
  if (this.recover) {
2041
- this.softError("Expected a class member: a field, 'function', 'constructor', 'get' or 'set'");
2014
+ this.softError("Expected a class member: a field, a method, 'constructor', 'get' or 'set'");
2042
2015
  this.advance();
2043
2016
  return void 0;
2044
2017
  }
2045
- this.error("Expected a class member: a field, 'function', 'constructor', 'get' or 'set'");
2018
+ this.error("Expected a class member: a field, a method, 'constructor', 'get' or 'set'");
2019
+ }
2020
+ /** A name at `offset` opening a method without `function`: `name(` or
2021
+ * `name<T>(`. A field's name is followed by `:` or `=` instead. */
2022
+ startsMethodShorthand(offset) {
2023
+ if (this.peek(offset).type !== "Identifier") return false;
2024
+ const next = this.peek(offset + 1);
2025
+ return (next.type === "Punctuator" || next.type === "Operator") && (next.value === "(" || next.value === "<");
2026
+ }
2027
+ /** A method, from its name on: `function` (if written) and the modifiers
2028
+ * are behind. */
2029
+ parseClassMethod(start, modifiers) {
2030
+ const isStatic = modifiers.has("static");
2031
+ const memberName = this.parseIdentifier();
2032
+ if (modifiers.has("abstract")) {
2033
+ const head = this.parseFunctionHead();
2034
+ const func = {
2035
+ type: "FunctionBody",
2036
+ generics: head.generics,
2037
+ params: head.params,
2038
+ hasVarargs: head.hasVarargs,
2039
+ returnType: head.returnType,
2040
+ predicate: head.predicate,
2041
+ body: { type: "Block", statements: [], ...spanFrom(head.start, this.previous()) },
2042
+ ...spanFrom(head.start, this.previous())
2043
+ };
2044
+ if (!isStatic) bindThis(func, start);
2045
+ if (!head.returnType) this.problem("An abstract method has no body to infer from: write its return type");
2046
+ if (this.checkPunctuator("{")) this.problem("An abstract method has no body");
2047
+ return { type: "ClassMethod", name: memberName, isStatic, func, isAbstract: true, ...spanFrom(start, this.previous()) };
2048
+ }
2049
+ const signatures = [];
2050
+ let written = memberName;
2051
+ while (true) {
2052
+ const head = this.parseFunctionHead();
2053
+ const skip = this.classOverloadContinuation(memberName.name, isStatic);
2054
+ if (skip !== void 0) {
2055
+ if (!isStatic) bindThisParam(head.params, start);
2056
+ signatures.push({ ...this.headToSignature(head), name: written });
2057
+ for (let i = 0; i < skip; i++) this.advance();
2058
+ written = this.parseIdentifier();
2059
+ continue;
2060
+ }
2061
+ const func = this.headToBody(head, start);
2062
+ if (!isStatic) bindThis(func, start);
2063
+ return {
2064
+ type: "ClassMethod",
2065
+ name: memberName,
2066
+ isStatic,
2067
+ func,
2068
+ signatures: signatures.length ? signatures : void 0,
2069
+ ...spanFrom(start, this.previous())
2070
+ };
2071
+ }
2046
2072
  }
2047
- /** Give a class method its `this`: a real first parameter, the way
2048
- * `function T:m()` gets a real `self`. Every later pass — scopes, types,
2049
- * arity, lowering — then sees an ordinary parameter and needs to know
2050
- * nothing about classes. */
2051
2073
  /** After a bodyless head inside a class body, does another declaration of
2052
- * the same member follow? Then the head was an overload signature. */
2053
- isClassOverloadContinuation(name, isStatic) {
2054
- const offset = isStatic ? 1 : 0;
2055
- if (isStatic && !this.checkIdentifierValue("static")) return false;
2074
+ * the same member follow? Then the head was an overload signature, and
2075
+ * this is how many tokens stand before the next one's name: a `;` that
2076
+ * ends the head, as TypeScript writes it, the modifiers, and `function`
2077
+ * when it is written. `undefined` when no overload follows. Each head may
2078
+ * be written either way, with `function` or without. */
2079
+ classOverloadContinuation(name, isStatic) {
2080
+ let offset = 0;
2081
+ if (this.punctuatorAt(0, ";")) offset++;
2082
+ let sawStatic = false;
2083
+ while (true) {
2084
+ const t = this.peek(offset);
2085
+ const word = t.type === "Identifier" ? t.value : void 0;
2086
+ if (word === void 0 || !CLASS_MODIFIERS.has(word) || this.startsMethodShorthand(offset)) break;
2087
+ if (word === "static") sawStatic = true;
2088
+ offset++;
2089
+ }
2090
+ if (sawStatic !== isStatic) return void 0;
2056
2091
  const keyword = this.peek(offset);
2057
- if (!(keyword.type === "Keyword" && keyword.value === "function")) return false;
2058
- const named = this.peek(offset + 1);
2059
- return named.type === "Identifier" && named.value === name;
2092
+ const hasKeyword = keyword.type === "Keyword" && keyword.value === "function";
2093
+ if (hasKeyword) offset++;
2094
+ const named = this.peek(offset);
2095
+ if (!(named.type === "Identifier" && named.value === name)) return void 0;
2096
+ if (!hasKeyword && !this.startsMethodShorthand(offset)) return void 0;
2097
+ return offset;
2060
2098
  }
2061
2099
  operatorAt(ahead, value) {
2062
2100
  const token = this.peek(ahead);
@@ -3893,6 +3931,10 @@ var Analyzer = class {
3893
3931
  moduleScope = this.globalScope;
3894
3932
  /** How many function bodies enclose the walk. */
3895
3933
  functionDepth = 0;
3934
+ /** How many loops enclose the walk inside the nearest function body. A
3935
+ * function starts again at 0: `continue` in `xs:map(x => ...)` has no
3936
+ * loop to go on with, whatever loop the call is written in. */
3937
+ loopDepth = 0;
3896
3938
  /** Function declarations already declared by their block's hoisting, with
3897
3939
  * the function depth of that block. */
3898
3940
  hoisted = /* @__PURE__ */ new Map();
@@ -4245,11 +4287,11 @@ var Analyzer = class {
4245
4287
  return;
4246
4288
  case "WhileStatement":
4247
4289
  this.visitExpression(stmt.condition, scope);
4248
- this.visitBlockInNewScope(stmt.body, scope);
4290
+ this.inLoop(() => this.visitBlockInNewScope(stmt.body, scope));
4249
4291
  return;
4250
4292
  case "RepeatStatement": {
4251
4293
  const bodyScope = childScope(scope);
4252
- this.visitBlock(stmt.body, bodyScope);
4294
+ this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
4253
4295
  this.visitExpression(stmt.condition, bodyScope);
4254
4296
  return;
4255
4297
  }
@@ -4267,14 +4309,14 @@ var Analyzer = class {
4267
4309
  if (stmt.step) this.visitExpression(stmt.step, scope);
4268
4310
  const bodyScope = childScope(scope);
4269
4311
  this.declare(bodyScope, stmt.variable.name, "for-numeric", stmt.variable);
4270
- this.visitBlock(stmt.body, bodyScope);
4312
+ this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
4271
4313
  return;
4272
4314
  }
4273
4315
  case "GenericForStatement": {
4274
4316
  this.visitExpression(stmt.iterator, scope);
4275
4317
  const bodyScope = childScope(scope);
4276
4318
  this.declarePattern(bodyScope, stmt.variable, "for-generic", scope, stmt.kind === "const");
4277
- this.visitBlock(stmt.body, bodyScope);
4319
+ this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
4278
4320
  return;
4279
4321
  }
4280
4322
  case "ReturnStatement":
@@ -4282,6 +4324,15 @@ var Analyzer = class {
4282
4324
  return;
4283
4325
  case "BreakStatement":
4284
4326
  case "ContinueStatement":
4327
+ if (this.loopDepth === 0) {
4328
+ const word = stmt.type === "BreakStatement" ? "break" : "continue";
4329
+ this.diagnostics.push({
4330
+ node: stmt,
4331
+ 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`,
4332
+ kind: "outside-loop"
4333
+ });
4334
+ }
4335
+ return;
4285
4336
  case "ErrorStatement":
4286
4337
  return;
4287
4338
  case "DeclareStatement":
@@ -4337,6 +4388,15 @@ var Analyzer = class {
4337
4388
  return;
4338
4389
  }
4339
4390
  }
4391
+ /** Visit a loop's body, where `break` and `continue` have a loop. */
4392
+ inLoop(visit) {
4393
+ this.loopDepth++;
4394
+ try {
4395
+ visit();
4396
+ } finally {
4397
+ this.loopDepth--;
4398
+ }
4399
+ }
4340
4400
  // ---------------- functions ----------------
4341
4401
  visitFunctionBody(func, outerScope, isMethod = false) {
4342
4402
  const fnScope = childScope(outerScope);
@@ -4352,11 +4412,14 @@ var Analyzer = class {
4352
4412
  }
4353
4413
  });
4354
4414
  this.visitType(func.returnType, fnScope);
4415
+ const loops = this.loopDepth;
4355
4416
  this.functionDepth++;
4417
+ this.loopDepth = 0;
4356
4418
  try {
4357
4419
  this.visitBlock(func.body, fnScope);
4358
4420
  } finally {
4359
4421
  this.functionDepth--;
4422
+ this.loopDepth = loops;
4360
4423
  }
4361
4424
  }
4362
4425
  /** An overload signature: no body and no bindings, but its types can hold
@@ -4610,11 +4673,14 @@ declare metatable string: {
4610
4673
  -- answer \`nil\` rather than JavaScript's -1. \`sort\` takes Lua's comparator
4611
4674
  -- (true when \`a\` comes first). \`push\`, \`pop\`, \`shift\`, \`unshift\`,
4612
4675
  -- \`sort\` and \`reverse\` change the array; everything else returns a new one.
4676
+ -- A \`filter\` whose test is a guard \u2014 \`v is S\`, or \`v => v ~= nil\` \u2014
4677
+ -- answers an array of what it let through.
4613
4678
  declare metatable<T> T[]: {
4614
4679
  __index: {
4615
4680
  find: (self: T[], test: (value: T, index: number) => boolean) => T | nil,
4616
4681
  findIndex: (self: T[], test: (value: T, index: number) => boolean) => number | nil,
4617
- filter: (self: T[], test: (value: T, index: number) => boolean) => T[],
4682
+ filter: (<S extends T>(self: T[], test: (value: T, index: number) => value is S) => S[])
4683
+ & ((self: T[], test: (value: T, index: number) => boolean) => T[]),
4618
4684
  map: <U>(self: T[], transform: (value: T, index: number) => U) => U[],
4619
4685
  forEach: (self: T[], visit: (value: T, index: number) => nil) => nil,
4620
4686
  some: (self: T[], test: (value: T, index: number) => boolean) => boolean,
@@ -4648,11 +4714,34 @@ declare metatable<T> T[]: {
4648
4714
  -- \`nil\` when it is missing; an indexer's keys follow the named ones. A
4649
4715
  -- member of the table's own with one of these names is what a call reaches,
4650
4716
  -- and a class instance, which has a metatable of its own, has none of them.
4717
+ --
4718
+ -- The rest are JavaScript's \`Object\` functions, written as methods of the
4719
+ -- table they take first:
4720
+ --
4721
+ -- point:hasOwn("x") -- Object.hasOwn(point, "x")
4722
+ -- point:assign({ z: 3 }) -- Object.assign(point, { z: 3 })
4723
+ -- point:freeze() -- Object.freeze(point)
4724
+ --
4725
+ -- \`hasOwn\` asks the table itself, not what its metatable's \`__index\`
4726
+ -- lends it. \`assign\` copies each source's members onto the table, later ones
4727
+ -- over earlier ones, skipping a \`nil\` source, and answers the table. \`freeze\`
4728
+ -- is Luau's \`table.freeze\`; under Lua 5.1, which cannot freeze a table, the
4729
+ -- table is answered as it is and \`isFrozen\` is false.
4651
4730
  declare metatable<T extends {}> T: {
4652
4731
  __index: {
4653
4732
  keys: (self: T) => ObjectKeys<T>,
4654
4733
  values: (self: T) => ObjectValues<T>,
4655
4734
  entries: (self: T) => ObjectEntries<T>,
4735
+
4736
+ hasOwn: (self: T, key: unknown) => boolean,
4737
+ hasOwnProperty: (self: T, key: unknown) => boolean,
4738
+ assign: (<U>(self: T, source: U) => T & U)
4739
+ & (<U, V>(self: T, a: U, b: V) => T & U & V)
4740
+ & (<U, V, W>(self: T, a: U, b: V, c: W) => T & U & V & W)
4741
+ -- Generic like the rest: a plain signature would be tried first.
4742
+ & (<S extends {}>(self: T, ...sources: S[]) => T),
4743
+ freeze: (self: T) => Readonly<T>,
4744
+ isFrozen: (self: T) => boolean,
4656
4745
  },
4657
4746
  }
4658
4747
  `;
@@ -4753,8 +4842,14 @@ function substitute(t, subst) {
4753
4842
  if (subst.size === 0) return t;
4754
4843
  if (!containsTypeParam(t)) return t;
4755
4844
  switch (t.kind) {
4756
- case "typeParam":
4757
- return subst.get(t.name) ?? t;
4845
+ case "typeParam": {
4846
+ const bound = subst.get(t.name);
4847
+ if (bound) return bound;
4848
+ if (t.constraint && containsTypeParam(t.constraint)) {
4849
+ return { ...t, constraint: substitute(t.constraint, new Map([...subst, [t.name, t]])) };
4850
+ }
4851
+ return t;
4852
+ }
4758
4853
  case "array":
4759
4854
  return { ...arrayOf(substitute(t.element, subst)), readonly: t.readonly };
4760
4855
  case "tuple":
@@ -4899,6 +4994,10 @@ function unify(param, arg, vars, out, how = "union") {
4899
4994
  if (arg.kind === "function") {
4900
4995
  param.params.forEach((p, i) => arg.params[i] && unify(p.type, arg.params[i].type, vars, out));
4901
4996
  unify(param.returns, arg.returns, vars, out);
4997
+ const wanted = param.predicate, given = arg.predicate;
4998
+ if (wanted?.type && given?.type && wanted.param === given.param && !wanted.asserts && !given.asserts) {
4999
+ unify(wanted.type, given.type, vars, out);
5000
+ }
4902
5001
  }
4903
5002
  return;
4904
5003
  case "object":
@@ -5154,6 +5253,11 @@ function isAssignableInner(a, b) {
5154
5253
  for (let i = 0; i < n; i++) {
5155
5254
  if (!isAssignable(a.params[i].type, b.params[i].type) && !isAssignable(b.params[i].type, a.params[i].type)) return false;
5156
5255
  }
5256
+ const wanted = b.predicate;
5257
+ if (wanted?.type && !wanted.asserts) {
5258
+ const given = a.predicate;
5259
+ if (!given?.type || given.asserts || given.param !== wanted.param || !isAssignable(given.type, wanted.type)) return false;
5260
+ }
5157
5261
  return isAssignable(a.returns, b.returns);
5158
5262
  }
5159
5263
  if (b.kind === "typeParam") return false;
@@ -5288,7 +5392,7 @@ function containsFreeTypeParam(t, seen, bound) {
5288
5392
  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));
5289
5393
  case "function": {
5290
5394
  const inner = t.typeParams?.length ? /* @__PURE__ */ new Set([...bound, ...t.typeParams]) : bound;
5291
- return t.params.some((p) => containsTypeParam(p.type, seen, inner)) || !!t.varargs && containsTypeParam(t.varargs, seen, inner) || containsTypeParam(t.returns, seen, inner);
5395
+ 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);
5292
5396
  }
5293
5397
  case "genericRef":
5294
5398
  return t.typeArguments.some((a) => containsTypeParam(a, seen, bound));
@@ -5713,9 +5817,14 @@ function isFreshLiteralExpr(e) {
5713
5817
  case "StringLiteral":
5714
5818
  case "BooleanLiteral":
5715
5819
  case "InterpolatedStringExpression":
5820
+ return true;
5821
+ // A table or array literal widened the literals written in it as it
5822
+ // was read (`widenUnlessAsked`), and keeps what it read from
5823
+ // elsewhere — `{ key: name }`, `[...names]` — so there is nothing
5824
+ // left for the binding to widen.
5716
5825
  case "TableExpression":
5717
5826
  case "ArrayExpression":
5718
- return true;
5827
+ return false;
5719
5828
  case "ParenthesizedExpression":
5720
5829
  return isFreshLiteralExpr(e.expression);
5721
5830
  case "UnaryExpression":
@@ -5730,6 +5839,30 @@ function isFreshLiteralExpr(e) {
5730
5839
  return false;
5731
5840
  }
5732
5841
  }
5842
+ function overloadTier(f) {
5843
+ const takesGuard = f.params.some((p) => p.type.kind === "function" && p.type.predicate?.type && !p.type.predicate.asserts);
5844
+ if (takesGuard) return 0;
5845
+ return (f.typeParams?.length ?? 0) > 0 ? 2 : 1;
5846
+ }
5847
+ function writesLiteral(e) {
5848
+ switch (e.type) {
5849
+ case "NumberLiteral":
5850
+ case "StringLiteral":
5851
+ case "BooleanLiteral":
5852
+ case "InterpolatedStringExpression":
5853
+ return true;
5854
+ case "ParenthesizedExpression":
5855
+ return writesLiteral(e.expression);
5856
+ case "UnaryExpression":
5857
+ return writesLiteral(e.argument);
5858
+ case "IfElseExpression":
5859
+ return e.clauses.some((c) => writesLiteral(c.body)) || writesLiteral(e.alternate);
5860
+ case "BinaryExpression":
5861
+ return (e.operator === "or" || e.operator === "and") && (writesLiteral(e.left) || writesLiteral(e.right));
5862
+ default:
5863
+ return false;
5864
+ }
5865
+ }
5733
5866
  function collectInferNames(node) {
5734
5867
  const out = [];
5735
5868
  const walk = (n) => {
@@ -8112,7 +8245,9 @@ var TypeAnalyzer = class {
8112
8245
  const waits = written.map((a, i) => this.waitsOnInference(a, expected[i]));
8113
8246
  if (!waits.some(Boolean)) return written.map((a) => this.infer(a, env));
8114
8247
  const argTypes = written.map((a, i) => waits[i] ? this.quietly(() => this.infer(a, env)) : this.infer(a, env));
8115
- const picked = this.pickOverload(fns, argTypes, (f) => extra(f, argTypes), this.spreadOf(written, selfOf(fns[0])));
8248
+ const spread = this.spreadOf(written, selfOf(fns[0]));
8249
+ const provisional = argTypes.map((t, i) => waits[i] ? anyType : t);
8250
+ const picked = this.pickOverload(fns, argTypes, (f) => extra(f, argTypes), spread) ?? this.pickOverload(fns, provisional, (f) => extra(f, provisional), spread);
8116
8251
  const subst = picked?.typeParams?.length ? this.inferTypeArgs(picked, extra(picked, argTypes)) : void 0;
8117
8252
  written.forEach((a, i) => {
8118
8253
  if (!waits[i]) return;
@@ -8303,6 +8438,16 @@ var TypeAnalyzer = class {
8303
8438
  }
8304
8439
  const id = this.bindingIdByName(p.name, p);
8305
8440
  const t = this.paramType(p, env);
8441
+ if (p.default && p.typeAnnotation) {
8442
+ const given = this.infer(p.default, env);
8443
+ const declared = this.resolveType(p.typeAnnotation);
8444
+ if (this.emitDiagnostics && !isAssignable(given, declared)) {
8445
+ this.diagnostics.push({
8446
+ node: p.default,
8447
+ message: `Type '${briefType(given)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(given, declared)
8448
+ });
8449
+ }
8450
+ }
8306
8451
  if (id !== void 0) {
8307
8452
  this.bindingType.set(id, t);
8308
8453
  this.setBinding(env, id, t);
@@ -8400,9 +8545,9 @@ var TypeAnalyzer = class {
8400
8545
  * lets `typeof` declare `(v: number) -> "number"` alongside a trailing
8401
8546
  * `<T>(v: T) -> string` and still pick the precise one. */
8402
8547
  pickOverload(fns, argTypes, argsFor, spread) {
8403
- for (const generic of [false, true]) {
8548
+ for (const tier of [0, 1, 2]) {
8404
8549
  for (const f of fns) {
8405
- if ((f.typeParams?.length ?? 0) > 0 !== generic) continue;
8550
+ if (overloadTier(f) !== tier) continue;
8406
8551
  if (this.overloadAccepts(f, argsFor ? argsFor(f) : argTypes, spread)) return f;
8407
8552
  }
8408
8553
  }
@@ -8428,7 +8573,7 @@ var TypeAnalyzer = class {
8428
8573
  if (position < 0) return void 0;
8429
8574
  const members = this.expand(argTypes[position]).types;
8430
8575
  if (members.length > 32) return void 0;
8431
- const rank = (f) => ((f.typeParams?.length ?? 0) > 0 ? fns.length : 0) + fns.indexOf(f);
8576
+ const rank = (f) => overloadTier(f) * fns.length + fns.indexOf(f);
8432
8577
  const limit = picked ? rank(picked) : Infinity;
8433
8578
  const results = [];
8434
8579
  for (const member of members) {
@@ -8821,10 +8966,38 @@ var TypeAnalyzer = class {
8821
8966
  returns,
8822
8967
  void 0,
8823
8968
  names,
8824
- this.resolvePredicate(func.predicate, params)
8969
+ func.predicate || func.returnType ? this.resolvePredicate(func.predicate, params) : this.inferPredicate(func, params, returns, bodyEnv)
8825
8970
  ), func.params);
8826
8971
  });
8827
8972
  }
8973
+ /** `v => v ~= nil` is a guard without saying so, as TypeScript reads it:
8974
+ * a body that is one `return` of a boolean which, true, narrows a
8975
+ * parameter — and, false, leaves it exactly what that excludes. Then
8976
+ * `list:filter(v => v ~= nil)` knows what it kept. */
8977
+ inferPredicate(func, params, returns, env) {
8978
+ const only = func.body.statements.length === 1 ? func.body.statements[0] : void 0;
8979
+ if (only?.type !== "ReturnStatement" || !only.argument) return void 0;
8980
+ if (returns.kind === "any" || returns.kind === "never" || !isAssignable(returns, booleanType)) return void 0;
8981
+ const cond = only.argument;
8982
+ for (let i = 0; i < params.length; i++) {
8983
+ const p = func.params[i];
8984
+ if (p.pattern || p.rest) continue;
8985
+ const id = this.bindingIdByName(p.name, p);
8986
+ if (id === void 0) continue;
8987
+ const declared = params[i].type;
8988
+ const key = bindKey(id);
8989
+ const t = forkEnv(env);
8990
+ const f = forkEnv(env);
8991
+ this.silently(() => this.applyNarrowing(cond, env, t, f));
8992
+ const yes = t.get(key);
8993
+ if (!yes || equalTypes(yes, declared) || !isAssignable(yes, declared)) continue;
8994
+ const no = this.expand(f.get(key) ?? declared);
8995
+ const rest = no.kind === "union" ? no.types : [no];
8996
+ if (rest.some((m) => m.kind !== "never" && isAssignable(m, yes))) continue;
8997
+ return { param: i, type: yes, asserts: false };
8998
+ }
8999
+ return void 0;
9000
+ }
8828
9001
  /** Where the return types of the function being walked are collected, so
8829
9002
  * each is read where it is written — inside the branch that narrowed it —
8830
9003
  * rather than in whatever state the body ends in. */
@@ -9946,6 +10119,27 @@ var TypeAnalyzer = class {
9946
10119
  elseEnv = whenFalse;
9947
10120
  }
9948
10121
  branches.push(this.infer(expr.alternate, elseEnv));
10122
+ const bodies = [...expr.clauses.map((c) => c.body), expr.alternate].map(unwrapParens);
10123
+ const isEmpty = (e) => e.type === "TableExpression" && e.fields.length === 0 || e.type === "ArrayExpression" && e.elements.length === 0;
10124
+ const others = union(branches.filter((_, i) => !isEmpty(bodies[i])));
10125
+ const parts = (others.kind === "union" ? others.types : [others]).map((m) => this.expand(m)).filter((m) => !(m.kind === "primitive" && m.name === "nil"));
10126
+ const fits = (e) => {
10127
+ if (!parts.length) return void 0;
10128
+ if (e.type === "ArrayExpression") {
10129
+ if (parts.every((m) => m.kind === "array")) return others;
10130
+ return parts.every((m) => m.kind === "array" || m.kind === "tuple") ? tuple([]) : void 0;
10131
+ }
10132
+ return parts.every((m) => m.kind === "object" && !m.class && [...m.properties.values()].every((prop) => prop.optional)) ? others : void 0;
10133
+ };
10134
+ if (bodies.some(isEmpty) && others.kind !== "never" && others.kind !== "any" && bodies.every((body) => !isEmpty(body) || fits(body))) {
10135
+ const typed = bodies.map((body, i) => {
10136
+ if (!isEmpty(body)) return branches[i];
10137
+ const t = fits(body);
10138
+ this.typeOf.set(body, t);
10139
+ return t;
10140
+ });
10141
+ return union(typed);
10142
+ }
9949
10143
  return union(branches);
9950
10144
  }
9951
10145
  }
@@ -10252,23 +10446,87 @@ var TypeAnalyzer = class {
10252
10446
  if (wantedTuple && !asConst) return this.inferTupleLiteral(expr, env, wantedTuple);
10253
10447
  const elems = [];
10254
10448
  let hadSpread = false;
10449
+ const written = [];
10255
10450
  for (const el of expr.elements) {
10256
10451
  if (el.type === "SpreadElement") {
10257
10452
  hadSpread = true;
10258
- const s = this.infer(el.argument, env);
10259
- if (s.kind === "array") elems.push(s.element);
10260
- else if (s.kind === "tuple") elems.push(...s.elements);
10261
- else elems.push(unknownType);
10453
+ const spread = this.infer(el.argument, env);
10454
+ written.push({ spread: true, type: spread });
10455
+ elems.push(...this.spreadElements(spread));
10262
10456
  } else {
10263
- elems.push(asConst ? this.inferAsConst(el, env) : this.infer(el, env));
10457
+ const t = asConst ? this.inferAsConst(el, env) : this.infer(el, env);
10458
+ written.push({ spread: false, type: asConst ? t : this.widenUnlessAsked(t, el) });
10459
+ elems.push(t);
10264
10460
  }
10265
10461
  }
10266
10462
  if (asConst && !hadSpread) return tuple(elems);
10463
+ if (hadSpread) {
10464
+ const tuples = this.spreadTuples(written);
10465
+ if (tuples) return union(tuples.map((elements) => tuple(elements)));
10466
+ }
10267
10467
  return arrayOf(elems.length ? union(elems.map((t, i) => {
10268
10468
  const element = expr.elements[i];
10269
10469
  return asConst || !element || element.type === "SpreadElement" ? t : this.widenUnlessAsked(t, element);
10270
10470
  })) : unknownType);
10271
10471
  }
10472
+ /** The tuples an array literal with spreads is, when every spread is a
10473
+ * tuple of known length or a union of them: one per way the unions go.
10474
+ * `undefined` when a spread's length is not known (an array, a tuple
10475
+ * with a rest), or the ways multiply past what is worth listing. */
10476
+ spreadTuples(written) {
10477
+ const MAX = 32;
10478
+ let variants = [[]];
10479
+ for (const { spread, type } of written) {
10480
+ if (!spread) {
10481
+ variants = variants.map((v) => [...v, type]);
10482
+ continue;
10483
+ }
10484
+ const options = this.fixedTuples(type);
10485
+ if (!options) return void 0;
10486
+ const next = [];
10487
+ for (const v of variants) for (const option of options) next.push([...v, ...option]);
10488
+ if (next.length > MAX) return void 0;
10489
+ variants = next;
10490
+ }
10491
+ return variants;
10492
+ }
10493
+ /** A tuple of known length's members, or a union of such tuples' — each
10494
+ * a way the value can be. `nil` in a union spreads nothing. */
10495
+ fixedTuples(t, depth = 0) {
10496
+ if (depth > 8) return void 0;
10497
+ const held = this.expand(t);
10498
+ if (held.kind === "tuple") return held.rest ? void 0 : [held.elements];
10499
+ if (held.kind !== "union") return void 0;
10500
+ const out = [];
10501
+ for (const member of held.types) {
10502
+ if (member.kind === "primitive" && member.name === "nil") continue;
10503
+ const options = this.fixedTuples(member, depth + 1);
10504
+ if (!options) return void 0;
10505
+ out.push(...options);
10506
+ }
10507
+ return out.length ? out : void 0;
10508
+ }
10509
+ /** What `...xs` puts into an array: an array's element, a tuple's members,
10510
+ * and for a union each member's — `...(host ? extra : [])` adds `extra`'s
10511
+ * elements and the empty tuple nothing. Anything else, `unknown`. */
10512
+ spreadElements(t, depth = 0) {
10513
+ const held = this.expand(t);
10514
+ if (depth > 8) return [unknownType];
10515
+ switch (held.kind) {
10516
+ case "array":
10517
+ return [held.element];
10518
+ case "tuple":
10519
+ return tupleMembers(held);
10520
+ case "union":
10521
+ return held.types.filter((m) => !(m.kind === "primitive" && m.name === "nil")).flatMap((m) => this.spreadElements(m, depth + 1));
10522
+ case "typeParam":
10523
+ return held.constraint ? this.spreadElements(held.constraint, depth + 1) : [unknownType];
10524
+ case "any":
10525
+ return [anyType];
10526
+ default:
10527
+ return [unknownType];
10528
+ }
10529
+ }
10272
10530
  /** `[a, b, ...more]` where a tuple is wanted: the places it names are the
10273
10531
  * tuple's, and what comes after them — more elements, or a spread — is
10274
10532
  * its rest. */
@@ -10279,10 +10537,7 @@ var TypeAnalyzer = class {
10279
10537
  expr.elements.forEach((element, i) => {
10280
10538
  if (element.type === "SpreadElement") {
10281
10539
  spread = true;
10282
- const held = this.expand(this.infer(element.argument, env));
10283
- if (held.kind === "array") tail.push(held.element);
10284
- else if (held.kind === "tuple") tail.push(...tupleMembers(held));
10285
- else tail.push(unknownType);
10540
+ tail.push(...this.spreadElements(this.infer(element.argument, env)));
10286
10541
  return;
10287
10542
  }
10288
10543
  const t = this.widenUnlessAsked(this.infer(element, env), element);
@@ -10299,6 +10554,7 @@ var TypeAnalyzer = class {
10299
10554
  * The context was recorded by `applyContext` before the value was
10300
10555
  * inferred, so this is a lookup rather than a second pass. */
10301
10556
  widenUnlessAsked(value, at) {
10557
+ if (!writesLiteral(at)) return value;
10302
10558
  const wanted = this.expectedTypeOf.get(at);
10303
10559
  if (value.kind === "literal") {
10304
10560
  const arms = this.contextArms.get(at);
@@ -11671,7 +11927,53 @@ function aliasTargets(config, specifier) {
11671
11927
 
11672
11928
  // src/project/sourcemap.ts
11673
11929
  var import_node_path4 = require("path");
11930
+
11931
+ // src/luau.ts
11932
+ var LUAU_KEYWORDS = /* @__PURE__ */ new Set([
11933
+ "and",
11934
+ "break",
11935
+ "do",
11936
+ "else",
11937
+ "elseif",
11938
+ "end",
11939
+ "false",
11940
+ "for",
11941
+ "function",
11942
+ "if",
11943
+ "in",
11944
+ "local",
11945
+ "nil",
11946
+ "not",
11947
+ "or",
11948
+ "repeat",
11949
+ "return",
11950
+ "then",
11951
+ "true",
11952
+ "until",
11953
+ "while"
11954
+ ]);
11674
11955
  var IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
11956
+ function isIdentifier(name) {
11957
+ return IDENTIFIER.test(name);
11958
+ }
11959
+ function isLuauName(name) {
11960
+ return IDENTIFIER.test(name) && !LUAU_KEYWORDS.has(name);
11961
+ }
11962
+ var NAMED_ESCAPES = {
11963
+ "\\": "\\\\",
11964
+ '"': '\\"',
11965
+ "\n": "\\n",
11966
+ "\r": "\\r",
11967
+ " ": "\\t"
11968
+ };
11969
+ function escapeLuauString(text) {
11970
+ return text.replace(/[\\"\x00-\x1f\x7f]/g, (c) => NAMED_ESCAPES[c] ?? `\\${c.charCodeAt(0).toString().padStart(3, "0")}`);
11971
+ }
11972
+ function luauString(text) {
11973
+ return `"${escapeLuauString(text)}"`;
11974
+ }
11975
+
11976
+ // src/project/sourcemap.ts
11675
11977
  var INSTANCE_MEMBERS = /* @__PURE__ */ new Set(["Name", "ClassName", "Parent", "Archivable"]);
11676
11978
  var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".tilua", ".luau", ".lua"]);
11677
11979
  function sourceMapTypes(text, path, options) {
@@ -11698,7 +12000,7 @@ function sourceMapTypes(text, path, options) {
11698
12000
  const alias = aliasFor(segments);
11699
12001
  aliasOfNode.set(node, alias);
11700
12002
  for (const filePath of node.filePaths ?? []) aliasOfFile.set(fileKey((0, import_node_path4.resolve)(directory, filePath)), alias);
11701
- const className = IDENTIFIER.test(node.className) && options.classes.has(node.className) ? node.className : "Instance";
12003
+ const className = isIdentifier(node.className) && options.classes.has(node.className) ? node.className : "Instance";
11702
12004
  const taken = options.membersOf?.(className) ?? INSTANCE_MEMBERS;
11703
12005
  const members = [];
11704
12006
  if (parent) members.push(`Parent: ${parent}`);
@@ -11707,7 +12009,7 @@ function sourceMapTypes(text, path, options) {
11707
12009
  if (!isNode(child)) continue;
11708
12010
  const childAlias = visit(child, [...segments, child.name], alias);
11709
12011
  const service = child.name === child.className && options.classes.has(child.className);
11710
- if (!IDENTIFIER.test(child.name) || taken.has(child.name) && !service || named.has(child.name)) continue;
12012
+ if (!isIdentifier(child.name) || taken.has(child.name) && !service || named.has(child.name)) continue;
11711
12013
  named.add(child.name);
11712
12014
  members.push(`${child.name}: ${childAlias}`);
11713
12015
  }
@@ -11749,6 +12051,34 @@ function fileKey(path) {
11749
12051
  return process.platform === "win32" ? normalized.toLowerCase() : normalized;
11750
12052
  }
11751
12053
 
12054
+ // src/loweringHelpers.ts
12055
+ function withoutNil2(type) {
12056
+ if (!type) return [];
12057
+ const members = type.kind === "union" ? type.types : [type];
12058
+ return members.filter((m) => !(m.kind === "primitive" && m.name === "nil"));
12059
+ }
12060
+ function isFunctionType(type) {
12061
+ const members = withoutNil2(type);
12062
+ return members.length > 0 && members.every((m) => m.kind === "function" || m.kind === "intersection" && m.types.every(isFunctionType));
12063
+ }
12064
+ function everyObject(type, test) {
12065
+ const members = withoutNil2(type);
12066
+ return members.length > 0 && members.every((m) => m.kind === "object" && test(m));
12067
+ }
12068
+ function isInstanceOf(type, className) {
12069
+ return everyObject(type, (m) => m.class !== void 0 && m.class.ancestors.includes(className));
12070
+ }
12071
+ function hasMembers(type, names) {
12072
+ return everyObject(type, (m) => names.every((name) => m.properties.has(name)));
12073
+ }
12074
+ function returnsTuple(receiver, method) {
12075
+ return everyObject(receiver, (m) => {
12076
+ const found = m.properties.get(method)?.type;
12077
+ const signatures = !found ? [] : found.kind === "intersection" ? found.types : [found];
12078
+ return signatures.length > 0 && signatures.every((f) => f.kind === "function" && f.returns.kind === "tuple");
12079
+ });
12080
+ }
12081
+
11752
12082
  // src/index.ts
11753
12083
  var tilua = {
11754
12084
  tokenize,
@@ -11769,6 +12099,7 @@ var index_default = tilua;
11769
12099
  CONFIG_FILE_NAMES,
11770
12100
  Keywords,
11771
12101
  LANGUAGE_GLOBALS,
12102
+ LUAU_KEYWORDS,
11772
12103
  LexError,
11773
12104
  Operators,
11774
12105
  PRELUDE_SOURCE,
@@ -11789,20 +12120,27 @@ var index_default = tilua;
11789
12120
  difference,
11790
12121
  directivesOf,
11791
12122
  equalTypes,
12123
+ escapeLuauString,
11792
12124
  falsyType,
11793
12125
  findConfig,
11794
12126
  fn,
11795
12127
  formatType,
11796
12128
  getBinding,
12129
+ hasMembers,
11797
12130
  intersection,
11798
12131
  isAssignable,
11799
12132
  isClassType,
12133
+ isFunctionType,
11800
12134
  isGlobal,
12135
+ isIdentifier,
12136
+ isInstanceOf,
12137
+ isLuauName,
11801
12138
  isPossiblyFalsy,
11802
12139
  isPossiblyTruthy,
11803
12140
  isUnassignedGlobal,
11804
12141
  literal,
11805
12142
  loadConfig,
12143
+ luauString,
11806
12144
  matchInfer,
11807
12145
  moduleCandidates,
11808
12146
  moduleExports,
@@ -11825,6 +12163,7 @@ var index_default = tilua;
11825
12163
  readDirectives,
11826
12164
  resolveModulePath,
11827
12165
  resolveTypeLibraries,
12166
+ returnsTuple,
11828
12167
  setAliasExpander,
11829
12168
  setDeferredBound,
11830
12169
  sourceMapTypes,
@@ -11843,5 +12182,6 @@ var index_default = tilua;
11843
12182
  unknownType,
11844
12183
  widen,
11845
12184
  withAliasName,
11846
- withoutAliasName
12185
+ withoutAliasName,
12186
+ withoutNil
11847
12187
  });