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.
- package/CHANGELOG.md +3 -0
- package/README.md +27 -40
- package/api/generated/bench-latest.json +43 -21
- package/api/generated/lsp-index.json +1 -1
- package/api/manifests/roblox-target.manifest.json +5 -5
- package/api/normalized/roblox-target.profile.json +8729 -267
- package/api/roblox/luau-builtins.d.luau +185 -0
- package/api/roblox/surface.json +1 -0
- package/api/roblox-api.lock.json +11 -2
- package/bin/cluaupp.js +38 -0
- package/editors/vscode/syntaxes/clpp.tmLanguage.json +211 -0
- package/examples/README.md +9 -9
- package/examples/game/cluaupp.config.json +2 -2
- package/examples/game/default.project.json +4 -15
- package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau +8 -0
- package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau.map.json +13 -0
- package/examples/game/out/ServerScriptService/Leaderstats.server.luau +26 -0
- package/examples/game/out/ServerScriptService/Leaderstats.server.luau.map.json +13 -0
- package/examples/game/out/ServerScriptService/Main.server.luau +15 -0
- package/examples/game/out/ServerScriptService/Main.server.luau.map.json +13 -0
- package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau +10 -0
- package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau.map.json +13 -0
- package/examples/game/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
- package/examples/game/src/ServerScriptService/Leaderstats.server.clp +18 -0
- package/examples/game/src/ServerScriptService/Main.server.clp +9 -0
- package/examples/game/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
- package/generated/api/build-profile.js +1 -1
- package/generated/api/loader.js +2 -2
- package/generated/api/surface/build.js +328 -0
- package/generated/api/surface/luau-defs.js +595 -0
- package/generated/api/surface/model.js +8 -0
- package/generated/clpp/contract.js +6 -3
- package/generated/clpp/handshake.js +36 -0
- package/generated/clpp/modules.js +20 -9
- package/generated/clpp/runner.js +62 -31
- package/generated/compile.js +18 -1
- package/generated/compiler/ast.js +3 -0
- package/generated/compiler/checker/calls.js +999 -0
- package/generated/compiler/checker/checker.js +1215 -0
- package/generated/compiler/checker/exprs.js +791 -0
- package/generated/compiler/checker/flow.js +168 -0
- package/generated/compiler/checker/members.js +763 -0
- package/generated/compiler/checker/statements.js +792 -0
- package/generated/compiler/checker/symbols.js +90 -0
- package/generated/compiler/checker/typeops.js +348 -0
- package/generated/compiler/checker/yields.js +115 -0
- package/generated/compiler/diagnostics.js +138 -0
- package/generated/compiler/emit/emitter.js +2356 -0
- package/generated/compiler/emit/luau-types.js +137 -0
- package/generated/compiler/emit/names.js +130 -0
- package/generated/compiler/emit/runtime.js +432 -0
- package/generated/compiler/lexer.js +403 -0
- package/generated/compiler/parser.js +1881 -0
- package/generated/compiler/project.js +332 -0
- package/generated/compiler/roblox/api.js +388 -0
- package/generated/compiler/roblox/context.js +100 -0
- package/generated/compiler/source.js +59 -0
- package/generated/compiler/stdlib.js +232 -0
- package/generated/compiler/types.js +250 -0
- package/generated/target/build-cache.js +4 -4
- package/generated/target/gen-rojo-tree.js +15 -0
- package/generated/target/source-map.js +61 -8
- package/generated/target/studio-bridge.js +1 -0
- package/generated/utils/project.js +83 -25
- package/native/bin/.gitkeep +1 -0
- package/package.json +17 -6
- package/plugins/CluauppNav/Plugin.luau +28 -4
- package/rust/cluaupp/Cargo.lock +795 -0
- package/rust/cluaupp/Cargo.toml +17 -0
- package/rust/cluaupp/src/main.rs +717 -0
- package/scripts/postinstall-native.js +56 -0
- package/skills/README.md +3 -3
- package/skills/clpp-hive-flare/SKILL.md +30 -26
- package/skills/clpp-language/SKILL.md +67 -74
- package/skills/clpp-language/compiler.md +24 -36
- package/skills/clpp-style/SKILL.md +31 -44
- package/src/api/build-profile.ts +1 -1
- package/src/api/loader.ts +2 -2
- package/src/api/surface/build.ts +374 -0
- package/src/api/surface/luau-defs.ts +635 -0
- package/src/api/surface/model.ts +96 -0
- package/src/clpp/contract.ts +20 -3
- package/src/clpp/handshake.ts +25 -0
- package/src/clpp/modules.ts +17 -10
- package/src/clpp/runner.ts +52 -34
- package/src/compile.ts +20 -2
- package/src/compiler/ast.ts +336 -0
- package/src/compiler/checker/calls.ts +1025 -0
- package/src/compiler/checker/checker.ts +1352 -0
- package/src/compiler/checker/exprs.ts +805 -0
- package/src/compiler/checker/flow.ts +174 -0
- package/src/compiler/checker/members.ts +793 -0
- package/src/compiler/checker/statements.ts +802 -0
- package/src/compiler/checker/symbols.ts +219 -0
- package/src/compiler/checker/typeops.ts +355 -0
- package/src/compiler/checker/yields.ts +120 -0
- package/src/compiler/diagnostics.ts +163 -0
- package/src/compiler/emit/emitter.ts +2486 -0
- package/src/compiler/emit/luau-types.ts +143 -0
- package/src/compiler/emit/names.ts +130 -0
- package/src/compiler/emit/runtime.ts +438 -0
- package/src/compiler/lexer.ts +444 -0
- package/src/compiler/parser.ts +1943 -0
- package/src/compiler/project.ts +354 -0
- package/src/compiler/roblox/api.ts +408 -0
- package/src/compiler/roblox/context.ts +108 -0
- package/src/compiler/source.ts +67 -0
- package/src/compiler/stdlib.ts +256 -0
- package/src/compiler/types.ts +313 -0
- package/src/target/build-cache.ts +5 -8
- package/src/target/gen-rojo-tree.ts +15 -0
- package/src/target/source-map.ts +65 -27
- package/src/target/studio-bridge.ts +3 -0
- package/src/types.ts +3 -0
- package/src/utils/project.ts +79 -31
- package/templates/README.md +7 -0
- package/templates/{game → game-modern}/cluaupp.config.json +2 -2
- package/templates/{game → game-modern}/package.json +1 -2
- package/templates/game-modern/rokit.toml +2 -0
- package/templates/game-modern/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
- package/templates/game-modern/src/ServerScriptService/Handlers/Guard.server.clp +4 -0
- package/templates/game-modern/src/ServerScriptService/Main.server.clp +8 -0
- package/templates/game-modern/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
- package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau +0 -29
- package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau.map.json +0 -13
- package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau +0 -83
- package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau.map.json +0 -13
- package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau +0 -18
- package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau.map.json +0 -13
- package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau +0 -14
- package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau.map.json +0 -13
- package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau +0 -15
- package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau.map.json +0 -13
- package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau +0 -50
- package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau.map.json +0 -13
- package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau +0 -12
- package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau.map.json +0 -13
- package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau +0 -16
- package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau.map.json +0 -13
- package/examples/game/src/Client/Controllers/DataController.client.clpp +0 -7
- package/examples/game/src/Client/Main.client.clpp +0 -4
- package/examples/game/src/Declarations/Net.flare +0 -5
- package/examples/game/src/Include/GameTypes.clh +0 -40
- package/examples/game/src/Modules/Core/Guard.server.clpp +0 -7
- package/examples/game/src/Modules/Core/PlayerHandler.server.clpp +0 -23
- package/examples/game/src/Server/Boot/DataBoot.server.clpp +0 -13
- package/examples/game/src/Server/Main.server.clpp +0 -4
- package/templates/game/.clangd +0 -25
- package/templates/game/.vscode/extensions.json +0 -8
- package/templates/game/.vscode/settings.json +0 -17
- package/templates/game/Packages/.gitkeep +0 -0
- package/templates/game/compile_flags.txt +0 -6
- package/templates/game/default.project.json +0 -41
- package/templates/game/include/clpp/datatypes.clh +0 -575
- package/templates/game/include/clpp/generated/enums.clh +0 -4920
- package/templates/game/include/clpp/generated/instances.clh +0 -87182
- package/templates/game/include/clpp/libs/arrayindexer.clh +0 -12
- package/templates/game/include/clpp/libs/axiom.clh +0 -154
- package/templates/game/include/clpp/libs/bloom.clh +0 -58
- package/templates/game/include/clpp/libs/coil.clh +0 -37
- package/templates/game/include/clpp/libs/crest.clh +0 -22
- package/templates/game/include/clpp/libs/echo.clh +0 -52
- package/templates/game/include/clpp/libs/ember.clh +0 -17
- package/templates/game/include/clpp/libs/flare.clh +0 -65
- package/templates/game/include/clpp/libs/gleam.clh +0 -111
- package/templates/game/include/clpp/libs/guide.clh +0 -42
- package/templates/game/include/clpp/libs/helm.clh +0 -31
- package/templates/game/include/clpp/libs/hive.clh +0 -20
- package/templates/game/include/clpp/libs/keep.clh +0 -158
- package/templates/game/include/clpp/libs/lens.clh +0 -24
- package/templates/game/include/clpp/libs/mint.clh +0 -28
- package/templates/game/include/clpp/libs/net.clh +0 -31
- package/templates/game/include/clpp/libs/occlude.clh +0 -15
- package/templates/game/include/clpp/libs/pin.clh +0 -28
- package/templates/game/include/clpp/libs/promise.clh +0 -24
- package/templates/game/include/clpp/libs/roster.clh +0 -123
- package/templates/game/include/clpp/libs/shift.clh +0 -22
- package/templates/game/include/clpp/libs/spark.clh +0 -28
- package/templates/game/include/clpp/libs/stage.clh +0 -23
- package/templates/game/include/clpp/libs/sweep.clh +0 -23
- package/templates/game/include/clpp/libs/trace.clh +0 -18
- package/templates/game/include/clpp/libs/ward.clh +0 -19
- package/templates/game/include/clpp/libs.clh +0 -31
- package/templates/game/include/clpp/roblox.clh +0 -41
- package/templates/game/rokit.toml +0 -5
- package/templates/game/src/Client/Controllers/DataController.client.clpp +0 -7
- package/templates/game/src/Client/Main.client.clpp +0 -4
- package/templates/game/src/Declarations/Net.flare +0 -5
- package/templates/game/src/Include/GameTypes.clh +0 -40
- package/templates/game/src/Modules/Core/Guard.server.clpp +0 -7
- package/templates/game/src/Modules/Core/PlayerHandler.server.clpp +0 -24
- package/templates/game/src/Server/Boot/DataBoot.server.clpp +0 -13
- package/templates/game/src/Server/Main.server.clpp +0 -4
- package/templates/game/stylua.toml +0 -9
- package/templates/game/wally.toml +0 -12
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/** Symbols, scopes and the per-module declaration registries shared by checker and emitter. */
|
|
2
|
+
|
|
3
|
+
import type { EnumDecl, Expr, FuncDecl, StructDecl, SymbolRef } from "../ast.js";
|
|
4
|
+
import type { RunContext } from "../roblox/context.js";
|
|
5
|
+
import type { Span } from "../source.js";
|
|
6
|
+
import type { StdModule } from "../stdlib.js";
|
|
7
|
+
import type { Type } from "../types.js";
|
|
8
|
+
|
|
9
|
+
export type SymKind = "var" | "param" | "func" | "const" | "signal" | "module" | "struct" | "enum" | "variant" | "alias" | "namespace" | "self" | "typeparam";
|
|
10
|
+
|
|
11
|
+
export interface Sym extends SymbolRef {
|
|
12
|
+
kind: SymKind;
|
|
13
|
+
type: Type;
|
|
14
|
+
mutable: boolean;
|
|
15
|
+
span: Span;
|
|
16
|
+
file: string;
|
|
17
|
+
topLevel: boolean;
|
|
18
|
+
/** Function nesting depth where it was declared (0 = file level). */
|
|
19
|
+
depth: number;
|
|
20
|
+
constValue?: number | string | boolean | null;
|
|
21
|
+
observable?: boolean;
|
|
22
|
+
atomic?: boolean;
|
|
23
|
+
/** Referenced from a function nested deeper than its declaration. */
|
|
24
|
+
captured?: boolean;
|
|
25
|
+
used?: boolean;
|
|
26
|
+
assigned?: boolean;
|
|
27
|
+
/** Struct/enum/variant id for type symbols. */
|
|
28
|
+
typeId?: string;
|
|
29
|
+
link?: LinkTarget;
|
|
30
|
+
decl?: FuncDecl;
|
|
31
|
+
/** CL++ signal: handlers connected with `~>`. */
|
|
32
|
+
handlers?: Expr[];
|
|
33
|
+
signalParams?: Type[] | null;
|
|
34
|
+
/** Mutated after declaration (copy elision needs to know). */
|
|
35
|
+
mutatedFields?: boolean;
|
|
36
|
+
/** Declared with `[[server]]` / `[[client]]`. */
|
|
37
|
+
contextOnly?: "server" | "client";
|
|
38
|
+
/** Doc comment. */
|
|
39
|
+
doc?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class Scope {
|
|
43
|
+
readonly values = new Map<string, Sym>();
|
|
44
|
+
readonly types = new Map<string, Sym>();
|
|
45
|
+
/** Narrowed types for paths (`sym#3`, `sym#3.Character`). */
|
|
46
|
+
readonly narrowed = new Map<string, Type>();
|
|
47
|
+
|
|
48
|
+
constructor(
|
|
49
|
+
readonly parent: Scope | null,
|
|
50
|
+
readonly kind: "file" | "function" | "block" | "loop",
|
|
51
|
+
readonly depth: number,
|
|
52
|
+
) {}
|
|
53
|
+
|
|
54
|
+
lookup(name: string): Sym | undefined {
|
|
55
|
+
let cur: Scope | null = this;
|
|
56
|
+
while (cur) {
|
|
57
|
+
const s = cur.values.get(name);
|
|
58
|
+
if (s) {
|
|
59
|
+
return s;
|
|
60
|
+
}
|
|
61
|
+
cur = cur.parent;
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
lookupType(name: string): Sym | undefined {
|
|
67
|
+
let cur: Scope | null = this;
|
|
68
|
+
while (cur) {
|
|
69
|
+
const s = cur.types.get(name);
|
|
70
|
+
if (s) {
|
|
71
|
+
return s;
|
|
72
|
+
}
|
|
73
|
+
cur = cur.parent;
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
narrowedType(key: string): Type | undefined {
|
|
79
|
+
let cur: Scope | null = this;
|
|
80
|
+
while (cur) {
|
|
81
|
+
if (cur.narrowed.has(key)) {
|
|
82
|
+
return cur.narrowed.get(key);
|
|
83
|
+
}
|
|
84
|
+
cur = cur.parent;
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Drop narrowings of a path and everything below it (after an assignment). */
|
|
90
|
+
invalidate(prefix: string): void {
|
|
91
|
+
let cur: Scope | null = this;
|
|
92
|
+
while (cur) {
|
|
93
|
+
for (const key of [...cur.narrowed.keys()]) {
|
|
94
|
+
if (key === prefix || key.startsWith(`${prefix}.`)) {
|
|
95
|
+
cur.narrowed.delete(key);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
cur = cur.parent;
|
|
99
|
+
}
|
|
100
|
+
// Mark as explicitly unknown at this level so outer narrowings stop applying.
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
allValueNames(): string[] {
|
|
104
|
+
const out: string[] = [];
|
|
105
|
+
let cur: Scope | null = this;
|
|
106
|
+
while (cur) {
|
|
107
|
+
out.push(...cur.values.keys());
|
|
108
|
+
cur = cur.parent;
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface FieldInfo {
|
|
115
|
+
name: string;
|
|
116
|
+
type: Type;
|
|
117
|
+
owner: string;
|
|
118
|
+
isPrivate: boolean;
|
|
119
|
+
defaultValue: Expr | null;
|
|
120
|
+
span: Span;
|
|
121
|
+
doc?: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface MethodInfo {
|
|
125
|
+
name: string;
|
|
126
|
+
owner: string;
|
|
127
|
+
/** Struct id that first declared this method (the root of overrides). */
|
|
128
|
+
root: string;
|
|
129
|
+
decl: FuncDecl;
|
|
130
|
+
type: Extract<Type, { k: "func" }>;
|
|
131
|
+
isStatic: boolean;
|
|
132
|
+
isAbstract: boolean;
|
|
133
|
+
isFinal: boolean;
|
|
134
|
+
isPrivate: boolean;
|
|
135
|
+
/** Does the method assign to `self` fields? */
|
|
136
|
+
mutatesSelf?: boolean;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface StructInfo {
|
|
140
|
+
id: string;
|
|
141
|
+
name: string;
|
|
142
|
+
/** Name used in emitted Luau (renamed on collisions with Roblox types). */
|
|
143
|
+
luauName: string;
|
|
144
|
+
moduleId: string;
|
|
145
|
+
decl: StructDecl;
|
|
146
|
+
typeParams: string[];
|
|
147
|
+
bases: string[];
|
|
148
|
+
/** Every field in constructor order (inherited first, diamond-deduplicated). */
|
|
149
|
+
fields: FieldInfo[];
|
|
150
|
+
methods: Map<string, MethodInfo>;
|
|
151
|
+
subclasses: string[];
|
|
152
|
+
isAbstract: boolean;
|
|
153
|
+
isFinal: boolean;
|
|
154
|
+
resolved: boolean;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface EnumInfo {
|
|
158
|
+
id: string;
|
|
159
|
+
name: string;
|
|
160
|
+
luauName: string;
|
|
161
|
+
moduleId: string;
|
|
162
|
+
decl: EnumDecl;
|
|
163
|
+
cases: Array<{ name: string; payload: Type | null }>;
|
|
164
|
+
hasPayload: boolean;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface VariantInfo {
|
|
168
|
+
id: string;
|
|
169
|
+
name: string;
|
|
170
|
+
alternatives: Type[];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface ExportedFunc {
|
|
174
|
+
name: string;
|
|
175
|
+
type: Extract<Type, { k: "func" }>;
|
|
176
|
+
contextOnly?: "server" | "client";
|
|
177
|
+
yields?: boolean;
|
|
178
|
+
doc?: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface ModuleExports {
|
|
182
|
+
moduleId: string;
|
|
183
|
+
file: string;
|
|
184
|
+
kind: "script" | "module";
|
|
185
|
+
context: RunContext;
|
|
186
|
+
funcs: Map<string, ExportedFunc>;
|
|
187
|
+
structs: Map<string, StructInfo>;
|
|
188
|
+
enums: Map<string, EnumInfo>;
|
|
189
|
+
variants: Map<string, VariantInfo>;
|
|
190
|
+
aliases: Map<string, Type>;
|
|
191
|
+
consts: Map<string, { type: Type; value: number | string | boolean | null }>;
|
|
192
|
+
namespaces: Map<string, Map<string, ExportedFunc>>;
|
|
193
|
+
/** DataModel path of the compiled ModuleScript, when known. */
|
|
194
|
+
dmPath: string[] | null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export type LinkTarget =
|
|
198
|
+
| { kind: "roblox" }
|
|
199
|
+
| { kind: "std"; module: StdModule }
|
|
200
|
+
| { kind: "clp"; exports: ModuleExports; dmPath: string[] | null; file: string }
|
|
201
|
+
| { kind: "luau"; dmPath: string[]; exports?: ModuleExports; label: string }
|
|
202
|
+
| { kind: "error"; message: string; notes?: string[] };
|
|
203
|
+
|
|
204
|
+
export function emptyExports(moduleId: string, file: string, kind: "script" | "module", context: RunContext): ModuleExports {
|
|
205
|
+
return {
|
|
206
|
+
moduleId,
|
|
207
|
+
file,
|
|
208
|
+
kind,
|
|
209
|
+
context,
|
|
210
|
+
funcs: new Map(),
|
|
211
|
+
structs: new Map(),
|
|
212
|
+
enums: new Map(),
|
|
213
|
+
variants: new Map(),
|
|
214
|
+
aliases: new Map(),
|
|
215
|
+
consts: new Map(),
|
|
216
|
+
namespaces: new Map(),
|
|
217
|
+
dmPath: null,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/** Assignability, joins and member lookups that do not need checker state. */
|
|
2
|
+
|
|
3
|
+
import type { RobloxApi } from "../roblox/api.js";
|
|
4
|
+
import { isDynamic, prune, sameType, T, type Type } from "../types.js";
|
|
5
|
+
import type { StructInfo } from "./symbols.js";
|
|
6
|
+
|
|
7
|
+
export interface TypeEnv {
|
|
8
|
+
api: RobloxApi;
|
|
9
|
+
struct(id: string): StructInfo | undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type Assignability = "ok" | "nullable" | "narrowing" | "no";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Can a value of type `from` be stored where `to` is expected?
|
|
16
|
+
* "nullable": only differs by a possible null (`Part?` into `Part`) — error in strict, warning otherwise.
|
|
17
|
+
* "narrowing": float into int — always an error (would lose the fraction).
|
|
18
|
+
*/
|
|
19
|
+
export function assignable(env: TypeEnv, to: Type, from: Type): Assignability {
|
|
20
|
+
const a = prune(to);
|
|
21
|
+
const b = prune(from);
|
|
22
|
+
if (a === b || isDynamic(a) || isDynamic(b) || b.k === "never") {
|
|
23
|
+
return "ok";
|
|
24
|
+
}
|
|
25
|
+
if (a.k === "infer") {
|
|
26
|
+
a.resolved = b;
|
|
27
|
+
return "ok";
|
|
28
|
+
}
|
|
29
|
+
if (a.k === "param" || b.k === "param") {
|
|
30
|
+
return "ok";
|
|
31
|
+
}
|
|
32
|
+
if (b.k === "union") {
|
|
33
|
+
let worst: Assignability = "ok";
|
|
34
|
+
for (const m of b.members) {
|
|
35
|
+
const r = assignable(env, a, m);
|
|
36
|
+
if (r === "no") {
|
|
37
|
+
return "no";
|
|
38
|
+
}
|
|
39
|
+
if (r !== "ok") {
|
|
40
|
+
worst = r;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return worst;
|
|
44
|
+
}
|
|
45
|
+
switch (a.k) {
|
|
46
|
+
case "void":
|
|
47
|
+
return b.k === "void" ? "ok" : "no";
|
|
48
|
+
case "null":
|
|
49
|
+
return b.k === "null" ? "ok" : "no";
|
|
50
|
+
case "option": {
|
|
51
|
+
if (b.k === "null") {
|
|
52
|
+
return "ok";
|
|
53
|
+
}
|
|
54
|
+
if (b.k === "option") {
|
|
55
|
+
return assignable(env, a.inner, b.inner) === "no" ? "no" : assignable(env, a.inner, b.inner);
|
|
56
|
+
}
|
|
57
|
+
return assignable(env, a.inner, b);
|
|
58
|
+
}
|
|
59
|
+
case "union": {
|
|
60
|
+
let best: Assignability = "no";
|
|
61
|
+
for (const m of a.members) {
|
|
62
|
+
const r = assignable(env, m, b);
|
|
63
|
+
if (r === "ok") {
|
|
64
|
+
return "ok";
|
|
65
|
+
}
|
|
66
|
+
if (r !== "no") {
|
|
67
|
+
best = r;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (b.k === "null" && a.members.some((m) => prune(m).k === "null")) {
|
|
71
|
+
return "ok";
|
|
72
|
+
}
|
|
73
|
+
return best;
|
|
74
|
+
}
|
|
75
|
+
case "float":
|
|
76
|
+
return b.k === "float" || b.k === "int" || (b.k === "lit" && typeof b.value === "number") ? "ok" : nullableOr(env, a, b);
|
|
77
|
+
case "int":
|
|
78
|
+
if (b.k === "int" || (b.k === "lit" && typeof b.value === "number" && Number.isInteger(b.value))) {
|
|
79
|
+
return "ok";
|
|
80
|
+
}
|
|
81
|
+
if (b.k === "float" || (b.k === "lit" && typeof b.value === "number")) {
|
|
82
|
+
return "narrowing";
|
|
83
|
+
}
|
|
84
|
+
if (b.k === "enum") {
|
|
85
|
+
return "ok";
|
|
86
|
+
}
|
|
87
|
+
return nullableOr(env, a, b);
|
|
88
|
+
case "bool":
|
|
89
|
+
return b.k === "bool" || (b.k === "lit" && typeof b.value === "boolean") ? "ok" : nullableOr(env, a, b);
|
|
90
|
+
case "string":
|
|
91
|
+
return b.k === "string" || (b.k === "lit" && typeof b.value === "string") ? "ok" : nullableOr(env, a, b);
|
|
92
|
+
case "lit":
|
|
93
|
+
return b.k === "lit" && b.value === a.value ? "ok" : nullableOr(env, a, b);
|
|
94
|
+
case "list":
|
|
95
|
+
if (b.k === "list") {
|
|
96
|
+
if (!a.typed || !b.typed) {
|
|
97
|
+
return "ok";
|
|
98
|
+
}
|
|
99
|
+
const r = assignable(env, a.elem, b.elem);
|
|
100
|
+
return r === "ok" || r === "nullable" ? r : "no";
|
|
101
|
+
}
|
|
102
|
+
if (b.k === "tuple") {
|
|
103
|
+
return b.elems.every((e) => assignable(env, a.elem, e) === "ok") ? "ok" : "no";
|
|
104
|
+
}
|
|
105
|
+
return nullableOr(env, a, b);
|
|
106
|
+
case "dict":
|
|
107
|
+
if (b.k === "dict") {
|
|
108
|
+
return assignable(env, a.key, b.key) !== "no" && assignable(env, a.value, b.value) !== "no" ? "ok" : "no";
|
|
109
|
+
}
|
|
110
|
+
if (b.k === "table") {
|
|
111
|
+
return Object.values(b.fields).every((f) => assignable(env, a.value, f) !== "no") ? "ok" : "no";
|
|
112
|
+
}
|
|
113
|
+
return nullableOr(env, a, b);
|
|
114
|
+
case "table":
|
|
115
|
+
if (b.k === "table") {
|
|
116
|
+
for (const [k, v] of Object.entries(a.fields)) {
|
|
117
|
+
const bf = b.fields[k];
|
|
118
|
+
if (!bf) {
|
|
119
|
+
if (prune(v).k !== "option") {
|
|
120
|
+
return "no";
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (assignable(env, v, bf) === "no") {
|
|
125
|
+
return "no";
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return "ok";
|
|
129
|
+
}
|
|
130
|
+
if (b.k === "dict") {
|
|
131
|
+
return "ok";
|
|
132
|
+
}
|
|
133
|
+
return nullableOr(env, a, b);
|
|
134
|
+
case "struct":
|
|
135
|
+
if (b.k === "struct") {
|
|
136
|
+
if (a.id === b.id) {
|
|
137
|
+
return a.args.length === 0 || b.args.length === 0 || a.args.every((x, i) => sameType(x, b.args[i] ?? T.any)) ? "ok" : "no";
|
|
138
|
+
}
|
|
139
|
+
return structExtends(env, b.id, a.id) ? "ok" : "no";
|
|
140
|
+
}
|
|
141
|
+
return nullableOr(env, a, b);
|
|
142
|
+
case "enum":
|
|
143
|
+
case "variant":
|
|
144
|
+
if (b.k === a.k && b.id === a.id) {
|
|
145
|
+
return "ok";
|
|
146
|
+
}
|
|
147
|
+
if (a.k === "variant") {
|
|
148
|
+
return a.alternatives.some((alt) => assignable(env, alt, b) === "ok") ? "ok" : nullableOr(env, a, b);
|
|
149
|
+
}
|
|
150
|
+
return nullableOr(env, a, b);
|
|
151
|
+
case "result":
|
|
152
|
+
if (b.k === "result") {
|
|
153
|
+
const ok = assignable(env, a.ok, b.ok);
|
|
154
|
+
const err = assignable(env, a.err, b.err);
|
|
155
|
+
return ok !== "no" && err !== "no" ? "ok" : "no";
|
|
156
|
+
}
|
|
157
|
+
return nullableOr(env, a, b);
|
|
158
|
+
case "task":
|
|
159
|
+
return b.k === "task" ? "ok" : nullableOr(env, a, b);
|
|
160
|
+
case "func":
|
|
161
|
+
if (b.k === "func") {
|
|
162
|
+
return "ok";
|
|
163
|
+
}
|
|
164
|
+
return nullableOr(env, a, b);
|
|
165
|
+
case "instance":
|
|
166
|
+
if (b.k === "instance") {
|
|
167
|
+
return b.cls === a.cls || env.api.isA(b.cls, a.cls) ? "ok" : "no";
|
|
168
|
+
}
|
|
169
|
+
return nullableOr(env, a, b);
|
|
170
|
+
case "datatype":
|
|
171
|
+
if (b.k === "datatype") {
|
|
172
|
+
return b.name === a.name ? "ok" : "no";
|
|
173
|
+
}
|
|
174
|
+
return nullableOr(env, a, b);
|
|
175
|
+
case "rbxEnumItem":
|
|
176
|
+
if (b.k === "rbxEnumItem") {
|
|
177
|
+
return a.enumName === "*" || b.enumName === a.enumName || b.enumName === "*" ? "ok" : "no";
|
|
178
|
+
}
|
|
179
|
+
return nullableOr(env, a, b);
|
|
180
|
+
case "rbxEnum":
|
|
181
|
+
return b.k === "rbxEnum" && (a.enumName === "*" || a.enumName === b.enumName) ? "ok" : "no";
|
|
182
|
+
case "rbxSignal":
|
|
183
|
+
return b.k === "rbxSignal" ? "ok" : "no";
|
|
184
|
+
case "tuple":
|
|
185
|
+
return b.k === "tuple" && b.elems.length >= a.elems.length ? "ok" : "no";
|
|
186
|
+
case "thread":
|
|
187
|
+
case "buffer":
|
|
188
|
+
return b.k === a.k ? "ok" : nullableOr(env, a, b);
|
|
189
|
+
default:
|
|
190
|
+
return sameType(a, b) ? "ok" : "no";
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function nullableOr(env: TypeEnv, to: Type, from: Type): Assignability {
|
|
195
|
+
if (from.k === "option") {
|
|
196
|
+
return assignable(env, to, from.inner) === "ok" ? "nullable" : "no";
|
|
197
|
+
}
|
|
198
|
+
if (from.k === "null") {
|
|
199
|
+
return "nullable";
|
|
200
|
+
}
|
|
201
|
+
return "no";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function structExtends(env: TypeEnv, childId: string, baseId: string): boolean {
|
|
205
|
+
const seen = new Set<string>();
|
|
206
|
+
const stack = [childId];
|
|
207
|
+
while (stack.length) {
|
|
208
|
+
const id = stack.pop()!;
|
|
209
|
+
if (id === baseId) {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
if (seen.has(id)) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
seen.add(id);
|
|
216
|
+
const info = env.struct(id);
|
|
217
|
+
if (info) {
|
|
218
|
+
stack.push(...info.bases);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Least upper bound used for ternaries, list literals and inferred returns. */
|
|
225
|
+
export function join(env: TypeEnv, a: Type, b: Type): Type {
|
|
226
|
+
const x = prune(a);
|
|
227
|
+
const y = prune(b);
|
|
228
|
+
if (isDynamic(x)) {
|
|
229
|
+
return y;
|
|
230
|
+
}
|
|
231
|
+
if (isDynamic(y)) {
|
|
232
|
+
return x;
|
|
233
|
+
}
|
|
234
|
+
if (x.k === "never") {
|
|
235
|
+
return y;
|
|
236
|
+
}
|
|
237
|
+
if (y.k === "never") {
|
|
238
|
+
return x;
|
|
239
|
+
}
|
|
240
|
+
if (sameType(x, y)) {
|
|
241
|
+
return x;
|
|
242
|
+
}
|
|
243
|
+
if (x.k === "null") {
|
|
244
|
+
return T.option(y);
|
|
245
|
+
}
|
|
246
|
+
if (y.k === "null") {
|
|
247
|
+
return T.option(x);
|
|
248
|
+
}
|
|
249
|
+
if (x.k === "option" || y.k === "option") {
|
|
250
|
+
const inner = join(env, x.k === "option" ? x.inner : x, y.k === "option" ? y.inner : y);
|
|
251
|
+
return T.option(inner);
|
|
252
|
+
}
|
|
253
|
+
const numeric = (t: Type) => t.k === "int" || t.k === "float" || (t.k === "lit" && typeof t.value === "number");
|
|
254
|
+
if (numeric(x) && numeric(y)) {
|
|
255
|
+
const isInt = (t: Type) => t.k === "int" || (t.k === "lit" && Number.isInteger(t.value));
|
|
256
|
+
return isInt(x) && isInt(y) ? T.int : T.float;
|
|
257
|
+
}
|
|
258
|
+
if (x.k === "lit" && y.k === "lit" && typeof x.value === typeof y.value) {
|
|
259
|
+
return typeof x.value === "string" ? T.string : typeof x.value === "boolean" ? T.bool : T.float;
|
|
260
|
+
}
|
|
261
|
+
if ((x.k === "lit" && typeof x.value === "string" && y.k === "string") || (y.k === "lit" && typeof y.value === "string" && x.k === "string")) {
|
|
262
|
+
return T.string;
|
|
263
|
+
}
|
|
264
|
+
if (x.k === "instance" && y.k === "instance") {
|
|
265
|
+
return T.instance(commonClass(env, x.cls, y.cls));
|
|
266
|
+
}
|
|
267
|
+
if (x.k === "struct" && y.k === "struct") {
|
|
268
|
+
if (structExtends(env, x.id, y.id)) {
|
|
269
|
+
return y;
|
|
270
|
+
}
|
|
271
|
+
if (structExtends(env, y.id, x.id)) {
|
|
272
|
+
return x;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (x.k === "list" && y.k === "list") {
|
|
276
|
+
return T.list(join(env, x.elem, y.elem), x.typed && y.typed);
|
|
277
|
+
}
|
|
278
|
+
return { k: "union", members: [x, y] };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function commonClass(env: TypeEnv, a: string, b: string): string {
|
|
282
|
+
const chain = new Set<string>();
|
|
283
|
+
let cur: string | null = a;
|
|
284
|
+
while (cur) {
|
|
285
|
+
chain.add(cur);
|
|
286
|
+
cur = env.api.classes[cur]?.superclass ?? null;
|
|
287
|
+
}
|
|
288
|
+
cur = b;
|
|
289
|
+
while (cur) {
|
|
290
|
+
if (chain.has(cur)) {
|
|
291
|
+
return cur;
|
|
292
|
+
}
|
|
293
|
+
cur = env.api.classes[cur]?.superclass ?? null;
|
|
294
|
+
}
|
|
295
|
+
return "Instance";
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Replace generic parameters by their bindings. */
|
|
299
|
+
export function substitute(t: Type, bindings: Map<string, Type>): Type {
|
|
300
|
+
if (bindings.size === 0) {
|
|
301
|
+
return t;
|
|
302
|
+
}
|
|
303
|
+
const p = prune(t);
|
|
304
|
+
switch (p.k) {
|
|
305
|
+
case "param":
|
|
306
|
+
return bindings.get(p.name) ?? p;
|
|
307
|
+
case "list":
|
|
308
|
+
return T.list(substitute(p.elem, bindings), p.typed);
|
|
309
|
+
case "dict":
|
|
310
|
+
return T.dict(substitute(p.key, bindings), substitute(p.value, bindings));
|
|
311
|
+
case "option":
|
|
312
|
+
return T.option(substitute(p.inner, bindings));
|
|
313
|
+
case "result":
|
|
314
|
+
return { k: "result", ok: substitute(p.ok, bindings), err: substitute(p.err, bindings) };
|
|
315
|
+
case "task":
|
|
316
|
+
return { k: "task", result: substitute(p.result, bindings) };
|
|
317
|
+
case "struct":
|
|
318
|
+
return p.args.length ? { ...p, args: p.args.map((x) => substitute(x, bindings)) } : p;
|
|
319
|
+
case "func":
|
|
320
|
+
return {
|
|
321
|
+
...p,
|
|
322
|
+
params: p.params.map((x) => ({ ...x, type: substitute(x.type, bindings) })),
|
|
323
|
+
ret: substitute(p.ret, bindings),
|
|
324
|
+
variadic: p.variadic ? substitute(p.variadic, bindings) : null,
|
|
325
|
+
};
|
|
326
|
+
case "union":
|
|
327
|
+
return { k: "union", members: p.members.map((m) => substitute(m, bindings)) };
|
|
328
|
+
case "tuple":
|
|
329
|
+
return { k: "tuple", elems: p.elems.map((m) => substitute(m, bindings)) };
|
|
330
|
+
default:
|
|
331
|
+
return p;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Bind generic parameters by matching a declared type against an argument type. */
|
|
336
|
+
export function bindGenerics(declared: Type, actual: Type, bindings: Map<string, Type>): void {
|
|
337
|
+
const d = prune(declared);
|
|
338
|
+
const a = prune(actual);
|
|
339
|
+
if (d.k === "param") {
|
|
340
|
+
if (!bindings.has(d.name) && !isDynamic(a)) {
|
|
341
|
+
bindings.set(d.name, a.k === "lit" ? (typeof a.value === "string" ? T.string : typeof a.value === "boolean" ? T.bool : Number.isInteger(a.value) ? T.int : T.float) : a);
|
|
342
|
+
}
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (d.k === "list" && a.k === "list") {
|
|
346
|
+
bindGenerics(d.elem, a.elem, bindings);
|
|
347
|
+
} else if (d.k === "option") {
|
|
348
|
+
bindGenerics(d.inner, a.k === "option" ? a.inner : a, bindings);
|
|
349
|
+
} else if (d.k === "dict" && a.k === "dict") {
|
|
350
|
+
bindGenerics(d.key, a.key, bindings);
|
|
351
|
+
bindGenerics(d.value, a.value, bindings);
|
|
352
|
+
} else if (d.k === "struct" && a.k === "struct") {
|
|
353
|
+
d.args.forEach((x, i) => a.args[i] && bindGenerics(x, a.args[i], bindings));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/** Can executing these statements yield the current thread (task.wait, WaitForChild, *Async, :Wait())? */
|
|
2
|
+
|
|
3
|
+
import type { Expr, Stmt } from "../ast.js";
|
|
4
|
+
import { prune } from "../types.js";
|
|
5
|
+
import type { Checker } from "./checker.js";
|
|
6
|
+
|
|
7
|
+
const YIELDING_GLOBALS = new Set(["wait", "Wait"]);
|
|
8
|
+
const YIELDING_LIBRARY = new Set(["task.wait", "coroutine.yield"]);
|
|
9
|
+
|
|
10
|
+
export function mayYield(c: Checker, body: Stmt[]): boolean {
|
|
11
|
+
let found = false;
|
|
12
|
+
const visitExpr = (e: Expr | null | undefined): void => {
|
|
13
|
+
if (!e || found) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (exprYields(c, e)) {
|
|
17
|
+
found = true;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
for (const [key, value] of Object.entries(e)) {
|
|
21
|
+
if (key === "ty" || key === "res" || key === "span" || key === "narrowedFrom") {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (key === "body" && e.kind === "Function") {
|
|
25
|
+
continue; // a closure's body runs later
|
|
26
|
+
}
|
|
27
|
+
walk(value);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const walk = (node: unknown): void => {
|
|
31
|
+
if (found || !node || typeof node !== "object") {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(node)) {
|
|
35
|
+
for (const n of node) {
|
|
36
|
+
walk(n);
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const rec = node as { kind?: string };
|
|
41
|
+
if (rec.kind && isExprKind(rec.kind)) {
|
|
42
|
+
visitExpr(node as Expr);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const [key, value] of Object.entries(node)) {
|
|
46
|
+
if (key === "ty" || key === "res" || key === "span" || key === "sym") {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
walk(value);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
walk(body);
|
|
53
|
+
return found;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const EXPR_KINDS = new Set([
|
|
57
|
+
"Number", "String", "Template", "Bool", "Null", "Name", "SelfField", "Member", "Scope", "Index", "Call", "New", "Unary",
|
|
58
|
+
"Binary", "Ternary", "Function", "Spawn", "Await", "Thread", "Join", "Move", "PCall", "Parallel", "Update", "Cast", "Error",
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
function isExprKind(kind: string): boolean {
|
|
62
|
+
return EXPR_KINDS.has(kind);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function exprYields(c: Checker, e: Expr): boolean {
|
|
66
|
+
if (e.kind === "Await" || e.kind === "Join" || e.kind === "Parallel") {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
if (e.kind !== "Call") {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const callee = e.callee;
|
|
73
|
+
const res = callee.res;
|
|
74
|
+
if (!res) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
switch (res.kind) {
|
|
78
|
+
case "rbxMethod": {
|
|
79
|
+
const name = callee.kind === "Member" || callee.kind === "Scope" ? callee.name : "";
|
|
80
|
+
const hit = c.api.member(res.owner, name);
|
|
81
|
+
return Boolean(hit?.member.yields) || name === "WaitForChild" || /Async$/.test(name);
|
|
82
|
+
}
|
|
83
|
+
case "datatypeMember":
|
|
84
|
+
return res.datatype === "RBXScriptSignal" && res.member === "Wait";
|
|
85
|
+
case "global":
|
|
86
|
+
return YIELDING_GLOBALS.has(res.luau);
|
|
87
|
+
case "library":
|
|
88
|
+
return YIELDING_LIBRARY.has(`${res.lib}.${res.member}`);
|
|
89
|
+
case "func":
|
|
90
|
+
case "local": {
|
|
91
|
+
const decl = (res.sym as { decl?: { fn?: { mayYield?: boolean } } }).decl;
|
|
92
|
+
if (decl) {
|
|
93
|
+
return decl.fn?.mayYield ?? true;
|
|
94
|
+
}
|
|
95
|
+
const t = prune(callee.ty ?? { k: "any" });
|
|
96
|
+
return t.k !== "func";
|
|
97
|
+
}
|
|
98
|
+
case "method": {
|
|
99
|
+
const info = c.structs.get(res.struct);
|
|
100
|
+
const m = info?.methods.get(res.name);
|
|
101
|
+
return m?.decl.fn?.mayYield ?? true;
|
|
102
|
+
}
|
|
103
|
+
case "moduleMember":
|
|
104
|
+
return true;
|
|
105
|
+
case "builtin":
|
|
106
|
+
case "stdlib":
|
|
107
|
+
case "datatypeStatic":
|
|
108
|
+
case "rbxProperty":
|
|
109
|
+
case "rbxEvent":
|
|
110
|
+
case "enumCase":
|
|
111
|
+
case "struct":
|
|
112
|
+
case "signal":
|
|
113
|
+
return false;
|
|
114
|
+
case "dynamic":
|
|
115
|
+
case "luauMethod":
|
|
116
|
+
return true;
|
|
117
|
+
default:
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|