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