cluaupp 1.5.2 → 2.0.0-beta.2
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/CHANGELOG.md +8 -0
- package/README.md +29 -41
- package/api/generated/bench-latest.json +43 -21
- package/api/generated/lsp-index.json +1 -1
- package/api/manifests/roblox-target.manifest.json +5 -5
- package/api/normalized/roblox-target.profile.json +8729 -267
- package/api/roblox/luau-builtins.d.luau +185 -0
- package/api/roblox/surface.json +1 -0
- package/api/roblox-api.lock.json +11 -2
- package/bin/cluaupp.js +38 -0
- package/editors/vscode/README.md +1 -1
- package/editors/vscode/package.json +50 -15
- package/editors/vscode/snippets/flare.json +1 -1
- package/editors/vscode/snippets/hive.json +1 -1
- package/editors/vscode/syntaxes/clpp.tmLanguage.json +211 -0
- package/examples/README.md +9 -9
- package/examples/game/cluaupp.config.json +2 -2
- package/examples/game/default.project.json +4 -15
- package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau +8 -0
- package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau.map.json +13 -0
- package/examples/game/out/ServerScriptService/Leaderstats.server.luau +26 -0
- package/examples/game/out/ServerScriptService/Leaderstats.server.luau.map.json +13 -0
- package/examples/game/out/ServerScriptService/Main.server.luau +15 -0
- package/examples/game/out/ServerScriptService/Main.server.luau.map.json +13 -0
- package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau +10 -0
- package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau.map.json +13 -0
- package/examples/game/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
- package/examples/game/src/ServerScriptService/Leaderstats.server.clp +18 -0
- package/examples/game/src/ServerScriptService/Main.server.clp +9 -0
- package/examples/game/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
- package/generated/api/build-profile.js +1 -1
- package/generated/api/loader.js +2 -2
- package/generated/api/surface/build.js +328 -0
- package/generated/api/surface/luau-defs.js +595 -0
- package/generated/api/surface/model.js +8 -0
- package/generated/api-cli.js +2 -2
- package/generated/cli.js +18 -17
- package/generated/clpp/contract.js +6 -3
- package/generated/clpp/handshake.js +36 -0
- package/generated/clpp/modules.js +20 -9
- package/generated/clpp/runner.js +62 -31
- package/generated/compile.js +18 -1
- package/generated/compiler/ast.js +3 -0
- package/generated/compiler/checker/calls.js +999 -0
- package/generated/compiler/checker/checker.js +1215 -0
- package/generated/compiler/checker/exprs.js +791 -0
- package/generated/compiler/checker/flow.js +168 -0
- package/generated/compiler/checker/members.js +763 -0
- package/generated/compiler/checker/statements.js +792 -0
- package/generated/compiler/checker/symbols.js +90 -0
- package/generated/compiler/checker/typeops.js +348 -0
- package/generated/compiler/checker/yields.js +115 -0
- package/generated/compiler/diagnostics.js +138 -0
- package/generated/compiler/emit/emitter.js +2356 -0
- package/generated/compiler/emit/luau-types.js +137 -0
- package/generated/compiler/emit/names.js +130 -0
- package/generated/compiler/emit/runtime.js +432 -0
- package/generated/compiler/lexer.js +403 -0
- package/generated/compiler/parser.js +1881 -0
- package/generated/compiler/project.js +332 -0
- package/generated/compiler/roblox/api.js +388 -0
- package/generated/compiler/roblox/context.js +100 -0
- package/generated/compiler/source.js +59 -0
- package/generated/compiler/stdlib.js +232 -0
- package/generated/compiler/types.js +250 -0
- package/generated/intellisense.js +1 -15
- package/generated/lsp.js +1 -1
- package/generated/target/build-cache.js +4 -4
- package/generated/target/datamodel-generate.js +12 -0
- package/generated/target/gen-rojo-tree.js +15 -0
- package/generated/target/source-map.js +61 -8
- package/generated/target/studio-bridge.js +1 -0
- package/generated/utils/project.js +104 -31
- package/include/README.md +5 -0
- package/native/bin/.gitkeep +1 -0
- package/package.json +17 -6
- package/plugins/CluauppNav/Plugin.luau +28 -4
- package/rust/cluaupp/Cargo.lock +795 -0
- package/rust/cluaupp/Cargo.toml +17 -0
- package/rust/cluaupp/src/main.rs +717 -0
- package/scripts/postinstall-native.js +56 -0
- package/skills/README.md +3 -3
- package/skills/clpp-hive-flare/SKILL.md +30 -26
- package/skills/clpp-language/SKILL.md +67 -74
- package/skills/clpp-language/compiler.md +24 -36
- package/skills/clpp-style/SKILL.md +31 -44
- package/src/api/build-profile.ts +1 -1
- package/src/api/loader.ts +2 -2
- package/src/api/surface/build.ts +374 -0
- package/src/api/surface/luau-defs.ts +635 -0
- package/src/api/surface/model.ts +96 -0
- package/src/api-cli.ts +2 -2
- package/src/cli.ts +19 -18
- package/src/clpp/contract.ts +20 -3
- package/src/clpp/handshake.ts +25 -0
- package/src/clpp/modules.ts +17 -10
- package/src/clpp/runner.ts +52 -34
- package/src/compile.ts +20 -2
- package/src/compiler/ast.ts +336 -0
- package/src/compiler/checker/calls.ts +1025 -0
- package/src/compiler/checker/checker.ts +1352 -0
- package/src/compiler/checker/exprs.ts +805 -0
- package/src/compiler/checker/flow.ts +174 -0
- package/src/compiler/checker/members.ts +793 -0
- package/src/compiler/checker/statements.ts +802 -0
- package/src/compiler/checker/symbols.ts +219 -0
- package/src/compiler/checker/typeops.ts +355 -0
- package/src/compiler/checker/yields.ts +120 -0
- package/src/compiler/diagnostics.ts +163 -0
- package/src/compiler/emit/emitter.ts +2486 -0
- package/src/compiler/emit/luau-types.ts +143 -0
- package/src/compiler/emit/names.ts +130 -0
- package/src/compiler/emit/runtime.ts +438 -0
- package/src/compiler/lexer.ts +444 -0
- package/src/compiler/parser.ts +1943 -0
- package/src/compiler/project.ts +354 -0
- package/src/compiler/roblox/api.ts +408 -0
- package/src/compiler/roblox/context.ts +108 -0
- package/src/compiler/source.ts +67 -0
- package/src/compiler/stdlib.ts +256 -0
- package/src/compiler/types.ts +313 -0
- package/src/intellisense.ts +1 -13
- package/src/lsp.ts +1 -1
- package/src/target/build-cache.ts +5 -8
- package/src/target/datamodel-generate.ts +12 -1
- package/src/target/gen-rojo-tree.ts +15 -0
- package/src/target/source-map.ts +65 -27
- package/src/target/studio-bridge.ts +3 -0
- package/src/types.ts +3 -0
- package/src/utils/project.ts +99 -37
- package/templates/README.md +7 -0
- package/templates/{game → game-modern}/cluaupp.config.json +2 -2
- package/templates/{game → game-modern}/package.json +1 -2
- package/templates/game-modern/rokit.toml +2 -0
- package/templates/game-modern/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
- package/templates/game-modern/src/ServerScriptService/Handlers/Guard.server.clp +4 -0
- package/templates/game-modern/src/ServerScriptService/Main.server.clp +8 -0
- package/templates/game-modern/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
- package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau +0 -29
- package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau.map.json +0 -13
- package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau +0 -83
- package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau.map.json +0 -13
- package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau +0 -18
- package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau.map.json +0 -13
- package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau +0 -14
- package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau.map.json +0 -13
- package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau +0 -15
- package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau.map.json +0 -13
- package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau +0 -50
- package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau.map.json +0 -13
- package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau +0 -12
- package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau.map.json +0 -13
- package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau +0 -16
- package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau.map.json +0 -13
- package/examples/game/src/Client/Controllers/DataController.client.clpp +0 -7
- package/examples/game/src/Client/Main.client.clpp +0 -4
- package/examples/game/src/Declarations/Net.flare +0 -5
- package/examples/game/src/Include/GameTypes.clh +0 -40
- package/examples/game/src/Modules/Core/Guard.server.clpp +0 -7
- package/examples/game/src/Modules/Core/PlayerHandler.server.clpp +0 -23
- package/examples/game/src/Server/Boot/DataBoot.server.clpp +0 -13
- package/examples/game/src/Server/Main.server.clpp +0 -4
- package/templates/game/.clangd +0 -25
- package/templates/game/.vscode/extensions.json +0 -8
- package/templates/game/.vscode/settings.json +0 -17
- package/templates/game/Packages/.gitkeep +0 -0
- package/templates/game/compile_flags.txt +0 -6
- package/templates/game/default.project.json +0 -41
- package/templates/game/include/clpp/datatypes.clh +0 -575
- package/templates/game/include/clpp/generated/enums.clh +0 -4920
- package/templates/game/include/clpp/generated/instances.clh +0 -87182
- package/templates/game/include/clpp/libs/arrayindexer.clh +0 -12
- package/templates/game/include/clpp/libs/axiom.clh +0 -154
- package/templates/game/include/clpp/libs/bloom.clh +0 -58
- package/templates/game/include/clpp/libs/coil.clh +0 -37
- package/templates/game/include/clpp/libs/crest.clh +0 -22
- package/templates/game/include/clpp/libs/echo.clh +0 -52
- package/templates/game/include/clpp/libs/ember.clh +0 -17
- package/templates/game/include/clpp/libs/flare.clh +0 -65
- package/templates/game/include/clpp/libs/gleam.clh +0 -111
- package/templates/game/include/clpp/libs/guide.clh +0 -42
- package/templates/game/include/clpp/libs/helm.clh +0 -31
- package/templates/game/include/clpp/libs/hive.clh +0 -20
- package/templates/game/include/clpp/libs/keep.clh +0 -158
- package/templates/game/include/clpp/libs/lens.clh +0 -24
- package/templates/game/include/clpp/libs/mint.clh +0 -28
- package/templates/game/include/clpp/libs/net.clh +0 -31
- package/templates/game/include/clpp/libs/occlude.clh +0 -15
- package/templates/game/include/clpp/libs/pin.clh +0 -28
- package/templates/game/include/clpp/libs/promise.clh +0 -24
- package/templates/game/include/clpp/libs/roster.clh +0 -123
- package/templates/game/include/clpp/libs/shift.clh +0 -22
- package/templates/game/include/clpp/libs/spark.clh +0 -28
- package/templates/game/include/clpp/libs/stage.clh +0 -23
- package/templates/game/include/clpp/libs/sweep.clh +0 -23
- package/templates/game/include/clpp/libs/trace.clh +0 -18
- package/templates/game/include/clpp/libs/ward.clh +0 -19
- package/templates/game/include/clpp/libs.clh +0 -31
- package/templates/game/include/clpp/roblox.clh +0 -41
- package/templates/game/rokit.toml +0 -5
- package/templates/game/src/Client/Controllers/DataController.client.clpp +0 -7
- package/templates/game/src/Client/Main.client.clpp +0 -4
- package/templates/game/src/Declarations/Net.flare +0 -5
- package/templates/game/src/Include/GameTypes.clh +0 -40
- package/templates/game/src/Modules/Core/Guard.server.clpp +0 -7
- package/templates/game/src/Modules/Core/PlayerHandler.server.clpp +0 -24
- package/templates/game/src/Server/Boot/DataBoot.server.clpp +0 -13
- package/templates/game/src/Server/Main.server.clpp +0 -4
- package/templates/game/stylua.toml +0 -9
- package/templates/game/wally.toml +0 -12
|
@@ -0,0 +1,791 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Expression checking (names, operators, lambdas, async forms). Calls live in calls.ts, members in members.ts. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.CL_BUILTINS = exports.BUILTINS = void 0;
|
|
5
|
+
exports.checkExpr = checkExpr;
|
|
6
|
+
exports.checkCondition = checkCondition;
|
|
7
|
+
exports.exprPathKey = exprPathKey;
|
|
8
|
+
exports.rootSymbol = rootSymbol;
|
|
9
|
+
exports.checkFunctionExpr = checkFunctionExpr;
|
|
10
|
+
const diagnostics_js_1 = require("../diagnostics.js");
|
|
11
|
+
const context_js_1 = require("../roblox/context.js");
|
|
12
|
+
const types_js_1 = require("../types.js");
|
|
13
|
+
const calls_js_1 = require("./calls.js");
|
|
14
|
+
const checker_js_1 = require("./checker.js");
|
|
15
|
+
const statements_js_1 = require("./statements.js");
|
|
16
|
+
const flow_js_1 = require("./flow.js");
|
|
17
|
+
const members_js_1 = require("./members.js");
|
|
18
|
+
const typeops_js_1 = require("./typeops.js");
|
|
19
|
+
/** CL++ builtins that are always in scope. */
|
|
20
|
+
exports.BUILTINS = new Set([
|
|
21
|
+
"len", "list", "push", "pop", "insert", "remove", "find", "sort", "args", "array", "dictionary", "mutex", "lock",
|
|
22
|
+
"unlock", "fetch_add", "atomic_load", "actor", "Some", "Ok", "Err", "int", "float", "bool", "GetService",
|
|
23
|
+
]);
|
|
24
|
+
exports.CL_BUILTINS = exports.BUILTINS;
|
|
25
|
+
/** Luau libraries (need `@clpp.roblox`, except the ones CL++ itself has). */
|
|
26
|
+
const LIBRARIES = new Set(["math", "string", "table", "task", "coroutine", "bit32", "utf8", "buffer", "os", "debug", "vector"]);
|
|
27
|
+
const CORE_LIBRARIES = new Set(["task", "coroutine"]);
|
|
28
|
+
function checkExpr(c, e, expected, keepTuple = false) {
|
|
29
|
+
let t = checkExprInner(c, e, expected);
|
|
30
|
+
const p = (0, types_js_1.prune)(t);
|
|
31
|
+
if (p.k === "tuple" && !keepTuple && !(expected && (0, types_js_1.prune)(expected).k === "tuple")) {
|
|
32
|
+
// Luau keeps the first value of a multi-return in a single-value position.
|
|
33
|
+
t = p.elems[0] ?? types_js_1.T.void;
|
|
34
|
+
}
|
|
35
|
+
e.ty = t;
|
|
36
|
+
return t;
|
|
37
|
+
}
|
|
38
|
+
/** A condition: any value works (CL++ truthiness), but not `void`. */
|
|
39
|
+
function checkCondition(c, e) {
|
|
40
|
+
const t = checkExpr(c, e);
|
|
41
|
+
const p = (0, types_js_1.prune)(t);
|
|
42
|
+
if (p.k === "void") {
|
|
43
|
+
c.error("CL3013", e.span, "this expression has no value to test");
|
|
44
|
+
}
|
|
45
|
+
else if (p.k === "instance" || p.k === "datatype" || p.k === "func") {
|
|
46
|
+
// always truthy unless nullable — almost always a forgotten comparison
|
|
47
|
+
c.warn("CL3041", e.span, `${(0, types_js_1.typeToString)(p)} is always true here`, ["compare it explicitly, e.g. `x != null`"]);
|
|
48
|
+
}
|
|
49
|
+
return t;
|
|
50
|
+
}
|
|
51
|
+
/** Narrowing key for a readable path: `s12`, `s12.Character`, `self.hp`. */
|
|
52
|
+
function exprPathKey(e) {
|
|
53
|
+
switch (e.kind) {
|
|
54
|
+
case "Name":
|
|
55
|
+
if (e.res && (e.res.kind === "local" || e.res.kind === "const")) {
|
|
56
|
+
const sym = e.res.sym;
|
|
57
|
+
return sym ? `s${sym.id}` : null;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
case "SelfField":
|
|
61
|
+
return `self.${e.name}`;
|
|
62
|
+
case "Member": {
|
|
63
|
+
const base = exprPathKey(e.object);
|
|
64
|
+
if (!base) {
|
|
65
|
+
if (e.object.res?.kind === "service") {
|
|
66
|
+
return `svc:${e.object.res.service}.${e.name}`;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return `${base}.${e.name}`;
|
|
71
|
+
}
|
|
72
|
+
default:
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Symbol at the root of a place (`p` in `p.stats.hp`). */
|
|
77
|
+
function rootSymbol(e) {
|
|
78
|
+
switch (e.kind) {
|
|
79
|
+
case "Name":
|
|
80
|
+
return e.res && (e.res.kind === "local" || e.res.kind === "signal" || e.res.kind === "func") ? e.res.sym : null;
|
|
81
|
+
case "Member":
|
|
82
|
+
case "Index":
|
|
83
|
+
return rootSymbol(e.object);
|
|
84
|
+
default:
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function applyNarrowing(c, e, t) {
|
|
89
|
+
const key = exprPathKey(e);
|
|
90
|
+
if (!key) {
|
|
91
|
+
return t;
|
|
92
|
+
}
|
|
93
|
+
const narrowed = c.scope.narrowedType(key);
|
|
94
|
+
if (narrowed) {
|
|
95
|
+
e.narrowedFrom = t;
|
|
96
|
+
return narrowed;
|
|
97
|
+
}
|
|
98
|
+
return t;
|
|
99
|
+
}
|
|
100
|
+
function checkExprInner(c, e, expected) {
|
|
101
|
+
switch (e.kind) {
|
|
102
|
+
case "Number":
|
|
103
|
+
return e.isFloat ? types_js_1.T.float : types_js_1.T.int;
|
|
104
|
+
case "String": {
|
|
105
|
+
const ex = expected ? (0, types_js_1.prune)(expected) : null;
|
|
106
|
+
if (ex && (ex.k === "lit" || (ex.k === "union" && ex.members.some((m) => (0, types_js_1.prune)(m).k === "lit")) || (ex.k === "option" && (0, types_js_1.prune)(ex.inner).k === "union"))) {
|
|
107
|
+
return { k: "lit", value: e.value };
|
|
108
|
+
}
|
|
109
|
+
return types_js_1.T.string;
|
|
110
|
+
}
|
|
111
|
+
case "Template":
|
|
112
|
+
for (const part of e.parts) {
|
|
113
|
+
if (typeof part !== "string") {
|
|
114
|
+
const t = checkExpr(c, part);
|
|
115
|
+
if ((0, types_js_1.prune)(t).k === "void") {
|
|
116
|
+
c.error("CL3013", part.span, "this expression has no value to interpolate");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return types_js_1.T.string;
|
|
121
|
+
case "Bool":
|
|
122
|
+
return types_js_1.T.bool;
|
|
123
|
+
case "Null":
|
|
124
|
+
return types_js_1.T.null;
|
|
125
|
+
case "Name":
|
|
126
|
+
return checkName(c, e, expected);
|
|
127
|
+
case "SelfField":
|
|
128
|
+
return checkSelfField(c, e);
|
|
129
|
+
case "Member":
|
|
130
|
+
return applyNarrowing(c, e, (0, members_js_1.checkMember)(c, e, expected));
|
|
131
|
+
case "Scope":
|
|
132
|
+
return (0, members_js_1.checkScopeAccess)(c, e);
|
|
133
|
+
case "Index":
|
|
134
|
+
return checkIndex(c, e);
|
|
135
|
+
case "Call":
|
|
136
|
+
return (0, calls_js_1.checkCall)(c, e, expected);
|
|
137
|
+
case "New":
|
|
138
|
+
return (0, calls_js_1.checkNew)(c, e, expected);
|
|
139
|
+
case "Unary":
|
|
140
|
+
return checkUnary(c, e);
|
|
141
|
+
case "Binary":
|
|
142
|
+
return checkBinary(c, e, expected);
|
|
143
|
+
case "Ternary": {
|
|
144
|
+
checkCondition(c, e.cond);
|
|
145
|
+
const a = checkExpr(c, e.then, expected);
|
|
146
|
+
const b = checkExpr(c, e.else, expected);
|
|
147
|
+
return (0, typeops_js_1.join)(c, (0, checker_js_1.widenLiteral)(a), (0, checker_js_1.widenLiteral)(b));
|
|
148
|
+
}
|
|
149
|
+
case "Function":
|
|
150
|
+
return checkFunctionExpr(c, e, expected);
|
|
151
|
+
case "Spawn":
|
|
152
|
+
case "Thread": {
|
|
153
|
+
const inner = e.kind === "Spawn" ? e.call : e.call;
|
|
154
|
+
if (inner.kind !== "Call") {
|
|
155
|
+
c.error("CL3050", inner.span, `${e.kind === "Spawn" ? "spawn" : "thread"} needs a function call: \`${e.kind === "Spawn" ? "spawn" : "thread"} work(x)\``);
|
|
156
|
+
}
|
|
157
|
+
const t = (0, types_js_1.prune)(checkExpr(c, inner));
|
|
158
|
+
if (e.kind === "Thread") {
|
|
159
|
+
c.warn("CL3051", e.span, "Roblox has no native threads; `thread` runs the call as a task on the same thread", ["use an Actor for real parallel work"]);
|
|
160
|
+
}
|
|
161
|
+
return { k: "task", result: t.k === "task" ? t.result : t };
|
|
162
|
+
}
|
|
163
|
+
case "Await":
|
|
164
|
+
return checkAwait(c, e);
|
|
165
|
+
case "Join": {
|
|
166
|
+
const t = (0, types_js_1.prune)(checkExpr(c, e.operand));
|
|
167
|
+
c.noteYield();
|
|
168
|
+
if (t.k === "task") {
|
|
169
|
+
return t.result;
|
|
170
|
+
}
|
|
171
|
+
if (!(0, types_js_1.isDynamic)(t)) {
|
|
172
|
+
c.error("CL3052", e.operand.span, `join needs a thread/task, got ${(0, types_js_1.typeToString)(t)}`);
|
|
173
|
+
}
|
|
174
|
+
return types_js_1.T.any;
|
|
175
|
+
}
|
|
176
|
+
case "Move": {
|
|
177
|
+
const t = checkExpr(c, e.operand);
|
|
178
|
+
if (e.operand.kind !== "Name") {
|
|
179
|
+
c.error("CL3053", e.operand.span, "move(...) takes a variable");
|
|
180
|
+
}
|
|
181
|
+
else if (e.operand.res?.kind === "local") {
|
|
182
|
+
const sym = e.operand.res.sym;
|
|
183
|
+
if (!sym.mutable) {
|
|
184
|
+
c.error("CL3054", e.operand.span, `cannot move out of the immutable '${sym.name}'`, ["declare it with `let mut`"]);
|
|
185
|
+
}
|
|
186
|
+
sym.assigned = true;
|
|
187
|
+
}
|
|
188
|
+
return t;
|
|
189
|
+
}
|
|
190
|
+
case "PCall":
|
|
191
|
+
checkExpr(c, e.operand);
|
|
192
|
+
return types_js_1.T.int;
|
|
193
|
+
case "Parallel": {
|
|
194
|
+
let elem = types_js_1.T.never;
|
|
195
|
+
for (const call of e.calls) {
|
|
196
|
+
const t = (0, types_js_1.prune)(checkExpr(c, call));
|
|
197
|
+
const r = t.k === "task" ? t.result : t;
|
|
198
|
+
elem = (0, typeops_js_1.join)(c, elem, r);
|
|
199
|
+
}
|
|
200
|
+
c.noteYield();
|
|
201
|
+
return types_js_1.T.list((0, types_js_1.prune)(elem).k === "never" ? types_js_1.T.any : elem);
|
|
202
|
+
}
|
|
203
|
+
case "Update":
|
|
204
|
+
return checkUpdate(c, e);
|
|
205
|
+
case "Cast":
|
|
206
|
+
return checkCast(c, e);
|
|
207
|
+
case "Error":
|
|
208
|
+
return types_js_1.T.unknown;
|
|
209
|
+
default:
|
|
210
|
+
return types_js_1.T.unknown;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// ---------- names ----------
|
|
214
|
+
function checkName(c, e, expected) {
|
|
215
|
+
const sym = c.scope.lookup(e.name);
|
|
216
|
+
if (sym) {
|
|
217
|
+
sym.used = true;
|
|
218
|
+
if (c.fn && sym.depth < c.fn.depth && (sym.kind === "var" || sym.kind === "param" || sym.kind === "self" || sym.kind === "signal" || sym.kind === "const")) {
|
|
219
|
+
sym.captured = true;
|
|
220
|
+
}
|
|
221
|
+
switch (sym.kind) {
|
|
222
|
+
case "var":
|
|
223
|
+
case "param":
|
|
224
|
+
case "self":
|
|
225
|
+
e.res = { kind: "local", sym };
|
|
226
|
+
return applyNarrowing(c, e, sym.type);
|
|
227
|
+
case "const":
|
|
228
|
+
if (sym.constValue !== undefined) {
|
|
229
|
+
e.res = { kind: "const", value: sym.constValue };
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
e.res = { kind: "local", sym };
|
|
233
|
+
}
|
|
234
|
+
return sym.type;
|
|
235
|
+
case "func":
|
|
236
|
+
e.res = { kind: "func", sym };
|
|
237
|
+
if (sym.decl && (0, types_js_1.prune)(sym.type.ret).k === "infer" && !c.isChecking(sym.decl)) {
|
|
238
|
+
// the value of a function needs its inferred return type
|
|
239
|
+
}
|
|
240
|
+
return sym.type;
|
|
241
|
+
case "signal":
|
|
242
|
+
e.res = { kind: "signal", sym };
|
|
243
|
+
return sym.type;
|
|
244
|
+
case "module":
|
|
245
|
+
e.res = { kind: "module", alias: sym.name, moduleId: sym.type.ns?.moduleId ?? sym.name };
|
|
246
|
+
return sym.type;
|
|
247
|
+
case "struct":
|
|
248
|
+
e.res = { kind: "struct", name: sym.name, sym };
|
|
249
|
+
return sym.type;
|
|
250
|
+
case "enum":
|
|
251
|
+
e.res = { kind: "enum", name: sym.name, sym };
|
|
252
|
+
return sym.type;
|
|
253
|
+
case "variant":
|
|
254
|
+
e.res = { kind: "builtin", name: "variant" };
|
|
255
|
+
return sym.type;
|
|
256
|
+
case "namespace":
|
|
257
|
+
e.res = { kind: "module", alias: sym.name, moduleId: "namespace" };
|
|
258
|
+
return sym.type;
|
|
259
|
+
default:
|
|
260
|
+
e.res = { kind: "local", sym };
|
|
261
|
+
return sym.type;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return resolveGlobalName(c, e, expected);
|
|
265
|
+
}
|
|
266
|
+
function resolveGlobalName(c, e, expected) {
|
|
267
|
+
const name = e.name;
|
|
268
|
+
if (name === "self") {
|
|
269
|
+
c.error("CL2005", e.span, "'self' only exists inside a struct method");
|
|
270
|
+
return types_js_1.T.unknown;
|
|
271
|
+
}
|
|
272
|
+
if (name === "None") {
|
|
273
|
+
e.res = { kind: "builtin", name: "None" };
|
|
274
|
+
return expected && (0, types_js_1.prune)(expected).k === "option" ? expected : types_js_1.T.null;
|
|
275
|
+
}
|
|
276
|
+
if (exports.BUILTINS.has(name)) {
|
|
277
|
+
e.res = { kind: "builtin", name };
|
|
278
|
+
return types_js_1.T.func([], types_js_1.T.any);
|
|
279
|
+
}
|
|
280
|
+
if (name === "Vector2" || name === "Vector3") {
|
|
281
|
+
e.res = { kind: "global", luau: name };
|
|
282
|
+
return { k: "namespace", ns: { kind: "datatype", name } };
|
|
283
|
+
}
|
|
284
|
+
if (name === "string" || name === "type") {
|
|
285
|
+
// `string(x)` conversion / `string.format` / `type(x)` are handled by callers; as a value it is the library.
|
|
286
|
+
}
|
|
287
|
+
if (LIBRARIES.has(name)) {
|
|
288
|
+
if (!CORE_LIBRARIES.has(name)) {
|
|
289
|
+
c.requireRoblox(e.span, name);
|
|
290
|
+
}
|
|
291
|
+
e.res = { kind: "global", luau: name };
|
|
292
|
+
return { k: "namespace", ns: { kind: "library", name } };
|
|
293
|
+
}
|
|
294
|
+
const api = c.api;
|
|
295
|
+
switch (name) {
|
|
296
|
+
case "game":
|
|
297
|
+
c.requireRoblox(e.span, name);
|
|
298
|
+
e.res = { kind: "dataModel" };
|
|
299
|
+
return { k: "instance", cls: "DataModel", ...(c.input.dataModel ? { dm: c.input.dataModel } : {}) };
|
|
300
|
+
case "workspace":
|
|
301
|
+
case "Workspace":
|
|
302
|
+
c.requireRoblox(e.span, name);
|
|
303
|
+
e.res = { kind: "global", luau: "workspace" };
|
|
304
|
+
return { k: "instance", cls: "Workspace", ...(c.input.dataModel?.children.get("Workspace") ? { dm: c.input.dataModel.children.get("Workspace") } : {}) };
|
|
305
|
+
case "script":
|
|
306
|
+
c.requireRoblox(e.span, name);
|
|
307
|
+
e.res = { kind: "global", luau: "script" };
|
|
308
|
+
return types_js_1.T.instance(c.input.kind === "module" ? "ModuleScript" : c.input.context === "client" ? "LocalScript" : "Script");
|
|
309
|
+
case "plugin":
|
|
310
|
+
c.requireRoblox(e.span, name);
|
|
311
|
+
if (c.input.context !== "plugin") {
|
|
312
|
+
c.error("CTX6003", e.span, "'plugin' only exists in plugin scripts");
|
|
313
|
+
}
|
|
314
|
+
e.res = { kind: "global", luau: "plugin" };
|
|
315
|
+
return types_js_1.T.option(types_js_1.T.instance("Plugin"));
|
|
316
|
+
case "shared":
|
|
317
|
+
c.requireRoblox(e.span, name);
|
|
318
|
+
e.res = { kind: "global", luau: "shared" };
|
|
319
|
+
return types_js_1.T.any;
|
|
320
|
+
case "Enum":
|
|
321
|
+
c.requireRoblox(e.span, name);
|
|
322
|
+
e.res = { kind: "global", luau: "Enum" };
|
|
323
|
+
return { k: "rbxEnums" };
|
|
324
|
+
case "Instance":
|
|
325
|
+
c.requireRoblox(e.span, name);
|
|
326
|
+
e.res = { kind: "global", luau: "Instance" };
|
|
327
|
+
return { k: "namespace", ns: { kind: "instanceStatic" } };
|
|
328
|
+
default:
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
if (api.isService(name)) {
|
|
332
|
+
c.useService(name, e.span);
|
|
333
|
+
const rule = context_js_1.SERVICE_RULES[name];
|
|
334
|
+
if (rule) {
|
|
335
|
+
c.checkContext(rule.only, e.span, name, rule.why, rule.severity);
|
|
336
|
+
}
|
|
337
|
+
const cls = api.classes[name];
|
|
338
|
+
if (cls.deprecated) {
|
|
339
|
+
c.warn("RBX5010", e.span, `${name} is deprecated`);
|
|
340
|
+
}
|
|
341
|
+
e.res = { kind: "service", service: name };
|
|
342
|
+
return { k: "instance", cls: name, ...(c.input.dataModel?.children.get(name) ? { dm: c.input.dataModel.children.get(name) } : {}) };
|
|
343
|
+
}
|
|
344
|
+
if (api.hasDatatype(name)) {
|
|
345
|
+
c.requireRoblox(e.span, name);
|
|
346
|
+
e.res = { kind: "global", luau: name };
|
|
347
|
+
return { k: "namespace", ns: { kind: "datatype", name } };
|
|
348
|
+
}
|
|
349
|
+
const global = api.globals[name];
|
|
350
|
+
if (global) {
|
|
351
|
+
c.requireRoblox(e.span, name);
|
|
352
|
+
if (global.deprecated) {
|
|
353
|
+
const use = typeof global.deprecated === "string" ? global.deprecated : null;
|
|
354
|
+
c.warn("RBX5011", e.span, `${name}() is deprecated${use ? `; use ${use}` : ""}`, use ? [`${use} is faster and more precise`] : undefined);
|
|
355
|
+
}
|
|
356
|
+
e.res = { kind: "global", luau: name };
|
|
357
|
+
return api.globalFunction(name) ?? types_js_1.T.any;
|
|
358
|
+
}
|
|
359
|
+
if (api.hasClass(name)) {
|
|
360
|
+
c.requireRoblox(e.span, name);
|
|
361
|
+
c.error("RBX5012", e.span, `${name} is a Roblox class, not a value`, [`create one with \`${name}()\` or \`Instance.new("${name}")\``]);
|
|
362
|
+
return types_js_1.T.unknown;
|
|
363
|
+
}
|
|
364
|
+
const candidates = c.scope.allValueNames();
|
|
365
|
+
const s = (0, diagnostics_js_1.suggest)(name, [...candidates, ...exports.BUILTINS]);
|
|
366
|
+
const notes = [];
|
|
367
|
+
if (s) {
|
|
368
|
+
notes.push(`did you mean '${s}'?`);
|
|
369
|
+
}
|
|
370
|
+
c.error("CL2001", e.span, `undefined name '${name}'`, notes.length ? notes : undefined);
|
|
371
|
+
return types_js_1.T.unknown;
|
|
372
|
+
}
|
|
373
|
+
function checkSelfField(c, e) {
|
|
374
|
+
const fn = c.fn;
|
|
375
|
+
if (fn && fn.isMethod && fn.structId) {
|
|
376
|
+
const info = c.structs.get(fn.structId);
|
|
377
|
+
const field = info?.fields.find((f) => f.name === e.name);
|
|
378
|
+
if (field) {
|
|
379
|
+
if (fn.selfSym) {
|
|
380
|
+
fn.selfSym.used = true;
|
|
381
|
+
if (c.fn && fn.selfSym.depth < c.fn.depth) {
|
|
382
|
+
fn.selfSym.captured = true;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
e.res = { kind: "field", struct: fn.structId };
|
|
386
|
+
const key = `self.${e.name}`;
|
|
387
|
+
const narrowed = c.scope.narrowedType(key);
|
|
388
|
+
if (narrowed) {
|
|
389
|
+
e.narrowedFrom = field.type;
|
|
390
|
+
return narrowed;
|
|
391
|
+
}
|
|
392
|
+
return field.type;
|
|
393
|
+
}
|
|
394
|
+
const method = info?.methods.get(e.name);
|
|
395
|
+
if (method) {
|
|
396
|
+
e.res = { kind: "method", struct: fn.structId, name: e.name, isStatic: method.isStatic };
|
|
397
|
+
return method.type;
|
|
398
|
+
}
|
|
399
|
+
if (e.name !== "game" && e.name !== "workspace") {
|
|
400
|
+
const s = info ? (0, diagnostics_js_1.suggest)(e.name, [...info.fields.map((f) => f.name), ...info.methods.keys()]) : null;
|
|
401
|
+
c.error("CL2006", e.nameSpan, `${info?.name ?? "this struct"} has no field '${e.name}'`, s ? [`did you mean '@${s}'?`] : undefined);
|
|
402
|
+
return types_js_1.T.unknown;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (e.name === "game") {
|
|
406
|
+
c.requireRoblox(e.span, "@game");
|
|
407
|
+
e.res = { kind: "dataModel" };
|
|
408
|
+
return { k: "instance", cls: "DataModel", ...(c.input.dataModel ? { dm: c.input.dataModel } : {}) };
|
|
409
|
+
}
|
|
410
|
+
if (e.name === "workspace") {
|
|
411
|
+
c.requireRoblox(e.span, "@workspace");
|
|
412
|
+
e.res = { kind: "global", luau: "workspace" };
|
|
413
|
+
return types_js_1.T.instance("Workspace");
|
|
414
|
+
}
|
|
415
|
+
c.usesShared = true;
|
|
416
|
+
e.res = { kind: "sharedField", name: e.name };
|
|
417
|
+
return types_js_1.T.any;
|
|
418
|
+
}
|
|
419
|
+
// ---------- indexing ----------
|
|
420
|
+
function checkIndex(c, e) {
|
|
421
|
+
const objType = (0, types_js_1.prune)(checkExpr(c, e.object));
|
|
422
|
+
if (e.index.kind === "Binary" && e.index.op === "..") {
|
|
423
|
+
const a = checkExpr(c, e.index.left);
|
|
424
|
+
const b = checkExpr(c, e.index.right);
|
|
425
|
+
e.index.ty = types_js_1.T.any;
|
|
426
|
+
for (const [t, x] of [[a, e.index.left], [b, e.index.right]]) {
|
|
427
|
+
if (!(0, types_js_1.isIntLike)(t) && !(0, types_js_1.isDynamic)(t)) {
|
|
428
|
+
c.error("CL3060", x.span, `slice bounds must be int, got ${(0, types_js_1.typeToString)(t)}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (objType.k === "list" || objType.k === "string" || (0, types_js_1.isDynamic)(objType) || (objType.k === "lit" && typeof objType.value === "string")) {
|
|
432
|
+
return objType.k === "lit" ? types_js_1.T.string : objType;
|
|
433
|
+
}
|
|
434
|
+
c.error("CL3061", e.object.span, `cannot slice ${(0, types_js_1.typeToString)(objType)}`);
|
|
435
|
+
return types_js_1.T.unknown;
|
|
436
|
+
}
|
|
437
|
+
const indexType = checkExpr(c, e.index);
|
|
438
|
+
switch (objType.k) {
|
|
439
|
+
case "list":
|
|
440
|
+
if (!(0, types_js_1.isIntLike)(indexType) && !(0, types_js_1.isDynamic)(indexType)) {
|
|
441
|
+
c.error("CL3060", e.index.span, `list index must be int, got ${(0, types_js_1.typeToString)(indexType)}`, (0, types_js_1.isNumeric)(indexType) ? ["convert with int(...)"] : undefined);
|
|
442
|
+
}
|
|
443
|
+
if ((e.index.kind === "Number" && e.index.value < 0) || (e.index.kind === "Unary" && e.index.op === "-" && e.index.operand.kind === "Number")) {
|
|
444
|
+
c.error("CL3062", e.index.span, "negative index: CL++ lists start at 0");
|
|
445
|
+
}
|
|
446
|
+
return objType.elem;
|
|
447
|
+
case "string":
|
|
448
|
+
case "lit":
|
|
449
|
+
if (!(0, types_js_1.isIntLike)(indexType) && !(0, types_js_1.isDynamic)(indexType)) {
|
|
450
|
+
c.error("CL3060", e.index.span, `text index must be int, got ${(0, types_js_1.typeToString)(indexType)}`);
|
|
451
|
+
}
|
|
452
|
+
return types_js_1.T.string;
|
|
453
|
+
case "dict":
|
|
454
|
+
if ((0, typeops_js_1.assignable)(c, objType.key, indexType) === "no") {
|
|
455
|
+
c.error("CL3063", e.index.span, `dictionary key must be ${(0, types_js_1.typeToString)(objType.key)}, got ${(0, types_js_1.typeToString)(indexType)}`);
|
|
456
|
+
}
|
|
457
|
+
return objType.value;
|
|
458
|
+
case "table": {
|
|
459
|
+
if (e.index.kind === "String") {
|
|
460
|
+
const f = objType.fields[e.index.value];
|
|
461
|
+
if (f) {
|
|
462
|
+
return f;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return types_js_1.T.any;
|
|
466
|
+
}
|
|
467
|
+
case "tuple":
|
|
468
|
+
if (e.index.kind === "Number" && Number.isInteger(e.index.value)) {
|
|
469
|
+
return objType.elems[e.index.value] ?? types_js_1.T.unknown;
|
|
470
|
+
}
|
|
471
|
+
return types_js_1.T.any;
|
|
472
|
+
case "instance":
|
|
473
|
+
if (!(0, types_js_1.isStringLike)(indexType)) {
|
|
474
|
+
c.error("CL3064", e.index.span, `${objType.cls}[…] needs a child name (string)`);
|
|
475
|
+
}
|
|
476
|
+
return types_js_1.T.option(types_js_1.T.instance("Instance"));
|
|
477
|
+
case "buffer":
|
|
478
|
+
c.error("CL3065", e.span, "read buffers with buffer.readu8(b, offset) and friends");
|
|
479
|
+
return types_js_1.T.int;
|
|
480
|
+
case "option":
|
|
481
|
+
c.strictError("CL3011", e.object.span, `${(0, types_js_1.typeToString)(objType)} may be null; check it before indexing`);
|
|
482
|
+
return types_js_1.T.any;
|
|
483
|
+
case "any":
|
|
484
|
+
case "unknown":
|
|
485
|
+
case "infer":
|
|
486
|
+
return types_js_1.T.any;
|
|
487
|
+
default:
|
|
488
|
+
c.error("CL3066", e.span, `cannot index ${(0, types_js_1.typeToString)(objType)}`);
|
|
489
|
+
return types_js_1.T.unknown;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
// ---------- operators ----------
|
|
493
|
+
function checkUnary(c, e) {
|
|
494
|
+
const t = (0, types_js_1.prune)(checkExpr(c, e.operand));
|
|
495
|
+
if (e.loose && (e.op === "-" || e.op === "~") && e.operand.kind === "Binary" && e.operand.op !== "and" && e.operand.op !== "or") {
|
|
496
|
+
c.warn("CL1203", e.span, `unary '${e.op}' applies to the whole '${e.operand.op}' expression (CL++ precedence)`, [
|
|
497
|
+
`this is ${e.op}(a ${e.operand.op} b); write (${e.op}a) ${e.operand.op} b if you meant only the left side`,
|
|
498
|
+
]);
|
|
499
|
+
}
|
|
500
|
+
switch (e.op) {
|
|
501
|
+
case "-":
|
|
502
|
+
if ((0, types_js_1.isNumeric)(t)) {
|
|
503
|
+
return (0, checker_js_1.widenLiteral)(t);
|
|
504
|
+
}
|
|
505
|
+
if (t.k === "datatype" && c.api.unaryMinus(t)) {
|
|
506
|
+
return t;
|
|
507
|
+
}
|
|
508
|
+
if (!(0, types_js_1.isDynamic)(t)) {
|
|
509
|
+
c.error("CL3070", e.span, `cannot negate ${(0, types_js_1.typeToString)(t)}`);
|
|
510
|
+
}
|
|
511
|
+
return (0, types_js_1.isDynamic)(t) ? types_js_1.T.float : types_js_1.T.unknown;
|
|
512
|
+
case "~":
|
|
513
|
+
if (!(0, types_js_1.isIntLike)(t) && !(0, types_js_1.isDynamic)(t)) {
|
|
514
|
+
c.error("CL3071", e.span, `'~' needs an int, got ${(0, types_js_1.typeToString)(t)}`);
|
|
515
|
+
}
|
|
516
|
+
return types_js_1.T.int;
|
|
517
|
+
case "not":
|
|
518
|
+
if (t.k === "void") {
|
|
519
|
+
c.error("CL3013", e.operand.span, "this expression has no value to test");
|
|
520
|
+
}
|
|
521
|
+
return types_js_1.T.bool;
|
|
522
|
+
default:
|
|
523
|
+
return types_js_1.T.unknown;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function checkBinary(c, e, expected) {
|
|
527
|
+
const op = e.op;
|
|
528
|
+
if (op === "and" || op === "or") {
|
|
529
|
+
const l = (0, types_js_1.prune)(checkExpr(c, e.left));
|
|
530
|
+
// `a and b`: b is only evaluated when a holds, so it sees a's narrowings.
|
|
531
|
+
const scope = c.pushScope("block");
|
|
532
|
+
if (op === "and") {
|
|
533
|
+
for (const [k, v] of narrowingsOf(c, e.left, true)) {
|
|
534
|
+
scope.narrowed.set(k, v);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
for (const [k, v] of narrowingsOf(c, e.left, false)) {
|
|
539
|
+
scope.narrowed.set(k, v);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
const r = (0, types_js_1.prune)(checkExpr(c, e.right, op === "or" ? expected : undefined));
|
|
543
|
+
c.popScope();
|
|
544
|
+
if (op === "or" && (l.k === "option" || ((l.k === "instance" || l.k === "any") && r.k !== "bool"))) {
|
|
545
|
+
// Luau value-or: `FindFirstChild("A") or FindFirstChild("B")`, `name or "guest"`
|
|
546
|
+
e.res = { kind: "builtin", name: "valueOr" };
|
|
547
|
+
return (0, typeops_js_1.join)(c, (0, types_js_1.nonNull)(l), r);
|
|
548
|
+
}
|
|
549
|
+
return types_js_1.T.bool;
|
|
550
|
+
}
|
|
551
|
+
const l = (0, types_js_1.prune)(checkExpr(c, e.left));
|
|
552
|
+
const r = (0, types_js_1.prune)(checkExpr(c, e.right, op === "==" || op === "!=" ? l : undefined));
|
|
553
|
+
switch (op) {
|
|
554
|
+
case ".:":
|
|
555
|
+
for (const [t, x] of [[l, e.left], [r, e.right]]) {
|
|
556
|
+
if (t.k === "void") {
|
|
557
|
+
c.error("CL3013", x.span, "this expression has no value to join");
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return types_js_1.T.string;
|
|
561
|
+
case "..":
|
|
562
|
+
c.error("CL3072", e.opSpan, "'..' makes a slice range and only works inside `x[a .. b]`", ["join text with `.:`"]);
|
|
563
|
+
return types_js_1.T.unknown;
|
|
564
|
+
case "+":
|
|
565
|
+
case "-":
|
|
566
|
+
case "*":
|
|
567
|
+
case "/":
|
|
568
|
+
case "%":
|
|
569
|
+
return arithmetic(c, e, l, r);
|
|
570
|
+
case "==":
|
|
571
|
+
case "!=":
|
|
572
|
+
comparable(c, e, l, r);
|
|
573
|
+
return types_js_1.T.bool;
|
|
574
|
+
case "<":
|
|
575
|
+
case "<=":
|
|
576
|
+
case ">":
|
|
577
|
+
case ">=":
|
|
578
|
+
if (((0, types_js_1.isNumeric)(l) && (0, types_js_1.isNumeric)(r)) || ((0, types_js_1.isStringLike)(l) && (0, types_js_1.isStringLike)(r)) || (0, types_js_1.isDynamic)(l) || (0, types_js_1.isDynamic)(r) || l.k === "param" || r.k === "param") {
|
|
579
|
+
return types_js_1.T.bool;
|
|
580
|
+
}
|
|
581
|
+
if (l.k === "option" || r.k === "option") {
|
|
582
|
+
c.strictError("CL3011", e.span, "comparing a value that may be null");
|
|
583
|
+
return types_js_1.T.bool;
|
|
584
|
+
}
|
|
585
|
+
c.error("CL3073", e.opSpan, `cannot order ${(0, types_js_1.typeToString)(l)} and ${(0, types_js_1.typeToString)(r)} with '${op}'`);
|
|
586
|
+
return types_js_1.T.bool;
|
|
587
|
+
case "&":
|
|
588
|
+
case "|":
|
|
589
|
+
case "^":
|
|
590
|
+
case "shl":
|
|
591
|
+
case "shr":
|
|
592
|
+
for (const [t, x] of [[l, e.left], [r, e.right]]) {
|
|
593
|
+
if (!(0, types_js_1.isIntLike)(t) && !(0, types_js_1.isDynamic)(t)) {
|
|
594
|
+
c.error("CL3074", x.span, `'${op}' works on ints, got ${(0, types_js_1.typeToString)(t)}`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return types_js_1.T.int;
|
|
598
|
+
default:
|
|
599
|
+
return types_js_1.T.unknown;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
function narrowingsOf(c, e, truthy) {
|
|
603
|
+
return (0, flow_js_1.conditionNarrowings)(c, e, truthy);
|
|
604
|
+
}
|
|
605
|
+
function arithmetic(c, e, l, r) {
|
|
606
|
+
const op = e.op;
|
|
607
|
+
if ((0, types_js_1.isNumeric)(l) && (0, types_js_1.isNumeric)(r)) {
|
|
608
|
+
if ((0, types_js_1.isIntLike)(l) && (0, types_js_1.isIntLike)(r) && l.k !== "enum" && r.k !== "enum") {
|
|
609
|
+
if (op === "/" && e.right.kind === "Number" && e.right.value === 0) {
|
|
610
|
+
c.error("CL3075", e.right.span, "division by zero");
|
|
611
|
+
}
|
|
612
|
+
return types_js_1.T.int;
|
|
613
|
+
}
|
|
614
|
+
return types_js_1.T.float;
|
|
615
|
+
}
|
|
616
|
+
if (l.k === "param" || r.k === "param") {
|
|
617
|
+
return l.k === "param" ? l : r;
|
|
618
|
+
}
|
|
619
|
+
if ((0, types_js_1.isDynamic)(l) || (0, types_js_1.isDynamic)(r)) {
|
|
620
|
+
if (l.k === "datatype" || r.k === "datatype") {
|
|
621
|
+
return l.k === "datatype" ? l : r;
|
|
622
|
+
}
|
|
623
|
+
return (0, types_js_1.isDynamic)(l) && (0, types_js_1.isDynamic)(r) ? types_js_1.T.any : types_js_1.T.float;
|
|
624
|
+
}
|
|
625
|
+
if (l.k === "datatype" || r.k === "datatype") {
|
|
626
|
+
const result = c.api.operator(op, l, r);
|
|
627
|
+
if (result) {
|
|
628
|
+
return result;
|
|
629
|
+
}
|
|
630
|
+
c.error("CL3076", e.opSpan, `'${op}' is not defined for ${(0, types_js_1.typeToString)(l)} and ${(0, types_js_1.typeToString)(r)}`);
|
|
631
|
+
return types_js_1.T.unknown;
|
|
632
|
+
}
|
|
633
|
+
if (l.k === "struct" && (op === "+" || op === "-")) {
|
|
634
|
+
const fn = c.scope.lookup(`operator${op}`);
|
|
635
|
+
if (fn && fn.kind === "func") {
|
|
636
|
+
fn.used = true;
|
|
637
|
+
e.res = { kind: "func", sym: fn };
|
|
638
|
+
return (0, types_js_1.prune)(fn.type.ret);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (op === "+" && ((0, types_js_1.isStringLike)(l) || (0, types_js_1.isStringLike)(r))) {
|
|
642
|
+
c.error("CL3077", e.opSpan, "text is joined with `.:` in CL++, not `+`", [`"a" .: b`]);
|
|
643
|
+
return types_js_1.T.string;
|
|
644
|
+
}
|
|
645
|
+
if (l.k === "option" || r.k === "option") {
|
|
646
|
+
c.strictError("CL3011", e.span, `arithmetic on a value that may be null`);
|
|
647
|
+
return types_js_1.T.float;
|
|
648
|
+
}
|
|
649
|
+
c.error("CL3078", e.opSpan, `'${op}' needs numbers, got ${(0, types_js_1.typeToString)(l)} and ${(0, types_js_1.typeToString)(r)}`);
|
|
650
|
+
return types_js_1.T.unknown;
|
|
651
|
+
}
|
|
652
|
+
function comparable(c, e, l, r) {
|
|
653
|
+
if ((0, types_js_1.isDynamic)(l) || (0, types_js_1.isDynamic)(r) || l.k === "null" || r.k === "null") {
|
|
654
|
+
if ((l.k === "null" && isPlainValue(r)) || (r.k === "null" && isPlainValue(l))) {
|
|
655
|
+
const other = l.k === "null" ? r : l;
|
|
656
|
+
c.warn("CL3042", e.span, `${(0, types_js_1.typeToString)(other)} is never null, so this is always ${e.op === "==" ? "false" : "true"}`);
|
|
657
|
+
}
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
if ((0, typeops_js_1.assignable)(c, (0, checker_js_1.widenLiteral)(l), r) !== "no" || (0, typeops_js_1.assignable)(c, (0, checker_js_1.widenLiteral)(r), l) !== "no") {
|
|
661
|
+
if (l.k === "lit" && r.k === "lit" && l.value !== r.value) {
|
|
662
|
+
c.warn("CL3043", e.span, `this comparison is always ${e.op === "==" ? "false" : "true"}`);
|
|
663
|
+
}
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
if ((0, types_js_1.isNumeric)(l) && (0, types_js_1.isNumeric)(r)) {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
c.error("CL3044", e.span, `${(0, types_js_1.typeToString)(l)} and ${(0, types_js_1.typeToString)(r)} can never be equal`);
|
|
670
|
+
}
|
|
671
|
+
function isPlainValue(t) {
|
|
672
|
+
const p = (0, types_js_1.prune)(t);
|
|
673
|
+
return p.k === "int" || p.k === "float" || p.k === "bool" || p.k === "string" || p.k === "datatype" || p.k === "lit" || (0, types_js_1.isValueTable)(p);
|
|
674
|
+
}
|
|
675
|
+
function checkUpdate(c, e) {
|
|
676
|
+
const target = (0, members_js_1.checkAssignTarget)(c, e.target, true);
|
|
677
|
+
const t = (0, types_js_1.prune)(target.type);
|
|
678
|
+
if (!(0, types_js_1.isNumeric)(t) && !(0, types_js_1.isDynamic)(t)) {
|
|
679
|
+
c.error("CL3079", e.span, `'${e.delta > 0 ? "++" : "--"}' needs a number, got ${(0, types_js_1.typeToString)(t)}`);
|
|
680
|
+
}
|
|
681
|
+
const key = exprPathKey(e.target);
|
|
682
|
+
if (key) {
|
|
683
|
+
c.scope.invalidate(key);
|
|
684
|
+
}
|
|
685
|
+
e.target.ty = target.type;
|
|
686
|
+
return (0, checker_js_1.widenLiteral)(t);
|
|
687
|
+
}
|
|
688
|
+
function checkCast(c, e) {
|
|
689
|
+
const from = (0, types_js_1.prune)(checkExpr(c, e.operand));
|
|
690
|
+
const to = c.resolveType(e.type);
|
|
691
|
+
const target = (0, types_js_1.prune)(to);
|
|
692
|
+
if ((0, types_js_1.isDynamic)(from) || (0, types_js_1.isDynamic)(target)) {
|
|
693
|
+
return to;
|
|
694
|
+
}
|
|
695
|
+
const base = from.k === "option" ? (0, types_js_1.prune)(from.inner) : from;
|
|
696
|
+
if ((0, typeops_js_1.assignable)(c, target, base) === "ok") {
|
|
697
|
+
return to;
|
|
698
|
+
}
|
|
699
|
+
if (base.k === "instance" && target.k === "instance" && c.api.isA(target.cls, base.cls)) {
|
|
700
|
+
return to; // downcast: WaitForChild("Map") as Model
|
|
701
|
+
}
|
|
702
|
+
if ((0, typeops_js_1.assignable)(c, base, target) === "ok") {
|
|
703
|
+
return to;
|
|
704
|
+
}
|
|
705
|
+
c.error("CL3080", e.span, `cannot convert ${(0, types_js_1.typeToString)(from)} to ${(0, types_js_1.typeToString)(target)}`, (0, types_js_1.isNumeric)(base) || (0, types_js_1.isNumeric)(target) ? ["convert numbers with int(x) / float(x) / string(x)"] : undefined);
|
|
706
|
+
return to;
|
|
707
|
+
}
|
|
708
|
+
function checkAwait(c, e) {
|
|
709
|
+
const t = (0, types_js_1.prune)(checkExpr(c, e.operand));
|
|
710
|
+
c.noteYield();
|
|
711
|
+
if (t.k === "task") {
|
|
712
|
+
return t.result;
|
|
713
|
+
}
|
|
714
|
+
if (t.k === "rbxSignal") {
|
|
715
|
+
// await part.Touched → part.Touched:Wait()
|
|
716
|
+
return t.args.length === 0 ? types_js_1.T.void : t.args.length === 1 ? t.args[0] : { k: "tuple", elems: t.args };
|
|
717
|
+
}
|
|
718
|
+
if (!(0, types_js_1.isDynamic)(t)) {
|
|
719
|
+
c.warn("CL3055", e.operand.span, `await on ${(0, types_js_1.typeToString)(t)} does nothing`, ["await a task, an async call or a Roblox event"]);
|
|
720
|
+
}
|
|
721
|
+
return t;
|
|
722
|
+
}
|
|
723
|
+
// ---------- functions as values ----------
|
|
724
|
+
function checkFunctionExpr(c, e, expected) {
|
|
725
|
+
const exp = expected ? (0, types_js_1.prune)(expected) : null;
|
|
726
|
+
const expFn = exp && exp.k === "func" ? exp : exp && exp.k === "option" && (0, types_js_1.prune)(exp.inner).k === "func" ? (0, types_js_1.prune)(exp.inner) : null;
|
|
727
|
+
const outer = c.fn;
|
|
728
|
+
const declaredRet = e.returnType ? c.resolveType(e.returnType) : null;
|
|
729
|
+
const ctx = {
|
|
730
|
+
decl: null,
|
|
731
|
+
declaredRet,
|
|
732
|
+
returns: [],
|
|
733
|
+
depth: (outer?.depth ?? 0) + 1,
|
|
734
|
+
isMethod: outer?.isMethod ?? false,
|
|
735
|
+
structId: outer?.structId ?? null,
|
|
736
|
+
selfSym: outer?.selfSym ?? null,
|
|
737
|
+
isAsync: false,
|
|
738
|
+
yields: false,
|
|
739
|
+
mutatesSelf: false,
|
|
740
|
+
contextOnly: outer?.contextOnly,
|
|
741
|
+
isParallel: false,
|
|
742
|
+
sym: null,
|
|
743
|
+
};
|
|
744
|
+
c.fn = ctx;
|
|
745
|
+
c.pushScope("function");
|
|
746
|
+
const params = e.params.map((p, i) => {
|
|
747
|
+
let t;
|
|
748
|
+
if (p.type) {
|
|
749
|
+
t = c.resolveType(p.type);
|
|
750
|
+
const want = expFn?.params[i]?.type;
|
|
751
|
+
if (want && (0, typeops_js_1.assignable)(c, t, want) === "no" && !(0, types_js_1.isDynamic)(want)) {
|
|
752
|
+
c.error("CL3036", p.span, `parameter '${p.name}' is ${(0, types_js_1.typeToString)(t)}, but ${(0, types_js_1.typeToString)(want)} is passed here`);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
else if (expFn && expFn.params[i]) {
|
|
756
|
+
t = expFn.params[i].type;
|
|
757
|
+
}
|
|
758
|
+
else if (p.variadic && expFn?.variadic) {
|
|
759
|
+
t = types_js_1.T.list(expFn.variadic);
|
|
760
|
+
}
|
|
761
|
+
else {
|
|
762
|
+
t = { k: "infer", resolved: null };
|
|
763
|
+
}
|
|
764
|
+
p.ty = p.variadic && !p.type && !expFn?.variadic ? types_js_1.T.list(types_js_1.T.any) : t;
|
|
765
|
+
return { name: p.name, type: t, optional: p.defaultValue !== null };
|
|
766
|
+
});
|
|
767
|
+
c.declareParams(e.params);
|
|
768
|
+
let ret;
|
|
769
|
+
if (Array.isArray(e.body)) {
|
|
770
|
+
for (const s of e.body) {
|
|
771
|
+
(0, statements_js_1.checkStatement)(c, s);
|
|
772
|
+
}
|
|
773
|
+
c.finishFunction(ctx, e.body, e.span, "function");
|
|
774
|
+
ret = declaredRet ?? (ctx.returns.length ? ctx.returns.reduce((a, b) => (0, typeops_js_1.join)(c, a, b)) : types_js_1.T.void);
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
const t = checkExpr(c, e.body, expFn?.ret);
|
|
778
|
+
c.markCopy(e.body);
|
|
779
|
+
ret = declaredRet ?? (0, checker_js_1.widenLiteral)(t);
|
|
780
|
+
if (declaredRet) {
|
|
781
|
+
c.expectAssignable(declaredRet, t, e.body.span, "cannot return this value");
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
c.popScope();
|
|
785
|
+
c.fn = outer;
|
|
786
|
+
e.fn = { mayYield: ctx.yields };
|
|
787
|
+
if (ctx.mutatesSelf && outer) {
|
|
788
|
+
outer.mutatesSelf = true;
|
|
789
|
+
}
|
|
790
|
+
return types_js_1.T.func(params, ret);
|
|
791
|
+
}
|