Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/tests/Fixture.cpp
2723 views
1
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2
#include "Fixture.h"
3
4
#include "Luau/AstQuery.h"
5
#include "Luau/BuiltinDefinitions.h"
6
#include "Luau/Common.h"
7
#include "Luau/Constraint.h"
8
#include "Luau/FileResolver.h"
9
#include "Luau/ModuleResolver.h"
10
#include "Luau/NotNull.h"
11
#include "Luau/Parser.h"
12
#include "Luau/PrettyPrinter.h"
13
#include "Luau/Subtyping.h"
14
#include "Luau/Type.h"
15
#include "Luau/TypeAttach.h"
16
#include "Luau/TypeInfer.h"
17
18
#include "doctest.h"
19
20
#include <algorithm>
21
#include <limits>
22
#include <sstream>
23
#include <string_view>
24
#include <iostream>
25
#include <fstream>
26
27
static const char* mainModuleName = "MainModule";
28
29
LUAU_FASTFLAG(DebugLuauLogSolverToJsonFile)
30
31
LUAU_FASTFLAGVARIABLE(DebugLuauForceAllNewSolverTests);
32
LUAU_FASTFLAGVARIABLE(DebugLuauForceAllOldSolverTests);
33
34
LUAU_FASTINT(LuauStackGuardThreshold)
35
LUAU_FASTFLAG(DebugLuauForceOldSolver)
36
37
extern std::optional<unsigned> randomSeed; // tests/main.cpp
38
39
namespace Luau
40
{
41
42
static std::string getNodeName(const TestRequireNode* node)
43
{
44
std::string name;
45
size_t lastSlash = node->moduleName.find_last_of('/');
46
if (lastSlash != std::string::npos)
47
name = node->moduleName.substr(lastSlash + 1);
48
else
49
name = node->moduleName;
50
51
return name;
52
}
53
54
std::string TestRequireNode::getLabel() const
55
{
56
return getNodeName(this);
57
}
58
59
std::string TestRequireNode::getPathComponent() const
60
{
61
return getNodeName(this);
62
}
63
64
static std::vector<std::string_view> splitStringBySlashes(std::string_view str)
65
{
66
std::vector<std::string_view> components;
67
size_t pos = 0;
68
size_t nextPos = str.find_first_of('/', pos);
69
if (nextPos == std::string::npos)
70
{
71
components.push_back(str);
72
return components;
73
}
74
while (nextPos != std::string::npos)
75
{
76
components.push_back(str.substr(pos, nextPos - pos));
77
pos = nextPos + 1;
78
nextPos = str.find_first_of('/', pos);
79
}
80
components.push_back(str.substr(pos));
81
return components;
82
}
83
84
std::unique_ptr<RequireNode> TestRequireNode::resolvePathToNode(const std::string& path) const
85
{
86
std::vector<std::string_view> components = splitStringBySlashes(path);
87
LUAU_ASSERT((components.empty() || components[0] == "." || components[0] == "..") && "Path must begin with ./ or ../ in test");
88
89
std::vector<std::string_view> normalizedComponents = splitStringBySlashes(moduleName);
90
normalizedComponents.pop_back();
91
LUAU_ASSERT(!normalizedComponents.empty() && "Must have a root module");
92
93
for (std::string_view component : components)
94
{
95
if (component == "..")
96
{
97
if (normalizedComponents.empty())
98
LUAU_ASSERT(!"Cannot go above root module in test");
99
else
100
normalizedComponents.pop_back();
101
}
102
else if (!component.empty() && component != ".")
103
{
104
normalizedComponents.emplace_back(component);
105
}
106
}
107
108
std::string moduleName;
109
for (size_t i = 0; i < normalizedComponents.size(); i++)
110
{
111
if (i > 0)
112
moduleName += '/';
113
moduleName += normalizedComponents[i];
114
}
115
116
if (allSources->count(moduleName) == 0)
117
return nullptr;
118
119
return std::make_unique<TestRequireNode>(moduleName, allSources);
120
}
121
122
std::vector<std::unique_ptr<RequireNode>> TestRequireNode::getChildren() const
123
{
124
std::vector<std::unique_ptr<RequireNode>> result;
125
for (const auto& entry : *allSources)
126
{
127
if (std::string_view(entry.first).substr(0, moduleName.size()) == moduleName && entry.first.size() > moduleName.size() &&
128
entry.first[moduleName.size()] == '/' && entry.first.find('/', moduleName.size() + 1) == std::string::npos)
129
{
130
result.push_back(std::make_unique<TestRequireNode>(entry.first, allSources));
131
}
132
}
133
return result;
134
}
135
136
std::vector<RequireAlias> TestRequireNode::getAvailableAliases() const
137
{
138
return {RequireAlias("defaultalias")};
139
}
140
141
std::unique_ptr<RequireNode> TestRequireSuggester::getNode(const ModuleName& name) const
142
{
143
return std::make_unique<TestRequireNode>(name, &resolver->source);
144
}
145
146
std::optional<ModuleInfo> TestFileResolver::resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr)
147
{
148
if (auto name = pathExprToModuleName(currentModuleName, pathExpr))
149
return {{*name, false}};
150
151
return std::nullopt;
152
}
153
154
const ModulePtr TestFileResolver::getModule(const ModuleName& moduleName) const
155
{
156
LUAU_ASSERT(false);
157
return nullptr;
158
}
159
160
bool TestFileResolver::moduleExists(const ModuleName& moduleName) const
161
{
162
auto it = source.find(moduleName);
163
return (it != source.end());
164
}
165
166
std::optional<SourceCode> TestFileResolver::readSource(const ModuleName& name)
167
{
168
auto it = source.find(name);
169
if (it == source.end())
170
return std::nullopt;
171
172
SourceCode::Type sourceType = SourceCode::Module;
173
174
auto it2 = sourceTypes.find(name);
175
if (it2 != sourceTypes.end())
176
sourceType = it2->second;
177
178
return SourceCode{it->second, sourceType};
179
}
180
181
std::optional<ModuleInfo> TestFileResolver::resolveModule(const ModuleInfo* context, AstExpr* expr, const TypeCheckLimits& limits)
182
{
183
if (AstExprGlobal* g = expr->as<AstExprGlobal>())
184
{
185
if (g->name == "game")
186
return ModuleInfo{"game"};
187
if (g->name == "workspace")
188
return ModuleInfo{"workspace"};
189
if (g->name == "script")
190
return context ? std::optional<ModuleInfo>(*context) : std::nullopt;
191
}
192
else if (AstExprIndexName* i = expr->as<AstExprIndexName>(); i && context)
193
{
194
if (i->index == "Parent")
195
{
196
std::string_view view = context->name;
197
size_t lastSeparatorIndex = view.find_last_of('/');
198
199
if (lastSeparatorIndex == std::string_view::npos)
200
return std::nullopt;
201
202
return ModuleInfo{ModuleName(view.substr(0, lastSeparatorIndex)), context->optional};
203
}
204
else
205
{
206
return ModuleInfo{context->name + '/' + i->index.value, context->optional};
207
}
208
}
209
else if (AstExprIndexExpr* i = expr->as<AstExprIndexExpr>(); i && context)
210
{
211
if (AstExprConstantString* index = i->index->as<AstExprConstantString>())
212
{
213
return ModuleInfo{context->name + '/' + std::string(index->value.data, index->value.size), context->optional};
214
}
215
}
216
else if (AstExprCall* call = expr->as<AstExprCall>(); call && call->self && call->args.size >= 1 && context)
217
{
218
if (AstExprConstantString* index = call->args.data[0]->as<AstExprConstantString>())
219
{
220
AstName func = call->func->as<AstExprIndexName>()->index;
221
222
if (func == "GetService" && context->name == "game")
223
return ModuleInfo{"game/" + std::string(index->value.data, index->value.size)};
224
}
225
}
226
227
return std::nullopt;
228
}
229
230
std::string TestFileResolver::getHumanReadableModuleName(const ModuleName& name) const
231
{
232
// We have a handful of tests that need to distinguish between a canonical
233
// ModuleName and the human-readable version so we apply a simple transform
234
// here: We replace all slashes with dots.
235
std::string result = name;
236
for (size_t i = 0; i < result.size(); ++i)
237
{
238
if (result[i] == '/')
239
result[i] = '.';
240
}
241
242
return result;
243
}
244
245
std::optional<std::string> TestFileResolver::getEnvironmentForModule(const ModuleName& name) const
246
{
247
auto it = environments.find(name);
248
if (it != environments.end())
249
return it->second;
250
251
return std::nullopt;
252
}
253
254
const Config& TestConfigResolver::getConfig(const ModuleName& name, const TypeCheckLimits& limits) const
255
{
256
auto it = configFiles.find(name);
257
if (it != configFiles.end())
258
return it->second;
259
260
return defaultConfig;
261
}
262
263
Fixture::Fixture(bool prepareAutocomplete)
264
: forAutocomplete(prepareAutocomplete)
265
{
266
}
267
268
Fixture::~Fixture()
269
{
270
Luau::resetPrintLine();
271
}
272
273
AstStatBlock* Fixture::parse(const std::string& source, const ParseOptions& parseOptions)
274
{
275
sourceModule.reset(new SourceModule);
276
277
ParseResult result = Parser::parse(source.c_str(), source.length(), *sourceModule->names, *sourceModule->allocator, parseOptions);
278
279
sourceModule->name = fromString(mainModuleName);
280
sourceModule->root = result.root;
281
sourceModule->mode = parseMode(result.hotcomments);
282
sourceModule->hotcomments = std::move(result.hotcomments);
283
284
if (!result.errors.empty())
285
{
286
// if AST is available, check how lint and typecheck handle error nodes
287
if (result.root)
288
{
289
if (!FFlag::DebugLuauForceOldSolver)
290
{
291
Mode mode = sourceModule->mode ? *sourceModule->mode : Mode::Strict;
292
Frontend::Stats stats;
293
ModulePtr module = Luau::check(
294
*sourceModule,
295
mode,
296
{},
297
getBuiltins(),
298
NotNull{&ice},
299
NotNull{&moduleResolver},
300
NotNull{&fileResolver},
301
getFrontend().globals.globalScope,
302
getFrontend().globals.globalTypeFunctionScope,
303
/*prepareModuleScope*/ nullptr,
304
getFrontend().options,
305
{},
306
false,
307
stats,
308
{}
309
);
310
311
Luau::lint(sourceModule->root, *sourceModule->names, getFrontend().globals.globalScope, module.get(), sourceModule->hotcomments, {});
312
}
313
else
314
{
315
TypeChecker typeChecker(getFrontend().globals.globalScope, &moduleResolver, getBuiltins(), &getFrontend().iceHandler);
316
ModulePtr module = typeChecker.check(*sourceModule, sourceModule->mode.value_or(Luau::Mode::Nonstrict), std::nullopt);
317
318
Luau::lint(sourceModule->root, *sourceModule->names, getFrontend().globals.globalScope, module.get(), sourceModule->hotcomments, {});
319
}
320
}
321
322
throw ParseErrors(result.errors);
323
}
324
325
return result.root;
326
}
327
328
CheckResult Fixture::check(Mode mode, const std::string& source, std::optional<FrontendOptions> options)
329
{
330
// Force the frontend here
331
getFrontend();
332
ModuleName mm = fromString(mainModuleName);
333
configResolver.defaultConfig.mode = mode;
334
fileResolver.source[mm] = std::move(source);
335
getFrontend().markDirty(mm);
336
getFrontend().clearStats();
337
338
CheckResult result = getFrontend().check(mm, options);
339
340
return result;
341
}
342
343
CheckResult Fixture::check(const std::string& source, std::optional<FrontendOptions> options)
344
{
345
return check(Mode::Strict, source, options);
346
}
347
348
LintResult Fixture::lint(const std::string& source, const std::optional<LintOptions>& lintOptions)
349
{
350
ModuleName mm = fromString(mainModuleName);
351
configResolver.defaultConfig.mode = Mode::Strict;
352
fileResolver.source[mm] = std::move(source);
353
getFrontend().markDirty(mm);
354
355
return lintModule(mm, lintOptions);
356
}
357
358
LintResult Fixture::lintModule(const ModuleName& moduleName, const std::optional<LintOptions>& lintOptions)
359
{
360
FrontendOptions options = getFrontend().options;
361
options.runLintChecks = true;
362
options.enabledLintWarnings = lintOptions;
363
364
CheckResult result = getFrontend().check(moduleName, options);
365
366
return result.lintResult;
367
}
368
369
ParseResult Fixture::parseEx(const std::string& source, const ParseOptions& options)
370
{
371
ParseResult result = tryParse(source, options);
372
if (!result.errors.empty())
373
throw ParseErrors(result.errors);
374
375
return result;
376
}
377
378
ParseResult Fixture::tryParse(const std::string& source, const ParseOptions& parseOptions)
379
{
380
ParseOptions options = parseOptions;
381
options.allowDeclarationSyntax = true;
382
383
sourceModule.reset(new SourceModule);
384
ParseResult result = Parser::parse(source.c_str(), source.length(), *sourceModule->names, *sourceModule->allocator, options);
385
sourceModule->root = result.root;
386
return result;
387
}
388
389
ParseResult Fixture::matchParseError(const std::string& source, const std::string& message, std::optional<Location> location)
390
{
391
ParseOptions options;
392
options.allowDeclarationSyntax = true;
393
394
sourceModule.reset(new SourceModule);
395
ParseResult result = Parser::parse(source.c_str(), source.length(), *sourceModule->names, *sourceModule->allocator, options);
396
397
CHECK_MESSAGE(!result.errors.empty(), "Expected a parse error in '" << source << "'");
398
399
if (!result.errors.empty())
400
{
401
CHECK_EQ(result.errors.front().getMessage(), message);
402
403
if (location)
404
CHECK_EQ(result.errors.front().getLocation(), *location);
405
}
406
407
return result;
408
}
409
410
ParseResult Fixture::matchParseErrorPrefix(const std::string& source, const std::string& prefix)
411
{
412
ParseOptions options;
413
options.allowDeclarationSyntax = true;
414
415
sourceModule.reset(new SourceModule);
416
ParseResult result = Parser::parse(source.c_str(), source.length(), *sourceModule->names, *sourceModule->allocator, options);
417
418
CHECK_MESSAGE(!result.errors.empty(), "Expected a parse error in '" << source << "'");
419
420
if (!result.errors.empty())
421
{
422
const std::string& message = result.errors.front().getMessage();
423
CHECK_GE(message.length(), prefix.length());
424
CHECK_EQ(prefix, message.substr(0, prefix.size()));
425
}
426
427
return result;
428
}
429
430
ModulePtr Fixture::getMainModule(bool forAutocomplete)
431
{
432
if (forAutocomplete && FFlag::DebugLuauForceOldSolver)
433
return getFrontend().moduleResolverForAutocomplete.getModule(fromString(mainModuleName));
434
435
return getFrontend().moduleResolver.getModule(fromString(mainModuleName));
436
}
437
438
SourceModule* Fixture::getMainSourceModule()
439
{
440
return getFrontend().getSourceModule(fromString(mainModuleName));
441
}
442
443
std::optional<PrimitiveType::Type> Fixture::getPrimitiveType(TypeId ty)
444
{
445
REQUIRE(ty != nullptr);
446
447
TypeId aType = follow(ty);
448
REQUIRE(aType != nullptr);
449
450
const PrimitiveType* pt = get<PrimitiveType>(aType);
451
if (pt != nullptr)
452
return pt->type;
453
else
454
return std::nullopt;
455
}
456
457
std::optional<TypeId> Fixture::getType(const std::string& name, bool forAutocomplete)
458
{
459
ModulePtr module = getMainModule(forAutocomplete);
460
REQUIRE(module);
461
462
if (!module->hasModuleScope())
463
return std::nullopt;
464
465
if (!FFlag::DebugLuauForceOldSolver)
466
return linearSearchForBinding(module->getModuleScope().get(), name.c_str());
467
else
468
return lookupName(module->getModuleScope(), name);
469
}
470
471
TypeId Fixture::requireType(const std::string& name)
472
{
473
std::optional<TypeId> ty = getType(name);
474
REQUIRE_MESSAGE(bool(ty), "Unable to requireType \"" << name << "\"");
475
return follow(*ty);
476
}
477
478
TypeId Fixture::requireType(const ModuleName& moduleName, const std::string& name)
479
{
480
ModulePtr module = getFrontend().moduleResolver.getModule(moduleName);
481
REQUIRE(module);
482
return requireType(module, name);
483
}
484
485
TypeId Fixture::requireType(const ModulePtr& module, const std::string& name)
486
{
487
if (!module->hasModuleScope())
488
FAIL("requireType: module scope data is not available");
489
490
return requireType(module->getModuleScope(), name);
491
}
492
493
TypeId Fixture::requireType(const ScopePtr& scope, const std::string& name)
494
{
495
std::optional<TypeId> ty = lookupName(scope, name);
496
REQUIRE_MESSAGE(ty, "requireType: No type \"" << name << "\"");
497
return *ty;
498
}
499
500
std::optional<TypeId> Fixture::findTypeAtPosition(Position position)
501
{
502
ModulePtr module = getMainModule();
503
SourceModule* sourceModule = getMainSourceModule();
504
return Luau::findTypeAtPosition(*module, *sourceModule, position);
505
}
506
507
std::optional<TypeId> Fixture::findExpectedTypeAtPosition(Position position)
508
{
509
ModulePtr module = getMainModule();
510
SourceModule* sourceModule = getMainSourceModule();
511
return Luau::findExpectedTypeAtPosition(*module, *sourceModule, position);
512
}
513
514
TypeId Fixture::requireTypeAtPosition(Position position)
515
{
516
auto ty = findTypeAtPosition(position);
517
REQUIRE_MESSAGE(ty, "requireTypeAtPosition: No type at position " << position);
518
return *ty;
519
}
520
521
std::optional<TypeId> Fixture::lookupType(const std::string& name)
522
{
523
ModulePtr module = getMainModule();
524
525
if (!module->hasModuleScope())
526
return std::nullopt;
527
528
if (auto typeFun = module->getModuleScope()->lookupType(name))
529
return typeFun->type;
530
531
return std::nullopt;
532
}
533
534
std::optional<TypeId> Fixture::lookupImportedType(const std::string& moduleAlias, const std::string& name)
535
{
536
ModulePtr module = getMainModule();
537
538
if (!module->hasModuleScope())
539
FAIL("lookupImportedType: module scope data is not available");
540
541
if (auto typeFun = module->getModuleScope()->lookupImportedType(moduleAlias, name))
542
return typeFun->type;
543
544
return std::nullopt;
545
}
546
547
TypeId Fixture::requireTypeAlias(const std::string& name)
548
{
549
std::optional<TypeId> ty = lookupType(name);
550
REQUIRE(ty);
551
return follow(*ty);
552
}
553
554
TypeId Fixture::requireExportedType(const ModuleName& moduleName, const std::string& name)
555
{
556
ModulePtr module = getFrontend().moduleResolver.getModule(moduleName);
557
REQUIRE(module);
558
559
auto it = module->exportedTypeBindings.find(name);
560
REQUIRE(it != module->exportedTypeBindings.end());
561
562
return it->second.type;
563
}
564
565
TypeId Fixture::parseType(std::string_view src)
566
{
567
return getFrontend().parseType(
568
NotNull{&allocator}, NotNull{&nameTable}, NotNull{&getFrontend().iceHandler}, TypeCheckLimits{}, NotNull{&arena}, src
569
);
570
}
571
572
std::string Fixture::decorateWithTypes(const std::string& code)
573
{
574
fileResolver.source[mainModuleName] = code;
575
576
Luau::CheckResult typeInfo = getFrontend().check(mainModuleName);
577
578
SourceModule* sourceModule = getFrontend().getSourceModule(mainModuleName);
579
attachTypeData(*sourceModule, *getFrontend().moduleResolver.getModule(mainModuleName));
580
581
return prettyPrintWithTypes(*sourceModule->root);
582
}
583
584
void Fixture::dumpErrors(std::ostream& os, const std::vector<TypeError>& errors)
585
{
586
for (const auto& error : errors)
587
{
588
os << std::endl;
589
os << "Error: " << error << std::endl;
590
591
std::string_view source = fileResolver.source[error.moduleName];
592
std::vector<std::string_view> lines = Luau::split(source, '\n');
593
594
if (error.location.begin.line >= lines.size())
595
{
596
os << "\tSource not available?" << std::endl;
597
continue;
598
}
599
600
std::string_view theLine = lines[error.location.begin.line];
601
os << "Line:\t" << theLine << std::endl;
602
int startCol = error.location.begin.column;
603
int endCol = error.location.end.line == error.location.begin.line ? error.location.end.column : int(theLine.size());
604
605
os << '\t' << std::string(startCol, ' ') << std::string(std::max(1, endCol - startCol), '-') << std::endl;
606
}
607
}
608
609
void Fixture::registerTestTypes()
610
{
611
addGlobalBinding(getFrontend().globals, "game", getBuiltins()->anyType, "@luau");
612
addGlobalBinding(getFrontend().globals, "workspace", getBuiltins()->anyType, "@luau");
613
addGlobalBinding(getFrontend().globals, "script", getBuiltins()->anyType, "@luau");
614
}
615
616
void Fixture::dumpErrors(const CheckResult& cr)
617
{
618
if (hasDumpedErrors)
619
return;
620
hasDumpedErrors = true;
621
std::string error = getErrors(cr);
622
if (!error.empty())
623
MESSAGE(error);
624
}
625
626
void Fixture::dumpErrors(const ModulePtr& module)
627
{
628
if (hasDumpedErrors)
629
return;
630
hasDumpedErrors = true;
631
std::stringstream ss;
632
dumpErrors(ss, module->errors);
633
if (!ss.str().empty())
634
MESSAGE(ss.str());
635
}
636
637
void Fixture::dumpErrors(const Module& module)
638
{
639
if (hasDumpedErrors)
640
return;
641
hasDumpedErrors = true;
642
std::stringstream ss;
643
dumpErrors(ss, module.errors);
644
if (!ss.str().empty())
645
MESSAGE(ss.str());
646
}
647
648
std::string Fixture::getErrors(const CheckResult& cr)
649
{
650
std::stringstream ss;
651
dumpErrors(ss, cr.errors);
652
return ss.str();
653
}
654
655
void Fixture::validateErrors(const std::vector<Luau::TypeError>& errors)
656
{
657
std::ostringstream oss;
658
659
// This helps us validate that error stringification doesn't crash, using both user-facing and internal test-only representation
660
// Also we exercise error comparison to make sure it's at least able to compare the error equal to itself
661
for (const Luau::TypeError& e : errors)
662
{
663
oss.clear();
664
oss << e;
665
toString(e);
666
// CHECK(e == e); TODO: this doesn't work due to union/intersection type vars
667
}
668
}
669
670
LoadDefinitionFileResult Fixture::loadDefinition(const std::string& source, bool forAutocomplete)
671
{
672
GlobalTypes& globals = forAutocomplete ? getFrontend().globalsForAutocomplete : getFrontend().globals;
673
unfreeze(globals.globalTypes);
674
LoadDefinitionFileResult result = getFrontend().loadDefinitionFile(
675
globals, globals.globalScope, source, "@test", /* captureComments */ false, /* typecheckForAutocomplete */ forAutocomplete
676
);
677
freeze(globals.globalTypes);
678
679
if (result.module)
680
dumpErrors(result.module);
681
REQUIRE_MESSAGE(result.success, "loadDefinition: unable to load definition file");
682
return result;
683
}
684
685
NotNull<BuiltinTypes> Fixture::getBuiltins()
686
{
687
if (!builtinTypes)
688
getFrontend();
689
return NotNull{builtinTypes};
690
}
691
692
const BuiltinTypeFunctions& Fixture::getBuiltinTypeFunctions()
693
{
694
return *getBuiltins()->typeFunctions;
695
}
696
697
Frontend& Fixture::getFrontend()
698
{
699
if (frontend)
700
return *frontend;
701
702
Frontend& f = frontend.emplace(
703
FFlag::DebugLuauForceOldSolver ? SolverMode::Old : SolverMode::New,
704
&fileResolver,
705
&configResolver,
706
FrontendOptions{
707
/* retainFullTypeGraphs= */ true, /* forAutocomplete */ false, /* runLintChecks */ false, /* randomConstraintResolutionSeed */ randomSeed
708
}
709
);
710
711
builtinTypes = f.builtinTypes;
712
// Fixture::Fixture begins here
713
configResolver.defaultConfig.mode = Mode::Strict;
714
configResolver.defaultConfig.enabledLint.warningMask = ~0ull;
715
configResolver.defaultConfig.parseOptions.captureComments = true;
716
717
Luau::freeze(f.globals.globalTypes);
718
Luau::freeze(f.globalsForAutocomplete.globalTypes);
719
720
Luau::setPrintLine([](auto s) {});
721
722
if (FFlag::DebugLuauLogSolverToJsonFile)
723
{
724
f.writeJsonLog = [&](const Luau::ModuleName& moduleName, std::string log)
725
{
726
std::string path = moduleName + ".log.json";
727
size_t pos = moduleName.find_last_of('/');
728
if (pos != std::string::npos)
729
path = moduleName.substr(pos + 1);
730
731
std::ofstream os(path);
732
733
os << log << std::endl;
734
MESSAGE("Wrote JSON log to ", path);
735
};
736
}
737
return *frontend;
738
}
739
740
void Fixture::limitStackSize(size_t size)
741
{
742
// The FInt is designed to trip when the amount of available address
743
// space goes below some threshold, but for this API, the convenient thing
744
// is to specify how much the test should be allowed to use. We need to
745
// do a tiny amount of arithmetic to convert.
746
747
uintptr_t addressSpaceSize = getStackAddressSpaceSize();
748
749
dynamicScopedInts.emplace_back(FInt::LuauStackGuardThreshold, (int)(addressSpaceSize - size));
750
}
751
752
BuiltinsFixture::BuiltinsFixture(bool prepareAutocomplete)
753
: Fixture(prepareAutocomplete)
754
{
755
}
756
757
Frontend& BuiltinsFixture::getFrontend()
758
{
759
if (frontend)
760
return *frontend;
761
762
Frontend& f = Fixture::getFrontend();
763
// Do builtins fixture things now
764
Luau::unfreeze(f.globals.globalTypes);
765
Luau::unfreeze(f.globalsForAutocomplete.globalTypes);
766
767
registerBuiltinGlobals(f, f.globals);
768
if (forAutocomplete)
769
registerBuiltinGlobals(f, f.globalsForAutocomplete, /*typeCheckForAutocomplete*/ true);
770
registerTestTypes();
771
772
Luau::freeze(f.globals.globalTypes);
773
Luau::freeze(f.globalsForAutocomplete.globalTypes);
774
775
return *frontend;
776
}
777
778
bool IsSubtypeFixture::isSubtype(TypeId a, TypeId b)
779
{
780
ModulePtr module = getMainModule();
781
REQUIRE(module);
782
783
if (!module->hasModuleScope())
784
FAIL("isSubtype: module scope data is not available");
785
786
UnifierSharedState sharedState{&ice};
787
NotNull<Scope> scope{module->getModuleScope().get()};
788
Normalizer normalizer{
789
&arena,
790
NotNull{builtinTypes},
791
NotNull{&sharedState},
792
FFlag::DebugLuauForceOldSolver ? SolverMode::Old : SolverMode::New,
793
};
794
795
if (FFlag::DebugLuauForceOldSolver)
796
{
797
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
798
u.tryUnify(a, b);
799
return !u.failure;
800
}
801
else
802
{
803
TypeArena arena;
804
TypeCheckLimits limits;
805
TypeFunctionRuntime typeFunctionRuntime{NotNull{&ice}, NotNull{&limits}};
806
807
Subtyping subtyping{NotNull{builtinTypes}, NotNull{&arena}, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}};
808
return subtyping.isSubtype(a, b, scope).isSubtype;
809
}
810
}
811
812
813
static std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr)
814
{
815
const AstExprIndexName* indexName = pathExpr.as<AstExprIndexName>();
816
if (!indexName)
817
return {};
818
819
std::vector<std::string_view> segments{indexName->index.value};
820
821
while (true)
822
{
823
if (AstExprIndexName* in = indexName->expr->as<AstExprIndexName>())
824
{
825
segments.push_back(in->index.value);
826
indexName = in;
827
continue;
828
}
829
else if (AstExprGlobal* indexNameAsGlobal = indexName->expr->as<AstExprGlobal>())
830
{
831
segments.push_back(indexNameAsGlobal->name.value);
832
break;
833
}
834
else if (AstExprLocal* indexNameAsLocal = indexName->expr->as<AstExprLocal>())
835
{
836
segments.push_back(indexNameAsLocal->local->name.value);
837
break;
838
}
839
else
840
return {};
841
}
842
843
std::reverse(segments.begin(), segments.end());
844
return segments;
845
}
846
847
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& segments)
848
{
849
if (segments.empty())
850
return std::nullopt;
851
852
std::vector<std::string_view> result;
853
854
auto it = segments.begin();
855
856
if (*it == "script" && !currentModuleName.empty())
857
{
858
result = split(currentModuleName, '/');
859
++it;
860
}
861
862
for (; it != segments.end(); ++it)
863
{
864
if (result.size() > 1 && *it == "Parent")
865
result.pop_back();
866
else
867
result.push_back(*it);
868
}
869
870
return join(result, "/");
871
}
872
873
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& pathExpr)
874
{
875
std::vector<std::string_view> segments = parsePathExpr(pathExpr);
876
return pathExprToModuleName(currentModuleName, segments);
877
}
878
879
ModuleName fromString(std::string_view name)
880
{
881
return ModuleName(name);
882
}
883
884
std::string rep(const std::string& s, size_t n)
885
{
886
std::string r;
887
r.reserve(s.length() * n);
888
for (size_t i = 0; i < n; ++i)
889
r += s;
890
return r;
891
}
892
893
bool isInArena(TypeId t, const TypeArena& arena)
894
{
895
return arena.types.contains(t);
896
}
897
898
void dumpErrors(const ModulePtr& module)
899
{
900
for (const auto& error : module->errors)
901
std::cout << "Error: " << error << std::endl;
902
}
903
904
void dump(const std::string& name, TypeId ty)
905
{
906
std::cout << name << '\t' << toString(ty, {true}) << std::endl;
907
}
908
909
std::optional<TypeId> lookupName(ScopePtr scope, const std::string& name)
910
{
911
auto binding = scope->linearSearchForBinding(name);
912
if (binding)
913
return binding->typeId;
914
else
915
return std::nullopt;
916
}
917
918
std::optional<TypeId> linearSearchForBinding(Scope* scope, const char* name)
919
{
920
while (scope)
921
{
922
for (const auto& [n, ty] : scope->bindings)
923
{
924
if (n.astName() == name)
925
return ty.typeId;
926
}
927
928
scope = scope->parent.get();
929
}
930
931
return std::nullopt;
932
}
933
934
void registerHiddenTypes(Frontend& frontend)
935
{
936
GlobalTypes& globals = frontend.globals;
937
938
unfreeze(globals.globalTypes);
939
940
TypeId t = globals.globalTypes.addType(GenericType{"T", Polarity::Mixed});
941
GenericTypeDefinition genericT{t};
942
943
TypeId u = globals.globalTypes.addType(GenericType{"U", Polarity::Mixed});
944
GenericTypeDefinition genericU{u};
945
946
ScopePtr globalScope = globals.globalScope;
947
globalScope->exportedTypeBindings["Not"] = TypeFun{{genericT}, globals.globalTypes.addType(NegationType{t})};
948
globalScope->exportedTypeBindings["Mt"] = TypeFun{{genericT, genericU}, globals.globalTypes.addType(MetatableType{t, u})};
949
globalScope->exportedTypeBindings["fun"] = TypeFun{{}, frontend.builtinTypes->functionType};
950
globalScope->exportedTypeBindings["cls"] = TypeFun{{}, frontend.builtinTypes->externType};
951
globalScope->exportedTypeBindings["err"] = TypeFun{{}, frontend.builtinTypes->errorType};
952
globalScope->exportedTypeBindings["tbl"] = TypeFun{{}, frontend.builtinTypes->tableType};
953
954
freeze(globals.globalTypes);
955
}
956
957
void createSomeExternTypes(Frontend& frontend)
958
{
959
GlobalTypes& globals = frontend.globals;
960
961
TypeArena& arena = globals.globalTypes;
962
unfreeze(arena);
963
964
ScopePtr moduleScope = globals.globalScope;
965
966
TypeId parentType = arena.addType(ExternType{"Parent", {}, frontend.builtinTypes->externType, std::nullopt, {}, nullptr, "Test", {}});
967
968
ExternType* parentExternType = getMutable<ExternType>(parentType);
969
parentExternType->props["method"] = {makeFunction(arena, parentType, {}, {})};
970
971
parentExternType->props["virtual_method"] = {makeFunction(arena, parentType, {}, {})};
972
973
addGlobalBinding(globals, "Parent", {parentType});
974
moduleScope->exportedTypeBindings["Parent"] = TypeFun{{}, parentType};
975
976
TypeId childType = arena.addType(ExternType{"Child", {}, parentType, std::nullopt, {}, nullptr, "Test", {}});
977
978
addGlobalBinding(globals, "Child", {childType});
979
moduleScope->exportedTypeBindings["Child"] = TypeFun{{}, childType};
980
981
TypeId anotherChildType = arena.addType(ExternType{"AnotherChild", {}, parentType, std::nullopt, {}, nullptr, "Test", {}});
982
983
addGlobalBinding(globals, "AnotherChild", {anotherChildType});
984
moduleScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildType};
985
986
TypeId unrelatedType = arena.addType(ExternType{"Unrelated", {}, frontend.builtinTypes->externType, std::nullopt, {}, nullptr, "Test", {}});
987
988
addGlobalBinding(globals, "Unrelated", {unrelatedType});
989
moduleScope->exportedTypeBindings["Unrelated"] = TypeFun{{}, unrelatedType};
990
991
for (const auto& [name, ty] : moduleScope->exportedTypeBindings)
992
persist(ty.type);
993
994
freeze(arena);
995
}
996
997
void dump(const std::vector<Constraint>& constraints)
998
{
999
ToStringOptions opts;
1000
for (const auto& c : constraints)
1001
printf("%s\n", toString(c, opts).c_str());
1002
}
1003
1004
} // namespace Luau
1005
1006