@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/README.md +29 -10
- package/dist/index.cjs +515 -93
- package/dist/index.d.cts +84 -5
- package/dist/index.d.ts +84 -5
- package/dist/index.js +504 -92
- package/package.json +1 -1
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
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
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
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.
|
|
2602
|
-
this.
|
|
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(
|
|
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))
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
10726
|
-
|
|
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
|
-
|
|
10731
|
-
|
|
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
|
|
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
|
-
|
|
10782
|
-
|
|
10783
|
-
|
|
10784
|
-
|
|
10785
|
-
|
|
10786
|
-
|
|
10787
|
-
|
|
10788
|
-
|
|
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
|
-
*
|
|
10791
|
-
*
|
|
10792
|
-
*
|
|
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 =
|
|
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 (!
|
|
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
|
});
|