cluaupp 1.5.2 → 2.0.0-beta.1

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.
Files changed (195) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/README.md +27 -40
  3. package/api/generated/bench-latest.json +43 -21
  4. package/api/generated/lsp-index.json +1 -1
  5. package/api/manifests/roblox-target.manifest.json +5 -5
  6. package/api/normalized/roblox-target.profile.json +8729 -267
  7. package/api/roblox/luau-builtins.d.luau +185 -0
  8. package/api/roblox/surface.json +1 -0
  9. package/api/roblox-api.lock.json +11 -2
  10. package/bin/cluaupp.js +38 -0
  11. package/editors/vscode/syntaxes/clpp.tmLanguage.json +211 -0
  12. package/examples/README.md +9 -9
  13. package/examples/game/cluaupp.config.json +2 -2
  14. package/examples/game/default.project.json +4 -15
  15. package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau +8 -0
  16. package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau.map.json +13 -0
  17. package/examples/game/out/ServerScriptService/Leaderstats.server.luau +26 -0
  18. package/examples/game/out/ServerScriptService/Leaderstats.server.luau.map.json +13 -0
  19. package/examples/game/out/ServerScriptService/Main.server.luau +15 -0
  20. package/examples/game/out/ServerScriptService/Main.server.luau.map.json +13 -0
  21. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau +10 -0
  22. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau.map.json +13 -0
  23. package/examples/game/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
  24. package/examples/game/src/ServerScriptService/Leaderstats.server.clp +18 -0
  25. package/examples/game/src/ServerScriptService/Main.server.clp +9 -0
  26. package/examples/game/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
  27. package/generated/api/build-profile.js +1 -1
  28. package/generated/api/loader.js +2 -2
  29. package/generated/api/surface/build.js +328 -0
  30. package/generated/api/surface/luau-defs.js +595 -0
  31. package/generated/api/surface/model.js +8 -0
  32. package/generated/clpp/contract.js +6 -3
  33. package/generated/clpp/handshake.js +36 -0
  34. package/generated/clpp/modules.js +20 -9
  35. package/generated/clpp/runner.js +62 -31
  36. package/generated/compile.js +18 -1
  37. package/generated/compiler/ast.js +3 -0
  38. package/generated/compiler/checker/calls.js +999 -0
  39. package/generated/compiler/checker/checker.js +1215 -0
  40. package/generated/compiler/checker/exprs.js +791 -0
  41. package/generated/compiler/checker/flow.js +168 -0
  42. package/generated/compiler/checker/members.js +763 -0
  43. package/generated/compiler/checker/statements.js +792 -0
  44. package/generated/compiler/checker/symbols.js +90 -0
  45. package/generated/compiler/checker/typeops.js +348 -0
  46. package/generated/compiler/checker/yields.js +115 -0
  47. package/generated/compiler/diagnostics.js +138 -0
  48. package/generated/compiler/emit/emitter.js +2356 -0
  49. package/generated/compiler/emit/luau-types.js +137 -0
  50. package/generated/compiler/emit/names.js +130 -0
  51. package/generated/compiler/emit/runtime.js +432 -0
  52. package/generated/compiler/lexer.js +403 -0
  53. package/generated/compiler/parser.js +1881 -0
  54. package/generated/compiler/project.js +332 -0
  55. package/generated/compiler/roblox/api.js +388 -0
  56. package/generated/compiler/roblox/context.js +100 -0
  57. package/generated/compiler/source.js +59 -0
  58. package/generated/compiler/stdlib.js +232 -0
  59. package/generated/compiler/types.js +250 -0
  60. package/generated/target/build-cache.js +4 -4
  61. package/generated/target/gen-rojo-tree.js +15 -0
  62. package/generated/target/source-map.js +61 -8
  63. package/generated/target/studio-bridge.js +1 -0
  64. package/generated/utils/project.js +83 -25
  65. package/native/bin/.gitkeep +1 -0
  66. package/package.json +17 -6
  67. package/plugins/CluauppNav/Plugin.luau +28 -4
  68. package/rust/cluaupp/Cargo.lock +795 -0
  69. package/rust/cluaupp/Cargo.toml +17 -0
  70. package/rust/cluaupp/src/main.rs +717 -0
  71. package/scripts/postinstall-native.js +56 -0
  72. package/skills/README.md +3 -3
  73. package/skills/clpp-hive-flare/SKILL.md +30 -26
  74. package/skills/clpp-language/SKILL.md +67 -74
  75. package/skills/clpp-language/compiler.md +24 -36
  76. package/skills/clpp-style/SKILL.md +31 -44
  77. package/src/api/build-profile.ts +1 -1
  78. package/src/api/loader.ts +2 -2
  79. package/src/api/surface/build.ts +374 -0
  80. package/src/api/surface/luau-defs.ts +635 -0
  81. package/src/api/surface/model.ts +96 -0
  82. package/src/clpp/contract.ts +20 -3
  83. package/src/clpp/handshake.ts +25 -0
  84. package/src/clpp/modules.ts +17 -10
  85. package/src/clpp/runner.ts +52 -34
  86. package/src/compile.ts +20 -2
  87. package/src/compiler/ast.ts +336 -0
  88. package/src/compiler/checker/calls.ts +1025 -0
  89. package/src/compiler/checker/checker.ts +1352 -0
  90. package/src/compiler/checker/exprs.ts +805 -0
  91. package/src/compiler/checker/flow.ts +174 -0
  92. package/src/compiler/checker/members.ts +793 -0
  93. package/src/compiler/checker/statements.ts +802 -0
  94. package/src/compiler/checker/symbols.ts +219 -0
  95. package/src/compiler/checker/typeops.ts +355 -0
  96. package/src/compiler/checker/yields.ts +120 -0
  97. package/src/compiler/diagnostics.ts +163 -0
  98. package/src/compiler/emit/emitter.ts +2486 -0
  99. package/src/compiler/emit/luau-types.ts +143 -0
  100. package/src/compiler/emit/names.ts +130 -0
  101. package/src/compiler/emit/runtime.ts +438 -0
  102. package/src/compiler/lexer.ts +444 -0
  103. package/src/compiler/parser.ts +1943 -0
  104. package/src/compiler/project.ts +354 -0
  105. package/src/compiler/roblox/api.ts +408 -0
  106. package/src/compiler/roblox/context.ts +108 -0
  107. package/src/compiler/source.ts +67 -0
  108. package/src/compiler/stdlib.ts +256 -0
  109. package/src/compiler/types.ts +313 -0
  110. package/src/target/build-cache.ts +5 -8
  111. package/src/target/gen-rojo-tree.ts +15 -0
  112. package/src/target/source-map.ts +65 -27
  113. package/src/target/studio-bridge.ts +3 -0
  114. package/src/types.ts +3 -0
  115. package/src/utils/project.ts +79 -31
  116. package/templates/README.md +7 -0
  117. package/templates/{game → game-modern}/cluaupp.config.json +2 -2
  118. package/templates/{game → game-modern}/package.json +1 -2
  119. package/templates/game-modern/rokit.toml +2 -0
  120. package/templates/game-modern/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
  121. package/templates/game-modern/src/ServerScriptService/Handlers/Guard.server.clp +4 -0
  122. package/templates/game-modern/src/ServerScriptService/Main.server.clp +8 -0
  123. package/templates/game-modern/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
  124. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau +0 -29
  125. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau.map.json +0 -13
  126. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau +0 -83
  127. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau.map.json +0 -13
  128. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau +0 -18
  129. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau.map.json +0 -13
  130. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau +0 -14
  131. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau.map.json +0 -13
  132. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau +0 -15
  133. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau.map.json +0 -13
  134. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau +0 -50
  135. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau.map.json +0 -13
  136. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau +0 -12
  137. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau.map.json +0 -13
  138. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau +0 -16
  139. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau.map.json +0 -13
  140. package/examples/game/src/Client/Controllers/DataController.client.clpp +0 -7
  141. package/examples/game/src/Client/Main.client.clpp +0 -4
  142. package/examples/game/src/Declarations/Net.flare +0 -5
  143. package/examples/game/src/Include/GameTypes.clh +0 -40
  144. package/examples/game/src/Modules/Core/Guard.server.clpp +0 -7
  145. package/examples/game/src/Modules/Core/PlayerHandler.server.clpp +0 -23
  146. package/examples/game/src/Server/Boot/DataBoot.server.clpp +0 -13
  147. package/examples/game/src/Server/Main.server.clpp +0 -4
  148. package/templates/game/.clangd +0 -25
  149. package/templates/game/.vscode/extensions.json +0 -8
  150. package/templates/game/.vscode/settings.json +0 -17
  151. package/templates/game/Packages/.gitkeep +0 -0
  152. package/templates/game/compile_flags.txt +0 -6
  153. package/templates/game/default.project.json +0 -41
  154. package/templates/game/include/clpp/datatypes.clh +0 -575
  155. package/templates/game/include/clpp/generated/enums.clh +0 -4920
  156. package/templates/game/include/clpp/generated/instances.clh +0 -87182
  157. package/templates/game/include/clpp/libs/arrayindexer.clh +0 -12
  158. package/templates/game/include/clpp/libs/axiom.clh +0 -154
  159. package/templates/game/include/clpp/libs/bloom.clh +0 -58
  160. package/templates/game/include/clpp/libs/coil.clh +0 -37
  161. package/templates/game/include/clpp/libs/crest.clh +0 -22
  162. package/templates/game/include/clpp/libs/echo.clh +0 -52
  163. package/templates/game/include/clpp/libs/ember.clh +0 -17
  164. package/templates/game/include/clpp/libs/flare.clh +0 -65
  165. package/templates/game/include/clpp/libs/gleam.clh +0 -111
  166. package/templates/game/include/clpp/libs/guide.clh +0 -42
  167. package/templates/game/include/clpp/libs/helm.clh +0 -31
  168. package/templates/game/include/clpp/libs/hive.clh +0 -20
  169. package/templates/game/include/clpp/libs/keep.clh +0 -158
  170. package/templates/game/include/clpp/libs/lens.clh +0 -24
  171. package/templates/game/include/clpp/libs/mint.clh +0 -28
  172. package/templates/game/include/clpp/libs/net.clh +0 -31
  173. package/templates/game/include/clpp/libs/occlude.clh +0 -15
  174. package/templates/game/include/clpp/libs/pin.clh +0 -28
  175. package/templates/game/include/clpp/libs/promise.clh +0 -24
  176. package/templates/game/include/clpp/libs/roster.clh +0 -123
  177. package/templates/game/include/clpp/libs/shift.clh +0 -22
  178. package/templates/game/include/clpp/libs/spark.clh +0 -28
  179. package/templates/game/include/clpp/libs/stage.clh +0 -23
  180. package/templates/game/include/clpp/libs/sweep.clh +0 -23
  181. package/templates/game/include/clpp/libs/trace.clh +0 -18
  182. package/templates/game/include/clpp/libs/ward.clh +0 -19
  183. package/templates/game/include/clpp/libs.clh +0 -31
  184. package/templates/game/include/clpp/roblox.clh +0 -41
  185. package/templates/game/rokit.toml +0 -5
  186. package/templates/game/src/Client/Controllers/DataController.client.clpp +0 -7
  187. package/templates/game/src/Client/Main.client.clpp +0 -4
  188. package/templates/game/src/Declarations/Net.flare +0 -5
  189. package/templates/game/src/Include/GameTypes.clh +0 -40
  190. package/templates/game/src/Modules/Core/Guard.server.clpp +0 -7
  191. package/templates/game/src/Modules/Core/PlayerHandler.server.clpp +0 -24
  192. package/templates/game/src/Server/Boot/DataBoot.server.clpp +0 -13
  193. package/templates/game/src/Server/Main.server.clpp +0 -4
  194. package/templates/game/stylua.toml +0 -9
  195. package/templates/game/wally.toml +0 -12
@@ -0,0 +1,802 @@
1
+ /** Statement checking. */
2
+
3
+ import type { Expr, IterKind, MatchArm, Pattern, Stmt } from "../ast.js";
4
+ import { isDynamic, isNumeric, isStringLike, nonNull, prune, T, typeToString, type Type } from "../types.js";
5
+ import { foldConstant, widenLiteral, type Checker } from "./checker.js";
6
+ import { checkCondition, checkExpr, exprPathKey, rootSymbol } from "./exprs.js";
7
+ import { alwaysExits, applyNarrowings, conditionNarrowings, containsBreak } from "./flow.js";
8
+ import { checkAssignTarget } from "./members.js";
9
+ import type { Sym } from "./symbols.js";
10
+ import { assignable } from "./typeops.js";
11
+ import { mayYield } from "./yields.js";
12
+
13
+ export function checkStatement(c: Checker, stmt: Stmt): void {
14
+ switch (stmt.kind) {
15
+ case "Var":
16
+ checkVar(c, stmt);
17
+ return;
18
+ case "Assign":
19
+ checkAssign(c, stmt);
20
+ return;
21
+ case "Expr":
22
+ checkExprStatement(c, stmt);
23
+ return;
24
+ case "If":
25
+ checkIf(c, stmt);
26
+ return;
27
+ case "While": {
28
+ const cond = checkCondition(c, stmt.cond);
29
+ void cond;
30
+ c.loopDepth++;
31
+ const scope = c.pushScope("loop");
32
+ applyNarrowings(scope, conditionNarrowings(c, stmt.cond, true));
33
+ for (const s of stmt.body) {
34
+ checkStatement(c, s);
35
+ }
36
+ c.popScope();
37
+ c.loopDepth--;
38
+ checkInfiniteLoop(c, stmt, stmt.cond.kind === "Bool" && stmt.cond.value, stmt.body);
39
+ return;
40
+ }
41
+ case "For": {
42
+ c.pushScope("loop");
43
+ if (stmt.init) {
44
+ checkStatement(c, stmt.init);
45
+ }
46
+ if (stmt.cond) {
47
+ checkCondition(c, stmt.cond);
48
+ }
49
+ c.loopDepth++;
50
+ c.pushScope("block");
51
+ for (const s of stmt.body) {
52
+ checkStatement(c, s);
53
+ }
54
+ c.popScope();
55
+ c.loopDepth--;
56
+ if (stmt.step) {
57
+ checkStatement(c, stmt.step);
58
+ }
59
+ c.popScope();
60
+ checkInfiniteLoop(c, stmt, stmt.cond === null || (stmt.cond.kind === "Bool" && stmt.cond.value), stmt.body);
61
+ return;
62
+ }
63
+ case "ForIn":
64
+ checkForIn(c, stmt);
65
+ return;
66
+ case "Break":
67
+ if (c.loopDepth === 0 && c.switchDepth === 0) {
68
+ c.error("CL4003", stmt.span, "break outside of a loop or switch");
69
+ }
70
+ return;
71
+ case "Continue":
72
+ if (c.loopDepth === 0) {
73
+ c.error("CL4003", stmt.span, "continue outside of a loop");
74
+ }
75
+ return;
76
+ case "Return":
77
+ checkReturn(c, stmt);
78
+ return;
79
+ case "Switch":
80
+ checkSwitch(c, stmt);
81
+ return;
82
+ case "Match":
83
+ checkMatch(c, stmt);
84
+ return;
85
+ case "Try": {
86
+ c.pushScope("block");
87
+ for (const s of stmt.body) {
88
+ checkStatement(c, s);
89
+ }
90
+ c.popScope();
91
+ c.pushScope("block");
92
+ if (stmt.catchName) {
93
+ const sym = c.declare(stmt.catchName.name, "var", T.string, stmt.catchName.span);
94
+ sym.used = true;
95
+ stmt.catchName.sym = sym;
96
+ }
97
+ for (const s of stmt.handler) {
98
+ checkStatement(c, s);
99
+ }
100
+ c.popScope();
101
+ return;
102
+ }
103
+ case "Output": {
104
+ const t = checkExpr(c, stmt.value);
105
+ if (prune(t).k === "void") {
106
+ c.error("CL3013", stmt.value.span, "this expression has no value to print");
107
+ }
108
+ return;
109
+ }
110
+ case "Connect":
111
+ checkConnect(c, stmt);
112
+ return;
113
+ case "Signal": {
114
+ if (c.scope === c.fileScope && stmt.name.sym) {
115
+ return; // declared in the declare pass
116
+ }
117
+ const params = stmt.params.map((p) => c.resolveType(p));
118
+ const sym = c.declare(stmt.name.name, "signal", { k: "clSignal", params: params.length ? params : null }, stmt.name.span);
119
+ sym.signalParams = params.length ? params : null;
120
+ sym.used = true;
121
+ stmt.name.sym = sym;
122
+ return;
123
+ }
124
+ case "Using": {
125
+ const mod = c.scope.lookup(stmt.module);
126
+ if (!mod || mod.kind !== "module") {
127
+ c.error("CL2001", stmt.span, `'${stmt.module}' is not a linked module`);
128
+ return;
129
+ }
130
+ mod.used = true;
131
+ const fake: Expr = {
132
+ kind: "Member",
133
+ object: { kind: "Name", name: stmt.module, typeArgs: null, span: stmt.span },
134
+ name: stmt.member,
135
+ nameSpan: stmt.memberSpan,
136
+ typeArgs: null,
137
+ span: stmt.span,
138
+ };
139
+ const t = checkExpr(c, fake);
140
+ const sym = c.declare(stmt.member, "const", t, stmt.memberSpan);
141
+ sym.used = true;
142
+ stmt.target = fake;
143
+ stmt.sym = sym;
144
+ if (fake.res?.kind === "const") {
145
+ sym.constValue = fake.res.value;
146
+ }
147
+ return;
148
+ }
149
+ case "Block":
150
+ c.pushScope("block");
151
+ for (const s of stmt.body) {
152
+ checkStatement(c, s);
153
+ }
154
+ c.popScope();
155
+ return;
156
+ case "Decl": {
157
+ const decl = stmt.decl;
158
+ if (decl.kind === "Func" && c.fn) {
159
+ const sym = c.declare(decl.name, "func", c.signatureOf(decl, null), decl.nameSpan);
160
+ sym.decl = decl;
161
+ sym.used = true;
162
+ c.funcSyms.set(decl, sym);
163
+ checkNestedFunction(c, decl, sym);
164
+ return;
165
+ }
166
+ if (c.fn || c.scope !== c.fileScope) {
167
+ c.error("CL2030", stmt.span, `${decl.kind.toLowerCase()} declarations must be at the top level of the file`);
168
+ }
169
+ return;
170
+ }
171
+ default:
172
+ return;
173
+ }
174
+ }
175
+
176
+ function checkNestedFunction(c: Checker, decl: Extract<Stmt, { kind: "Decl" }>["decl"] & { kind: "Func" }, sym: Sym): void {
177
+ const outer = c.fn!;
178
+ const ctx = { ...outer, decl, declaredRet: decl.returnType ? c.resolveType(decl.returnType) : null, returns: [] as Type[], depth: outer.depth + 1, yields: false, sym };
179
+ c.fn = ctx;
180
+ c.pushScope("function");
181
+ c.declareParams(decl.params);
182
+ for (const s of decl.body || []) {
183
+ checkStatement(c, s);
184
+ }
185
+ c.finishFunction(ctx, decl.body || [], decl.nameSpan, decl.name);
186
+ c.popScope();
187
+ c.fn = outer;
188
+ if (ctx.yields) {
189
+ outer.yields = true;
190
+ }
191
+ const fnType = sym.type as Extract<Type, { k: "func" }>;
192
+ if (!decl.returnType && prune(fnType.ret).k === "infer") {
193
+ (prune(fnType.ret) as { resolved: Type | null }).resolved = ctx.returns.length ? ctx.returns.reduce((a, b) => (sameish(a, b) ? a : T.any)) : T.void;
194
+ }
195
+ (decl.fn ||= {}).mayYield = ctx.yields;
196
+ }
197
+
198
+ function sameish(a: Type, b: Type): boolean {
199
+ return typeToString(a) === typeToString(b);
200
+ }
201
+
202
+ // ---------- declarations ----------
203
+
204
+ function checkVar(c: Checker, stmt: Extract<Stmt, { kind: "Var" }>): void {
205
+ const predeclared = stmt.predeclared === true;
206
+ let declared: Type | null = null;
207
+ if (stmt.declaredType) {
208
+ declared = c.resolveType(stmt.declaredType);
209
+ } else if (stmt.decltypeOf) {
210
+ declared = widenLiteral(checkExpr(c, stmt.decltypeOf));
211
+ } else if (stmt.declKind === "atomic") {
212
+ declared = T.int;
213
+ }
214
+ let initType: Type | null = null;
215
+ if (stmt.init) {
216
+ initType = checkExpr(c, stmt.init, declared ?? undefined, stmt.unpack);
217
+ if (prune(initType).k === "void") {
218
+ c.error("CL3013", stmt.init.span, "this expression has no value");
219
+ initType = T.unknown;
220
+ }
221
+ if (declared) {
222
+ c.expectAssignable(declared, initType, stmt.init.span, `cannot initialize '${stmt.names.map((n) => n.name).join(", ")}'`);
223
+ }
224
+ c.markCopy(stmt.init);
225
+ } else if (!declared || (prune(declared).k !== "option" && prune(declared).k !== "any")) {
226
+ const name = stmt.names[0].name;
227
+ c.error("CL3014", stmt.span, `'${name}' needs an initial value`, declared && prune(declared).k === "instance" ? [`write \`${typeToString(declared)}? ${name} = null;\` for an Instance that starts empty`] : ["CL++ variables are always initialized: `let x = …;`"]);
228
+ }
229
+ if (stmt.declKind === "constexpr" && stmt.init && foldConstant(stmt.init) === undefined) {
230
+ c.error("CL3015", stmt.init.span, "constexpr needs a value computable at compile time (literals and arithmetic on them)");
231
+ }
232
+ const mutable = stmt.declKind === "let mut" || stmt.declKind === "typed" || stmt.declKind === "auto" || stmt.declKind === "observable" || stmt.declKind === "decltype";
233
+ if (stmt.unpack) {
234
+ const src = prune(initType ?? T.unknown);
235
+ stmt.names.forEach((n, i) => {
236
+ let t: Type = T.any;
237
+ if (src.k === "list") {
238
+ t = src.elem;
239
+ } else if (src.k === "tuple") {
240
+ t = src.elems[i] ?? T.void;
241
+ if (i >= src.elems.length) {
242
+ c.error("CL3016", n.span, `the value only has ${src.elems.length} element(s)`);
243
+ }
244
+ } else if (!isDynamic(src)) {
245
+ c.error("CL3016", stmt.init?.span ?? stmt.span, `cannot destructure ${typeToString(src)}; expected a list`);
246
+ }
247
+ const sym = c.declare(n.name, "var", widenLiteral(t), n.span, mutable);
248
+ n.sym = sym;
249
+ });
250
+ return;
251
+ }
252
+ const n = stmt.names[0];
253
+ let type: Type = declared ?? widenLiteral(initType ?? T.unknown);
254
+ if (stmt.declaredType && stmt.declaredType.kind === "Literal") {
255
+ type = declared!;
256
+ }
257
+ if (predeclared && n.sym) {
258
+ const pre = n.sym as Sym;
259
+ if (prune(pre.type).k === "unknown") {
260
+ pre.type = type;
261
+ }
262
+ return;
263
+ }
264
+ const kind = stmt.declKind === "const" || stmt.declKind === "constexpr" ? "const" : "var";
265
+ const sym = c.declare(n.name, kind, type, n.span, mutable);
266
+ n.sym = sym;
267
+ if (kind === "const" && stmt.init) {
268
+ const v = foldConstant(stmt.init);
269
+ if (v !== undefined) {
270
+ sym.constValue = v;
271
+ }
272
+ }
273
+ if (stmt.declKind === "observable") {
274
+ sym.observable = true;
275
+ sym.handlers = [];
276
+ }
277
+ if (stmt.declKind === "atomic") {
278
+ sym.atomic = true;
279
+ }
280
+ if (initType && prune(initType).k !== "null" && prune(type).k === "option") {
281
+ // `Part? p = Part()` is known non-null until reassigned.
282
+ const key = `s${sym.id}`;
283
+ if (prune(initType).k !== "option") {
284
+ c.scope.narrowed.set(key, nonNull(type));
285
+ }
286
+ }
287
+ }
288
+
289
+ // ---------- assignment ----------
290
+
291
+ function checkAssign(c: Checker, stmt: Extract<Stmt, { kind: "Assign" }>): void {
292
+ const target = checkAssignTarget(c, stmt.target, stmt.op !== "=");
293
+ const targetType = target.type;
294
+ let valueType = checkExpr(c, stmt.value, stmt.op === "=" ? targetType : undefined);
295
+ if (stmt.op === "=") {
296
+ c.expectAssignable(targetType, valueType, stmt.value.span, "cannot assign");
297
+ c.markCopy(stmt.value);
298
+ } else {
299
+ const op = stmt.op[0];
300
+ const tt = prune(targetType);
301
+ const vt = prune(valueType);
302
+ if (isNumeric(tt) && isNumeric(vt)) {
303
+ if (tt.k === "int" && (vt.k === "float" || (vt.k === "lit" && !Number.isInteger(vt.value)))) {
304
+ c.error("CL3012", stmt.value.span, `'${stmt.op}' would store a float in the int '${describeTarget(stmt.target)}'`, ["convert with int(...) or declare the variable as float"]);
305
+ }
306
+ } else if (tt.k === "datatype" || vt.k === "datatype") {
307
+ const result = c.api.operator(op, tt, vt);
308
+ if (!result) {
309
+ c.error("CL3017", stmt.span, `'${stmt.op}' is not defined for ${typeToString(tt)} and ${typeToString(vt)}`);
310
+ } else {
311
+ valueType = result;
312
+ }
313
+ } else if (!isDynamic(tt) && !isDynamic(vt)) {
314
+ if (stmt.op === "+=" && isStringLike(tt)) {
315
+ c.error("CL3017", stmt.span, "strings are joined with `.:` in CL++", [`write \`${describeTarget(stmt.target)} = ${describeTarget(stmt.target)} .: …;\``]);
316
+ } else {
317
+ c.error("CL3017", stmt.span, `'${stmt.op}' needs numbers, got ${typeToString(tt)} and ${typeToString(vt)}`);
318
+ }
319
+ }
320
+ }
321
+ const key = exprPathKey(stmt.target);
322
+ if (key) {
323
+ c.scope.invalidate(key);
324
+ const vt = prune(valueType);
325
+ if (stmt.op === "=" && prune(targetType).k === "option" && vt.k !== "option" && vt.k !== "null" && !isDynamic(vt)) {
326
+ c.scope.narrowed.set(key, nonNull(targetType));
327
+ }
328
+ }
329
+ }
330
+
331
+ function describeTarget(e: Expr): string {
332
+ switch (e.kind) {
333
+ case "Name":
334
+ return e.name;
335
+ case "Member":
336
+ return `${describeTarget(e.object)}.${e.name}`;
337
+ case "SelfField":
338
+ return `@${e.name}`;
339
+ case "Index":
340
+ return `${describeTarget(e.object)}[…]`;
341
+ default:
342
+ return "value";
343
+ }
344
+ }
345
+
346
+ function checkExprStatement(c: Checker, stmt: Extract<Stmt, { kind: "Expr" }>): void {
347
+ const e = stmt.expr;
348
+ checkExpr(c, e);
349
+ switch (e.kind) {
350
+ case "Call":
351
+ case "New":
352
+ case "Update":
353
+ case "Await":
354
+ case "Spawn":
355
+ case "Thread":
356
+ case "Join":
357
+ case "Parallel":
358
+ case "PCall":
359
+ case "Error":
360
+ return;
361
+ default:
362
+ c.warn("CL4004", e.span, "this expression has no effect", ["did you mean an assignment or a call?"]);
363
+ }
364
+ }
365
+
366
+ // ---------- control flow ----------
367
+
368
+ function checkIf(c: Checker, stmt: Extract<Stmt, { kind: "If" }>): void {
369
+ if (stmt.letName) {
370
+ const t = checkExpr(c, stmt.cond);
371
+ const scope = c.pushScope("block");
372
+ const sym = c.declare(stmt.letName.name, "var", widenLiteral(nonNull(t)), stmt.letName.span);
373
+ sym.used = true;
374
+ stmt.letName.sym = sym;
375
+ void scope;
376
+ for (const s of stmt.then) {
377
+ checkStatement(c, s);
378
+ }
379
+ c.popScope();
380
+ if (stmt.else) {
381
+ c.pushScope("block");
382
+ for (const s of stmt.else) {
383
+ checkStatement(c, s);
384
+ }
385
+ c.popScope();
386
+ }
387
+ return;
388
+ }
389
+ checkCondition(c, stmt.cond);
390
+ const yes = conditionNarrowings(c, stmt.cond, true);
391
+ const no = conditionNarrowings(c, stmt.cond, false);
392
+ const thenScope = c.pushScope("block");
393
+ applyNarrowings(thenScope, yes);
394
+ for (const s of stmt.then) {
395
+ checkStatement(c, s);
396
+ }
397
+ c.popScope();
398
+ if (stmt.else) {
399
+ const elseScope = c.pushScope("block");
400
+ applyNarrowings(elseScope, no);
401
+ for (const s of stmt.else) {
402
+ checkStatement(c, s);
403
+ }
404
+ c.popScope();
405
+ }
406
+ const thenExits = alwaysExits(c, stmt.then);
407
+ const elseExits = stmt.else ? alwaysExits(c, stmt.else) : false;
408
+ if (thenExits && !elseExits) {
409
+ applyNarrowings(c.scope, no);
410
+ } else if (elseExits && !thenExits) {
411
+ applyNarrowings(c.scope, yes);
412
+ }
413
+ }
414
+
415
+ function iterationOf(c: Checker, t: Type, span: Expr["span"]): { kind: IterKind; elem: Type } {
416
+ const p = prune(t);
417
+ switch (p.k) {
418
+ case "int":
419
+ case "lit":
420
+ if (typeof (p as { value?: unknown }).value === "string") {
421
+ return { kind: "string", elem: T.string };
422
+ }
423
+ return { kind: "range", elem: T.int };
424
+ case "float":
425
+ c.error("CL3018", span, "for … in over a number needs an int count", ["convert with int(...)"]);
426
+ return { kind: "range", elem: T.int };
427
+ case "string":
428
+ return { kind: "string", elem: T.string };
429
+ case "list":
430
+ return { kind: "list", elem: p.elem };
431
+ case "tuple":
432
+ return { kind: "list", elem: p.elems.reduce((a, b) => (typeToString(a) === typeToString(b) ? a : T.any), p.elems[0] ?? T.any) };
433
+ case "dict":
434
+ return { kind: "dict", elem: p.key };
435
+ case "table":
436
+ return { kind: "dict", elem: T.string };
437
+ case "option":
438
+ c.strictError("CL3011", span, `${typeToString(p)} may be null; check it before iterating`);
439
+ return iterationOf(c, p.inner, span);
440
+ case "any":
441
+ case "unknown":
442
+ case "infer":
443
+ return { kind: "generic", elem: T.any };
444
+ case "instance":
445
+ c.error("CL3018", span, `cannot iterate over ${p.cls} itself`, ["iterate its children: `for (let child in x.GetChildren())`"]);
446
+ return { kind: "generic", elem: T.any };
447
+ default:
448
+ c.error("CL3018", span, `cannot iterate over ${typeToString(p)}`);
449
+ return { kind: "generic", elem: T.any };
450
+ }
451
+ }
452
+
453
+ function checkForIn(c: Checker, stmt: Extract<Stmt, { kind: "ForIn" }>): void {
454
+ const it = checkExpr(c, stmt.iterable);
455
+ const { kind, elem } = iterationOf(c, it, stmt.iterable.span);
456
+ stmt.iterKind = kind;
457
+ let varType = widenLiteral(elem);
458
+ if (stmt.declaredType) {
459
+ const declared = c.resolveType(stmt.declaredType);
460
+ const r = assignable(c, declared, elem);
461
+ const de = prune(declared);
462
+ const ee = prune(elem);
463
+ const downcast = de.k === "instance" && ee.k === "instance" && c.api.isA(de.cls, ee.cls);
464
+ if (r !== "ok" && !downcast) {
465
+ c.expectAssignable(declared, elem, stmt.iterable.span, `loop variable '${stmt.name.name}'`);
466
+ }
467
+ varType = declared;
468
+ if (downcast) {
469
+ stmt.downcast = true;
470
+ }
471
+ }
472
+ c.loopDepth++;
473
+ c.pushScope("loop");
474
+ const sym = c.declare(stmt.name.name, "var", varType, stmt.name.span, stmt.mutable);
475
+ sym.used = sym.used || stmt.name.name === "_";
476
+ stmt.name.sym = sym;
477
+ for (const s of stmt.body) {
478
+ checkStatement(c, s);
479
+ }
480
+ c.popScope();
481
+ c.loopDepth--;
482
+ if (!sym.used && !stmt.name.name.startsWith("_")) {
483
+ // reported by popScope as unused
484
+ }
485
+ }
486
+
487
+ function checkReturn(c: Checker, stmt: Extract<Stmt, { kind: "Return" }>): void {
488
+ const fn = c.fn;
489
+ if (!fn) {
490
+ if (c.input.kind === "module") {
491
+ c.error("CL4005", stmt.span, "return outside of a function");
492
+ } else if (stmt.value) {
493
+ c.error("CL4005", stmt.span, "a script cannot return a value");
494
+ }
495
+ if (stmt.value) {
496
+ checkExpr(c, stmt.value);
497
+ }
498
+ return;
499
+ }
500
+ const expected = fn.declaredRet;
501
+ if (!stmt.value) {
502
+ if (expected && !isDynamic(expected) && prune(expected).k !== "void" && prune(expected).k !== "option") {
503
+ c.error("CL4006", stmt.span, `'${fn.decl?.name ?? "function"}' must return ${typeToString(expected)}`);
504
+ }
505
+ fn.returns.push(T.void);
506
+ return;
507
+ }
508
+ const t = checkExpr(c, stmt.value, expected ?? undefined);
509
+ // Returning a local hands it over (it dies with the frame); only aliased places need a copy.
510
+ if (!(stmt.value.kind === "Name" && stmt.value.res?.kind === "local")) {
511
+ c.markCopy(stmt.value);
512
+ }
513
+ if (expected) {
514
+ if (prune(expected).k === "void" && prune(t).k !== "void") {
515
+ c.error("CL4007", stmt.value.span, `'${fn.decl?.name ?? "function"}' is declared -> void and cannot return a value`);
516
+ } else {
517
+ c.expectAssignable(expected, t, stmt.value.span, "cannot return this value");
518
+ }
519
+ }
520
+ fn.returns.push(t);
521
+ }
522
+
523
+ function checkSwitch(c: Checker, stmt: Extract<Stmt, { kind: "Switch" }>): void {
524
+ const subject = checkExpr(c, stmt.subject);
525
+ c.switchDepth++;
526
+ for (const k of stmt.cases) {
527
+ if (k.test) {
528
+ const t = checkExpr(c, k.test, subject);
529
+ if (assignable(c, widenLiteral(subject), t) === "no" && assignable(c, widenLiteral(t), subject) === "no") {
530
+ c.error("CL3019", k.test.span, `case ${typeToString(t)} can never match a ${typeToString(subject)}`);
531
+ }
532
+ }
533
+ c.pushScope("block");
534
+ for (const s of k.body) {
535
+ checkStatement(c, s);
536
+ }
537
+ c.popScope();
538
+ }
539
+ c.switchDepth--;
540
+ }
541
+
542
+ // ---------- match ----------
543
+
544
+ function checkMatch(c: Checker, stmt: Extract<Stmt, { kind: "Match" }>): void {
545
+ const subjectType = checkExpr(c, stmt.subject);
546
+ const subject = prune(subjectType);
547
+ const covered = new Set<string>();
548
+ let wildcard = false;
549
+ for (const arm of stmt.arms) {
550
+ if (wildcard) {
551
+ c.warn("CL4008", arm.span, "this arm is unreachable: an earlier `_` arm matches everything");
552
+ }
553
+ c.pushScope("block");
554
+ const bound = checkPattern(c, arm.pattern, subject, covered);
555
+ if (arm.pattern.kind === "Wildcard" && !arm.guard) {
556
+ wildcard = true;
557
+ }
558
+ if (bound && arm.pattern.kind === "Case" && arm.pattern.binding) {
559
+ const sym = c.declare(arm.pattern.binding.name, "var", bound, arm.pattern.binding.span);
560
+ sym.used = true;
561
+ arm.pattern.binding.sym = sym;
562
+ }
563
+ if (arm.guard) {
564
+ checkCondition(c, arm.guard);
565
+ applyNarrowings(c.scope, conditionNarrowings(c, arm.guard, true));
566
+ }
567
+ for (const s of arm.body) {
568
+ checkStatement(c, s);
569
+ }
570
+ c.popScope();
571
+ if (arm.guard && arm.pattern.kind === "Case") {
572
+ covered.delete(arm.pattern.name);
573
+ }
574
+ }
575
+ const required = requiredCases(c, subject);
576
+ if (required && !wildcard) {
577
+ const missing = required.filter((r) => !covered.has(r));
578
+ if (missing.length) {
579
+ c.error("CL4009", stmt.span, `non-exhaustive match: missing ${missing.map((m) => `'${m}'`).join(", ")}`, ["add the missing arms or a `_ ~> …` arm"]);
580
+ } else {
581
+ stmt.exhaustive = true;
582
+ }
583
+ } else if (wildcard) {
584
+ stmt.exhaustive = true;
585
+ }
586
+ }
587
+
588
+ function requiredCases(c: Checker, subject: Type): string[] | null {
589
+ switch (subject.k) {
590
+ case "enum": {
591
+ const info = c.enums.get(subject.id);
592
+ return info ? info.cases.map((k) => k.name) : null;
593
+ }
594
+ case "option":
595
+ return ["Some", "None"];
596
+ case "result":
597
+ return ["Ok", "Err"];
598
+ case "bool":
599
+ return ["true", "false"];
600
+ default:
601
+ return null;
602
+ }
603
+ }
604
+
605
+ /** Resolve a pattern against the subject; returns the type bound by `Case(x)`. */
606
+ function checkPattern(c: Checker, pattern: Pattern, subject: Type, covered: Set<string>): Type | null {
607
+ if (pattern.kind === "Wildcard") {
608
+ return null;
609
+ }
610
+ if (pattern.kind === "Value") {
611
+ const t = checkExpr(c, pattern.expr, subject);
612
+ if (pattern.expr.kind === "Bool") {
613
+ covered.add(String(pattern.expr.value));
614
+ }
615
+ if (pattern.expr.res?.kind === "enumCase" && subject.k === "enum") {
616
+ const info = c.enums.get(subject.id);
617
+ const name = info?.cases[pattern.expr.res.index]?.name;
618
+ if (name) {
619
+ covered.add(name);
620
+ }
621
+ }
622
+ if (assignable(c, widenLiteral(subject), t) === "no" && assignable(c, widenLiteral(t), subject) === "no" && !isDynamic(subject)) {
623
+ c.error("CL3019", pattern.span, `pattern ${typeToString(t)} can never match a ${typeToString(subject)}`);
624
+ }
625
+ return null;
626
+ }
627
+ const name = pattern.name;
628
+ switch (subject.k) {
629
+ case "enum": {
630
+ const info = c.enums.get(subject.id);
631
+ const index = info ? info.cases.findIndex((k) => k.name === name) : -1;
632
+ if (!info || index < 0) {
633
+ return valueFallback(c, pattern, subject);
634
+ }
635
+ const kase = info.cases[index];
636
+ pattern.res = { kind: "enumCase", enumName: info.id, index, hasPayload: Boolean(kase.payload), anyPayload: info.hasPayload };
637
+ covered.add(name);
638
+ if (pattern.binding && !kase.payload) {
639
+ c.error("CL3031", pattern.span, `${info.name}.${name} carries no value to bind`);
640
+ }
641
+ return kase.payload;
642
+ }
643
+ case "option":
644
+ if (name === "Some") {
645
+ covered.add("Some");
646
+ pattern.res = { kind: "builtin", name: "Some" };
647
+ return subject.inner;
648
+ }
649
+ if (name === "None") {
650
+ covered.add("None");
651
+ pattern.res = { kind: "builtin", name: "None" };
652
+ return null;
653
+ }
654
+ return valueFallback(c, pattern, subject);
655
+ case "result":
656
+ if (name === "Ok" || name === "Err") {
657
+ covered.add(name);
658
+ pattern.res = { kind: "builtin", name };
659
+ return name === "Ok" ? subject.ok : subject.err;
660
+ }
661
+ return valueFallback(c, pattern, subject);
662
+ case "rbxEnumItem": {
663
+ const items = c.api.enums[subject.enumName]?.items || [];
664
+ if (items.includes(name)) {
665
+ pattern.res = { kind: "rbxEnumItem", enumName: subject.enumName, item: name };
666
+ return null;
667
+ }
668
+ return valueFallback(c, pattern, subject);
669
+ }
670
+ default:
671
+ if (name === "Some" || name === "None") {
672
+ if (!isDynamic(subject)) {
673
+ c.error("CL3032", pattern.span, `${name} only matches an Option, but the subject is ${typeToString(subject)}`);
674
+ }
675
+ pattern.res = { kind: "builtin", name };
676
+ return pattern.binding ? T.any : null;
677
+ }
678
+ return valueFallback(c, pattern, subject);
679
+ }
680
+ }
681
+
682
+ /** `case` name that is not a variant: compare against a constant/variable with that name. */
683
+ function valueFallback(c: Checker, pattern: Extract<Pattern, { kind: "Case" }>, subject: Type): Type | null {
684
+ if (pattern.binding) {
685
+ c.error("CL3033", pattern.span, `'${pattern.name}' is not a case of ${typeToString(subject)}`);
686
+ return T.unknown;
687
+ }
688
+ const expr: Expr = pattern.qualifier
689
+ ? { kind: "Member", object: pattern.qualifier, name: pattern.name, nameSpan: pattern.span, typeArgs: null, span: pattern.span }
690
+ : { kind: "Name", name: pattern.name, typeArgs: null, span: pattern.span };
691
+ const t = checkExpr(c, expr, subject);
692
+ pattern.valueExpr = expr;
693
+ if (assignable(c, widenLiteral(subject), t) === "no" && !isDynamic(t) && !isDynamic(subject)) {
694
+ const info = subject.k === "enum" ? c.enums.get(subject.id) : null;
695
+ c.error("CL3033", pattern.span, `'${pattern.name}' is not a case of ${typeToString(subject)}`, info ? [`cases: ${info.cases.map((k) => k.name).join(", ")}`] : undefined);
696
+ }
697
+ return null;
698
+ }
699
+
700
+ // ---------- connections ----------
701
+
702
+ function checkConnect(c: Checker, stmt: Extract<Stmt, { kind: "Connect" }>): void {
703
+ const signalType = prune(checkExpr(c, stmt.signal));
704
+ if (signalType.k === "clSignal") {
705
+ const sym = rootSymbol(stmt.signal);
706
+ const expected: Type = signalType.params ? T.func(signalType.params.map((p, i) => ({ name: `arg${i + 1}`, type: p, optional: false })), T.void) : T.func([], T.void);
707
+ const handlerType = prune(checkExpr(c, stmt.handler, signalType.params ? expected : undefined));
708
+ if (handlerType.k !== "func" && !isDynamic(handlerType)) {
709
+ c.error("CL3034", stmt.handler.span, "a signal connects to a function name or `func (…) { … }`");
710
+ }
711
+ if (sym) {
712
+ (sym.handlers ||= []).push(stmt.handler);
713
+ sym.used = true;
714
+ }
715
+ stmt.target = "cl";
716
+ return;
717
+ }
718
+ if (signalType.k === "rbxSignal") {
719
+ const expected = T.func(signalType.args.map((a, i) => ({ name: `arg${i + 1}`, type: a, optional: false })), T.void);
720
+ const handlerType = prune(checkExpr(c, stmt.handler, expected));
721
+ checkHandler(c, handlerType, signalType.args, stmt.handler.span);
722
+ stmt.target = "rbx";
723
+ return;
724
+ }
725
+ if (!isDynamic(signalType)) {
726
+ c.error("CL3034", stmt.signal.span, `'~>' connects a signal or a Roblox event, not ${typeToString(signalType)}`);
727
+ }
728
+ checkExpr(c, stmt.handler);
729
+ stmt.target = "rbx";
730
+ }
731
+
732
+ export function checkHandler(c: Checker, handlerType: Type, args: Type[], span: Expr["span"]): void {
733
+ const h = prune(handlerType);
734
+ if (h.k !== "func") {
735
+ if (!isDynamic(h)) {
736
+ c.error("CL3034", span, `expected a function to handle the event, got ${typeToString(h)}`);
737
+ }
738
+ return;
739
+ }
740
+ if (h.params.length > args.length && !h.params.slice(args.length).every((p) => p.optional)) {
741
+ c.error("CL3035", span, `the handler takes ${h.params.length} parameter(s) but the event only passes ${args.length}`, [
742
+ `event arguments: (${args.map((a) => typeToString(a)).join(", ")})`,
743
+ ]);
744
+ return;
745
+ }
746
+ h.params.forEach((p, i) => {
747
+ const arg = args[i];
748
+ if (!arg) {
749
+ return;
750
+ }
751
+ const pt = prune(p.type);
752
+ if (pt.k === "infer" && !pt.resolved) {
753
+ pt.resolved = arg;
754
+ return;
755
+ }
756
+ const r = assignable(c, p.type, arg);
757
+ if (r === "no") {
758
+ c.error("CL3036", span, `handler parameter '${p.name}' is ${typeToString(p.type)}, but the event passes ${typeToString(arg)}`);
759
+ }
760
+ });
761
+ }
762
+
763
+ // ---------- loops that never yield ----------
764
+
765
+ function checkInfiniteLoop(c: Checker, stmt: Stmt, forever: boolean, body: Stmt[]): void {
766
+ if (!forever || containsBreak(body) || alwaysExits(c, body) || containsReturn(body)) {
767
+ return;
768
+ }
769
+ if (!mayYield(c, body)) {
770
+ c.error("CL4020", stmt.span, "this loop never ends and never yields: it freezes the game", [
771
+ "wait inside it (`task.wait()`, `RunService.Heartbeat.Wait()`) or connect to an event instead",
772
+ ]);
773
+ }
774
+ }
775
+
776
+ function containsReturn(body: Stmt[]): boolean {
777
+ const visit = (s: Stmt): boolean => {
778
+ switch (s.kind) {
779
+ case "Return":
780
+ return true;
781
+ case "If":
782
+ return s.then.some(visit) || (s.else ? s.else.some(visit) : false);
783
+ case "Block":
784
+ return s.body.some(visit);
785
+ case "While":
786
+ case "For":
787
+ case "ForIn":
788
+ return s.body.some(visit);
789
+ case "Try":
790
+ return s.body.some(visit) || s.handler.some(visit);
791
+ case "Match":
792
+ return s.arms.some((a: MatchArm) => a.body.some(visit));
793
+ case "Switch":
794
+ return s.cases.some((k) => k.body.some(visit));
795
+ default:
796
+ return false;
797
+ }
798
+ };
799
+ return body.some(visit);
800
+ }
801
+
802
+ export { isNumeric };