@tilua/parser 3.0.1 → 4.0.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
 
@@ -701,6 +711,8 @@ var UNUSED_EXPECT_ERROR = "Unused '@tilua-expect-error' directive";
701
711
 
702
712
  // src/ast/builders.ts
703
713
  var NO_DOTS = "tilua has no bare '...': take a rest parameter, '...args: T[]', or 'scriptArgs' for the script's own";
714
+ var NO_MEMBER_NAME = "Expected a name after '.'";
715
+ var NO_TYPEOF_CALL = "'typeof' is an operator, not a function: write 'typeof v'";
704
716
  var ACCESSIBILITIES = /* @__PURE__ */ new Set(["public", "private", "protected"]);
705
717
  var CLASS_MODIFIERS = /* @__PURE__ */ new Set([...ACCESSIBILITIES, "static", "abstract", "override", "readonly"]);
706
718
  function bindThis(func, at) {
@@ -708,7 +720,7 @@ function bindThis(func, at) {
708
720
  func.isMethod = true;
709
721
  }
710
722
  function bindThisParam(params, at) {
711
- params.unshift({ type: "FunctionParameter", name: "this", ...spanFrom(at, at) });
723
+ params.unshift({ type: "FunctionParameter", name: "this", implicit: true, ...spanFrom(at, at) });
712
724
  }
713
725
  var ParseError = class extends Error {
714
726
  constructor(message, line, column) {
@@ -1765,7 +1777,7 @@ var Parser = class {
1765
1777
  };
1766
1778
  }
1767
1779
  if (isMethod) {
1768
- func.params.unshift({ type: "FunctionParameter", name: "self", ...spanFrom(target, target) });
1780
+ func.params.unshift({ type: "FunctionParameter", name: "self", implicit: true, ...spanFrom(target, target) });
1769
1781
  func.isMethod = true;
1770
1782
  }
1771
1783
  return {
@@ -1959,47 +1971,7 @@ var Parser = class {
1959
1971
  const isStatic = modifiers.has("static");
1960
1972
  if (this.checkKeyword("function")) {
1961
1973
  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
- }
1974
+ return this.parseClassMethod(start, modifiers);
2003
1975
  }
2004
1976
  if (!isStatic && this.checkIdentifierValue("constructor") && this.punctuatorAt(1, "(")) {
2005
1977
  this.advance();
@@ -2009,6 +1981,9 @@ var Parser = class {
2009
1981
  bindThis(func, start);
2010
1982
  return { type: "ClassConstructor", func, ...spanFrom(start, this.previous()) };
2011
1983
  }
1984
+ if (this.startsMethodShorthand(0)) {
1985
+ return this.parseClassMethod(start, modifiers);
1986
+ }
2012
1987
  if ((this.checkIdentifierValue("get") || this.checkIdentifierValue("set")) && this.peek(1).type === "Identifier" && this.punctuatorAt(2, "(")) {
2013
1988
  const kind = this.advance().value;
2014
1989
  const memberName = this.parseIdentifier();
@@ -2038,25 +2013,90 @@ var Parser = class {
2038
2013
  return { type: "ClassField", name: memberName, isStatic, typeAnnotation, init, ...spanFrom(start, this.previous()) };
2039
2014
  }
2040
2015
  if (this.recover) {
2041
- this.softError("Expected a class member: a field, 'function', 'constructor', 'get' or 'set'");
2016
+ this.softError("Expected a class member: a field, a method, 'constructor', 'get' or 'set'");
2042
2017
  this.advance();
2043
2018
  return void 0;
2044
2019
  }
2045
- this.error("Expected a class member: a field, 'function', 'constructor', 'get' or 'set'");
2020
+ this.error("Expected a class member: a field, a method, 'constructor', 'get' or 'set'");
2021
+ }
2022
+ /** A name at `offset` opening a method without `function`: `name(` or
2023
+ * `name<T>(`. A field's name is followed by `:` or `=` instead. */
2024
+ startsMethodShorthand(offset) {
2025
+ if (this.peek(offset).type !== "Identifier") return false;
2026
+ const next = this.peek(offset + 1);
2027
+ return (next.type === "Punctuator" || next.type === "Operator") && (next.value === "(" || next.value === "<");
2028
+ }
2029
+ /** A method, from its name on: `function` (if written) and the modifiers
2030
+ * are behind. */
2031
+ parseClassMethod(start, modifiers) {
2032
+ const isStatic = modifiers.has("static");
2033
+ const memberName = this.parseIdentifier();
2034
+ if (modifiers.has("abstract")) {
2035
+ const head = this.parseFunctionHead();
2036
+ const func = {
2037
+ type: "FunctionBody",
2038
+ generics: head.generics,
2039
+ params: head.params,
2040
+ hasVarargs: head.hasVarargs,
2041
+ returnType: head.returnType,
2042
+ predicate: head.predicate,
2043
+ body: { type: "Block", statements: [], ...spanFrom(head.start, this.previous()) },
2044
+ ...spanFrom(head.start, this.previous())
2045
+ };
2046
+ if (!isStatic) bindThis(func, start);
2047
+ if (!head.returnType) this.problem("An abstract method has no body to infer from: write its return type");
2048
+ if (this.checkPunctuator("{")) this.problem("An abstract method has no body");
2049
+ return { type: "ClassMethod", name: memberName, isStatic, func, isAbstract: true, ...spanFrom(start, this.previous()) };
2050
+ }
2051
+ const signatures = [];
2052
+ let written = memberName;
2053
+ while (true) {
2054
+ const head = this.parseFunctionHead();
2055
+ const skip = this.classOverloadContinuation(memberName.name, isStatic);
2056
+ if (skip !== void 0) {
2057
+ if (!isStatic) bindThisParam(head.params, start);
2058
+ signatures.push({ ...this.headToSignature(head), name: written });
2059
+ for (let i = 0; i < skip; i++) this.advance();
2060
+ written = this.parseIdentifier();
2061
+ continue;
2062
+ }
2063
+ const func = this.headToBody(head, start);
2064
+ if (!isStatic) bindThis(func, start);
2065
+ return {
2066
+ type: "ClassMethod",
2067
+ name: memberName,
2068
+ isStatic,
2069
+ func,
2070
+ signatures: signatures.length ? signatures : void 0,
2071
+ ...spanFrom(start, this.previous())
2072
+ };
2073
+ }
2046
2074
  }
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
2075
  /** 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;
2076
+ * the same member follow? Then the head was an overload signature, and
2077
+ * this is how many tokens stand before the next one's name: a `;` that
2078
+ * ends the head, as TypeScript writes it, the modifiers, and `function`
2079
+ * when it is written. `undefined` when no overload follows. Each head may
2080
+ * be written either way, with `function` or without. */
2081
+ classOverloadContinuation(name, isStatic) {
2082
+ let offset = 0;
2083
+ if (this.punctuatorAt(0, ";")) offset++;
2084
+ let sawStatic = false;
2085
+ while (true) {
2086
+ const t = this.peek(offset);
2087
+ const word = t.type === "Identifier" ? t.value : void 0;
2088
+ if (word === void 0 || !CLASS_MODIFIERS.has(word) || this.startsMethodShorthand(offset)) break;
2089
+ if (word === "static") sawStatic = true;
2090
+ offset++;
2091
+ }
2092
+ if (sawStatic !== isStatic) return void 0;
2056
2093
  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;
2094
+ const hasKeyword = keyword.type === "Keyword" && keyword.value === "function";
2095
+ if (hasKeyword) offset++;
2096
+ const named = this.peek(offset);
2097
+ if (!(named.type === "Identifier" && named.value === name)) return void 0;
2098
+ if (!hasKeyword && !this.startsMethodShorthand(offset)) return void 0;
2099
+ return offset;
2060
2100
  }
2061
2101
  operatorAt(ahead, value) {
2062
2102
  const token = this.peek(ahead);
@@ -2210,6 +2250,7 @@ var Parser = class {
2210
2250
  * `:` Identifier followed by one of Lua's call forms. */
2211
2251
  startsMethodCall(offset = 0) {
2212
2252
  if (this.peek(offset + 1).type !== "Identifier") return false;
2253
+ if (this.startsOwnLine(this.peek(offset), this.peek(offset + 1))) return false;
2213
2254
  const after = this.peek(offset + 2);
2214
2255
  if (after.type === "Punctuator") {
2215
2256
  const v = String(after.value);
@@ -2235,6 +2276,23 @@ var Parser = class {
2235
2276
  const isString = t.type === "InterpolatedString" || t.type === "Literal" && t.kind === "string";
2236
2277
  return isString && this.punctuatorAt(1, ":") && this.startsMethodCall(1);
2237
2278
  }
2279
+ /** Is there no member name at `offset`, after the `.` or `:` before it? */
2280
+ namelessAfter(offset) {
2281
+ const name = this.peek(offset);
2282
+ if (name.type !== "Identifier") return true;
2283
+ const before = offset === 0 ? this.previous() : this.peek(offset - 1);
2284
+ return before !== void 0 && this.startsOwnLine(before, name);
2285
+ }
2286
+ /** Recovery only. `obj.` may go on to its name on a later line, as in
2287
+ * JavaScript — but while `obj.` is still being typed, the line below is
2288
+ * whatever statement was already there, and reading it as the name turns
2289
+ * one unfinished line into errors about the next. So, as a missing `}`
2290
+ * is placed, the indentation says which: a line that carries the access
2291
+ * on is indented deeper than the line it carries on from. */
2292
+ startsOwnLine(before, name) {
2293
+ if (!this.recover || name.line.start <= before.line.end) return false;
2294
+ return this.indentOf(name) <= this.indentOf(before);
2295
+ }
2238
2296
  /** Does the next token start right where the current one ends? */
2239
2297
  touchesNext() {
2240
2298
  const current = this.current();
@@ -2276,6 +2334,7 @@ var Parser = class {
2276
2334
  const t = this.current();
2277
2335
  if (t.type === "Keyword" && t.value === "not") return "not";
2278
2336
  if (t.type === "Operator" && (t.value === "-" || t.value === "#")) return t.value;
2337
+ if (t.type === "Identifier" && t.value === "typeof") return "typeof";
2279
2338
  return null;
2280
2339
  }
2281
2340
  /** Runs `fn` inside a bracket, where a `:` can only ever be a method
@@ -2375,6 +2434,10 @@ var Parser = class {
2375
2434
  const op = this.isUnaryOperator();
2376
2435
  if (op) {
2377
2436
  const opTok = this.advance();
2437
+ const next = this.current();
2438
+ if (op === "typeof" && this.checkPunctuator("(") && next.line.start === opTok.line.end && next.column.start === opTok.column.end) {
2439
+ this.problemAt(next, NO_TYPEOF_CALL);
2440
+ }
2378
2441
  const argument = this.parseExpression(UNARY_PRECEDENCE);
2379
2442
  return {
2380
2443
  type: "UnaryExpression",
@@ -2544,6 +2607,13 @@ var Parser = class {
2544
2607
  if (this.checkPunctuator("?") && this.touchesNext()) {
2545
2608
  const next = this.peek(1);
2546
2609
  const punct = next.type === "Punctuator" ? String(next.value) : void 0;
2610
+ if (punct === "." && this.recover && this.namelessAfter(2) && !this.punctuatorAt(2, "[") && !this.punctuatorAt(2, "(")) {
2611
+ this.advance();
2612
+ this.advance();
2613
+ this.problemAt(this.previous(), NO_MEMBER_NAME);
2614
+ base = { type: "ErrorExpression", expression: base, ...spanFrom(base, this.previous()) };
2615
+ break;
2616
+ }
2547
2617
  if (punct === "." && this.peek(2).type === "Identifier") {
2548
2618
  this.advance();
2549
2619
  this.advance();
@@ -2598,9 +2668,9 @@ var Parser = class {
2598
2668
  }
2599
2669
  }
2600
2670
  if (this.matchPunctuator(".")) {
2601
- if (this.recover && !this.checkType("Identifier")) {
2602
- this.softError("Expected identifier");
2603
- base = { type: "ErrorExpression", ...spanFrom(base, this.previous()) };
2671
+ if (this.namelessAfter(0)) {
2672
+ this.problemAt(this.previous(), NO_MEMBER_NAME);
2673
+ base = { type: "ErrorExpression", expression: base, ...spanFrom(base, this.previous()) };
2604
2674
  break;
2605
2675
  }
2606
2676
  const prop = this.parseIdentifier();
@@ -2613,6 +2683,12 @@ var Parser = class {
2613
2683
  base = { type: "IndexExpression", object: base, index, ...spanFrom(base, this.previous()) };
2614
2684
  continue;
2615
2685
  }
2686
+ if (this.recover && !this.ternaryColons && this.checkPunctuator(":") && this.peek(1).line.start > this.current().line.end && !this.startsMethodCall()) {
2687
+ this.advance();
2688
+ this.problemAt(this.previous(), "Expected a name after ':'");
2689
+ base = { type: "ErrorExpression", expression: base, ...spanFrom(base, this.previous()) };
2690
+ break;
2691
+ }
2616
2692
  if (this.checkPunctuator(":") && this.startsMethodCall() && this.takeMethodColon()) {
2617
2693
  this.advance();
2618
2694
  const method = this.parseIdentifier();
@@ -3456,6 +3532,7 @@ var Parser = class {
3456
3532
  return { type: "TupleTypeNode", elements, ...rest ? { rest } : {}, ...spanFrom(t, this.previous()) };
3457
3533
  }
3458
3534
  if (t.type === "Identifier" && t.value === "typeof" && this.peek(1).type === "Punctuator" && this.peek(1).value === "(") {
3535
+ this.problemAt(this.peek(1), NO_TYPEOF_CALL);
3459
3536
  this.advance();
3460
3537
  this.advance();
3461
3538
  const expression = this.parseExpression();
@@ -3836,7 +3913,7 @@ var BinaryOperators = [
3836
3913
  "and",
3837
3914
  "or"
3838
3915
  ];
3839
- var UnaryOperators = ["-", "not", "#"];
3916
+ var UnaryOperators = ["-", "not", "#", "typeof"];
3840
3917
 
3841
3918
  // src/ast/analyzeScopes.ts
3842
3919
  function getBinding(analysis, id) {
@@ -4463,7 +4540,9 @@ var Analyzer = class {
4463
4540
  case "BooleanLiteral":
4464
4541
  case "NumberLiteral":
4465
4542
  case "StringLiteral":
4543
+ return;
4466
4544
  case "ErrorExpression":
4545
+ if (expr.expression) this.visitExpression(expr.expression, scope);
4467
4546
  return;
4468
4547
  case "InterpolatedStringExpression":
4469
4548
  for (const part of expr.parts) {
@@ -4676,11 +4755,34 @@ declare metatable<T> T[]: {
4676
4755
  -- \`nil\` when it is missing; an indexer's keys follow the named ones. A
4677
4756
  -- member of the table's own with one of these names is what a call reaches,
4678
4757
  -- and a class instance, which has a metatable of its own, has none of them.
4758
+ --
4759
+ -- The rest are JavaScript's \`Object\` functions, written as methods of the
4760
+ -- table they take first:
4761
+ --
4762
+ -- point:hasOwn("x") -- Object.hasOwn(point, "x")
4763
+ -- point:assign({ z: 3 }) -- Object.assign(point, { z: 3 })
4764
+ -- point:freeze() -- Object.freeze(point)
4765
+ --
4766
+ -- \`hasOwn\` asks the table itself, not what its metatable's \`__index\`
4767
+ -- lends it. \`assign\` copies each source's members onto the table, later ones
4768
+ -- over earlier ones, skipping a \`nil\` source, and answers the table. \`freeze\`
4769
+ -- is Luau's \`table.freeze\`; under Lua 5.1, which cannot freeze a table, the
4770
+ -- table is answered as it is and \`isFrozen\` is false.
4679
4771
  declare metatable<T extends {}> T: {
4680
4772
  __index: {
4681
4773
  keys: (self: T) => ObjectKeys<T>,
4682
4774
  values: (self: T) => ObjectValues<T>,
4683
4775
  entries: (self: T) => ObjectEntries<T>,
4776
+
4777
+ hasOwn: (self: T, key: unknown) => boolean,
4778
+ hasOwnProperty: (self: T, key: unknown) => boolean,
4779
+ assign: (<U>(self: T, source: U) => T & U)
4780
+ & (<U, V>(self: T, a: U, b: V) => T & U & V)
4781
+ & (<U, V, W>(self: T, a: U, b: V, c: W) => T & U & V & W)
4782
+ -- Generic like the rest: a plain signature would be tried first.
4783
+ & (<S extends {}>(self: T, ...sources: S[]) => T),
4784
+ freeze: (self: T) => Readonly<T>,
4785
+ isFrozen: (self: T) => boolean,
4684
4786
  },
4685
4787
  }
4686
4788
  `;
@@ -5746,6 +5848,13 @@ function mergeEnv(a, b, base) {
5746
5848
  }
5747
5849
  return out;
5748
5850
  }
5851
+ function joinNarrowings(into, a, aFrom, b, bFrom) {
5852
+ for (const [key, left] of a) {
5853
+ const right = b.get(key);
5854
+ if (right === void 0 || left === aFrom.get(key) || right === bFrom.get(key)) continue;
5855
+ into.set(key, union([left, right]));
5856
+ }
5857
+ }
5749
5858
  function mergeIndexer(a, b) {
5750
5859
  return a ? { key: union([a.key, b.key]), value: union([a.value, b.value]) } : b;
5751
5860
  }
@@ -6025,6 +6134,10 @@ function unwrapParens(e) {
6025
6134
  while (e.type === "ParenthesizedExpression") e = e.expression;
6026
6135
  return e;
6027
6136
  }
6137
+ function memberLabel(label, key) {
6138
+ if (label === void 0 || key === void 0) return void 0;
6139
+ return typeof key === "number" ? `${label}[${key}]` : `${label}.${key}`;
6140
+ }
6028
6141
  function expressionLabel(e, depth = 0) {
6029
6142
  if (depth > 6) return void 0;
6030
6143
  const args = (list) => {
@@ -6096,6 +6209,7 @@ var TypeAnalyzer = class {
6096
6209
  metatables = [];
6097
6210
  metatableCache = /* @__PURE__ */ new WeakMap();
6098
6211
  methodSources = /* @__PURE__ */ new Map();
6212
+ typeofTables = /* @__PURE__ */ new Map();
6099
6213
  /** Public: each alias resolved once (generic aliases keep their params as
6100
6214
  * `typeParam` nodes in the body). */
6101
6215
  aliases = new LazyMap();
@@ -6174,7 +6288,7 @@ var TypeAnalyzer = class {
6174
6288
  const given = this.options.globalTypes?.[name];
6175
6289
  if (given) this.bindingType.set(id, given);
6176
6290
  else if (this.libGlobalTypes.has(name)) this.bindingType.defer(id, () => this.libGlobalTypes.get(name) ?? anyType);
6177
- else if (LANGUAGE_GLOBALS.includes(name)) this.bindingType.set(id, arrayOf(unknownType));
6291
+ else if (LANGUAGE_GLOBALS.includes(name)) this.bindingType.set(id, arrayOf(declaredUnknownType));
6178
6292
  else this.bindingType.set(id, anyType);
6179
6293
  }
6180
6294
  setAliasExpander((t) => this.expand(t));
@@ -6197,6 +6311,7 @@ var TypeAnalyzer = class {
6197
6311
  aliases: this.resolveDeferredAliases(),
6198
6312
  loops: this.loops,
6199
6313
  methodSources: this.methodSources,
6314
+ typeofTables: this.typeofTables,
6200
6315
  metatableMembers: (type) => this.metatableMembers(type),
6201
6316
  diagnostics: this.diagnostics
6202
6317
  };
@@ -7047,7 +7162,12 @@ var TypeAnalyzer = class {
7047
7162
  this.aliases.defer(name, () => this.classType(cls));
7048
7163
  continue;
7049
7164
  }
7050
- if (this.dependsOnTypeQuery(name)) continue;
7165
+ if (this.dependsOnTypeQuery(name)) {
7166
+ if (def.runtimeClass) {
7167
+ this.aliases.defer(name, () => this.withTypeParams(def.params, () => this.resolveDef(def)));
7168
+ }
7169
+ continue;
7170
+ }
7051
7171
  this.withTypeParams(def.params, () => {
7052
7172
  this.aliases.set(name, this.resolveDef(def));
7053
7173
  });
@@ -7055,14 +7175,16 @@ var TypeAnalyzer = class {
7055
7175
  }
7056
7176
  typeQueryDependents = /* @__PURE__ */ new Map();
7057
7177
  /** Does alias `name` contain a `typeof`, itself or through an alias it
7058
- * names? */
7178
+ * names? Its parameters count: `class C<K extends Config["key"]>`
7179
+ * resolves the bound along with the class. */
7059
7180
  dependsOnTypeQuery(name, visiting = /* @__PURE__ */ new Set()) {
7060
7181
  const known = this.typeQueryDependents.get(name);
7061
7182
  if (known !== void 0) return known;
7062
7183
  const def = this.aliasDefs.get(name);
7063
7184
  if (!def || def.class || visiting.has(name)) return false;
7064
7185
  visiting.add(name);
7065
- const result = containsTypeQuery(def.node) || referencedTypeNames(def.node).some((other) => other !== name && this.dependsOnTypeQuery(other, visiting));
7186
+ const nodes = [def.node, ...def.params.flatMap((p) => [p.constraint, p.default])];
7187
+ const result = nodes.some(containsTypeQuery) || referencedTypeNames(nodes).some((other) => other !== name && this.dependsOnTypeQuery(other, visiting));
7066
7188
  visiting.delete(name);
7067
7189
  this.typeQueryDependents.set(name, result);
7068
7190
  return result;
@@ -7787,7 +7909,7 @@ var TypeAnalyzer = class {
7787
7909
  }
7788
7910
  }
7789
7911
  const mode = this.initIsAsConst(source) ? "asconst" : !isFreshLiteralExpr(source) ? "keep" : stmt.kind === "const" ? "const" : "widen";
7790
- this.bindPattern(target, inferred, env, mode);
7912
+ this.bindPattern(target, inferred, env, mode, source && expressionLabel(source));
7791
7913
  if (stmt.kind === "const") {
7792
7914
  this.correlateDestructuring(target, inferred, env);
7793
7915
  this.correlateIndexed(target, source, env);
@@ -7880,9 +8002,10 @@ var TypeAnalyzer = class {
7880
8002
  } else if (target.type === "MemberExpression" || target.type === "IndexExpression") {
7881
8003
  this.infer(target, env);
7882
8004
  this.checkReadonlyAssign(target, env);
8005
+ this.checkMemberAssign(stmt, target, vt, env);
7883
8006
  this.assignToRef(target, isFreshLiteralExpr(source) ? widen(vt) : vt, env);
7884
8007
  } else if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
7885
- this.reassignPattern(target, vt, env);
8008
+ this.reassignPattern(target, vt, env, expressionLabel(source));
7886
8009
  }
7887
8010
  return;
7888
8011
  }
@@ -9248,7 +9371,8 @@ var TypeAnalyzer = class {
9248
9371
  }
9249
9372
  /** Like `bindPattern`, but the leaves are *existing* bindings (a `{a} = t`
9250
9373
  * assignment). Updates their flow type; keeps a declared annotation. */
9251
- reassignPattern(target, valueType, env) {
9374
+ reassignPattern(target, valueType, env, label) {
9375
+ if (target.type === "ObjectPattern" || target.type === "ArrayPattern") valueType = this.destructured(target, valueType, label);
9252
9376
  switch (target.type) {
9253
9377
  case "IdentifierPattern": {
9254
9378
  const id = this.scopes.bindingOf.get(target);
@@ -9273,21 +9397,43 @@ var TypeAnalyzer = class {
9273
9397
  for (const p of target.properties) {
9274
9398
  const key = !p.computed && p.key.type === "Identifier" ? p.key.name : !p.computed && p.key.type === "StringLiteral" ? p.key.value : void 0;
9275
9399
  const pt = key !== void 0 ? this.propertyType(valueType, key) : unknownType;
9276
- this.reassignPattern(p.value, this.withDefault(pt, p.default, env), env);
9400
+ this.reassignPattern(p.value, this.withDefault(pt, p.default, env), env, memberLabel(label, key));
9277
9401
  }
9278
9402
  if (target.rest) this.reassignPattern(target.rest, this.withoutKeys(valueType, target.properties), env);
9279
9403
  return;
9280
9404
  }
9281
9405
  case "ArrayPattern": {
9282
9406
  target.elements.forEach((el, i) => {
9283
- if (el) this.reassignPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env);
9407
+ if (el) this.reassignPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env, memberLabel(label, i + 1));
9284
9408
  });
9285
9409
  if (target.rest) this.reassignPattern(target.rest, this.restAfter(valueType, target.elements.length), env);
9286
9410
  return;
9287
9411
  }
9288
9412
  }
9289
9413
  }
9290
- bindPattern(target, valueType, env, mode) {
9414
+ destructuredReported = /* @__PURE__ */ new WeakSet();
9415
+ /** What a pattern reads from. `const { a } = v` is `v.a`, and fails where
9416
+ * that would: on a `v` that may be nil, and on one that is `unknown`.
9417
+ * `label` is the value as it would be written — `args[1]` for the inner
9418
+ * pattern of `const [{ a }] = args`. */
9419
+ destructured(pattern, raw, label) {
9420
+ let type = raw;
9421
+ const report = (message) => {
9422
+ if (!this.emitDiagnostics || this.destructuredReported.has(pattern)) return;
9423
+ this.destructuredReported.add(pattern);
9424
+ this.diagnostics.push({ node: pattern, message: `${label === void 0 ? "Object" : `'${label}'`} ${message}` });
9425
+ };
9426
+ if (this.includesNil(type)) {
9427
+ const t = this.expand(type);
9428
+ report(t.kind === "primitive" ? "is nil" : "is possibly nil. Check it first, or give it a default");
9429
+ type = withoutNil(t);
9430
+ }
9431
+ const read = this.expand(type);
9432
+ if (read.kind === "unknown" && read.declared) report("is of type 'unknown'");
9433
+ return type;
9434
+ }
9435
+ bindPattern(target, valueType, env, mode, label) {
9436
+ if (target.type === "ObjectPattern" || target.type === "ArrayPattern") valueType = this.destructured(target, valueType, label);
9291
9437
  switch (target.type) {
9292
9438
  case "IdentifierPattern": {
9293
9439
  const id = this.bindingIdByName(target.name, target);
@@ -9308,7 +9454,7 @@ var TypeAnalyzer = class {
9308
9454
  for (const p of target.properties) {
9309
9455
  const key = !p.computed && p.key.type === "Identifier" ? p.key.name : !p.computed && p.key.type === "StringLiteral" ? p.key.value : void 0;
9310
9456
  const propType = key !== void 0 ? this.propertyType(valueType, key) : unknownType;
9311
- this.bindPattern(p.value, this.withDefault(propType, p.default, env), env, mode);
9457
+ this.bindPattern(p.value, this.withDefault(propType, p.default, env), env, mode, memberLabel(label, key));
9312
9458
  }
9313
9459
  if (target.rest) this.bindPattern(target.rest, this.withoutKeys(valueType, target.properties), env, mode);
9314
9460
  return;
@@ -9316,7 +9462,7 @@ var TypeAnalyzer = class {
9316
9462
  case "ArrayPattern": {
9317
9463
  target.elements.forEach((el, i) => {
9318
9464
  if (!el) return;
9319
- this.bindPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env, mode);
9465
+ this.bindPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env, mode, memberLabel(label, i + 1));
9320
9466
  });
9321
9467
  if (target.rest) this.bindPattern(target.rest, this.restAfter(valueType, target.elements.length), env, mode);
9322
9468
  return;
@@ -9881,6 +10027,7 @@ var TypeAnalyzer = class {
9881
10027
  }
9882
10028
  // Broken syntax is reported by the parser; nothing more to say.
9883
10029
  case "ErrorExpression":
10030
+ if (expr.expression) this.infer(expr.expression, env);
9884
10031
  return anyType;
9885
10032
  case "Identifier": {
9886
10033
  const id = this.bindingIdOf(expr);
@@ -9944,6 +10091,14 @@ var TypeAnalyzer = class {
9944
10091
  this.checkOperands(expr, "#", arg);
9945
10092
  return numberType;
9946
10093
  }
10094
+ case "typeof": {
10095
+ const names = this.typeofNamesOf(arg);
10096
+ const tables = names && [...names].filter((name) => name === "array" || name === "object");
10097
+ if (tables?.length === 1) this.typeofTables.set(expr, tables[0]);
10098
+ else this.typeofTables.delete(expr);
10099
+ const answers = names?.size ? [...names] : [...this.typeofTypes().keys()];
10100
+ return union(answers.map((name) => literal(name)));
10101
+ }
9947
10102
  }
9948
10103
  return arg;
9949
10104
  }
@@ -9986,7 +10141,9 @@ var TypeAnalyzer = class {
9986
10141
  return booleanType;
9987
10142
  case "==":
9988
10143
  case "~=":
9989
- this.checkOverlap(expr, op, l, r);
10144
+ if (!this.checkTypeofName(expr, expr.left, expr.right) && !this.checkTypeofName(expr, expr.right, expr.left)) {
10145
+ this.checkOverlap(expr, op, l, r);
10146
+ }
9990
10147
  return booleanType;
9991
10148
  case "+":
9992
10149
  case "-":
@@ -10187,6 +10344,7 @@ var TypeAnalyzer = class {
10187
10344
  const fromMetatable = this.receiverMetatableMember(objType, expr.method.name);
10188
10345
  if (fromMetatable) this.methodSources.set(expr, fromMetatable.source);
10189
10346
  const method = fromMetatable?.type ?? this.propertyType(objType, expr.method.name);
10347
+ this.typeOf.set(expr.method, method);
10190
10348
  const united = this.unionSignatures(method);
10191
10349
  const fns = united ?? this.overloadsOf(method);
10192
10350
  const explicit = this.explicitTypeArguments(expr, fns);
@@ -10327,6 +10485,43 @@ var TypeAnalyzer = class {
10327
10485
  message: `Cannot assign to '${name}' because it is a read-only property`
10328
10486
  });
10329
10487
  }
10488
+ /** `t.x = v` against what `x` was declared as. Only a property the
10489
+ * table's type names is checked: a key it does not name is the Lua
10490
+ * table growing, which `checkMissingMember` is not about. */
10491
+ checkMemberAssign(stmt, target, value, env) {
10492
+ if (!this.emitDiagnostics) return;
10493
+ let name;
10494
+ if (target.type === "MemberExpression") {
10495
+ name = target.property.name;
10496
+ } else {
10497
+ const index = this.expand(this.infer(target.index, env));
10498
+ if (index.kind !== "literal" || typeof index.value !== "string") return;
10499
+ name = index.value;
10500
+ }
10501
+ if (target.object.type === "Identifier") {
10502
+ const id = this.bindingIdOf(target.object);
10503
+ if (id !== void 0 && this.scopes.bindings.get(id)?.declaredBy === "namespace") return;
10504
+ }
10505
+ const declared = this.declaredProperty(this.expand(this.infer(target.object, env)), name);
10506
+ if (!declared || declared.kind === "any" || declared.kind === "unknown") return;
10507
+ if (this.fitsAnnotation(stmt.value, declared, value, env)) return;
10508
+ this.diagnostics.push({
10509
+ node: stmt,
10510
+ message: `Type '${briefType(value)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(value, declared)
10511
+ });
10512
+ }
10513
+ /** The type a property was declared with, where the type names it. */
10514
+ declaredProperty(t, name) {
10515
+ if (t.kind === "object") {
10516
+ const p = t.properties.get(name);
10517
+ return p && (p.optional ? optional(p.type) : p.type);
10518
+ }
10519
+ if (t.kind === "intersection") {
10520
+ const parts = t.types.flatMap((m) => this.declaredProperty(this.expand(m), name) ?? []);
10521
+ return parts.length ? intersection(parts) : void 0;
10522
+ }
10523
+ return void 0;
10524
+ }
10330
10525
  isReadonlyProperty(t, name) {
10331
10526
  if (t.kind === "object") return t.properties.get(name)?.readonly === true;
10332
10527
  if (t.kind === "intersection" || t.kind === "union") {
@@ -10722,13 +10917,23 @@ var TypeAnalyzer = class {
10722
10917
  if (cond.type === "BinaryExpression") {
10723
10918
  const { operator: op, left, right } = cond;
10724
10919
  if (op === "and") {
10725
- this.applyNarrowing(left, env, t, forkEnv(env));
10726
- this.applyNarrowing(right, t, t, forkEnv(t));
10920
+ const before = forkEnv(env);
10921
+ const leftFailed = forkEnv(env);
10922
+ this.applyNarrowing(left, env, t, leftFailed);
10923
+ const between = forkEnv(t);
10924
+ const rightFailed = forkEnv(t);
10925
+ this.applyNarrowing(right, t, t, rightFailed);
10926
+ joinNarrowings(f, leftFailed, before, rightFailed, between);
10727
10927
  return;
10728
10928
  }
10729
10929
  if (op === "or") {
10730
- this.applyNarrowing(left, env, forkEnv(env), f);
10731
- this.applyNarrowing(right, f, forkEnv(f), f);
10930
+ const before = forkEnv(env);
10931
+ const leftHeld = forkEnv(env);
10932
+ this.applyNarrowing(left, env, leftHeld, f);
10933
+ const between = forkEnv(f);
10934
+ const rightHeld = forkEnv(f);
10935
+ this.applyNarrowing(right, f, rightHeld, f);
10936
+ joinNarrowings(t, leftHeld, before, rightHeld, between);
10732
10937
  return;
10733
10938
  }
10734
10939
  if (op === "==" || op === "~=") {
@@ -10747,11 +10952,13 @@ var TypeAnalyzer = class {
10747
10952
  this.narrowOptionalLinks(cond, env, t);
10748
10953
  }
10749
10954
  /** `a == b` / `a ~= b`. Handles, in order: a declaration-driven
10750
- * `typeof(x) == "..."` test, a literal/`nil` comparison against a
10955
+ * `typeof x == "..."` test, a literal/`nil` comparison against a
10751
10956
  * reference, and a reference-to-reference comparison. */
10752
10957
  narrowByComparison(left, right, eq, env, t, f) {
10753
10958
  const yes = eq ? t : f;
10754
10959
  const no = eq ? f : t;
10960
+ if (this.narrowByTypeof(left, right, yes, no, env)) return;
10961
+ if (this.narrowByTypeof(right, left, yes, no, env)) return;
10755
10962
  if (this.narrowByCallResult(left, right, yes, no, env)) return;
10756
10963
  if (this.narrowByCallResult(right, left, yes, no, env)) return;
10757
10964
  const litOf = (e) => {
@@ -10778,18 +10985,149 @@ var TypeAnalyzer = class {
10778
10985
  this.narrowRef(right, env, yes, no, (cur) => ({ yes: narrowTo(cur, lt), no: cur }));
10779
10986
  }
10780
10987
  }
10781
- /** Declaration-driven `typeof(x) == "number"`.
10782
- *
10783
- * `typeof` is not special-cased: it is declared in the definitions as an
10784
- * overload set whose members return *string-literal* types
10785
- * (`(value: number) -> "number"`, ...). So for `f(x) == "lit"` we keep the
10786
- * parameter types of the overloads that can return `"lit"`, and that union
10787
- * is what `x` narrows to. Any user function declared the same way gets the
10788
- * same treatment for free.
10988
+ // --------------------------------------------------------
10989
+ // typeof
10990
+ // --------------------------------------------------------
10991
+ //
10992
+ // `typeof v` answers what Luau's `typeof` does, except for a table: that
10993
+ // is an `"array"` or an `"object"`. The names of a runtime's own values —
10994
+ // Roblox's `"Instance"`, `"Vector3"` — are a library's to give: it
10995
+ // declares `type TypeofNames = { Vector3: Vector3, Instance: Instance }`,
10996
+ // each property the name `typeof` answers for a value of that type. A
10997
+ // value several of them fit is the first one's.
10998
+ typeofTypesCache;
10999
+ /** Every name `typeof` answers, and what a value it answered that for
11000
+ * is — `unknown` where nothing more is known. */
11001
+ typeofTypes() {
11002
+ if (this.typeofTypesCache) return this.typeofTypesCache;
11003
+ const types = /* @__PURE__ */ new Map([
11004
+ ["nil", nilType],
11005
+ ["boolean", booleanType],
11006
+ ["number", numberType],
11007
+ ["string", stringType],
11008
+ ["function", fn([], declaredUnknownType, declaredUnknownType)],
11009
+ ["array", arrayOf(declaredUnknownType)],
11010
+ ["object", objectType([], { key: declaredUnknownType, value: declaredUnknownType })],
11011
+ ["thread", threadType],
11012
+ ["buffer", bufferType],
11013
+ ["userdata", unknownType]
11014
+ ]);
11015
+ for (const [name, type] of this.typeofLibraryNames()) if (!types.has(name)) types.set(name, type);
11016
+ return this.typeofTypesCache = types;
11017
+ }
11018
+ typeofLibraryCache;
11019
+ typeofLibraryNames() {
11020
+ if (this.typeofLibraryCache) return this.typeofLibraryCache;
11021
+ const declared = this.aliases.get("TypeofNames");
11022
+ const names = declared && this.expand(declared);
11023
+ return this.typeofLibraryCache = names?.kind === "object" ? [...names.properties].map(([name, property]) => [name, property.type]) : [];
11024
+ }
11025
+ /** The names `typeof` can answer for a value of type `raw`, or
11026
+ * `undefined` where the type does not say. */
11027
+ typeofNamesOf(raw, depth = 0) {
11028
+ if (depth > 8) return void 0;
11029
+ const t = this.expand(raw);
11030
+ switch (t.kind) {
11031
+ case "never":
11032
+ return /* @__PURE__ */ new Set();
11033
+ case "primitive":
11034
+ return /* @__PURE__ */ new Set([t.name]);
11035
+ case "literal":
11036
+ return /* @__PURE__ */ new Set([t.base]);
11037
+ case "templateLiteral":
11038
+ return /* @__PURE__ */ new Set(["string"]);
11039
+ case "array":
11040
+ case "tuple":
11041
+ return /* @__PURE__ */ new Set(["array"]);
11042
+ case "function":
11043
+ return /* @__PURE__ */ new Set(["function"]);
11044
+ case "object": {
11045
+ const named = t.class && this.typeofLibraryNames().find(([, type]) => isAssignable(t, type));
11046
+ return /* @__PURE__ */ new Set([named ? named[0] : "object"]);
11047
+ }
11048
+ case "union": {
11049
+ const all = /* @__PURE__ */ new Set();
11050
+ for (const member of t.types) {
11051
+ const names = this.typeofNamesOf(member, depth + 1);
11052
+ if (!names) return void 0;
11053
+ for (const name of names) all.add(name);
11054
+ }
11055
+ return all;
11056
+ }
11057
+ case "intersection": {
11058
+ const known = t.types.map((part) => this.typeofNamesOf(part, depth + 1)).filter((names) => names !== void 0);
11059
+ if (!known.length) return void 0;
11060
+ const common = [...known[0]].filter((name) => known.every((names) => names.has(name)));
11061
+ return new Set(common.length ? common : known[0]);
11062
+ }
11063
+ case "typeParam":
11064
+ return t.constraint ? this.typeofNamesOf(t.constraint, depth + 1) : void 0;
11065
+ case "difference":
11066
+ return this.typeofNamesOf(t.base, depth + 1);
11067
+ default:
11068
+ return void 0;
11069
+ }
11070
+ }
11071
+ /** `typeof v == "table"`: a name `typeof` never answers. Returns true if
11072
+ * the comparison needs no further checking. */
11073
+ checkTypeofName(node, typeofSide, nameSide) {
11074
+ const test = unwrapParens(typeofSide);
11075
+ if (test.type !== "UnaryExpression" || test.operator !== "typeof") return false;
11076
+ const name = this.asLiteral(unwrapParens(nameSide));
11077
+ if (typeof name !== "string") return false;
11078
+ if (name === "nil") return true;
11079
+ if (this.typeofTypes().has(name)) return false;
11080
+ if (this.emitDiagnostics) {
11081
+ this.diagnostics.push({
11082
+ node,
11083
+ message: name === "table" ? `'typeof' never answers "table": a table is an "array" or an "object"` : `'typeof' never answers "${name}"`
11084
+ });
11085
+ }
11086
+ return true;
11087
+ }
11088
+ /** `typeof x == "number"`: `x` is what answers that name where the test
11089
+ * holds, and what does not where it fails. Returns true if `testSide` is
11090
+ * a `typeof` compared with a name. */
11091
+ narrowByTypeof(testSide, nameSide, yes, no, env) {
11092
+ const test = unwrapParens(testSide);
11093
+ if (test.type !== "UnaryExpression" || test.operator !== "typeof") return false;
11094
+ const name = this.asLiteral(unwrapParens(nameSide));
11095
+ if (typeof name !== "string") return false;
11096
+ const answered = this.typeofTypes().get(name);
11097
+ if (!answered) return true;
11098
+ const opaque = answered.kind !== "unknown";
11099
+ const subtracts = opaque && name !== "array" && name !== "object";
11100
+ const members = (cur) => {
11101
+ const t = this.expand(cur);
11102
+ return t.kind === "union" ? t.types.flatMap(members) : [t];
11103
+ };
11104
+ if (this.refKeyOf(test.argument) !== void 0) {
11105
+ this.narrowRef(test.argument, env, yes, no, (cur) => {
11106
+ const kept = [];
11107
+ const dropped = [];
11108
+ for (const member of members(cur)) {
11109
+ const names = this.typeofNamesOf(member);
11110
+ if (!names) {
11111
+ kept.push(opaque ? narrowTo(member, answered) : member);
11112
+ dropped.push(subtracts ? narrowExclude(member, answered) : member);
11113
+ continue;
11114
+ }
11115
+ if (names.has(name)) kept.push(member);
11116
+ if (!(names.size === 1 && names.has(name))) dropped.push(member);
11117
+ }
11118
+ return { yes: union(kept), no: union(dropped) };
11119
+ });
11120
+ }
11121
+ this.narrowOptionalLinks(test.argument, env, name === "nil" ? no : yes);
11122
+ return true;
11123
+ }
11124
+ /** Declaration-driven `kind(x) == "number"`.
10789
11125
  *
10790
- * The analyzer knows nothing about the names `type` and `typeof`: with no
10791
- * definitions loaded there are no literal-returning overloads and this
10792
- * narrows nothing. Returns true if it handled the comparison. */
11126
+ * A function declared as an overload set whose members return
11127
+ * *string-literal* types (`(value: number) => "number"`, ...) says by its
11128
+ * answer what it was given. So for `f(x) == "lit"` we keep the parameter
11129
+ * types of the overloads that can return `"lit"`, and that union is what
11130
+ * `x` narrows to. Returns true if it handled the comparison. */
10793
11131
  narrowByCallResult(callSide, litSide, yes, no, env) {
10794
11132
  if (callSide.type !== "CallExpression" || callSide.arguments.length !== 1) return false;
10795
11133
  const name = this.asLiteral(litSide);
@@ -11866,7 +12204,53 @@ function aliasTargets(config, specifier) {
11866
12204
 
11867
12205
  // src/project/sourcemap.ts
11868
12206
  var import_node_path4 = require("path");
12207
+
12208
+ // src/luau.ts
12209
+ var LUAU_KEYWORDS = /* @__PURE__ */ new Set([
12210
+ "and",
12211
+ "break",
12212
+ "do",
12213
+ "else",
12214
+ "elseif",
12215
+ "end",
12216
+ "false",
12217
+ "for",
12218
+ "function",
12219
+ "if",
12220
+ "in",
12221
+ "local",
12222
+ "nil",
12223
+ "not",
12224
+ "or",
12225
+ "repeat",
12226
+ "return",
12227
+ "then",
12228
+ "true",
12229
+ "until",
12230
+ "while"
12231
+ ]);
11869
12232
  var IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
12233
+ function isIdentifier(name) {
12234
+ return IDENTIFIER.test(name);
12235
+ }
12236
+ function isLuauName(name) {
12237
+ return IDENTIFIER.test(name) && !LUAU_KEYWORDS.has(name);
12238
+ }
12239
+ var NAMED_ESCAPES = {
12240
+ "\\": "\\\\",
12241
+ '"': '\\"',
12242
+ "\n": "\\n",
12243
+ "\r": "\\r",
12244
+ " ": "\\t"
12245
+ };
12246
+ function escapeLuauString(text) {
12247
+ return text.replace(/[\\"\x00-\x1f\x7f]/g, (c) => NAMED_ESCAPES[c] ?? `\\${c.charCodeAt(0).toString().padStart(3, "0")}`);
12248
+ }
12249
+ function luauString(text) {
12250
+ return `"${escapeLuauString(text)}"`;
12251
+ }
12252
+
12253
+ // src/project/sourcemap.ts
11870
12254
  var INSTANCE_MEMBERS = /* @__PURE__ */ new Set(["Name", "ClassName", "Parent", "Archivable"]);
11871
12255
  var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".tilua", ".luau", ".lua"]);
11872
12256
  function sourceMapTypes(text, path, options) {
@@ -11893,7 +12277,7 @@ function sourceMapTypes(text, path, options) {
11893
12277
  const alias = aliasFor(segments);
11894
12278
  aliasOfNode.set(node, alias);
11895
12279
  for (const filePath of node.filePaths ?? []) aliasOfFile.set(fileKey((0, import_node_path4.resolve)(directory, filePath)), alias);
11896
- const className = IDENTIFIER.test(node.className) && options.classes.has(node.className) ? node.className : "Instance";
12280
+ const className = isIdentifier(node.className) && options.classes.has(node.className) ? node.className : "Instance";
11897
12281
  const taken = options.membersOf?.(className) ?? INSTANCE_MEMBERS;
11898
12282
  const members = [];
11899
12283
  if (parent) members.push(`Parent: ${parent}`);
@@ -11902,7 +12286,7 @@ function sourceMapTypes(text, path, options) {
11902
12286
  if (!isNode(child)) continue;
11903
12287
  const childAlias = visit(child, [...segments, child.name], alias);
11904
12288
  const service = child.name === child.className && options.classes.has(child.className);
11905
- if (!IDENTIFIER.test(child.name) || taken.has(child.name) && !service || named.has(child.name)) continue;
12289
+ if (!isIdentifier(child.name) || taken.has(child.name) && !service || named.has(child.name)) continue;
11906
12290
  named.add(child.name);
11907
12291
  members.push(`${child.name}: ${childAlias}`);
11908
12292
  }
@@ -11944,6 +12328,34 @@ function fileKey(path) {
11944
12328
  return process.platform === "win32" ? normalized.toLowerCase() : normalized;
11945
12329
  }
11946
12330
 
12331
+ // src/loweringHelpers.ts
12332
+ function withoutNil2(type) {
12333
+ if (!type) return [];
12334
+ const members = type.kind === "union" ? type.types : [type];
12335
+ return members.filter((m) => !(m.kind === "primitive" && m.name === "nil"));
12336
+ }
12337
+ function isFunctionType(type) {
12338
+ const members = withoutNil2(type);
12339
+ return members.length > 0 && members.every((m) => m.kind === "function" || m.kind === "intersection" && m.types.every(isFunctionType));
12340
+ }
12341
+ function everyObject(type, test) {
12342
+ const members = withoutNil2(type);
12343
+ return members.length > 0 && members.every((m) => m.kind === "object" && test(m));
12344
+ }
12345
+ function isInstanceOf(type, className) {
12346
+ return everyObject(type, (m) => m.class !== void 0 && m.class.ancestors.includes(className));
12347
+ }
12348
+ function hasMembers(type, names) {
12349
+ return everyObject(type, (m) => names.every((name) => m.properties.has(name)));
12350
+ }
12351
+ function returnsTuple(receiver, method) {
12352
+ return everyObject(receiver, (m) => {
12353
+ const found = m.properties.get(method)?.type;
12354
+ const signatures = !found ? [] : found.kind === "intersection" ? found.types : [found];
12355
+ return signatures.length > 0 && signatures.every((f) => f.kind === "function" && f.returns.kind === "tuple");
12356
+ });
12357
+ }
12358
+
11947
12359
  // src/index.ts
11948
12360
  var tilua = {
11949
12361
  tokenize,
@@ -11964,6 +12376,7 @@ var index_default = tilua;
11964
12376
  CONFIG_FILE_NAMES,
11965
12377
  Keywords,
11966
12378
  LANGUAGE_GLOBALS,
12379
+ LUAU_KEYWORDS,
11967
12380
  LexError,
11968
12381
  Operators,
11969
12382
  PRELUDE_SOURCE,
@@ -11984,20 +12397,27 @@ var index_default = tilua;
11984
12397
  difference,
11985
12398
  directivesOf,
11986
12399
  equalTypes,
12400
+ escapeLuauString,
11987
12401
  falsyType,
11988
12402
  findConfig,
11989
12403
  fn,
11990
12404
  formatType,
11991
12405
  getBinding,
12406
+ hasMembers,
11992
12407
  intersection,
11993
12408
  isAssignable,
11994
12409
  isClassType,
12410
+ isFunctionType,
11995
12411
  isGlobal,
12412
+ isIdentifier,
12413
+ isInstanceOf,
12414
+ isLuauName,
11996
12415
  isPossiblyFalsy,
11997
12416
  isPossiblyTruthy,
11998
12417
  isUnassignedGlobal,
11999
12418
  literal,
12000
12419
  loadConfig,
12420
+ luauString,
12001
12421
  matchInfer,
12002
12422
  moduleCandidates,
12003
12423
  moduleExports,
@@ -12020,6 +12440,7 @@ var index_default = tilua;
12020
12440
  readDirectives,
12021
12441
  resolveModulePath,
12022
12442
  resolveTypeLibraries,
12443
+ returnsTuple,
12023
12444
  setAliasExpander,
12024
12445
  setDeferredBound,
12025
12446
  sourceMapTypes,
@@ -12038,5 +12459,6 @@ var index_default = tilua;
12038
12459
  unknownType,
12039
12460
  widen,
12040
12461
  withAliasName,
12041
- withoutAliasName
12462
+ withoutAliasName,
12463
+ withoutNil
12042
12464
  });