Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/Analysis/src/BuiltinDefinitions.cpp
2725 views
1
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2
#include "Luau/BuiltinDefinitions.h"
3
4
#include "Luau/Ast.h"
5
#include "Luau/BuiltinTypeFunctions.h"
6
#include "Luau/Clone.h"
7
#include "Luau/Common.h"
8
#include "Luau/ConstraintGenerator.h"
9
#include "Luau/ConstraintSolver.h"
10
#include "Luau/DenseHash.h"
11
#include "Luau/Error.h"
12
#include "Luau/Frontend.h"
13
#include "Luau/Module.h"
14
#include "Luau/NotNull.h"
15
#include "Luau/Subtyping.h"
16
#include "Luau/Symbol.h"
17
#include "Luau/Type.h"
18
#include "Luau/TypeChecker2.h"
19
#include "Luau/TypeFunction.h"
20
#include "Luau/TypeInfer.h"
21
#include "Luau/TypePack.h"
22
#include "Luau/TypeUtils.h"
23
24
#include <algorithm>
25
#include <string_view>
26
27
/** FIXME: Many of these type definitions are not quite completely accurate.
28
*
29
* Some of them require richer generics than we have. For instance, we do not yet have a way to talk
30
* about a function that takes any number of values, but where each value must have some specific type.
31
*/
32
33
LUAU_FASTFLAGVARIABLE(LuauTableFreezeCheckIsSubtype)
34
LUAU_FASTFLAGVARIABLE(LuauSilenceDynamicFormatStringErrors)
35
LUAU_FASTFLAGVARIABLE(LuauPcallCallbackCanReturnZeroValues)
36
37
namespace Luau
38
{
39
40
struct MagicSelect final : MagicFunction
41
{
42
std::optional<WithPredicate<TypePackId>> handleOldSolver(
43
struct TypeChecker&,
44
const std::shared_ptr<struct Scope>&,
45
const class AstExprCall&,
46
WithPredicate<TypePackId>
47
) override;
48
bool infer(const MagicFunctionCallContext& ctx) override;
49
};
50
51
struct MagicSetMetatable final : MagicFunction
52
{
53
std::optional<WithPredicate<TypePackId>> handleOldSolver(
54
struct TypeChecker&,
55
const std::shared_ptr<struct Scope>&,
56
const class AstExprCall&,
57
WithPredicate<TypePackId>
58
) override;
59
bool infer(const MagicFunctionCallContext& ctx) override;
60
};
61
62
struct MagicAssert final : MagicFunction
63
{
64
std::optional<WithPredicate<TypePackId>> handleOldSolver(
65
struct TypeChecker&,
66
const std::shared_ptr<struct Scope>&,
67
const class AstExprCall&,
68
WithPredicate<TypePackId>
69
) override;
70
bool infer(const MagicFunctionCallContext& ctx) override;
71
};
72
73
struct MagicPack final : MagicFunction
74
{
75
std::optional<WithPredicate<TypePackId>> handleOldSolver(
76
struct TypeChecker&,
77
const std::shared_ptr<struct Scope>&,
78
const class AstExprCall&,
79
WithPredicate<TypePackId>
80
) override;
81
bool infer(const MagicFunctionCallContext& ctx) override;
82
};
83
84
struct MagicRequire final : MagicFunction
85
{
86
std::optional<WithPredicate<TypePackId>> handleOldSolver(
87
struct TypeChecker&,
88
const std::shared_ptr<struct Scope>&,
89
const class AstExprCall&,
90
WithPredicate<TypePackId>
91
) override;
92
bool infer(const MagicFunctionCallContext& ctx) override;
93
};
94
95
struct MagicClone final : MagicFunction
96
{
97
std::optional<WithPredicate<TypePackId>> handleOldSolver(
98
struct TypeChecker&,
99
const std::shared_ptr<struct Scope>&,
100
const class AstExprCall&,
101
WithPredicate<TypePackId>
102
) override;
103
bool infer(const MagicFunctionCallContext& ctx) override;
104
};
105
106
struct MagicFreeze final : MagicFunction
107
{
108
std::optional<WithPredicate<TypePackId>> handleOldSolver(
109
struct TypeChecker&,
110
const std::shared_ptr<struct Scope>&,
111
const class AstExprCall&,
112
WithPredicate<TypePackId>
113
) override;
114
bool infer(const MagicFunctionCallContext& ctx) override;
115
bool typeCheck(const MagicFunctionTypeCheckContext& ctx) override;
116
};
117
118
struct MagicFormat final : MagicFunction
119
{
120
std::optional<WithPredicate<TypePackId>> handleOldSolver(
121
struct TypeChecker&,
122
const std::shared_ptr<struct Scope>&,
123
const class AstExprCall&,
124
WithPredicate<TypePackId>
125
) override;
126
bool infer(const MagicFunctionCallContext& ctx) override;
127
bool typeCheck(const MagicFunctionTypeCheckContext& ctx) override;
128
};
129
130
struct MagicMatch final : MagicFunction
131
{
132
std::optional<WithPredicate<TypePackId>> handleOldSolver(
133
struct TypeChecker&,
134
const std::shared_ptr<struct Scope>&,
135
const class AstExprCall&,
136
WithPredicate<TypePackId>
137
) override;
138
bool infer(const MagicFunctionCallContext& ctx) override;
139
};
140
141
struct MagicGmatch final : MagicFunction
142
{
143
std::optional<WithPredicate<TypePackId>> handleOldSolver(
144
struct TypeChecker&,
145
const std::shared_ptr<struct Scope>&,
146
const class AstExprCall&,
147
WithPredicate<TypePackId>
148
) override;
149
bool infer(const MagicFunctionCallContext& ctx) override;
150
};
151
152
struct MagicFind final : MagicFunction
153
{
154
std::optional<WithPredicate<TypePackId>> handleOldSolver(
155
struct TypeChecker&,
156
const std::shared_ptr<struct Scope>&,
157
const class AstExprCall&,
158
WithPredicate<TypePackId>
159
) override;
160
bool infer(const MagicFunctionCallContext& ctx) override;
161
};
162
163
struct MagicPcall final : MagicFunction
164
{
165
std::optional<WithPredicate<TypePackId>> handleOldSolver(
166
struct TypeChecker&,
167
const std::shared_ptr<struct Scope>&,
168
const class AstExprCall&,
169
WithPredicate<TypePackId>
170
) override;
171
bool infer(const MagicFunctionCallContext& ctx) override;
172
};
173
174
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types)
175
{
176
return arena.addType(UnionType{std::move(types)});
177
}
178
179
TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types)
180
{
181
return arena.addType(IntersectionType{std::move(types)});
182
}
183
184
TypeId makeOption(NotNull<BuiltinTypes> builtinTypes, TypeArena& arena, TypeId t)
185
{
186
LUAU_ASSERT(t);
187
return makeUnion(arena, {builtinTypes->nilType, t});
188
}
189
190
TypeId makeFunction(
191
TypeArena& arena,
192
std::optional<TypeId> selfType,
193
std::initializer_list<TypeId> paramTypes,
194
std::initializer_list<TypeId> retTypes,
195
bool checked
196
)
197
{
198
return makeFunction(arena, selfType, {}, {}, paramTypes, {}, retTypes, checked);
199
}
200
201
TypeId makeFunction(
202
TypeArena& arena,
203
std::optional<TypeId> selfType,
204
std::initializer_list<TypeId> generics,
205
std::initializer_list<TypePackId> genericPacks,
206
std::initializer_list<TypeId> paramTypes,
207
std::initializer_list<TypeId> retTypes,
208
bool checked
209
)
210
{
211
return makeFunction(arena, selfType, generics, genericPacks, paramTypes, {}, retTypes, checked);
212
}
213
214
TypeId makeFunction(
215
TypeArena& arena,
216
std::optional<TypeId> selfType,
217
std::initializer_list<TypeId> paramTypes,
218
std::initializer_list<std::string> paramNames,
219
std::initializer_list<TypeId> retTypes,
220
bool checked
221
)
222
{
223
return makeFunction(arena, selfType, {}, {}, paramTypes, paramNames, retTypes, checked);
224
}
225
226
TypeId makeFunction(
227
TypeArena& arena,
228
std::optional<TypeId> selfType,
229
std::initializer_list<TypeId> generics,
230
std::initializer_list<TypePackId> genericPacks,
231
std::initializer_list<TypeId> paramTypes,
232
std::initializer_list<std::string> paramNames,
233
std::initializer_list<TypeId> retTypes,
234
bool checked
235
)
236
{
237
std::vector<TypeId> params;
238
if (selfType)
239
params.push_back(*selfType);
240
for (auto&& p : paramTypes)
241
params.push_back(p);
242
243
TypePackId paramPack = arena.addTypePack(std::move(params));
244
TypePackId retPack = arena.addTypePack(std::vector<TypeId>(retTypes));
245
FunctionType ftv{generics, genericPacks, paramPack, retPack, {}, selfType.has_value()};
246
247
if (selfType)
248
{
249
ftv.argNames.emplace_back(Luau::FunctionArgument{"self", {}});
250
}
251
252
if (paramNames.size() != 0)
253
{
254
for (auto&& p : paramNames)
255
ftv.argNames.emplace_back(Luau::FunctionArgument{p, Location{}});
256
}
257
else if (selfType)
258
{
259
// If argument names were not provided, but we have already added a name for 'self' argument, we have to fill remaining slots as well
260
for (size_t i = 0; i < paramTypes.size(); i++)
261
ftv.argNames.emplace_back(std::nullopt);
262
}
263
264
ftv.isCheckedFunction = checked;
265
266
return arena.addType(std::move(ftv));
267
}
268
269
void attachMagicFunction(TypeId ty, std::shared_ptr<MagicFunction> magic)
270
{
271
if (auto ftv = getMutable<FunctionType>(ty))
272
ftv->magic = std::move(magic);
273
else
274
LUAU_ASSERT(!"Got a non functional type");
275
}
276
277
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol)
278
{
279
return {
280
/* type */ ty,
281
/* deprecated */ false,
282
/* deprecatedSuggestion */ {},
283
/* location */ std::nullopt,
284
/* tags */ {},
285
documentationSymbol,
286
};
287
}
288
289
void addGlobalBinding(GlobalTypes& globals, const std::string& name, TypeId ty, const std::string& packageName)
290
{
291
addGlobalBinding(globals, globals.globalScope, name, ty, packageName);
292
}
293
294
void addGlobalBinding(GlobalTypes& globals, const std::string& name, Binding binding)
295
{
296
addGlobalBinding(globals, globals.globalScope, name, std::move(binding));
297
}
298
299
void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName)
300
{
301
std::string documentationSymbol = packageName + "/global/" + name;
302
addGlobalBinding(globals, scope, name, Binding{ty, Location{}, {}, {}, documentationSymbol});
303
}
304
305
void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, Binding binding)
306
{
307
scope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = binding;
308
}
309
310
std::optional<Binding> tryGetGlobalBinding(GlobalTypes& globals, const std::string& name)
311
{
312
AstName astName = globals.globalNames.names->getOrAdd(name.c_str());
313
auto it = globals.globalScope->bindings.find(astName);
314
if (it != globals.globalScope->bindings.end())
315
return it->second;
316
317
return std::nullopt;
318
}
319
320
TypeId getGlobalBinding(GlobalTypes& globals, const std::string& name)
321
{
322
auto t = tryGetGlobalBinding(globals, name);
323
LUAU_ASSERT(t.has_value());
324
return t->typeId;
325
}
326
327
Binding* tryGetGlobalBindingRef(GlobalTypes& globals, const std::string& name)
328
{
329
AstName astName = globals.globalNames.names->get(name.c_str());
330
if (astName == AstName())
331
return nullptr;
332
333
auto it = globals.globalScope->bindings.find(astName);
334
if (it != globals.globalScope->bindings.end())
335
return &it->second;
336
337
return nullptr;
338
}
339
340
void assignPropDocumentationSymbols(TableType::Props& props, const std::string& baseName)
341
{
342
for (auto& [name, prop] : props)
343
{
344
prop.documentationSymbol = baseName + "." + name;
345
}
346
}
347
348
static void finalizeGlobalBindings(ScopePtr scope)
349
{
350
for (const auto& pair : scope->bindings)
351
{
352
persist(pair.second.typeId);
353
354
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
355
{
356
if (!ttv->name)
357
ttv->name = "typeof(" + toString(pair.first) + ")";
358
}
359
}
360
}
361
362
void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeCheckForAutocomplete)
363
{
364
LUAU_ASSERT(!globals.globalTypes.types.isFrozen());
365
LUAU_ASSERT(!globals.globalTypes.typePacks.isFrozen());
366
367
TypeArena& arena = globals.globalTypes;
368
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
369
NotNull<Scope> globalScope{globals.globalScope.get()};
370
371
if (frontend.getLuauSolverMode() == SolverMode::New)
372
builtinTypes->typeFunctions->addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()});
373
374
375
LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile(
376
globals, globals.globalScope, getBuiltinDefinitionSource(), "@luau", /* captureComments */ false, typeCheckForAutocomplete
377
);
378
LUAU_ASSERT(loadResult.success);
379
380
TypeId genericK = arena.addType(GenericType{globalScope, "K", Polarity::Mixed});
381
TypeId genericV = arena.addType(GenericType{globalScope, "V", Polarity::Mixed});
382
TypeId mapOfKtoV = arena.addType(TableType{{}, TableIndexer(genericK, genericV), globals.globalScope->level, TableState::Generic});
383
384
std::optional<TypeId> stringMetatableTy = getMetatable(builtinTypes->stringType, builtinTypes);
385
LUAU_ASSERT(stringMetatableTy);
386
const TableType* stringMetatableTable = get<TableType>(follow(*stringMetatableTy));
387
LUAU_ASSERT(stringMetatableTable);
388
389
auto it = stringMetatableTable->props.find("__index");
390
LUAU_ASSERT(it != stringMetatableTable->props.end());
391
392
addGlobalBinding(globals, "string", *it->second.readTy, "@luau");
393
addGlobalBinding(globals, "string", *it->second.writeTy, "@luau");
394
395
// Setup 'vector' metatable
396
if (auto it = globals.globalScope->exportedTypeBindings.find("vector"); it != globals.globalScope->exportedTypeBindings.end())
397
{
398
TypeId vectorTy = it->second.type;
399
ExternType* vectorCls = getMutable<ExternType>(vectorTy);
400
401
vectorCls->metatable = arena.addType(TableType{{}, std::nullopt, TypeLevel{}, TableState::Sealed});
402
TableType* metatableTy = Luau::getMutable<TableType>(vectorCls->metatable);
403
404
metatableTy->props["__add"] = {makeFunction(arena, vectorTy, {vectorTy}, {vectorTy})};
405
metatableTy->props["__sub"] = {makeFunction(arena, vectorTy, {vectorTy}, {vectorTy})};
406
metatableTy->props["__unm"] = {makeFunction(arena, vectorTy, {}, {vectorTy})};
407
408
std::initializer_list<TypeId> mulOverloads{
409
makeFunction(arena, vectorTy, {vectorTy}, {vectorTy}),
410
makeFunction(arena, vectorTy, {builtinTypes->numberType}, {vectorTy}),
411
};
412
metatableTy->props["__mul"] = {makeIntersection(arena, mulOverloads)};
413
metatableTy->props["__div"] = {makeIntersection(arena, mulOverloads)};
414
metatableTy->props["__idiv"] = {makeIntersection(arena, mulOverloads)};
415
}
416
417
// next<K, V>(t: Table<K, V>, i: K?) -> (K?, V)
418
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(builtinTypes, arena, genericK)}});
419
TypePackId nextRetsTypePack = arena.addTypePack(TypePack{{makeOption(builtinTypes, arena, genericK), genericV}});
420
addGlobalBinding(globals, "next", arena.addType(FunctionType{{genericK, genericV}, {}, nextArgsTypePack, nextRetsTypePack}), "@luau");
421
422
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
423
424
TypeId pairsNext = arena.addType(FunctionType{nextArgsTypePack, nextRetsTypePack});
425
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, builtinTypes->nilType}});
426
427
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
428
addGlobalBinding(globals, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
429
430
TypeId genericMT = arena.addType(GenericType{globalScope, "MT", Polarity::Mixed});
431
432
TableType tab{TableState::Generic, globals.globalScope->level};
433
TypeId tabTy = arena.addType(std::move(tab));
434
435
TypeId tableMetaMT = arena.addType(MetatableType{tabTy, genericMT});
436
437
TypeId genericT = arena.addType(GenericType{globalScope, "T", Polarity::Mixed});
438
439
if (frontend.getLuauSolverMode() == SolverMode::New)
440
{
441
// getmetatable : <T>(T) -> getmetatable<T>
442
TypeId getmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypes->typeFunctions->getmetatableFunc, {genericT}});
443
addGlobalBinding(globals, "getmetatable", makeFunction(arena, std::nullopt, {genericT}, {}, {genericT}, {getmtReturn}), "@luau");
444
}
445
else
446
{
447
// getmetatable : <MT>({ @metatable MT, {+ +} }) -> MT
448
addGlobalBinding(globals, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau");
449
}
450
451
if (frontend.getLuauSolverMode() == SolverMode::New)
452
{
453
// setmetatable<T: {}, MT>(T, MT) -> setmetatable<T, MT>
454
TypeId setmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypes->typeFunctions->setmetatableFunc, {genericT, genericMT}});
455
addGlobalBinding(
456
globals, "setmetatable", makeFunction(arena, std::nullopt, {genericT, genericMT}, {}, {genericT, genericMT}, {setmtReturn}), "@luau"
457
);
458
}
459
else
460
{
461
// clang-format off
462
// setmetatable<T: {}, MT>(T, MT) -> { @metatable MT, T }
463
addGlobalBinding(globals, "setmetatable",
464
arena.addType(
465
FunctionType{
466
{genericMT},
467
{},
468
arena.addTypePack(TypePack{{tabTy, genericMT}}),
469
arena.addTypePack(TypePack{{tableMetaMT}})
470
}
471
), "@luau"
472
);
473
// clang-format on
474
}
475
476
finalizeGlobalBindings(globals.globalScope);
477
478
attachMagicFunction(getGlobalBinding(globals, "assert"), std::make_shared<MagicAssert>());
479
if (FFlag::LuauPcallCallbackCanReturnZeroValues)
480
attachMagicFunction(getGlobalBinding(globals, "pcall"), std::make_shared<MagicPcall>());
481
482
if (frontend.getLuauSolverMode() == SolverMode::New)
483
{
484
// declare function assert<T>(value: T, errorMessage: string?): intersect<T, ~(false?)>
485
TypeId genericT = arena.addType(GenericType{globalScope, "T", Polarity::Mixed});
486
487
TypeId refinedTy = arena.addType(
488
TypeFunctionInstanceType{
489
NotNull{&builtinTypes->typeFunctions->intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {}
490
}
491
);
492
493
TypeId assertTy = arena.addType(
494
FunctionType{
495
{genericT}, {}, arena.addTypePack(TypePack{{genericT, builtinTypes->optionalStringType}}), arena.addTypePack(TypePack{{refinedTy}})
496
}
497
);
498
addGlobalBinding(globals, "assert", assertTy, "@luau");
499
}
500
501
attachMagicFunction(getGlobalBinding(globals, "setmetatable"), std::make_shared<MagicSetMetatable>());
502
attachMagicFunction(getGlobalBinding(globals, "select"), std::make_shared<MagicSelect>());
503
504
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(globals, "table")))
505
{
506
if (frontend.getLuauSolverMode() == SolverMode::New)
507
{
508
// CLI-114044 - The new solver does not yet support generic tables,
509
// which act, in an odd way, like generics that are constrained to
510
// the top table type. We do the best we can by modelling these
511
// functions using unconstrained generics. It's not quite right,
512
// but it'll be ok for now.
513
TypeId genericTy = arena.addType(GenericType{globalScope, "T", Polarity::Mixed});
514
TypePackId thePack = arena.addTypePack({genericTy});
515
TypeId idTyWithMagic = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack});
516
ttv->props["freeze"] = makeProperty(idTyWithMagic, "@luau/global/table.freeze");
517
518
TypeId idTy = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack});
519
520
ttv->props["clone"] = makeProperty(idTy, "@luau/global/table.clone");
521
}
522
else
523
{
524
// tabTy is a generic table type which we can't express via declaration syntax yet
525
ttv->props["freeze"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.freeze");
526
ttv->props["clone"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.clone");
527
}
528
529
ttv->props["getn"].deprecated = true;
530
ttv->props["getn"].deprecatedSuggestion = "#";
531
ttv->props["foreach"].deprecated = true;
532
ttv->props["foreachi"].deprecated = true;
533
534
attachMagicFunction(*ttv->props["pack"].readTy, std::make_shared<MagicPack>());
535
attachMagicFunction(*ttv->props["clone"].readTy, std::make_shared<MagicClone>());
536
attachMagicFunction(*ttv->props["freeze"].readTy, std::make_shared<MagicFreeze>());
537
}
538
539
TypeId requireTy = getGlobalBinding(globals, "require");
540
attachTag(requireTy, kRequireTagName);
541
attachMagicFunction(requireTy, std::make_shared<MagicRequire>());
542
543
// Global scope cannot be the parent of the type checking environment because it can be changed by the embedder
544
globals.globalTypeFunctionScope->exportedTypeBindings = globals.globalScope->exportedTypeBindings;
545
globals.globalTypeFunctionScope->builtinTypeNames = globals.globalScope->builtinTypeNames;
546
547
// Type function runtime also removes a few standard libraries and globals, so we will take only the ones that are defined
548
static constexpr const char* typeFunctionRuntimeBindings[] = {
549
// Libraries
550
"math",
551
"table",
552
"string",
553
"bit32",
554
"utf8",
555
"buffer",
556
557
// Globals
558
"assert",
559
"error",
560
"print",
561
"next",
562
"ipairs",
563
"pairs",
564
"select",
565
"unpack",
566
"getmetatable",
567
"setmetatable",
568
"rawget",
569
"rawset",
570
"rawlen",
571
"rawequal",
572
"tonumber",
573
"tostring",
574
"type",
575
"typeof",
576
};
577
578
for (auto& name : typeFunctionRuntimeBindings)
579
{
580
AstName astName = globals.globalNames.names->get(name);
581
LUAU_ASSERT(astName.value);
582
583
globals.globalTypeFunctionScope->bindings[astName] = globals.globalScope->bindings[astName];
584
}
585
586
LoadDefinitionFileResult typeFunctionLoadResult = frontend.loadDefinitionFile(
587
globals, globals.globalTypeFunctionScope, getTypeFunctionDefinitionSource(), "@luau", /* captureComments */ false, false
588
);
589
LUAU_ASSERT(typeFunctionLoadResult.success);
590
591
finalizeGlobalBindings(globals.globalTypeFunctionScope);
592
}
593
594
static std::vector<TypeId> parseFormatString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size)
595
{
596
const char* options = "cdiouxXeEfgGqs*";
597
598
std::vector<TypeId> result;
599
600
for (size_t i = 0; i < size; ++i)
601
{
602
if (data[i] == '%')
603
{
604
i++;
605
606
if (i < size && data[i] == '%')
607
continue;
608
609
// we just ignore all characters (including flags/precision) up until first alphabetic character
610
while (i < size && !(data[i] > 0 && (isalpha(data[i]) || data[i] == '*')))
611
i++;
612
613
if (i == size)
614
break;
615
616
if (data[i] == 'q' || data[i] == 's')
617
result.push_back(builtinTypes->stringType);
618
else if (data[i] == '*')
619
result.push_back(builtinTypes->unknownType);
620
else if (strchr(options, data[i]))
621
result.push_back(builtinTypes->numberType);
622
else
623
result.push_back(builtinTypes->errorRecoveryType(builtinTypes->anyType));
624
}
625
}
626
627
return result;
628
}
629
630
std::optional<WithPredicate<TypePackId>> MagicFormat::handleOldSolver(
631
TypeChecker& typechecker,
632
const ScopePtr& scope,
633
const AstExprCall& expr,
634
WithPredicate<TypePackId> withPredicate
635
)
636
{
637
auto [paramPack, _predicates] = std::move(withPredicate);
638
639
TypeArena& arena = typechecker.currentModule->internalTypes;
640
641
AstExprConstantString* fmt = nullptr;
642
if (auto index = expr.func->as<AstExprIndexName>(); index && expr.self)
643
{
644
if (auto group = index->expr->as<AstExprGroup>())
645
fmt = group->expr->as<AstExprConstantString>();
646
else
647
fmt = index->expr->as<AstExprConstantString>();
648
}
649
650
if (!expr.self && expr.args.size > 0)
651
fmt = expr.args.data[0]->as<AstExprConstantString>();
652
653
if (!fmt)
654
return std::nullopt;
655
656
std::vector<TypeId> expected = parseFormatString(typechecker.builtinTypes, fmt->value.data, fmt->value.size);
657
const auto& [params, tail] = flatten(paramPack);
658
659
size_t paramOffset = 1;
660
size_t dataOffset = expr.self ? 0 : 1;
661
662
// unify the prefix one argument at a time
663
for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i)
664
{
665
Location location = expr.args.data[std::min(i + dataOffset, expr.args.size - 1)]->location;
666
667
typechecker.unify(params[i + paramOffset], expected[i], scope, location);
668
}
669
670
// if we know the argument count or if we have too many arguments for sure, we can issue an error
671
size_t numActualParams = params.size();
672
size_t numExpectedParams = expected.size() + 1; // + 1 for the format string
673
674
if (numExpectedParams != numActualParams && (!tail || numExpectedParams < numActualParams))
675
typechecker.reportError(TypeError{expr.location, CountMismatch{numExpectedParams, std::nullopt, numActualParams}});
676
677
return WithPredicate<TypePackId>{arena.addTypePack({typechecker.stringType})};
678
}
679
680
bool MagicFormat::infer(const MagicFunctionCallContext& context)
681
{
682
TypeArena* arena = context.solver->arena;
683
684
auto iter = begin(context.arguments);
685
686
// we'll suppress any errors for `string.format` if the format string is error suppressing.
687
if (iter == end(context.arguments) || shouldSuppressErrors(context.solver->normalizer, follow(*iter)) == ErrorSuppression::Suppress)
688
{
689
TypePackId resultPack = arena->addTypePack({context.solver->builtinTypes->stringType});
690
asMutable(context.result)->ty.emplace<BoundTypePack>(resultPack);
691
return true;
692
}
693
694
AstExprConstantString* fmt = nullptr;
695
if (auto index = context.callSite->func->as<AstExprIndexName>(); index && context.callSite->self)
696
fmt = unwrapGroup(index->expr)->as<AstExprConstantString>();
697
698
if (!context.callSite->self && context.callSite->args.size > 0)
699
fmt = context.callSite->args.data[0]->as<AstExprConstantString>();
700
701
std::optional<std::string_view> formatString;
702
if (fmt)
703
formatString = {fmt->value.data, fmt->value.size};
704
else if (auto singleton = get<SingletonType>(follow(*iter)))
705
{
706
if (auto stringSingleton = get<StringSingleton>(singleton))
707
formatString = {stringSingleton->value};
708
}
709
710
if (!formatString)
711
return false;
712
713
std::vector<TypeId> expected = parseFormatString(context.solver->builtinTypes, formatString->data(), formatString->size());
714
const auto& [params, tail] = flatten(context.arguments);
715
716
size_t paramOffset = 1;
717
718
// unify the prefix one argument at a time - needed if any of the involved types are free
719
for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i)
720
{
721
context.solver->unify(context.constraint, params[i + paramOffset], expected[i]);
722
}
723
724
// if we know the argument count or if we have too many arguments for sure, we can issue an error
725
size_t numActualParams = params.size();
726
size_t numExpectedParams = expected.size() + 1; // + 1 for the format string
727
728
if (numExpectedParams != numActualParams && (!tail || numExpectedParams < numActualParams))
729
context.solver->reportError(TypeError{context.callSite->location, CountMismatch{numExpectedParams, std::nullopt, numActualParams}});
730
731
// This is invoked at solve time, so we just need to provide a type for the result of :/.format
732
TypePackId resultPack = arena->addTypePack({context.solver->builtinTypes->stringType});
733
asMutable(context.result)->ty.emplace<BoundTypePack>(resultPack);
734
735
return true;
736
}
737
738
bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
739
{
740
auto iter = begin(context.arguments);
741
742
if (iter == end(context.arguments))
743
{
744
context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location);
745
return true;
746
}
747
748
// we'll suppress any errors for `string.format` if the format string is error suppressing.
749
if (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, follow(*iter)) == ErrorSuppression::Suppress)
750
{
751
return true;
752
}
753
754
AstExprConstantString* fmt = nullptr;
755
if (auto index = context.callSite->func->as<AstExprIndexName>(); index && context.callSite->self)
756
fmt = unwrapGroup(index->expr)->as<AstExprConstantString>();
757
758
if (!context.callSite->self && context.callSite->args.size > 0)
759
fmt = context.callSite->args.data[0]->as<AstExprConstantString>();
760
761
std::optional<std::string_view> formatString;
762
if (fmt)
763
formatString = {fmt->value.data, fmt->value.size};
764
else if (auto singleton = get<SingletonType>(follow(*iter)))
765
{
766
if (auto stringSingleton = get<StringSingleton>(singleton))
767
formatString = {stringSingleton->value};
768
}
769
770
if (FFlag::LuauSilenceDynamicFormatStringErrors)
771
{
772
if (!formatString)
773
return true;
774
}
775
else
776
{
777
if (!formatString)
778
{
779
context.typechecker->reportError(CannotCheckDynamicStringFormatCalls{}, context.callSite->location);
780
return true;
781
}
782
}
783
784
// CLI-150726: The block below effectively constructs a type pack and then type checks it by going parameter-by-parameter.
785
// This does _not_ handle cases like:
786
//
787
// local foo : () -> (...string) = (nil :: any)
788
// print(string.format("%s %d %s", foo()))
789
//
790
// ... which should be disallowed.
791
792
std::vector<TypeId> expected = parseFormatString(context.builtinTypes, formatString->data(), formatString->size());
793
794
const auto& [params, tail] = flatten(context.arguments);
795
796
size_t paramOffset = 1;
797
// Compare the expressions passed with the types the function expects to determine whether this function was called with : or .
798
bool calledWithSelf = expected.size() == context.callSite->args.size;
799
// unify the prefix one argument at a time
800
for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i)
801
{
802
TypeId actualTy = params[i + paramOffset];
803
TypeId expectedTy = expected[i];
804
Location location = context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location;
805
// use subtyping instead here
806
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);
807
808
if (!result.isSubtype)
809
{
810
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
811
{
812
case ErrorSuppression::Suppress:
813
break;
814
case ErrorSuppression::NormalizationFailed:
815
break;
816
case ErrorSuppression::DoNotSuppress:
817
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
818
819
if (!reasonings.suppressed)
820
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
821
}
822
}
823
}
824
825
return true;
826
}
827
828
static std::vector<TypeId> parsePatternString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size)
829
{
830
std::vector<TypeId> result;
831
int depth = 0;
832
bool parsingSet = false;
833
834
for (size_t i = 0; i < size; ++i)
835
{
836
if (data[i] == '%')
837
{
838
++i;
839
if (!parsingSet && i < size && data[i] == 'b')
840
i += 2;
841
}
842
else if (!parsingSet && data[i] == '[')
843
{
844
parsingSet = true;
845
if (i + 1 < size && data[i + 1] == ']')
846
i += 1;
847
}
848
else if (parsingSet && data[i] == ']')
849
{
850
parsingSet = false;
851
}
852
else if (data[i] == '(')
853
{
854
if (parsingSet)
855
continue;
856
857
if (i + 1 < size && data[i + 1] == ')')
858
{
859
i++;
860
result.push_back(builtinTypes->optionalNumberType);
861
continue;
862
}
863
864
++depth;
865
result.push_back(builtinTypes->optionalStringType);
866
}
867
else if (data[i] == ')')
868
{
869
if (parsingSet)
870
continue;
871
872
--depth;
873
874
if (depth < 0)
875
break;
876
}
877
}
878
879
if (depth != 0 || parsingSet)
880
return std::vector<TypeId>();
881
882
if (result.empty())
883
result.push_back(builtinTypes->optionalStringType);
884
885
return result;
886
}
887
888
std::optional<WithPredicate<TypePackId>> MagicGmatch::handleOldSolver(
889
TypeChecker& typechecker,
890
const ScopePtr& scope,
891
const AstExprCall& expr,
892
WithPredicate<TypePackId> withPredicate
893
)
894
{
895
auto [paramPack, _predicates] = std::move(withPredicate);
896
const auto& [params, tail] = flatten(paramPack);
897
898
if (params.size() != 2)
899
return std::nullopt;
900
901
TypeArena& arena = typechecker.currentModule->internalTypes;
902
903
AstExprConstantString* pattern = nullptr;
904
size_t index = expr.self ? 0 : 1;
905
if (expr.args.size > index)
906
pattern = expr.args.data[index]->as<AstExprConstantString>();
907
908
if (!pattern)
909
return std::nullopt;
910
911
std::vector<TypeId> returnTypes = parsePatternString(typechecker.builtinTypes, pattern->value.data, pattern->value.size);
912
913
if (returnTypes.empty())
914
return std::nullopt;
915
916
typechecker.unify(params[0], typechecker.stringType, scope, expr.args.data[0]->location);
917
918
const TypePackId emptyPack = arena.addTypePack({});
919
const TypePackId returnList = arena.addTypePack(std::move(returnTypes));
920
const TypeId iteratorType = arena.addType(FunctionType{emptyPack, returnList});
921
return WithPredicate<TypePackId>{arena.addTypePack({iteratorType})};
922
}
923
924
bool MagicGmatch::infer(const MagicFunctionCallContext& context)
925
{
926
const auto& [params, tail] = flatten(context.arguments);
927
928
if (params.size() != 2)
929
return false;
930
931
TypeArena* arena = context.solver->arena;
932
933
AstExprConstantString* pattern = nullptr;
934
size_t index = context.callSite->self ? 0 : 1;
935
if (context.callSite->args.size > index)
936
pattern = context.callSite->args.data[index]->as<AstExprConstantString>();
937
938
if (!pattern)
939
return false;
940
941
std::vector<TypeId> returnTypes = parsePatternString(context.solver->builtinTypes, pattern->value.data, pattern->value.size);
942
943
if (returnTypes.empty())
944
return false;
945
946
context.solver->unify(context.constraint, params[0], context.solver->builtinTypes->stringType);
947
948
const TypePackId emptyPack = arena->addTypePack({});
949
const TypePackId returnList = arena->addTypePack(std::move(returnTypes));
950
const TypeId iteratorType = arena->addType(FunctionType{emptyPack, returnList});
951
const TypePackId resTypePack = arena->addTypePack({iteratorType});
952
asMutable(context.result)->ty.emplace<BoundTypePack>(resTypePack);
953
954
return true;
955
}
956
957
std::optional<WithPredicate<TypePackId>> MagicMatch::handleOldSolver(
958
TypeChecker& typechecker,
959
const ScopePtr& scope,
960
const AstExprCall& expr,
961
WithPredicate<TypePackId> withPredicate
962
)
963
{
964
auto [paramPack, _predicates] = std::move(withPredicate);
965
const auto& [params, tail] = flatten(paramPack);
966
967
if (params.size() < 2 || params.size() > 3)
968
return std::nullopt;
969
970
TypeArena& arena = typechecker.currentModule->internalTypes;
971
972
AstExprConstantString* pattern = nullptr;
973
size_t patternIndex = expr.self ? 0 : 1;
974
if (expr.args.size > patternIndex)
975
pattern = expr.args.data[patternIndex]->as<AstExprConstantString>();
976
977
if (!pattern)
978
return std::nullopt;
979
980
std::vector<TypeId> returnTypes = parsePatternString(typechecker.builtinTypes, pattern->value.data, pattern->value.size);
981
982
if (returnTypes.empty())
983
return std::nullopt;
984
985
typechecker.unify(params[0], typechecker.stringType, scope, expr.args.data[0]->location);
986
987
const TypeId optionalNumber = arena.addType(UnionType{{typechecker.nilType, typechecker.numberType}});
988
989
size_t initIndex = expr.self ? 1 : 2;
990
if (params.size() == 3 && expr.args.size > initIndex)
991
typechecker.unify(params[2], optionalNumber, scope, expr.args.data[initIndex]->location);
992
993
const TypePackId returnList = arena.addTypePack(std::move(returnTypes));
994
return WithPredicate<TypePackId>{returnList};
995
}
996
997
bool MagicMatch::infer(const MagicFunctionCallContext& context)
998
{
999
const auto& [params, tail] = flatten(context.arguments);
1000
1001
if (params.size() < 2 || params.size() > 3)
1002
return false;
1003
1004
TypeArena* arena = context.solver->arena;
1005
1006
AstExprConstantString* pattern = nullptr;
1007
size_t patternIndex = context.callSite->self ? 0 : 1;
1008
if (context.callSite->args.size > patternIndex)
1009
pattern = context.callSite->args.data[patternIndex]->as<AstExprConstantString>();
1010
1011
if (!pattern)
1012
return false;
1013
1014
std::vector<TypeId> returnTypes = parsePatternString(context.solver->builtinTypes, pattern->value.data, pattern->value.size);
1015
1016
if (returnTypes.empty())
1017
return false;
1018
1019
context.solver->unify(context.constraint, params[0], context.solver->builtinTypes->stringType);
1020
1021
const TypeId optionalNumber = arena->addType(UnionType{{context.solver->builtinTypes->nilType, context.solver->builtinTypes->numberType}});
1022
1023
size_t initIndex = context.callSite->self ? 1 : 2;
1024
if (params.size() == 3 && context.callSite->args.size > initIndex)
1025
context.solver->unify(context.constraint, params[2], optionalNumber);
1026
1027
const TypePackId returnList = arena->addTypePack(std::move(returnTypes));
1028
asMutable(context.result)->ty.emplace<BoundTypePack>(returnList);
1029
1030
return true;
1031
}
1032
1033
std::optional<WithPredicate<TypePackId>> MagicFind::handleOldSolver(
1034
TypeChecker& typechecker,
1035
const ScopePtr& scope,
1036
const AstExprCall& expr,
1037
WithPredicate<TypePackId> withPredicate
1038
)
1039
{
1040
auto [paramPack, _predicates] = std::move(withPredicate);
1041
const auto& [params, tail] = flatten(paramPack);
1042
1043
if (params.size() < 2 || params.size() > 4)
1044
return std::nullopt;
1045
1046
TypeArena& arena = typechecker.currentModule->internalTypes;
1047
1048
AstExprConstantString* pattern = nullptr;
1049
size_t patternIndex = expr.self ? 0 : 1;
1050
if (expr.args.size > patternIndex)
1051
pattern = expr.args.data[patternIndex]->as<AstExprConstantString>();
1052
1053
if (!pattern)
1054
return std::nullopt;
1055
1056
bool plain = false;
1057
size_t plainIndex = expr.self ? 2 : 3;
1058
if (expr.args.size > plainIndex)
1059
{
1060
AstExprConstantBool* p = expr.args.data[plainIndex]->as<AstExprConstantBool>();
1061
plain = p && p->value;
1062
}
1063
1064
std::vector<TypeId> returnTypes;
1065
if (!plain)
1066
{
1067
returnTypes = parsePatternString(typechecker.builtinTypes, pattern->value.data, pattern->value.size);
1068
1069
if (returnTypes.empty())
1070
return std::nullopt;
1071
}
1072
1073
typechecker.unify(params[0], typechecker.stringType, scope, expr.args.data[0]->location);
1074
1075
const TypeId optionalNumber = arena.addType(UnionType{{typechecker.nilType, typechecker.numberType}});
1076
const TypeId optionalBoolean = arena.addType(UnionType{{typechecker.nilType, typechecker.booleanType}});
1077
1078
size_t initIndex = expr.self ? 1 : 2;
1079
if (params.size() >= 3 && expr.args.size > initIndex)
1080
typechecker.unify(params[2], optionalNumber, scope, expr.args.data[initIndex]->location);
1081
1082
if (params.size() == 4 && expr.args.size > plainIndex)
1083
typechecker.unify(params[3], optionalBoolean, scope, expr.args.data[plainIndex]->location);
1084
1085
returnTypes.insert(returnTypes.begin(), {optionalNumber, optionalNumber});
1086
1087
const TypePackId returnList = arena.addTypePack(std::move(returnTypes));
1088
return WithPredicate<TypePackId>{returnList};
1089
}
1090
1091
bool MagicFind::infer(const MagicFunctionCallContext& context)
1092
{
1093
const auto& [params, tail] = flatten(context.arguments);
1094
1095
if (params.size() < 2 || params.size() > 4)
1096
return false;
1097
1098
TypeArena* arena = context.solver->arena;
1099
NotNull<BuiltinTypes> builtinTypes = context.solver->builtinTypes;
1100
1101
AstExprConstantString* pattern = nullptr;
1102
size_t patternIndex = context.callSite->self ? 0 : 1;
1103
if (context.callSite->args.size > patternIndex)
1104
pattern = context.callSite->args.data[patternIndex]->as<AstExprConstantString>();
1105
1106
if (!pattern)
1107
return false;
1108
1109
bool plain = false;
1110
size_t plainIndex = context.callSite->self ? 2 : 3;
1111
if (context.callSite->args.size > plainIndex)
1112
{
1113
AstExprConstantBool* p = context.callSite->args.data[plainIndex]->as<AstExprConstantBool>();
1114
plain = p && p->value;
1115
}
1116
1117
std::vector<TypeId> returnTypes;
1118
if (!plain)
1119
{
1120
returnTypes = parsePatternString(builtinTypes, pattern->value.data, pattern->value.size);
1121
1122
if (returnTypes.empty())
1123
return false;
1124
}
1125
1126
context.solver->unify(context.constraint, params[0], builtinTypes->stringType);
1127
1128
const TypeId optionalNumber = arena->addType(UnionType{{builtinTypes->nilType, builtinTypes->numberType}});
1129
const TypeId optionalBoolean = arena->addType(UnionType{{builtinTypes->nilType, builtinTypes->booleanType}});
1130
1131
size_t initIndex = context.callSite->self ? 1 : 2;
1132
if (params.size() >= 3 && context.callSite->args.size > initIndex)
1133
context.solver->unify(context.constraint, params[2], optionalNumber);
1134
1135
if (params.size() == 4 && context.callSite->args.size > plainIndex)
1136
context.solver->unify(context.constraint, params[3], optionalBoolean);
1137
1138
returnTypes.insert(returnTypes.begin(), {optionalNumber, optionalNumber});
1139
1140
const TypePackId returnList = arena->addTypePack(std::move(returnTypes));
1141
asMutable(context.result)->ty.emplace<BoundTypePack>(returnList);
1142
return true;
1143
}
1144
1145
std::optional<WithPredicate<TypePackId>> MagicPcall::handleOldSolver(
1146
TypeChecker& typechecker,
1147
const ScopePtr& scope,
1148
const AstExprCall& expr,
1149
WithPredicate<TypePackId> withPredicate
1150
)
1151
{
1152
// pcall() is only magic in the new solver.
1153
return std::nullopt;
1154
}
1155
1156
// In the specific case that pcall's first argument returns 0 values, the result
1157
// of pcall is itself (boolean, unknown) Else treat it as an ordinary function
1158
// per its type in EmbeddedBuiltinDefinitions.cpp
1159
bool MagicPcall::infer(const MagicFunctionCallContext& ctx)
1160
{
1161
const auto [argHead, argTail] = flatten(ctx.arguments);
1162
1163
if (argHead.empty())
1164
return false;
1165
1166
TypeId fnTy = follow(argHead[0]);
1167
const FunctionType* fn = get<FunctionType>(fnTy);
1168
if (!fn)
1169
return false;
1170
1171
const auto [fnReturnHead, fnReturnTail] = flatten(fn->retTypes);
1172
if (!fnReturnHead.empty() || fnReturnTail.has_value())
1173
return false;
1174
1175
TypePackId res = ctx.solver->arena->addTypePack({ctx.solver->builtinTypes->booleanType, ctx.solver->builtinTypes->unknownType});
1176
asMutable(ctx.result)->ty.emplace<BoundTypePack>(res);
1177
1178
return true;
1179
}
1180
1181
TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes, SolverMode mode)
1182
{
1183
NotNull<TypeArena> arena{builtinTypes->arena.get()};
1184
1185
const TypeId nilType = builtinTypes->nilType;
1186
const TypeId numberType = builtinTypes->numberType;
1187
const TypeId booleanType = builtinTypes->booleanType;
1188
const TypeId stringType = builtinTypes->stringType;
1189
1190
const TypeId optionalNumber = arena->addType(UnionType{{nilType, numberType}});
1191
const TypeId optionalString = arena->addType(UnionType{{nilType, stringType}});
1192
const TypeId optionalBoolean = arena->addType(UnionType{{nilType, booleanType}});
1193
1194
const TypePackId oneStringPack = arena->addTypePack({stringType});
1195
const TypePackId anyTypePack = builtinTypes->anyTypePack;
1196
1197
const TypePackId variadicTailPack = mode == SolverMode::New ? builtinTypes->unknownTypePack : anyTypePack;
1198
const TypePackId emptyPack = arena->addTypePack({});
1199
const TypePackId stringVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{stringType}});
1200
const TypePackId numberVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{numberType}});
1201
1202
1203
FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, variadicTailPack}), oneStringPack};
1204
formatFTV.isCheckedFunction = true;
1205
const TypeId formatFn = arena->addType(std::move(formatFTV));
1206
attachMagicFunction(formatFn, std::make_shared<MagicFormat>());
1207
1208
1209
const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ true);
1210
1211
const TypeId replArgType = arena->addType(
1212
UnionType{
1213
{stringType,
1214
arena->addType(TableType({}, TableIndexer(stringType, stringType), TypeLevel{}, TableState::Generic)),
1215
makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ false)}
1216
}
1217
);
1218
const TypeId gsubFunc =
1219
makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}, /* checked */ false);
1220
const TypeId gmatchFunc =
1221
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})}, /* checked */ true);
1222
attachMagicFunction(gmatchFunc, std::make_shared<MagicGmatch>());
1223
1224
FunctionType matchFuncTy{
1225
arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})
1226
};
1227
matchFuncTy.isCheckedFunction = true;
1228
const TypeId matchFunc = arena->addType(std::move(matchFuncTy));
1229
attachMagicFunction(matchFunc, std::make_shared<MagicMatch>());
1230
1231
FunctionType findFuncTy{
1232
arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
1233
arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList})
1234
};
1235
findFuncTy.isCheckedFunction = true;
1236
const TypeId findFunc = arena->addType(std::move(findFuncTy));
1237
attachMagicFunction(findFunc, std::make_shared<MagicFind>());
1238
1239
// string.byte : string -> number? -> number? -> ...number
1240
FunctionType stringDotByte{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList};
1241
stringDotByte.isCheckedFunction = true;
1242
1243
// string.char : .... number -> string
1244
FunctionType stringDotChar{numberVariadicList, arena->addTypePack({stringType})};
1245
stringDotChar.isCheckedFunction = true;
1246
1247
// string.unpack : string -> string -> number? -> ...any
1248
FunctionType stringDotUnpack{
1249
arena->addTypePack(TypePack{{stringType, stringType, optionalNumber}}),
1250
variadicTailPack,
1251
};
1252
stringDotUnpack.isCheckedFunction = true;
1253
1254
TableType::Props stringLib = {
1255
{"byte", {arena->addType(std::move(stringDotByte))}},
1256
{"char", {arena->addType(std::move(stringDotChar))}},
1257
{"find", {findFunc}},
1258
{"format", {formatFn}}, // FIXME
1259
{"gmatch", {gmatchFunc}},
1260
{"gsub", {gsubFunc}},
1261
{"len", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}},
1262
{"lower", {stringToStringType}},
1263
{"match", {matchFunc}},
1264
{"rep", {makeFunction(*arena, stringType, {}, {}, {numberType}, {}, {stringType}, /* checked */ true)}},
1265
{"reverse", {stringToStringType}},
1266
{"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType}, /* checked */ true)}},
1267
{"upper", {stringToStringType}},
1268
{"split",
1269
{makeFunction(
1270
*arena,
1271
stringType,
1272
{},
1273
{},
1274
{optionalString},
1275
{},
1276
{arena->addType(TableType{{}, TableIndexer{numberType, stringType}, TypeLevel{}, TableState::Sealed})},
1277
/* checked */ true
1278
)}},
1279
{"pack",
1280
{arena->addType(
1281
FunctionType{
1282
arena->addTypePack(TypePack{{stringType}, variadicTailPack}),
1283
oneStringPack,
1284
}
1285
)}},
1286
{"packsize", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}},
1287
{"unpack", {arena->addType(std::move(stringDotUnpack))}},
1288
};
1289
1290
assignPropDocumentationSymbols(stringLib, "@luau/global/string");
1291
1292
TypeId tableType = arena->addType(TableType{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed});
1293
1294
if (TableType* ttv = getMutable<TableType>(tableType))
1295
ttv->name = "typeof(string)";
1296
1297
return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
1298
}
1299
1300
std::optional<WithPredicate<TypePackId>> MagicSelect::handleOldSolver(
1301
TypeChecker& typechecker,
1302
const ScopePtr& scope,
1303
const AstExprCall& expr,
1304
WithPredicate<TypePackId> withPredicate
1305
)
1306
{
1307
auto [paramPack, _predicates] = std::move(withPredicate);
1308
1309
(void)scope;
1310
1311
if (expr.args.size <= 0)
1312
{
1313
typechecker.reportError(TypeError{expr.location, GenericError{"select should take 1 or more arguments"}});
1314
return std::nullopt;
1315
}
1316
1317
AstExpr* arg1 = expr.args.data[0];
1318
if (AstExprConstantNumber* num = arg1->as<AstExprConstantNumber>())
1319
{
1320
const auto& [v, tail] = flatten(paramPack);
1321
1322
int offset = int(num->value);
1323
if (offset > 0)
1324
{
1325
if (size_t(offset) < v.size())
1326
{
1327
std::vector<TypeId> result(v.begin() + offset, v.end());
1328
return WithPredicate<TypePackId>{typechecker.currentModule->internalTypes.addTypePack(TypePack{std::move(result), tail})};
1329
}
1330
else if (tail)
1331
return WithPredicate<TypePackId>{*tail};
1332
}
1333
1334
typechecker.reportError(TypeError{arg1->location, GenericError{"bad argument #1 to select (index out of range)"}});
1335
}
1336
else if (AstExprConstantString* str = arg1->as<AstExprConstantString>())
1337
{
1338
if (str->value.size == 1 && str->value.data[0] == '#')
1339
return WithPredicate<TypePackId>{typechecker.currentModule->internalTypes.addTypePack({typechecker.numberType})};
1340
}
1341
1342
return std::nullopt;
1343
}
1344
1345
bool MagicSelect::infer(const MagicFunctionCallContext& context)
1346
{
1347
if (context.callSite->args.size <= 0)
1348
{
1349
context.solver->reportError(TypeError{context.callSite->location, GenericError{"select should take 1 or more arguments"}});
1350
return false;
1351
}
1352
1353
AstExpr* arg1 = context.callSite->args.data[0];
1354
1355
if (AstExprConstantNumber* num = arg1->as<AstExprConstantNumber>())
1356
{
1357
const auto& [v, tail] = flatten(context.arguments);
1358
1359
int offset = int(num->value);
1360
if (offset > 0)
1361
{
1362
if (size_t(offset) < v.size())
1363
{
1364
std::vector<TypeId> res(v.begin() + offset, v.end());
1365
TypePackId resTypePack = context.solver->arena->addTypePack({std::move(res), tail});
1366
asMutable(context.result)->ty.emplace<BoundTypePack>(resTypePack);
1367
}
1368
else if (tail)
1369
asMutable(context.result)->ty.emplace<BoundTypePack>(*tail);
1370
1371
return true;
1372
}
1373
1374
return false;
1375
}
1376
1377
if (AstExprConstantString* str = arg1->as<AstExprConstantString>())
1378
{
1379
if (str->value.size == 1 && str->value.data[0] == '#')
1380
{
1381
TypePackId numberTypePack = context.solver->arena->addTypePack({context.solver->builtinTypes->numberType});
1382
asMutable(context.result)->ty.emplace<BoundTypePack>(numberTypePack);
1383
return true;
1384
}
1385
}
1386
1387
return false;
1388
}
1389
1390
std::optional<WithPredicate<TypePackId>> MagicSetMetatable::handleOldSolver(
1391
TypeChecker& typechecker,
1392
const ScopePtr& scope,
1393
const AstExprCall& expr,
1394
WithPredicate<TypePackId> withPredicate
1395
)
1396
{
1397
auto [paramPack, _predicates] = std::move(withPredicate);
1398
1399
if (size(paramPack) < 2 && finite(paramPack))
1400
return std::nullopt;
1401
1402
TypeArena& arena = typechecker.currentModule->internalTypes;
1403
1404
std::vector<TypeId> expectedArgs = typechecker.unTypePack(scope, paramPack, 2, expr.location);
1405
1406
TypeId target = follow(expectedArgs[0]);
1407
TypeId mt = follow(expectedArgs[1]);
1408
1409
typechecker.tablify(target);
1410
typechecker.tablify(mt);
1411
1412
if (const auto& tab = get<TableType>(target))
1413
{
1414
if (target->persistent)
1415
{
1416
typechecker.reportError(TypeError{expr.location, CannotExtendTable{target, CannotExtendTable::Metatable}});
1417
}
1418
else
1419
{
1420
const TableType* mtTtv = get<TableType>(mt);
1421
MetatableType mtv{target, mt};
1422
if ((tab->name || tab->syntheticName) && (mtTtv && (mtTtv->name || mtTtv->syntheticName)))
1423
{
1424
std::string tableName = tab->name ? *tab->name : *tab->syntheticName;
1425
std::string metatableName = mtTtv->name ? *mtTtv->name : *mtTtv->syntheticName;
1426
1427
if (tableName == metatableName)
1428
mtv.syntheticName = tableName;
1429
}
1430
1431
TypeId mtTy = arena.addType(std::move(mtv));
1432
1433
if (expr.args.size < 1)
1434
return std::nullopt;
1435
1436
if (!expr.self)
1437
{
1438
AstExpr* targetExpr = expr.args.data[0];
1439
if (AstExprLocal* targetLocal = targetExpr->as<AstExprLocal>())
1440
{
1441
const Name targetName(targetLocal->local->name.value);
1442
scope->bindings[targetLocal->local] = Binding{mtTy, expr.location};
1443
}
1444
}
1445
1446
return WithPredicate<TypePackId>{arena.addTypePack({mtTy})};
1447
}
1448
}
1449
else if (get<AnyType>(target) || get<ErrorType>(target) || isTableIntersection(target))
1450
{
1451
}
1452
else if (isTableUnion(target))
1453
{
1454
const UnionType* ut = get<UnionType>(target);
1455
LUAU_ASSERT(ut);
1456
1457
std::vector<TypeId> resultParts;
1458
1459
for (TypeId ty : ut)
1460
resultParts.push_back(arena.addType(MetatableType{ty, mt}));
1461
1462
return WithPredicate<TypePackId>{arena.addTypePack({arena.addType(UnionType{std::move(resultParts)})})};
1463
}
1464
else
1465
{
1466
typechecker.reportError(TypeError{expr.location, GenericError{"setmetatable should take a table"}});
1467
}
1468
1469
return WithPredicate<TypePackId>{arena.addTypePack({target})};
1470
}
1471
1472
bool MagicSetMetatable::infer(const MagicFunctionCallContext&)
1473
{
1474
return false;
1475
}
1476
1477
std::optional<WithPredicate<TypePackId>> MagicAssert::handleOldSolver(
1478
TypeChecker& typechecker,
1479
const ScopePtr& scope,
1480
const AstExprCall& expr,
1481
WithPredicate<TypePackId> withPredicate
1482
)
1483
{
1484
auto [paramPack, predicates] = std::move(withPredicate);
1485
1486
TypeArena& arena = typechecker.currentModule->internalTypes;
1487
1488
auto [head, tail] = flatten(paramPack);
1489
if (head.empty() && tail)
1490
{
1491
std::optional<TypeId> fst = first(*tail);
1492
if (!fst)
1493
return WithPredicate<TypePackId>{paramPack};
1494
head.push_back(*fst);
1495
}
1496
1497
typechecker.resolve(predicates, scope, true);
1498
1499
if (head.size() > 0)
1500
{
1501
auto [ty, ok] = typechecker.pickTypesFromSense(head[0], true, typechecker.builtinTypes->nilType);
1502
if (get<NeverType>(*ty))
1503
head = {*ty};
1504
else
1505
head[0] = *ty;
1506
}
1507
1508
return WithPredicate<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
1509
}
1510
1511
bool MagicAssert::infer(const MagicFunctionCallContext&)
1512
{
1513
return false;
1514
}
1515
1516
std::optional<WithPredicate<TypePackId>> MagicPack::handleOldSolver(
1517
TypeChecker& typechecker,
1518
const ScopePtr& scope,
1519
const AstExprCall& expr,
1520
WithPredicate<TypePackId> withPredicate
1521
)
1522
{
1523
auto [paramPack, _predicates] = std::move(withPredicate);
1524
1525
TypeArena& arena = typechecker.currentModule->internalTypes;
1526
1527
const auto& [paramTypes, paramTail] = flatten(paramPack);
1528
1529
std::vector<TypeId> options;
1530
options.reserve(paramTypes.size());
1531
for (auto type : paramTypes)
1532
options.push_back(type);
1533
1534
if (paramTail)
1535
{
1536
if (const VariadicTypePack* vtp = get<VariadicTypePack>(*paramTail))
1537
options.push_back(vtp->ty);
1538
}
1539
1540
options = reduceUnion(options);
1541
1542
// table.pack() -> {| n: number, [number]: nil |}
1543
// table.pack(1) -> {| n: number, [number]: number |}
1544
// table.pack(1, "foo") -> {| n: number, [number]: number | string |}
1545
TypeId result = nullptr;
1546
if (options.empty())
1547
result = typechecker.nilType;
1548
else if (options.size() == 1)
1549
result = options[0];
1550
else
1551
result = arena.addType(UnionType{std::move(options)});
1552
1553
TypeId packedTable =
1554
arena.addType(TableType{{{"n", {typechecker.numberType}}}, TableIndexer(typechecker.numberType, result), scope->level, TableState::Sealed});
1555
1556
return WithPredicate<TypePackId>{arena.addTypePack({packedTable})};
1557
}
1558
1559
bool MagicPack::infer(const MagicFunctionCallContext& context)
1560
{
1561
1562
TypeArena* arena = context.solver->arena;
1563
1564
const auto& [paramTypes, paramTail] = flatten(context.arguments);
1565
1566
std::vector<TypeId> options;
1567
options.reserve(paramTypes.size());
1568
for (auto type : paramTypes)
1569
options.push_back(type);
1570
1571
if (paramTail)
1572
{
1573
if (const VariadicTypePack* vtp = get<VariadicTypePack>(*paramTail))
1574
options.push_back(vtp->ty);
1575
}
1576
1577
options = reduceUnion(options);
1578
1579
// table.pack() -> {| n: number, [number]: nil |}
1580
// table.pack(1) -> {| n: number, [number]: number |}
1581
// table.pack(1, "foo") -> {| n: number, [number]: number | string |}
1582
TypeId result = nullptr;
1583
if (options.empty())
1584
result = context.solver->builtinTypes->nilType;
1585
else if (options.size() == 1)
1586
result = options[0];
1587
else
1588
result = arena->addType(UnionType{std::move(options)});
1589
1590
TypeId numberType = context.solver->builtinTypes->numberType;
1591
TypeId packedTable = arena->addType(TableType{{{"n", {numberType}}}, TableIndexer(numberType, result), {}, TableState::Sealed});
1592
1593
TypePackId tableTypePack = arena->addTypePack({packedTable});
1594
asMutable(context.result)->ty.emplace<BoundTypePack>(tableTypePack);
1595
1596
return true;
1597
}
1598
1599
// ({+ +}) -> {+ +}
1600
// <T: {}>(T) -> T
1601
std::optional<WithPredicate<TypePackId>> MagicClone::handleOldSolver(
1602
TypeChecker& typechecker,
1603
const ScopePtr& scope,
1604
const AstExprCall& expr,
1605
WithPredicate<TypePackId> withPredicate
1606
)
1607
{
1608
auto [paramPack, _predicates] = std::move(withPredicate);
1609
1610
TypeArena& arena = typechecker.currentModule->internalTypes;
1611
1612
// in the old solver, nonstrict in particular is really bad about inferring `...any` for things that are definitely present
1613
// and the only real way for us to deal with this is to just be more permissive here
1614
const auto& [paramTypes, paramTail] = extendTypePack(arena, typechecker.builtinTypes, paramPack, 1);
1615
if (paramTypes.empty() || expr.args.size == 0)
1616
{
1617
typechecker.reportError(expr.argLocation, CountMismatch{1, std::nullopt, 0});
1618
return std::nullopt;
1619
}
1620
1621
TypeId inputType = follow(paramTypes[0]);
1622
1623
if (!get<TableType>(inputType) && !get<IntersectionType>(inputType))
1624
return std::nullopt;
1625
1626
if (auto intersectionTy = get<IntersectionType>(inputType))
1627
{
1628
for (auto ty : intersectionTy)
1629
if (!get<TableType>(ty))
1630
return std::nullopt;
1631
}
1632
1633
CloneState cloneState{typechecker.builtinTypes};
1634
TypeId resultType = shallowClone(inputType, arena, cloneState, /* clonePersistentTypes */ false);
1635
1636
TypePackId clonedTypePack = arena.addTypePack({resultType});
1637
return WithPredicate<TypePackId>{clonedTypePack};
1638
}
1639
1640
bool MagicClone::infer(const MagicFunctionCallContext& context)
1641
{
1642
TypeArena* arena = context.solver->arena;
1643
1644
const auto& [paramTypes, paramTail] = flatten(context.arguments);
1645
if (paramTypes.empty() || context.callSite->args.size == 0)
1646
{
1647
context.solver->reportError(CountMismatch{1, std::nullopt, 0}, context.callSite->argLocation);
1648
return false;
1649
}
1650
1651
TypeId inputType = follow(paramTypes[0]);
1652
1653
if (!get<TableType>(inputType))
1654
return false;
1655
1656
CloneState cloneState{context.solver->builtinTypes};
1657
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ true);
1658
1659
if (auto tableType = getMutable<TableType>(resultType))
1660
{
1661
tableType->scope = context.constraint->scope.get();
1662
}
1663
1664
trackInteriorFreeType(context.constraint->scope.get(), resultType);
1665
1666
TypePackId clonedTypePack = arena->addTypePack({resultType});
1667
asMutable(context.result)->ty.emplace<BoundTypePack>(clonedTypePack);
1668
1669
return true;
1670
}
1671
1672
static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCallContext& context)
1673
{
1674
TypeArena* arena = context.solver->arena;
1675
inputType = follow(inputType);
1676
if (auto mt = get<MetatableType>(inputType))
1677
{
1678
std::optional<TypeId> frozenTable = freezeTable(mt->table, context);
1679
1680
if (!frozenTable)
1681
return std::nullopt;
1682
1683
TypeId resultType = arena->addType(MetatableType{*frozenTable, mt->metatable, mt->syntheticName});
1684
1685
return resultType;
1686
}
1687
1688
if (get<TableType>(inputType))
1689
{
1690
// Clone the input type, this will become our final result type after we mutate it.
1691
CloneState cloneState{context.solver->builtinTypes};
1692
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ true);
1693
auto tableTy = getMutable<TableType>(resultType);
1694
// `clone` should not break this.
1695
LUAU_ASSERT(tableTy);
1696
tableTy->state = TableState::Sealed;
1697
1698
// We'll mutate the table to make every property type read-only.
1699
for (auto iter = tableTy->props.begin(); iter != tableTy->props.end();)
1700
{
1701
if (iter->second.isWriteOnly())
1702
iter = tableTy->props.erase(iter);
1703
else
1704
{
1705
iter->second.writeTy = std::nullopt;
1706
iter++;
1707
}
1708
}
1709
1710
return resultType;
1711
}
1712
1713
if (!FFlag::LuauTableFreezeCheckIsSubtype)
1714
{
1715
context.solver->reportError(TypeMismatch{context.solver->builtinTypes->tableType, inputType}, context.callSite->argLocation);
1716
}
1717
return std::nullopt;
1718
}
1719
1720
std::optional<WithPredicate<TypePackId>> MagicFreeze::handleOldSolver(
1721
struct TypeChecker&,
1722
const std::shared_ptr<struct Scope>&,
1723
const class AstExprCall&,
1724
WithPredicate<TypePackId>
1725
)
1726
{
1727
return std::nullopt;
1728
}
1729
1730
bool MagicFreeze::infer(const MagicFunctionCallContext& context)
1731
{
1732
TypeArena* arena = context.solver->arena;
1733
const DataFlowGraph* dfg = context.solver->dfg.get();
1734
Scope* scope = context.constraint->scope.get();
1735
1736
const auto& [paramTypes, paramTail] = extendTypePack(*arena, context.solver->builtinTypes, context.arguments, 1);
1737
if (paramTypes.empty() || context.callSite->args.size == 0)
1738
return false;
1739
1740
TypeId inputType = follow(paramTypes[0]);
1741
1742
AstExpr* targetExpr = context.callSite->args.data[0];
1743
std::optional<DefId> resultDef = dfg->getDefOptional(targetExpr);
1744
std::optional<TypeId> resultTy = resultDef ? scope->lookup(*resultDef) : std::nullopt;
1745
1746
if (resultTy && !get<BlockedType>(follow(resultTy)))
1747
{
1748
// If there's an existing result type, but it's _not_ blocked, then
1749
// we aren't type stating this builtin and should fall back to
1750
// regular inference.
1751
return false;
1752
}
1753
1754
std::optional<TypeId> frozenType = freezeTable(inputType, context);
1755
1756
// At this point: we know for sure that if `resultTy` exists, it is a
1757
// blocked type, and can safely emplace it.
1758
if (!frozenType)
1759
{
1760
if (resultTy)
1761
asMutable(*resultTy)->ty.emplace<BoundType>(context.solver->builtinTypes->errorType);
1762
asMutable(context.result)->ty.emplace<BoundTypePack>(context.solver->builtinTypes->errorTypePack);
1763
1764
return true;
1765
}
1766
1767
if (resultTy)
1768
asMutable(*resultTy)->ty.emplace<BoundType>(*frozenType);
1769
asMutable(context.result)->ty.emplace<BoundTypePack>(arena->addTypePack({*frozenType}));
1770
1771
return true;
1772
}
1773
1774
// MagicFreeze is a magic function because table.freeze is a bounded version of the identity function with a custom output (accepts any subtype of
1775
// `table` and returns a read-only version of that table).
1776
bool MagicFreeze::typeCheck(const MagicFunctionTypeCheckContext& ctx)
1777
{
1778
if (!FFlag::LuauTableFreezeCheckIsSubtype)
1779
return false;
1780
1781
const auto& [paramTypes, paramTail] = flatten(ctx.arguments);
1782
1783
if (paramTypes.size() < 1 && !paramTail)
1784
{
1785
ctx.typechecker->reportError(CountMismatch{1, 1, 0, CountMismatch::Arg, false, "table.freeze"}, ctx.callSite->location);
1786
return true;
1787
}
1788
1789
std::optional<TypeId> firstParamType;
1790
1791
if (paramTypes.size() > 0)
1792
{
1793
firstParamType = paramTypes[0];
1794
}
1795
else if (paramTail)
1796
{
1797
// TODO (CLI-185019): We ideally want to report a Count Mismatch error if there's no head but a variadic tail, but CountMismatch requires
1798
// actual count size, which we don't have with variadic tails, so we can't report it properly yet. Instead, we continue to typecheck with the
1799
// first argument in the variadic tail and report a type mismatch error based on that, which is more informative than reporting a count
1800
// mismatch where the head (paramTypes.size()) is 0.
1801
firstParamType = first(*paramTail);
1802
}
1803
1804
if (firstParamType)
1805
{
1806
// If a type is found, check if it is a subtype of table.
1807
ctx.typechecker->testIsSubtype(follow(*firstParamType), ctx.builtinTypes->tableType, ctx.callSite->location);
1808
}
1809
else
1810
{
1811
// If we can't get a type from the type or type pack, we testIsSubtype against the entire context's argument type pack to report a Type Pack
1812
// Mismatch error.
1813
TypePackId tableTyPack = ctx.typechecker->module->internalTypes.addTypePack({ctx.typechecker->builtinTypes->tableType});
1814
ctx.typechecker->testIsSubtype(follow(ctx.arguments), tableTyPack, ctx.callSite->location);
1815
return true;
1816
}
1817
1818
// Also report error if there's more than 1 argument explicitly provided to table.freeze.
1819
if (paramTypes.size() > 1)
1820
{
1821
ctx.typechecker->reportError(CountMismatch{1, 1, ctx.callSite->args.size, CountMismatch::Arg, false, "table.freeze"}, ctx.callSite->location);
1822
}
1823
1824
return true;
1825
}
1826
1827
static bool checkRequirePath(TypeChecker& typechecker, AstExpr* expr)
1828
{
1829
// require(foo.parent.bar) will technically work, but it depends on legacy goop that
1830
// Luau does not and could not support without a bunch of work. It's deprecated anyway, so
1831
// we'll warn here if we see it.
1832
bool good = true;
1833
AstExprIndexName* indexExpr = expr->as<AstExprIndexName>();
1834
1835
while (indexExpr)
1836
{
1837
if (indexExpr->index == "parent")
1838
{
1839
typechecker.reportError(indexExpr->indexLocation, DeprecatedApiUsed{"parent", "Parent"});
1840
good = false;
1841
}
1842
1843
indexExpr = indexExpr->expr->as<AstExprIndexName>();
1844
}
1845
1846
return good;
1847
}
1848
1849
std::optional<WithPredicate<TypePackId>> MagicRequire::handleOldSolver(
1850
TypeChecker& typechecker,
1851
const ScopePtr& scope,
1852
const AstExprCall& expr,
1853
WithPredicate<TypePackId> withPredicate
1854
)
1855
{
1856
TypeArena& arena = typechecker.currentModule->internalTypes;
1857
1858
if (expr.args.size != 1)
1859
{
1860
typechecker.reportError(TypeError{expr.location, GenericError{"require takes 1 argument"}});
1861
return std::nullopt;
1862
}
1863
1864
if (!checkRequirePath(typechecker, expr.args.data[0]))
1865
return std::nullopt;
1866
1867
if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModule->name, expr))
1868
return WithPredicate<TypePackId>{arena.addTypePack({typechecker.checkRequire(scope, *moduleInfo, expr.location)})};
1869
1870
return std::nullopt;
1871
}
1872
1873
static bool checkRequirePathDcr(NotNull<ConstraintSolver> solver, AstExpr* expr)
1874
{
1875
// require(foo.parent.bar) will technically work, but it depends on legacy goop that
1876
// Luau does not and could not support without a bunch of work. It's deprecated anyway, so
1877
// we'll warn here if we see it.
1878
bool good = true;
1879
AstExprIndexName* indexExpr = expr->as<AstExprIndexName>();
1880
1881
while (indexExpr)
1882
{
1883
if (indexExpr->index == "parent")
1884
{
1885
solver->reportError(DeprecatedApiUsed{"parent", "Parent"}, indexExpr->indexLocation);
1886
good = false;
1887
}
1888
1889
indexExpr = indexExpr->expr->as<AstExprIndexName>();
1890
}
1891
1892
return good;
1893
}
1894
1895
bool MagicRequire::infer(const MagicFunctionCallContext& context)
1896
{
1897
if (context.callSite->args.size != 1)
1898
{
1899
context.solver->reportError(GenericError{"require takes 1 argument"}, context.callSite->location);
1900
return false;
1901
}
1902
1903
if (!checkRequirePathDcr(context.solver, context.callSite->args.data[0]))
1904
return false;
1905
1906
if (auto moduleInfo = context.solver->moduleResolver->resolveModuleInfo(context.solver->module->name, *context.callSite))
1907
{
1908
TypeId moduleType = context.solver->resolveModule(*moduleInfo, context.callSite->location);
1909
TypePackId moduleResult = context.solver->arena->addTypePack({moduleType});
1910
asMutable(context.result)->ty.emplace<BoundTypePack>(moduleResult);
1911
1912
return true;
1913
}
1914
1915
return false;
1916
}
1917
1918
bool matchSetMetatable(const AstExprCall& call)
1919
{
1920
const char* smt = "setmetatable";
1921
1922
if (call.args.size != 2)
1923
return false;
1924
1925
const AstExprGlobal* funcAsGlobal = call.func->as<AstExprGlobal>();
1926
if (!funcAsGlobal || funcAsGlobal->name != smt)
1927
return false;
1928
1929
return true;
1930
}
1931
1932
bool matchTableFreeze(const AstExprCall& call)
1933
{
1934
if (call.args.size < 1)
1935
return false;
1936
1937
const AstExprIndexName* index = call.func->as<AstExprIndexName>();
1938
if (!index || index->index != "freeze")
1939
return false;
1940
1941
const AstExprGlobal* global = index->expr->as<AstExprGlobal>();
1942
if (!global || global->name != "table")
1943
return false;
1944
1945
return true;
1946
}
1947
1948
bool matchAssert(const AstExprCall& call)
1949
{
1950
if (call.args.size < 1)
1951
return false;
1952
1953
const AstExprGlobal* funcAsGlobal = call.func->as<AstExprGlobal>();
1954
if (!funcAsGlobal || funcAsGlobal->name != "assert")
1955
return false;
1956
1957
return true;
1958
}
1959
1960
bool matchTypeOf(const AstExprCall& call)
1961
{
1962
if (call.args.size != 1)
1963
return false;
1964
1965
const AstExprGlobal* funcAsGlobal = call.func->as<AstExprGlobal>();
1966
if (!funcAsGlobal || (funcAsGlobal->name != "typeof" && funcAsGlobal->name != "type"))
1967
return false;
1968
1969
return true;
1970
}
1971
1972
bool shouldTypestateForFirstArgument(const AstExprCall& call)
1973
{
1974
// TODO: magic function for setmetatable and assert and then add them
1975
return matchTableFreeze(call);
1976
}
1977
1978
} // namespace Luau
1979
1980