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.
Files changed (210) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +29 -41
  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/README.md +1 -1
  12. package/editors/vscode/package.json +50 -15
  13. package/editors/vscode/snippets/flare.json +1 -1
  14. package/editors/vscode/snippets/hive.json +1 -1
  15. package/editors/vscode/syntaxes/clpp.tmLanguage.json +211 -0
  16. package/examples/README.md +9 -9
  17. package/examples/game/cluaupp.config.json +2 -2
  18. package/examples/game/default.project.json +4 -15
  19. package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau +8 -0
  20. package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau.map.json +13 -0
  21. package/examples/game/out/ServerScriptService/Leaderstats.server.luau +26 -0
  22. package/examples/game/out/ServerScriptService/Leaderstats.server.luau.map.json +13 -0
  23. package/examples/game/out/ServerScriptService/Main.server.luau +15 -0
  24. package/examples/game/out/ServerScriptService/Main.server.luau.map.json +13 -0
  25. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau +10 -0
  26. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau.map.json +13 -0
  27. package/examples/game/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
  28. package/examples/game/src/ServerScriptService/Leaderstats.server.clp +18 -0
  29. package/examples/game/src/ServerScriptService/Main.server.clp +9 -0
  30. package/examples/game/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
  31. package/generated/api/build-profile.js +1 -1
  32. package/generated/api/loader.js +2 -2
  33. package/generated/api/surface/build.js +328 -0
  34. package/generated/api/surface/luau-defs.js +595 -0
  35. package/generated/api/surface/model.js +8 -0
  36. package/generated/api-cli.js +2 -2
  37. package/generated/cli.js +18 -17
  38. package/generated/clpp/contract.js +6 -3
  39. package/generated/clpp/handshake.js +36 -0
  40. package/generated/clpp/modules.js +20 -9
  41. package/generated/clpp/runner.js +62 -31
  42. package/generated/compile.js +18 -1
  43. package/generated/compiler/ast.js +3 -0
  44. package/generated/compiler/checker/calls.js +999 -0
  45. package/generated/compiler/checker/checker.js +1215 -0
  46. package/generated/compiler/checker/exprs.js +791 -0
  47. package/generated/compiler/checker/flow.js +168 -0
  48. package/generated/compiler/checker/members.js +763 -0
  49. package/generated/compiler/checker/statements.js +792 -0
  50. package/generated/compiler/checker/symbols.js +90 -0
  51. package/generated/compiler/checker/typeops.js +348 -0
  52. package/generated/compiler/checker/yields.js +115 -0
  53. package/generated/compiler/diagnostics.js +138 -0
  54. package/generated/compiler/emit/emitter.js +2356 -0
  55. package/generated/compiler/emit/luau-types.js +137 -0
  56. package/generated/compiler/emit/names.js +130 -0
  57. package/generated/compiler/emit/runtime.js +432 -0
  58. package/generated/compiler/lexer.js +403 -0
  59. package/generated/compiler/parser.js +1881 -0
  60. package/generated/compiler/project.js +332 -0
  61. package/generated/compiler/roblox/api.js +388 -0
  62. package/generated/compiler/roblox/context.js +100 -0
  63. package/generated/compiler/source.js +59 -0
  64. package/generated/compiler/stdlib.js +232 -0
  65. package/generated/compiler/types.js +250 -0
  66. package/generated/intellisense.js +1 -15
  67. package/generated/lsp.js +1 -1
  68. package/generated/target/build-cache.js +4 -4
  69. package/generated/target/datamodel-generate.js +12 -0
  70. package/generated/target/gen-rojo-tree.js +15 -0
  71. package/generated/target/source-map.js +61 -8
  72. package/generated/target/studio-bridge.js +1 -0
  73. package/generated/utils/project.js +104 -31
  74. package/include/README.md +5 -0
  75. package/native/bin/.gitkeep +1 -0
  76. package/package.json +17 -6
  77. package/plugins/CluauppNav/Plugin.luau +28 -4
  78. package/rust/cluaupp/Cargo.lock +795 -0
  79. package/rust/cluaupp/Cargo.toml +17 -0
  80. package/rust/cluaupp/src/main.rs +717 -0
  81. package/scripts/postinstall-native.js +56 -0
  82. package/skills/README.md +3 -3
  83. package/skills/clpp-hive-flare/SKILL.md +30 -26
  84. package/skills/clpp-language/SKILL.md +67 -74
  85. package/skills/clpp-language/compiler.md +24 -36
  86. package/skills/clpp-style/SKILL.md +31 -44
  87. package/src/api/build-profile.ts +1 -1
  88. package/src/api/loader.ts +2 -2
  89. package/src/api/surface/build.ts +374 -0
  90. package/src/api/surface/luau-defs.ts +635 -0
  91. package/src/api/surface/model.ts +96 -0
  92. package/src/api-cli.ts +2 -2
  93. package/src/cli.ts +19 -18
  94. package/src/clpp/contract.ts +20 -3
  95. package/src/clpp/handshake.ts +25 -0
  96. package/src/clpp/modules.ts +17 -10
  97. package/src/clpp/runner.ts +52 -34
  98. package/src/compile.ts +20 -2
  99. package/src/compiler/ast.ts +336 -0
  100. package/src/compiler/checker/calls.ts +1025 -0
  101. package/src/compiler/checker/checker.ts +1352 -0
  102. package/src/compiler/checker/exprs.ts +805 -0
  103. package/src/compiler/checker/flow.ts +174 -0
  104. package/src/compiler/checker/members.ts +793 -0
  105. package/src/compiler/checker/statements.ts +802 -0
  106. package/src/compiler/checker/symbols.ts +219 -0
  107. package/src/compiler/checker/typeops.ts +355 -0
  108. package/src/compiler/checker/yields.ts +120 -0
  109. package/src/compiler/diagnostics.ts +163 -0
  110. package/src/compiler/emit/emitter.ts +2486 -0
  111. package/src/compiler/emit/luau-types.ts +143 -0
  112. package/src/compiler/emit/names.ts +130 -0
  113. package/src/compiler/emit/runtime.ts +438 -0
  114. package/src/compiler/lexer.ts +444 -0
  115. package/src/compiler/parser.ts +1943 -0
  116. package/src/compiler/project.ts +354 -0
  117. package/src/compiler/roblox/api.ts +408 -0
  118. package/src/compiler/roblox/context.ts +108 -0
  119. package/src/compiler/source.ts +67 -0
  120. package/src/compiler/stdlib.ts +256 -0
  121. package/src/compiler/types.ts +313 -0
  122. package/src/intellisense.ts +1 -13
  123. package/src/lsp.ts +1 -1
  124. package/src/target/build-cache.ts +5 -8
  125. package/src/target/datamodel-generate.ts +12 -1
  126. package/src/target/gen-rojo-tree.ts +15 -0
  127. package/src/target/source-map.ts +65 -27
  128. package/src/target/studio-bridge.ts +3 -0
  129. package/src/types.ts +3 -0
  130. package/src/utils/project.ts +99 -37
  131. package/templates/README.md +7 -0
  132. package/templates/{game → game-modern}/cluaupp.config.json +2 -2
  133. package/templates/{game → game-modern}/package.json +1 -2
  134. package/templates/game-modern/rokit.toml +2 -0
  135. package/templates/game-modern/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
  136. package/templates/game-modern/src/ServerScriptService/Handlers/Guard.server.clp +4 -0
  137. package/templates/game-modern/src/ServerScriptService/Main.server.clp +8 -0
  138. package/templates/game-modern/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
  139. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau +0 -29
  140. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau.map.json +0 -13
  141. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau +0 -83
  142. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau.map.json +0 -13
  143. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau +0 -18
  144. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau.map.json +0 -13
  145. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau +0 -14
  146. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau.map.json +0 -13
  147. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau +0 -15
  148. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau.map.json +0 -13
  149. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau +0 -50
  150. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau.map.json +0 -13
  151. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau +0 -12
  152. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau.map.json +0 -13
  153. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau +0 -16
  154. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau.map.json +0 -13
  155. package/examples/game/src/Client/Controllers/DataController.client.clpp +0 -7
  156. package/examples/game/src/Client/Main.client.clpp +0 -4
  157. package/examples/game/src/Declarations/Net.flare +0 -5
  158. package/examples/game/src/Include/GameTypes.clh +0 -40
  159. package/examples/game/src/Modules/Core/Guard.server.clpp +0 -7
  160. package/examples/game/src/Modules/Core/PlayerHandler.server.clpp +0 -23
  161. package/examples/game/src/Server/Boot/DataBoot.server.clpp +0 -13
  162. package/examples/game/src/Server/Main.server.clpp +0 -4
  163. package/templates/game/.clangd +0 -25
  164. package/templates/game/.vscode/extensions.json +0 -8
  165. package/templates/game/.vscode/settings.json +0 -17
  166. package/templates/game/Packages/.gitkeep +0 -0
  167. package/templates/game/compile_flags.txt +0 -6
  168. package/templates/game/default.project.json +0 -41
  169. package/templates/game/include/clpp/datatypes.clh +0 -575
  170. package/templates/game/include/clpp/generated/enums.clh +0 -4920
  171. package/templates/game/include/clpp/generated/instances.clh +0 -87182
  172. package/templates/game/include/clpp/libs/arrayindexer.clh +0 -12
  173. package/templates/game/include/clpp/libs/axiom.clh +0 -154
  174. package/templates/game/include/clpp/libs/bloom.clh +0 -58
  175. package/templates/game/include/clpp/libs/coil.clh +0 -37
  176. package/templates/game/include/clpp/libs/crest.clh +0 -22
  177. package/templates/game/include/clpp/libs/echo.clh +0 -52
  178. package/templates/game/include/clpp/libs/ember.clh +0 -17
  179. package/templates/game/include/clpp/libs/flare.clh +0 -65
  180. package/templates/game/include/clpp/libs/gleam.clh +0 -111
  181. package/templates/game/include/clpp/libs/guide.clh +0 -42
  182. package/templates/game/include/clpp/libs/helm.clh +0 -31
  183. package/templates/game/include/clpp/libs/hive.clh +0 -20
  184. package/templates/game/include/clpp/libs/keep.clh +0 -158
  185. package/templates/game/include/clpp/libs/lens.clh +0 -24
  186. package/templates/game/include/clpp/libs/mint.clh +0 -28
  187. package/templates/game/include/clpp/libs/net.clh +0 -31
  188. package/templates/game/include/clpp/libs/occlude.clh +0 -15
  189. package/templates/game/include/clpp/libs/pin.clh +0 -28
  190. package/templates/game/include/clpp/libs/promise.clh +0 -24
  191. package/templates/game/include/clpp/libs/roster.clh +0 -123
  192. package/templates/game/include/clpp/libs/shift.clh +0 -22
  193. package/templates/game/include/clpp/libs/spark.clh +0 -28
  194. package/templates/game/include/clpp/libs/stage.clh +0 -23
  195. package/templates/game/include/clpp/libs/sweep.clh +0 -23
  196. package/templates/game/include/clpp/libs/trace.clh +0 -18
  197. package/templates/game/include/clpp/libs/ward.clh +0 -19
  198. package/templates/game/include/clpp/libs.clh +0 -31
  199. package/templates/game/include/clpp/roblox.clh +0 -41
  200. package/templates/game/rokit.toml +0 -5
  201. package/templates/game/src/Client/Controllers/DataController.client.clpp +0 -7
  202. package/templates/game/src/Client/Main.client.clpp +0 -4
  203. package/templates/game/src/Declarations/Net.flare +0 -5
  204. package/templates/game/src/Include/GameTypes.clh +0 -40
  205. package/templates/game/src/Modules/Core/Guard.server.clpp +0 -7
  206. package/templates/game/src/Modules/Core/PlayerHandler.server.clpp +0 -24
  207. package/templates/game/src/Server/Boot/DataBoot.server.clpp +0 -13
  208. package/templates/game/src/Server/Main.server.clpp +0 -4
  209. package/templates/game/stylua.toml +0 -9
  210. package/templates/game/wally.toml +0 -12
@@ -0,0 +1,1025 @@
1
+ /**
2
+ * Calls: user functions (with call-site inference for unannotated parameters), methods, CL++
3
+ * builtins, struct/Instance/datatype construction, enum cases, Option/Result, `GetService`,
4
+ * generic class arguments (`WaitForChild<Model>("Map")`) and overload resolution on the Roblox API.
5
+ */
6
+
7
+ import type { Arg, CallForm, Expr, FuncDecl, TypeNode } from "../ast.js";
8
+ import { suggest } from "../diagnostics.js";
9
+ import { isDynamic, isIntLike, isNumeric, isStringLike, nonNull, prune, T, typeToString, type FnParam, type Type } from "../types.js";
10
+ import { widenLiteral, type Checker } from "./checker.js";
11
+ import { checkExpr, rootSymbol } from "./exprs.js";
12
+ import { checkMember } from "./members.js";
13
+ import { checkHandler } from "./statements.js";
14
+ import type { StructInfo, Sym } from "./symbols.js";
15
+ import { assignable, bindGenerics, join, substitute } from "./typeops.js";
16
+
17
+ type FnType = Extract<Type, { k: "func" }>;
18
+ type CallExpr = Extract<Expr, { kind: "Call" }>;
19
+
20
+ /** Methods whose class-name string argument can be written as a type argument. */
21
+ const CLASS_ARG_METHODS = new Set([
22
+ "FindFirstChildOfClass", "FindFirstChildWhichIsA", "FindFirstAncestorOfClass", "FindFirstAncestorWhichIsA",
23
+ "IsA", "GetService", "FindService",
24
+ ]);
25
+ /** Methods whose result can be narrowed with a type argument (a checked downcast). */
26
+ /** Axiom functions that return an int when every argument is an int (`Axiom.Clamp(hp - 5, 0, 100)`). */
27
+ const INT_PRESERVING = new Set(["Clamp", "Min", "Max", "Abs", "Sign", "Floor", "Ceil", "Round", "Trunc", "Snap", "Wrap", "Pow", "Gcd", "Lcm", "Factorial", "Sum", "Approach"]);
28
+ const CAST_RESULT_METHODS = new Set(["WaitForChild", "FindFirstChild", "FindFirstDescendant", "FindFirstAncestor", "Clone"]);
29
+
30
+ function setCall(e: CallExpr, call: CallForm, args: Array<Expr | null>, variadicArgs?: Expr[]): void {
31
+ e.res = { kind: "call", call, args, ...(variadicArgs && variadicArgs.length ? { variadicArgs } : {}) };
32
+ }
33
+
34
+ export function checkCall(c: Checker, e: CallExpr, expected?: Type): Type {
35
+ const callee = e.callee;
36
+ if (e.legacyConnect) {
37
+ c.warn("CL1204", callee.span, "`event~>Connect(fn)` is Cluaupp 1.x syntax", ["write `event.Connect(fn)` or the statement `event ~> fn;`"]);
38
+ }
39
+ if (callee.kind === "Name" && !c.scope.lookup(callee.name)) {
40
+ const special = checkSpecialName(c, e, callee, expected);
41
+ if (special) {
42
+ return special;
43
+ }
44
+ }
45
+ if (callee.kind === "Name") {
46
+ const sym = c.scope.lookup(callee.name);
47
+ if (sym && sym.kind === "struct" && sym.typeId) {
48
+ sym.used = true;
49
+ callee.res = { kind: "struct", name: sym.name, sym };
50
+ return constructStruct(c, e, sym.typeId, callee.typeArgs, e.args, expected);
51
+ }
52
+ if (sym && sym.kind === "signal") {
53
+ return fireSignal(c, e, sym);
54
+ }
55
+ if (sym && sym.kind === "variant") {
56
+ sym.used = true;
57
+ callee.res = { kind: "builtin", name: "variant" };
58
+ const fn = sym.type as FnType;
59
+ const r = checkArgs(c, e, fn, callee.span, null);
60
+ setCall(e, { form: "variant" }, r.args);
61
+ return fn.ret;
62
+ }
63
+ }
64
+ if (callee.kind === "SelfField") {
65
+ const t = checkExpr(c, callee);
66
+ if (callee.res?.kind === "method") {
67
+ return finishCall(c, e, prune(t), callee.span, { form: "call" }, expected);
68
+ }
69
+ return finishCall(c, e, prune(t), callee.span, { form: "call" }, expected);
70
+ }
71
+ if (callee.kind === "Member") {
72
+ return checkMemberCall(c, e, callee, expected);
73
+ }
74
+ const t = prune(checkExpr(c, callee));
75
+ if (t.k === "namespace" && t.ns.kind === "struct") {
76
+ return constructStruct(c, e, t.ns.id, null, e.args, expected);
77
+ }
78
+ if (t.k === "namespace" && t.ns.kind === "datatype") {
79
+ return constructDatatype(c, e, t.ns.name, e.args);
80
+ }
81
+ return finishCall(c, e, t, callee.span, { form: "call" }, expected);
82
+ }
83
+
84
+ // ---------- names with special meaning ----------
85
+
86
+ function checkSpecialName(c: Checker, e: CallExpr, callee: Extract<Expr, { kind: "Name" }>, expected?: Type): Type | null {
87
+ const name = callee.name;
88
+ switch (name) {
89
+ case "len":
90
+ case "list":
91
+ case "array":
92
+ case "dictionary":
93
+ case "push":
94
+ case "pop":
95
+ case "insert":
96
+ case "remove":
97
+ case "find":
98
+ case "sort":
99
+ case "args":
100
+ case "mutex":
101
+ case "lock":
102
+ case "unlock":
103
+ case "fetch_add":
104
+ case "atomic_load":
105
+ case "actor":
106
+ case "Some":
107
+ case "Ok":
108
+ case "Err":
109
+ case "int":
110
+ case "float":
111
+ case "bool":
112
+ case "string":
113
+ case "GetService":
114
+ callee.res = { kind: "builtin", name };
115
+ callee.ty = T.any;
116
+ return checkBuiltin(c, e, name, callee.typeArgs, expected);
117
+ case "type":
118
+ c.requireRoblox(callee.span, "type");
119
+ callee.res = { kind: "global", luau: "type" };
120
+ callee.ty = T.any;
121
+ checkArgsLoose(c, e);
122
+ setCall(e, { form: "call" }, e.args.map((a) => a.value));
123
+ return T.string;
124
+ default:
125
+ break;
126
+ }
127
+ if (name === "Vector2" || name === "Vector3" || (c.api.hasDatatype(name) && !c.api.hasClass(name))) {
128
+ callee.res = { kind: "global", luau: name };
129
+ return constructDatatype(c, e, name, e.args);
130
+ }
131
+ if (name === "Vector4") {
132
+ c.error("RBX5002", callee.span, "Vector4 has no Roblox equivalent", ["use Color3 for colors or a struct"]);
133
+ checkArgsLoose(c, e);
134
+ return T.unknown;
135
+ }
136
+ if (c.api.hasClass(name) && !c.api.isService(name)) {
137
+ c.requireRoblox(callee.span, name);
138
+ callee.res = { kind: "global", luau: name };
139
+ return constructInstance(c, e, name, e.args, callee.span);
140
+ }
141
+ return null;
142
+ }
143
+
144
+ // ---------- builtins ----------
145
+
146
+ function argValue(e: CallExpr, i: number): Expr | null {
147
+ return e.args[i]?.value ?? null;
148
+ }
149
+
150
+ function arity(c: Checker, e: CallExpr, name: string, min: number, max = min): boolean {
151
+ if (e.args.some((a) => a.name !== null) && name !== "dictionary") {
152
+ c.error("CL3101", e.span, `${name}(…) does not take named arguments`);
153
+ }
154
+ if (e.args.length < min || e.args.length > max) {
155
+ c.error("CL3102", e.span, `${name} takes ${min === max ? min : `${min} to ${max}`} argument${max === 1 ? "" : "s"}, got ${e.args.length}`);
156
+ return false;
157
+ }
158
+ return true;
159
+ }
160
+
161
+ function checkArgsLoose(c: Checker, e: CallExpr): Type[] {
162
+ return e.args.map((a) => checkExpr(c, a.value));
163
+ }
164
+
165
+ function listOf(c: Checker, x: Expr | null, what: string): Extract<Type, { k: "list" }> | null {
166
+ if (!x) {
167
+ return null;
168
+ }
169
+ const t = prune(checkExpr(c, x));
170
+ if (t.k === "list") {
171
+ return t;
172
+ }
173
+ if (!isDynamic(t)) {
174
+ c.error("CL3103", x.span, `${what} needs a list, got ${typeToString(t)}`);
175
+ }
176
+ return null;
177
+ }
178
+
179
+ function requireMutableList(c: Checker, x: Expr | null, what: string): void {
180
+ if (!x) {
181
+ return;
182
+ }
183
+ const root = rootSymbol(x);
184
+ if (root && (root.kind === "var" || root.kind === "param") && !root.mutable) {
185
+ c.diags.error("CL2040", x.span, `${what} changes '${root.name}', which is declared with let`, ["declare it with `let mut`"], {
186
+ span: root.span,
187
+ text: `mut ${root.name}`,
188
+ title: `Make '${root.name}' mutable (let mut)`,
189
+ });
190
+ }
191
+ if (root) {
192
+ root.mutatedFields = true;
193
+ }
194
+ }
195
+
196
+ function elementSlot(list: Extract<Type, { k: "list" }>, value: Type): Type {
197
+ const elem = prune(list.elem);
198
+ if (elem.k === "infer" && !elem.resolved) {
199
+ elem.resolved = widenLiteral(value);
200
+ return elem.resolved;
201
+ }
202
+ return list.elem;
203
+ }
204
+
205
+ function checkBuiltin(c: Checker, e: CallExpr, name: string, typeArgs: TypeNode[] | null, expected?: Type): Type {
206
+ const builtin = (elemType?: Type) => setCall(e, { form: "builtin", name, ...(elemType ? { elemType } : {}) }, e.args.map((a) => a.value));
207
+ switch (name) {
208
+ case "len": {
209
+ arity(c, e, "len", 1);
210
+ const x = argValue(e, 0);
211
+ const t = x ? prune(checkExpr(c, x)) : T.unknown;
212
+ if (!(t.k === "list" || t.k === "string" || t.k === "lit" || t.k === "dict" || t.k === "buffer" || t.k === "table" || isDynamic(t))) {
213
+ c.error("CL3104", x?.span ?? e.span, `len(…) needs a list, text or dictionary, got ${typeToString(t)}`, t.k === "instance" ? ["for children use len(x.GetChildren())"] : undefined);
214
+ }
215
+ builtin(t);
216
+ return T.int;
217
+ }
218
+ case "list": {
219
+ const ex = expected ? prune(expected) : null;
220
+ if (e.args.length === 0) {
221
+ builtin();
222
+ return ex && ex.k === "list" ? ex : T.list({ k: "infer", resolved: null });
223
+ }
224
+ const want = ex && ex.k === "list" ? ex.elem : undefined;
225
+ let elem: Type = T.never;
226
+ for (const a of e.args) {
227
+ if (a.name) {
228
+ c.error("CL3101", a.nameSpan ?? a.value.span, "list(…) takes values, not named arguments", ["use dictionary(Key: value, …) for named entries"]);
229
+ }
230
+ const t = checkExpr(c, a.value, want);
231
+ c.markCopy(a.value);
232
+ elem = join(c, elem, widenLiteral(t));
233
+ }
234
+ builtin();
235
+ if (want) {
236
+ for (const a of e.args) {
237
+ c.expectAssignable(want, a.value.ty ?? T.unknown, a.value.span, "list element");
238
+ }
239
+ return ex!;
240
+ }
241
+ return T.list(prune(elem).k === "never" ? T.any : elem);
242
+ }
243
+ case "array": {
244
+ if (!typeArgs || typeArgs.length !== 1) {
245
+ c.error("CL3105", e.span, "array needs its element type: array<int>(1, 2, 3)");
246
+ checkArgsLoose(c, e);
247
+ builtin();
248
+ return T.list(T.unknown);
249
+ }
250
+ const elem = c.resolveType(typeArgs[0]);
251
+ for (const a of e.args) {
252
+ const t = checkExpr(c, a.value, elem);
253
+ c.expectAssignable(elem, t, a.value.span, "array element");
254
+ c.markCopy(a.value);
255
+ }
256
+ builtin(elem);
257
+ return T.list(elem);
258
+ }
259
+ case "dictionary": {
260
+ if (e.args.length && e.args.every((a) => a.name !== null)) {
261
+ // dictionary(Transparency: 1, Color: c) → { Transparency = 1, Color = c }
262
+ const fields: Record<string, Type> = {};
263
+ for (const a of e.args) {
264
+ fields[a.name!] = widenLiteral(checkExpr(c, a.value));
265
+ c.markCopy(a.value);
266
+ }
267
+ setCall(e, { form: "table", keys: e.args.map((a) => a.name!) }, e.args.map((a) => a.value));
268
+ return { k: "table", fields };
269
+ }
270
+ let key: Type = T.any;
271
+ let value: Type = T.any;
272
+ if (typeArgs && typeArgs.length === 2) {
273
+ key = c.resolveType(typeArgs[0]);
274
+ value = c.resolveType(typeArgs[1]);
275
+ } else if (typeArgs) {
276
+ c.error("CL3105", e.span, "dictionary takes two type arguments: dictionary<string, int>(…)");
277
+ } else if (expected && prune(expected).k === "dict") {
278
+ const ex = prune(expected) as Extract<Type, { k: "dict" }>;
279
+ key = ex.key;
280
+ value = ex.value;
281
+ } else if (e.args.length === 0) {
282
+ key = { k: "infer", resolved: null };
283
+ value = { k: "infer", resolved: null };
284
+ }
285
+ if (e.args.length % 2 !== 0) {
286
+ c.error("CL3106", e.span, "dictionary(…) takes key/value pairs: dictionary<string, int>(\"a\", 1, \"b\", 2)");
287
+ }
288
+ let inferredKey: Type = T.never;
289
+ let inferredValue: Type = T.never;
290
+ e.args.forEach((a, i) => {
291
+ const isKey = i % 2 === 0;
292
+ const want = isKey ? key : value;
293
+ const t = checkExpr(c, a.value, isDynamic(want) ? undefined : want);
294
+ c.markCopy(a.value);
295
+ if (!isDynamic(want)) {
296
+ c.expectAssignable(want, t, a.value.span, isKey ? "dictionary key" : "dictionary value");
297
+ } else if (isKey) {
298
+ inferredKey = join(c, inferredKey, widenLiteral(t));
299
+ } else {
300
+ inferredValue = join(c, inferredValue, widenLiteral(t));
301
+ }
302
+ });
303
+ builtin();
304
+ if (!typeArgs && e.args.length && isDynamic(key)) {
305
+ return T.dict(prune(inferredKey).k === "never" ? T.any : inferredKey, prune(inferredValue).k === "never" ? T.any : inferredValue);
306
+ }
307
+ return T.dict(key, value);
308
+ }
309
+ case "push": {
310
+ arity(c, e, "push", 2);
311
+ const list = listOf(c, argValue(e, 0), "push");
312
+ requireMutableList(c, argValue(e, 0), "push");
313
+ const v = argValue(e, 1);
314
+ if (v) {
315
+ const want = list ? elementSlot(list, prune(checkExpr(c, v, list.typed && !isDynamic(list.elem) ? list.elem : undefined))) : T.any;
316
+ if (list && list.typed) {
317
+ c.expectAssignable(want, v.ty ?? T.unknown, v.span, "push");
318
+ }
319
+ c.markCopy(v);
320
+ }
321
+ builtin();
322
+ return T.void;
323
+ }
324
+ case "pop": {
325
+ arity(c, e, "pop", 1);
326
+ const list = listOf(c, argValue(e, 0), "pop");
327
+ requireMutableList(c, argValue(e, 0), "pop");
328
+ builtin();
329
+ return list ? list.elem : T.any;
330
+ }
331
+ case "insert": {
332
+ arity(c, e, "insert", 3);
333
+ const list = listOf(c, argValue(e, 0), "insert");
334
+ requireMutableList(c, argValue(e, 0), "insert");
335
+ const i = argValue(e, 1);
336
+ if (i) {
337
+ const it = checkExpr(c, i);
338
+ if (!isIntLike(it) && !isDynamic(it)) {
339
+ c.error("CL3060", i.span, `insert position must be int, got ${typeToString(it)}`);
340
+ }
341
+ }
342
+ const v = argValue(e, 2);
343
+ if (v) {
344
+ const vt = checkExpr(c, v, list?.elem);
345
+ if (list) {
346
+ c.expectAssignable(elementSlot(list, vt), vt, v.span, "insert");
347
+ }
348
+ c.markCopy(v);
349
+ }
350
+ builtin();
351
+ return T.void;
352
+ }
353
+ case "remove": {
354
+ arity(c, e, "remove", 2);
355
+ const target = argValue(e, 0);
356
+ const t = target ? prune(checkExpr(c, target)) : T.unknown;
357
+ requireMutableList(c, target, "remove");
358
+ const k = argValue(e, 1);
359
+ const kt = k ? checkExpr(c, k) : T.unknown;
360
+ if (t.k === "list") {
361
+ if (!isIntLike(kt) && !isDynamic(kt)) {
362
+ c.error("CL3060", k!.span, `remove position must be int, got ${typeToString(kt)}`);
363
+ }
364
+ builtin(T.list(T.any));
365
+ return t.elem;
366
+ }
367
+ if (t.k === "dict") {
368
+ if (assignable(c, t.key, kt) === "no") {
369
+ c.error("CL3063", k!.span, `dictionary key must be ${typeToString(t.key)}`);
370
+ }
371
+ builtin(T.dict(T.any, T.any));
372
+ return t.value;
373
+ }
374
+ if (!isDynamic(t)) {
375
+ c.error("CL3103", target?.span ?? e.span, `remove needs a list or dictionary, got ${typeToString(t)}`);
376
+ }
377
+ builtin();
378
+ return T.any;
379
+ }
380
+ case "find": {
381
+ arity(c, e, "find", 2);
382
+ const list = listOf(c, argValue(e, 0), "find");
383
+ const v = argValue(e, 1);
384
+ if (v) {
385
+ const vt = checkExpr(c, v, list?.elem);
386
+ if (list && assignable(c, list.elem, vt) === "no" && assignable(c, vt, list.elem) === "no") {
387
+ c.error("CL3107", v.span, `find looks for ${typeToString(list.elem)}, got ${typeToString(vt)}`);
388
+ }
389
+ }
390
+ builtin(list?.elem);
391
+ return T.int;
392
+ }
393
+ case "sort": {
394
+ arity(c, e, "sort", 1);
395
+ const list = listOf(c, argValue(e, 0), "sort");
396
+ if (list && !isNumeric(list.elem) && !isStringLike(list.elem) && !isDynamic(list.elem)) {
397
+ c.error("CL3108", e.span, `sort orders numbers or text, not ${typeToString(list.elem)}`);
398
+ }
399
+ builtin();
400
+ return list ?? T.list(T.any);
401
+ }
402
+ case "args":
403
+ c.error("RBX5100", e.span, "args() has no meaning on Roblox: scripts get no command line");
404
+ builtin();
405
+ return T.list(T.string);
406
+ case "actor":
407
+ c.error("RBX5100", e.span, "actor(...) is the clpp VM sandbox; on Roblox use an Actor instance with parallel Luau");
408
+ checkArgsLoose(c, e);
409
+ builtin();
410
+ return T.any;
411
+ case "mutex":
412
+ arity(c, e, "mutex", 0);
413
+ builtin();
414
+ return T.datatype("__Mutex");
415
+ case "lock":
416
+ case "unlock": {
417
+ arity(c, e, name, 1);
418
+ checkArgsLoose(c, e);
419
+ if (name === "lock") {
420
+ c.noteYield();
421
+ }
422
+ builtin();
423
+ return T.void;
424
+ }
425
+ case "fetch_add":
426
+ case "atomic_load": {
427
+ arity(c, e, name, name === "fetch_add" ? 2 : 1);
428
+ const a = argValue(e, 0);
429
+ if (a) {
430
+ checkExpr(c, a);
431
+ const sym = a.kind === "Name" ? c.scope.lookup(a.name) : undefined;
432
+ if (!sym || !sym.atomic) {
433
+ c.error("CL3109", a.span, `${name} needs a variable declared with \`atomic\``);
434
+ }
435
+ }
436
+ if (name === "fetch_add" && argValue(e, 1)) {
437
+ checkExpr(c, argValue(e, 1)!);
438
+ }
439
+ builtin();
440
+ return T.int;
441
+ }
442
+ case "Some": {
443
+ arity(c, e, "Some", 1);
444
+ const ex = expected ? prune(expected) : null;
445
+ const inner = ex && ex.k === "option" ? ex.inner : undefined;
446
+ const t = argValue(e, 0) ? checkExpr(c, argValue(e, 0)!, inner) : T.unknown;
447
+ if (prune(t).k === "option" || prune(t).k === "null") {
448
+ c.error("CL3110", e.span, "Some(…) of a value that may already be null cannot be told apart from None on Roblox");
449
+ }
450
+ if (argValue(e, 0)) {
451
+ c.markCopy(argValue(e, 0)!);
452
+ }
453
+ setCall(e, { form: "some" }, e.args.map((a) => a.value));
454
+ return T.option(inner ?? widenLiteral(t));
455
+ }
456
+ case "Ok":
457
+ case "Err": {
458
+ arity(c, e, name, 1);
459
+ const ex = expected ? prune(expected) : null;
460
+ const res = ex && ex.k === "result" ? ex : null;
461
+ const want = res ? (name === "Ok" ? res.ok : res.err) : undefined;
462
+ const t = argValue(e, 0) ? widenLiteral(checkExpr(c, argValue(e, 0)!, want)) : T.unknown;
463
+ if (argValue(e, 0)) {
464
+ c.markCopy(argValue(e, 0)!);
465
+ }
466
+ setCall(e, { form: name === "Ok" ? "ok" : "err" }, e.args.map((a) => a.value));
467
+ return name === "Ok" ? { k: "result", ok: res?.ok ?? t, err: res?.err ?? T.any } : { k: "result", ok: res?.ok ?? T.any, err: res?.err ?? t };
468
+ }
469
+ case "int":
470
+ case "float":
471
+ case "bool":
472
+ case "string": {
473
+ arity(c, e, name, 1);
474
+ const x = argValue(e, 0);
475
+ const t = x ? prune(checkExpr(c, x)) : T.unknown;
476
+ if ((name === "int" || name === "float") && !isNumeric(t) && !isStringLike(t) && !isDynamic(t) && t.k !== "enum" && t.k !== "rbxEnumItem") {
477
+ c.error("CL3111", x?.span ?? e.span, `cannot convert ${typeToString(t)} to ${name}`);
478
+ }
479
+ if ((name === "int" || name === "float") && isStringLike(t)) {
480
+ c.warn("CL3112", x!.span, `${name}(text) is null-free on Roblox only if the text is a number`, ["prefer Text.toNumber(text), which reports bad input"]);
481
+ }
482
+ setCall(e, { form: "convert", to: name }, [x]);
483
+ return name === "int" ? T.int : name === "float" ? T.float : name === "bool" ? T.bool : T.string;
484
+ }
485
+ case "GetService": {
486
+ let service: string | null = null;
487
+ if (typeArgs && typeArgs.length === 1 && typeArgs[0].kind === "Named") {
488
+ service = typeArgs[0].name;
489
+ } else if (e.args.length === 1 && e.args[0].value.kind === "String") {
490
+ service = e.args[0].value.value;
491
+ }
492
+ if (!service) {
493
+ c.error("RBX5050", e.span, "GetService needs the service name: GetService<Players>() or GetService(\"Players\")");
494
+ return T.unknown;
495
+ }
496
+ return serviceCall(c, e, service, e.span);
497
+ }
498
+ default:
499
+ return T.unknown;
500
+ }
501
+ }
502
+
503
+ function serviceCall(c: Checker, e: CallExpr, service: string, span: Expr["span"]): Type {
504
+ if (!c.api.isService(service)) {
505
+ const s = suggest(service, c.api.services);
506
+ c.error("RBX5051", span, `'${service}' is not a Roblox service`, s ? [`did you mean ${s}?`] : undefined);
507
+ return T.unknown;
508
+ }
509
+ c.useService(service, span);
510
+ setCall(e, { form: "service", service }, []);
511
+ const dm = c.input.dataModel?.children.get(service);
512
+ return { k: "instance", cls: service, ...(dm ? { dm } : {}) };
513
+ }
514
+
515
+ // ---------- member calls ----------
516
+
517
+ function checkMemberCall(c: Checker, e: CallExpr, callee: Extract<Expr, { kind: "Member" }>, expected?: Type): Type {
518
+ const t = prune(checkMember(c, callee, undefined, { forCall: true }));
519
+ const res = callee.res;
520
+ if (!res) {
521
+ return finishCall(c, e, t, callee.span, { form: "call" }, expected);
522
+ }
523
+ switch (res.kind) {
524
+ case "enumCase":
525
+ if (res.hasPayload) {
526
+ const fn = t as FnType;
527
+ const r = checkArgs(c, e, fn, callee.span, null);
528
+ setCall(e, { form: "enumCase", enumId: res.enumName, index: res.index }, r.args);
529
+ return fn.ret;
530
+ }
531
+ c.error("CL3113", e.span, `this enum case carries no value; write it without parentheses`);
532
+ return t;
533
+ case "moduleMember":
534
+ if (res.memberKind === "struct" && t.k === "namespace" && t.ns.kind === "struct") {
535
+ return constructStruct(c, e, t.ns.id, callee.typeArgs, e.args, expected);
536
+ }
537
+ break;
538
+ case "builtin":
539
+ if (res.name === "OnChange") {
540
+ const fn = t as FnType;
541
+ const r = checkArgs(c, e, fn, callee.span, null);
542
+ const sym = c.scope.lookup((callee.object as Extract<Expr, { kind: "Name" }>).name)!;
543
+ (sym.handlers ||= []).push(...(r.args.filter(Boolean) as Expr[]));
544
+ setCall(e, { form: "observe", sym }, r.args);
545
+ return T.void;
546
+ }
547
+ break;
548
+ case "stdlib": {
549
+ const fn = t as FnType;
550
+ const r = checkArgs(c, e, fn, callee.span, null);
551
+ setCall(e, { form: "std", module: res.module, fn: res.member }, r.args);
552
+ if (res.module === "axiom" && INT_PRESERVING.has(res.member) && r.args.length && r.args.every((a) => a && isIntLike(a.ty ?? T.any))) {
553
+ return T.int;
554
+ }
555
+ return fn.ret;
556
+ }
557
+ case "library":
558
+ if (res.stringMethod) {
559
+ const r = checkArgs(c, e, t as FnType, callee.span, null);
560
+ setCall(e, { form: "stringMethod", name: callee.name }, r.args, r.rest);
561
+ return prune((t as FnType).ret);
562
+ }
563
+ break;
564
+ case "datatypeStatic":
565
+ if (res.datatype === "Instance" && res.member === "new") {
566
+ return instanceNewCall(c, e, callee);
567
+ }
568
+ break;
569
+ case "rbxMethod":
570
+ if (callee.name === "GetService" && (callee.typeArgs || e.args[0]?.value.kind === "String")) {
571
+ const name = callee.typeArgs?.[0]?.kind === "Named" ? callee.typeArgs[0].name : e.args[0]?.value.kind === "String" ? e.args[0].value.value : null;
572
+ if (name) {
573
+ return serviceCall(c, e, name, e.span);
574
+ }
575
+ }
576
+ if (callee.typeArgs && callee.typeArgs.length) {
577
+ return genericRobloxCall(c, e, callee, t as FnType, expected);
578
+ }
579
+ if (CLASS_ARG_METHODS.has(callee.name) && e.args[0]?.value.kind === "String") {
580
+ const cls = e.args[0].value.value;
581
+ if (!c.api.hasClass(cls)) {
582
+ const s = suggest(cls, Object.keys(c.api.classes));
583
+ c.error("RBX5052", e.args[0].value.span, `'${cls}' is not a Roblox class`, s ? [`did you mean "${s}"?`] : undefined);
584
+ }
585
+ const r = checkArgs(c, e, t as FnType, callee.span, null);
586
+ setCall(e, { form: "call" }, r.args, r.rest);
587
+ const ret = prune((t as FnType).ret);
588
+ if (c.api.hasClass(cls) && callee.name !== "IsA") {
589
+ return ret.k === "option" ? T.option(T.instance(cls)) : T.instance(cls);
590
+ }
591
+ return ret;
592
+ }
593
+ break;
594
+ case "datatypeMember":
595
+ if (res.datatype === "RBXScriptSignal" && (callee.name === "Connect" || callee.name === "Once" || callee.name === "ConnectParallel")) {
596
+ const signal = prune(callee.object.ty ?? T.any);
597
+ const r = checkArgs(c, e, t as FnType, callee.span, null);
598
+ if (signal.k === "rbxSignal" && r.args[0]) {
599
+ handlerCheck(c, r.args[0], signal.args);
600
+ }
601
+ setCall(e, { form: "call" }, r.args);
602
+ return T.datatype("RBXScriptConnection");
603
+ }
604
+ break;
605
+ default:
606
+ break;
607
+ }
608
+ if (t.k === "namespace" && t.ns.kind === "struct") {
609
+ return constructStruct(c, e, t.ns.id, callee.typeArgs, e.args, expected);
610
+ }
611
+ if (t.k === "namespace" && t.ns.kind === "datatype") {
612
+ return constructDatatype(c, e, t.ns.name, e.args);
613
+ }
614
+ return finishCall(c, e, t, callee.span, { form: "call" }, expected, callee.typeArgs);
615
+ }
616
+
617
+ function handlerCheck(c: Checker, handler: Expr, args: Type[]): void {
618
+ checkHandler(c, handler.ty ?? T.any, args, handler.span);
619
+ }
620
+
621
+ /** `x.WaitForChild<Model>("Map")`, `x.FindFirstChildOfClass<Humanoid>()`, `x.IsA<BasePart>()`. */
622
+ function genericRobloxCall(c: Checker, e: CallExpr, callee: Extract<Expr, { kind: "Member" }>, fn: FnType, expected?: Type): Type {
623
+ const node = callee.typeArgs![0];
624
+ const target = c.resolveType(node);
625
+ const p = prune(target);
626
+ if (p.k !== "instance") {
627
+ c.error("RBX5053", node.span, `${callee.name}<…> needs a Roblox class, got ${typeToString(target)}`);
628
+ const r = checkArgs(c, e, fn, callee.span, null);
629
+ setCall(e, { form: "call" }, r.args, r.rest);
630
+ return fn.ret;
631
+ }
632
+ if (CLASS_ARG_METHODS.has(callee.name)) {
633
+ const shifted: FnType = { ...fn, params: fn.params.slice(1) };
634
+ const r = checkArgs(c, e, shifted, callee.span, null);
635
+ setCall(e, { form: "classArg", cls: p.cls, cast: callee.name !== "IsA" }, r.args, r.rest);
636
+ const ret = prune(fn.ret);
637
+ if (callee.name === "IsA") {
638
+ return T.bool;
639
+ }
640
+ return ret.k === "option" ? T.option(p) : p;
641
+ }
642
+ if (CAST_RESULT_METHODS.has(callee.name)) {
643
+ const r = checkArgs(c, e, fn, callee.span, null);
644
+ setCall(e, { form: "cast", to: p }, r.args, r.rest);
645
+ const ret = prune(fn.ret);
646
+ return ret.k === "option" ? T.option(p) : p;
647
+ }
648
+ c.error("RBX5054", node.span, `${callee.name} does not take a type argument`, [
649
+ `type arguments work on ${[...CLASS_ARG_METHODS, ...CAST_RESULT_METHODS].join(", ")}`,
650
+ ]);
651
+ void expected;
652
+ return finishCall(c, e, fn, callee.span, { form: "call" }, undefined);
653
+ }
654
+
655
+ function instanceNewCall(c: Checker, e: CallExpr, callee: Extract<Expr, { kind: "Member" }>): Type {
656
+ const first = e.args[0]?.value;
657
+ checkArgsLoose(c, e);
658
+ setCall(e, { form: "call" }, e.args.map((a) => a.value));
659
+ if (!first || first.kind !== "String") {
660
+ if (first && !isStringLike(first.ty ?? T.any) && !isDynamic(first.ty ?? T.any)) {
661
+ c.error("RBX5055", first.span, "Instance.new needs a class name");
662
+ }
663
+ return T.instance("Instance");
664
+ }
665
+ const cls = first.value;
666
+ const problem = c.api.creationError(cls);
667
+ if (problem) {
668
+ const s = c.api.hasClass(cls) ? null : suggest(cls, Object.keys(c.api.classes));
669
+ c.error("RBX5056", first.span, problem, s ? [`did you mean "${s}"?`] : undefined);
670
+ return T.unknown;
671
+ }
672
+ if (e.args.length > 1) {
673
+ c.warn("PERF8001", e.args[1].value.span, "Instance.new with a parent sets Parent before the other properties (slow replication)", [
674
+ `create it first and set Parent last, or write ${cls}(Name: …, Parent: …)`,
675
+ ]);
676
+ }
677
+ if (c.api.classes[cls].deprecated) {
678
+ c.warn("RBX5010", first.span, `${cls} is deprecated`);
679
+ }
680
+ void callee;
681
+ return T.instance(cls);
682
+ }
683
+
684
+ // ---------- construction ----------
685
+
686
+ export function checkNew(c: Checker, e: Extract<Expr, { kind: "New" }>, expected?: Type): Type {
687
+ const target = e.target;
688
+ const asCall: CallExpr = { kind: "Call", callee: target, args: e.args, span: e.span };
689
+ let t: Type;
690
+ if (target.kind === "Name" && !c.scope.lookup(target.name) && c.api.hasClass(target.name)) {
691
+ c.requireRoblox(target.span, target.name);
692
+ target.res = { kind: "global", luau: target.name };
693
+ t = constructInstance(c, asCall, target.name, e.args, target.span);
694
+ } else {
695
+ t = checkCall(c, asCall, expected);
696
+ const form = asCall.res?.kind === "call" ? asCall.res.call.form : "call";
697
+ if (form !== "struct" && form !== "instance" && form !== "datatype") {
698
+ c.error("CL3114", e.span, "`new` builds a struct, a Roblox Instance or a datatype");
699
+ }
700
+ }
701
+ e.res = asCall.res;
702
+ return t;
703
+ }
704
+
705
+ function constructStruct(c: Checker, e: CallExpr, id: string, typeArgs: TypeNode[] | null, args: Arg[], expected?: Type): Type {
706
+ const info = c.structs.get(id);
707
+ if (!info) {
708
+ checkArgsLoose(c, e);
709
+ return T.unknown;
710
+ }
711
+ c.resolveStruct(info, new Set());
712
+ if (info.isAbstract) {
713
+ c.error("CL3115", e.span, `abstract type ${info.name} cannot be constructed`, ["construct one of the structs that inherit from it"]);
714
+ }
715
+ const bindings = new Map<string, Type>();
716
+ if (typeArgs && typeArgs.length) {
717
+ if (typeArgs.length !== info.typeParams.length) {
718
+ c.error("CL3001", e.span, `${info.name} takes ${info.typeParams.length} type argument(s)`);
719
+ }
720
+ info.typeParams.forEach((p, i) => typeArgs[i] && bindings.set(p, c.resolveType(typeArgs[i])));
721
+ } else if (expected && prune(expected).k === "struct" && (prune(expected) as Extract<Type, { k: "struct" }>).id === id) {
722
+ const ex = prune(expected) as Extract<Type, { k: "struct" }>;
723
+ info.typeParams.forEach((p, i) => ex.args[i] && prune(ex.args[i]).k !== "param" && bindings.set(p, ex.args[i]));
724
+ }
725
+ const params: FnParam[] = info.fields.map((f) => ({ name: f.name, type: f.type, optional: f.defaultValue !== null }));
726
+ const fn: FnType = { k: "func", params, ret: T.void, variadic: null };
727
+ const r = checkArgs(c, e, fn, e.callee.span, null, bindings);
728
+ setCall(e, { form: "struct", id }, r.args);
729
+ const args2 = info.typeParams.map((p) => bindings.get(p) ?? ({ k: "param", name: p } as Type));
730
+ void args;
731
+ return { k: "struct", id, name: info.name, args: info.typeParams.length ? args2 : [] };
732
+ }
733
+
734
+ function constructInstance(c: Checker, e: CallExpr, cls: string, args: Arg[], span: Expr["span"]): Type {
735
+ const problem = c.api.creationError(cls);
736
+ if (problem) {
737
+ c.error("RBX5056", span, problem);
738
+ }
739
+ if (c.api.classes[cls]?.deprecated) {
740
+ c.warn("RBX5010", span, `${cls} is deprecated`);
741
+ }
742
+ const props: Arg[] = [];
743
+ let parent: Expr | null = null;
744
+ for (const a of args) {
745
+ if (a.name === null) {
746
+ if (parent) {
747
+ c.error("RBX5057", a.value.span, `${cls}(…) takes properties by name; only one positional argument (the Parent) is allowed`, [`${cls}(Name: "X", Parent: workspace)`]);
748
+ checkExpr(c, a.value);
749
+ continue;
750
+ }
751
+ parent = a.value;
752
+ const pt = checkExpr(c, a.value);
753
+ c.expectAssignable(T.option(T.instance("Instance")), pt, a.value.span, "Parent");
754
+ continue;
755
+ }
756
+ const hit = c.api.member(cls, a.name);
757
+ if (!hit || (hit.member.kind !== "property" && hit.member.kind !== "callback")) {
758
+ const props2 = [...c.api.members(cls)].filter(([, h]) => h.member.kind === "property").map(([n]) => n);
759
+ const s = suggest(a.name, props2);
760
+ c.error("RBX5058", a.nameSpan ?? a.value.span, `${cls} has no property '${a.name}'`, s ? [`did you mean '${s}'?`] : undefined);
761
+ checkExpr(c, a.value);
762
+ continue;
763
+ }
764
+ if (hit.member.readonly) {
765
+ c.error("RBX5030", a.nameSpan ?? a.value.span, `${hit.owner}.${a.name} is read-only`);
766
+ }
767
+ const want = hit.member.type ? c.api.convert(hit.member.type) : T.any;
768
+ const vt = checkExpr(c, a.value, want);
769
+ c.expectAssignable(want, vt, a.value.span, `${cls}.${a.name}`);
770
+ if (a.name === "Parent") {
771
+ parent = a.value;
772
+ continue;
773
+ }
774
+ props.push(a);
775
+ }
776
+ setCall(e, { form: "instance", cls, props, parent }, []);
777
+ return T.instance(cls);
778
+ }
779
+
780
+ function constructDatatype(c: Checker, e: CallExpr, name: string, args: Arg[]): Type {
781
+ if (name !== "Vector3" && name !== "Vector2") {
782
+ c.requireRoblox(e.callee.span, name);
783
+ }
784
+ const positional = args.filter((a) => a.name === null);
785
+ const named = args.filter((a) => a.name !== null);
786
+ const ctor = c.api.datatypeStatic(name, "new");
787
+ if (!ctor || ctor.kind !== "function") {
788
+ c.error("RBX5059", e.callee.span, `${name} has no constructor`, Object.keys(c.api.datatypes[name]?.statics ?? {}).length ? [`use ${name}.${Object.keys(c.api.datatypes[name].statics)[0]}(…)`] : undefined);
789
+ checkArgsLoose(c, e);
790
+ return T.datatype(name);
791
+ }
792
+ const fn = prune(c.api.staticFunctionType(ctor)) as FnType;
793
+ const fake: CallExpr = { kind: "Call", callee: e.callee, args: positional, span: e.span };
794
+ const r = checkArgs(c, fake, fn, e.callee.span, null);
795
+ for (const a of named) {
796
+ const m = c.api.datatypeMember(name, a.name!);
797
+ if (!m || m.kind !== "property") {
798
+ c.error("RBX5058", a.nameSpan ?? a.value.span, `${name} has no property '${a.name}'`);
799
+ checkExpr(c, a.value);
800
+ continue;
801
+ }
802
+ const want = m.type ? c.api.convert(m.type) : T.any;
803
+ const vt = checkExpr(c, a.value, want);
804
+ c.expectAssignable(want, vt, a.value.span, `${name}.${a.name}`);
805
+ }
806
+ setCall(e, { form: "datatype", name, props: named }, r.args, r.rest);
807
+ return T.datatype(name);
808
+ }
809
+
810
+ function fireSignal(c: Checker, e: CallExpr, sym: Sym): Type {
811
+ sym.used = true;
812
+ (e.callee as Extract<Expr, { kind: "Name" }>).res = { kind: "signal", sym };
813
+ e.callee.ty = sym.type;
814
+ const types = checkArgsLoose(c, e);
815
+ if (sym.signalParams) {
816
+ if (types.length !== sym.signalParams.length) {
817
+ c.error("CL3102", e.span, `signal ${sym.name} passes ${sym.signalParams.length} value(s), got ${types.length}`);
818
+ }
819
+ types.forEach((t, i) => sym.signalParams![i] && c.expectAssignable(sym.signalParams![i], t, e.args[i].value.span, `signal ${sym.name}`));
820
+ }
821
+ setCall(e, { form: "signalFire", sym }, e.args.map((a) => a.value));
822
+ return T.void;
823
+ }
824
+
825
+ // ---------- generic calls ----------
826
+
827
+ function finishCall(c: Checker, e: CallExpr, t: Type, calleeSpan: Expr["span"], form: CallForm, expected?: Type, typeArgs?: TypeNode[] | null): Type {
828
+ const p = prune(t);
829
+ const callee = e.callee;
830
+ const explicitTypeArgs = typeArgs ?? (callee.kind === "Name" ? callee.typeArgs : null);
831
+ if (p.k === "func") {
832
+ const decl = calleeDecl(c, callee);
833
+ const bindings = new Map<string, Type>();
834
+ if (explicitTypeArgs && explicitTypeArgs.length) {
835
+ const names = p.generics ?? [];
836
+ if (names.length !== explicitTypeArgs.length) {
837
+ c.error("CL3001", calleeSpan, `this function takes ${names.length} type argument(s), got ${explicitTypeArgs.length}`);
838
+ }
839
+ names.forEach((n, i) => explicitTypeArgs[i] && bindings.set(n, c.resolveType(explicitTypeArgs[i])));
840
+ for (const w of decl?.where ?? []) {
841
+ const bound = bindings.get(w.name);
842
+ const constraint = c.resolveType(w.type);
843
+ if (bound && assignable(c, constraint, bound) === "no") {
844
+ c.error("CL3116", calleeSpan, `${w.name} must be ${typeToString(constraint)}, got ${typeToString(bound)}`);
845
+ }
846
+ }
847
+ }
848
+ const r = checkArgs(c, e, p, calleeSpan, decl, bindings);
849
+ setCall(e, form, r.args, r.rest);
850
+ const sym = callee.res && (callee.res.kind === "func" || callee.res.kind === "local") ? (callee.res.sym as Sym) : null;
851
+ if (sym?.contextOnly) {
852
+ c.checkContext(sym.contextOnly, calleeSpan, `${sym.name}()`, `it is marked [[${sym.contextOnly}]]`);
853
+ }
854
+ let ret = r.fn.ret;
855
+ if (decl && prune(ret).k === "infer" && !(prune(ret) as { resolved: Type | null }).resolved) {
856
+ c.ensureBodyChecked(decl);
857
+ ret = r.fn.ret;
858
+ }
859
+ if (decl?.fn?.mayYield || (decl && c.isChecking(decl))) {
860
+ c.noteYield();
861
+ }
862
+ if (callee.kind === "Member" && callee.res?.kind === "method") {
863
+ const info = c.structs.get(callee.res.struct);
864
+ const m = info?.methods.get(callee.name);
865
+ const root = rootSymbol(callee.object);
866
+ if (m && root) {
867
+ c.mutatingCalls.push({ root, method: m, span: e.span });
868
+ }
869
+ }
870
+ const out = substitute(prune(ret), bindings);
871
+ return prune(out).k === "infer" ? T.any : out;
872
+ }
873
+ checkArgsLoose(c, e);
874
+ setCall(e, form, e.args.map((a) => a.value));
875
+ if (isDynamic(p)) {
876
+ if (callee.kind === "Member" && callee.res?.kind === "dynamic" && /^[A-Z]/.test(callee.name) && c.strict) {
877
+ c.warn("CL3117", callee.nameSpan, `'${callee.name}' is called on a dynamic value; for a Luau method use '::'`, [`x::${callee.name}(…) emits x:${callee.name}(…)`]);
878
+ }
879
+ return T.any;
880
+ }
881
+ if (p.k === "clSignal") {
882
+ return T.void;
883
+ }
884
+ c.error("CL3118", calleeSpan, `${typeToString(p)} is not callable`);
885
+ void expected;
886
+ return T.unknown;
887
+ }
888
+
889
+ function calleeDecl(c: Checker, callee: Expr): FuncDecl | null {
890
+ const res = callee.res;
891
+ if (!res) {
892
+ return null;
893
+ }
894
+ if (res.kind === "func") {
895
+ return (res.sym as Sym).decl ?? null;
896
+ }
897
+ if (res.kind === "method") {
898
+ return c.structs.get(res.struct)?.methods.get(callee.kind === "Member" || callee.kind === "SelfField" ? callee.name : "")?.decl ?? null;
899
+ }
900
+ return null;
901
+ }
902
+
903
+ export interface CheckedArgs {
904
+ args: Array<Expr | null>;
905
+ rest: Expr[];
906
+ fn: FnType;
907
+ }
908
+
909
+ /** Match arguments to parameters (named, defaults, variadic), choose an overload, bind generics. */
910
+ export function checkArgs(c: Checker, e: CallExpr, fn: FnType, calleeSpan: Expr["span"], decl: FuncDecl | null, bindings: Map<string, Type> = new Map()): CheckedArgs {
911
+ const overloads = (fn.overloads as FnType[] | undefined) ?? [fn];
912
+ let chosen = overloads[0];
913
+ if (overloads.length > 1) {
914
+ const positional = e.args.filter((a) => a.name === null).map((a) => checkExpr(c, a.value));
915
+ const fits = (o: FnType) => {
916
+ const required = o.params.filter((p) => !p.optional).length;
917
+ if (positional.length < required || (positional.length > o.params.length && !o.variadic)) {
918
+ return false;
919
+ }
920
+ return positional.every((t, i) => {
921
+ const want = o.params[i]?.type ?? o.variadic ?? T.any;
922
+ return assignable(c, want, t) === "ok";
923
+ });
924
+ };
925
+ const match = overloads.find(fits);
926
+ if (!match) {
927
+ const sigs = overloads.map((o) => `(${o.params.map((p) => `${p.name}: ${typeToString(p.type)}${p.optional ? "?" : ""}`).join(", ")})`);
928
+ c.error("CL3119", calleeSpan, `no overload accepts (${positional.map((t) => typeToString(t)).join(", ")})`, sigs.slice(0, 6).map((s) => `candidate ${s}`));
929
+ return { args: e.args.map((a) => a.value), rest: [], fn: chosen };
930
+ }
931
+ chosen = match;
932
+ }
933
+ const params = chosen.params;
934
+ const slots: Array<Expr | null> = params.map(() => null);
935
+ const rest: Expr[] = [];
936
+ let position = 0;
937
+ let named = false;
938
+ for (const a of e.args) {
939
+ if (a.name !== null) {
940
+ named = true;
941
+ const idx = params.findIndex((p) => p.name === a.name);
942
+ if (idx < 0) {
943
+ const s = suggest(a.name, params.map((p) => p.name));
944
+ c.error("CL3120", a.nameSpan ?? a.value.span, `no parameter named '${a.name}'`, s ? [`did you mean '${s}'?`] : [`parameters: ${params.map((p) => p.name).join(", ") || "none"}`]);
945
+ checkExpr(c, a.value);
946
+ continue;
947
+ }
948
+ if (slots[idx]) {
949
+ c.error("CL3121", a.nameSpan ?? a.value.span, `'${a.name}' is passed twice`);
950
+ }
951
+ slots[idx] = a.value;
952
+ continue;
953
+ }
954
+ if (named) {
955
+ c.error("CL3122", a.value.span, "positional arguments must come before named ones");
956
+ }
957
+ if (position < params.length) {
958
+ slots[position++] = a.value;
959
+ } else if (chosen.variadic) {
960
+ rest.push(a.value);
961
+ position++;
962
+ } else {
963
+ position++;
964
+ }
965
+ }
966
+ const given = e.args.length;
967
+ const max = chosen.variadic ? Infinity : params.length;
968
+ if (given > max) {
969
+ c.error("CL3123", calleeSpan, `wrong number of arguments: expected ${params.length}, got ${given}`);
970
+ }
971
+ params.forEach((p, i) => {
972
+ const arg = slots[i];
973
+ if (!arg) {
974
+ if (!p.optional) {
975
+ c.error("CL3124", calleeSpan, `missing argument '${p.name}' (${typeToString(substitute(p.type, bindings))})`);
976
+ }
977
+ return;
978
+ }
979
+ let want = substitute(p.type, bindings);
980
+ const wantPruned = prune(want);
981
+ const learning = wantPruned.k === "infer" && !wantPruned.resolved;
982
+ const t = checkExpr(c, arg, learning ? undefined : want);
983
+ if (learning) {
984
+ // Learn the parameter type from its first call site.
985
+ const learned = widenLiteral(t);
986
+ if (prune(learned).k !== "null" && !isDynamic(learned)) {
987
+ (wantPruned as { resolved: Type | null }).resolved = learned;
988
+ }
989
+ want = learned;
990
+ } else {
991
+ bindGenerics(p.type, t, bindings);
992
+ want = substitute(p.type, bindings);
993
+ c.expectAssignable(want, t, arg.span, `argument '${p.name}'`);
994
+ }
995
+ if (decl) {
996
+ c.deferredCopies.push({ arg, decl, index: i });
997
+ } else {
998
+ c.markCopy(arg);
999
+ }
1000
+ });
1001
+ for (const r of rest) {
1002
+ const t = checkExpr(c, r, chosen.variadic ?? undefined);
1003
+ if (chosen.variadic && !isDynamic(chosen.variadic)) {
1004
+ c.expectAssignable(substitute(chosen.variadic, bindings), t, r.span, "argument");
1005
+ }
1006
+ c.markCopy(r);
1007
+ }
1008
+ const resolvedFn: FnType = bindings.size ? (substitute(chosen, bindings) as FnType) : chosen;
1009
+ return { args: trimTrailingNulls(slots), rest, fn: resolvedFn };
1010
+ }
1011
+
1012
+ function trimTrailingNulls(args: Array<Expr | null>): Array<Expr | null> {
1013
+ let end = args.length;
1014
+ while (end > 0 && args[end - 1] === null) {
1015
+ end--;
1016
+ }
1017
+ return args.slice(0, end);
1018
+ }
1019
+
1020
+ export function joinTypes(c: Checker, types: Type[]): Type {
1021
+ return types.reduce((a, b) => join(c, a, b), T.never);
1022
+ }
1023
+
1024
+ export { nonNull };
1025
+ export type { StructInfo };