Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/Analysis/src/Frontend.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/Frontend.h"
3
4
#include "Luau/BuiltinDefinitions.h"
5
#include "Luau/Common.h"
6
#include "Luau/Clone.h"
7
#include "Luau/Config.h"
8
#include "Luau/ConstraintGenerator.h"
9
#include "Luau/ConstraintSolver.h"
10
#include "Luau/DataFlowGraph.h"
11
#include "Luau/DcrLogger.h"
12
#include "Luau/ExpectedTypeVisitor.h"
13
#include "Luau/FileResolver.h"
14
#include "Luau/NonStrictTypeChecker.h"
15
#include "Luau/NotNull.h"
16
#include "Luau/Parser.h"
17
#include "Luau/Scope.h"
18
#include "Luau/TimeTrace.h"
19
#include "Luau/TypeArena.h"
20
#include "Luau/TypeCheckLimits.h"
21
#include "Luau/TypeChecker2.h"
22
#include "Luau/TypeInfer.h"
23
#include "Luau/VisitType.h"
24
25
#include <algorithm>
26
#include <chrono>
27
#include <condition_variable>
28
#include <exception>
29
#include <mutex>
30
#include <string>
31
32
LUAU_FASTINT(LuauTypeInferIterationLimit)
33
LUAU_FASTINT(LuauTypeInferRecursionLimit)
34
LUAU_FASTINT(LuauTarjanChildLimit)
35
LUAU_FASTFLAG(LuauInferInNoCheckMode)
36
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
37
LUAU_FASTFLAG(LuauSolverV2)
38
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
39
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
40
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
41
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
42
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
43
LUAU_FASTFLAGVARIABLE(DebugLuauAlwaysShowConstraintSolvingIncomplete)
44
LUAU_FASTFLAG(LuauOverloadGetsInstantiated)
45
46
LUAU_FASTFLAGVARIABLE(DebugLuauForceOldSolver)
47
48
namespace Luau
49
{
50
51
struct BuildQueueItem
52
{
53
ModuleName name;
54
ModuleName humanReadableName;
55
56
// Parameters
57
std::shared_ptr<SourceNode> sourceNode;
58
std::shared_ptr<SourceModule> sourceModule;
59
Config config;
60
ScopePtr environmentScope;
61
std::vector<RequireCycle> requireCycles;
62
FrontendOptions options;
63
bool recordJsonLog = false;
64
65
// Queue state
66
std::vector<size_t> reverseDeps;
67
int dirtyDependencies = 0;
68
bool processing = false;
69
70
// Result
71
std::exception_ptr exception;
72
ModulePtr module;
73
Frontend::Stats stats;
74
};
75
76
struct BuildQueueWorkState
77
{
78
std::function<void(std::function<void()> task)> executeTask_DEPRECATED;
79
std::function<void(std::vector<std::function<void()>> tasks)> executeTasks;
80
81
std::vector<BuildQueueItem> buildQueueItems;
82
83
std::mutex mtx;
84
std::condition_variable cv;
85
std::vector<size_t> readyQueueItems;
86
87
size_t processing = 0;
88
size_t remaining = 0;
89
};
90
91
std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments)
92
{
93
for (const HotComment& hc : hotcomments)
94
{
95
if (!hc.header)
96
continue;
97
98
if (hc.content == "nocheck")
99
return Mode::NoCheck;
100
101
if (hc.content == "nonstrict")
102
return Mode::Nonstrict;
103
104
if (hc.content == "strict")
105
return Mode::Strict;
106
}
107
108
return std::nullopt;
109
}
110
111
static void generateDocumentationSymbols(TypeId ty, const std::string& rootName)
112
{
113
// TODO: What do we do in this situation? This means that the definition
114
// file is exporting a type that is also a persistent type.
115
if (ty->persistent)
116
{
117
return;
118
}
119
120
asMutable(ty)->documentationSymbol = rootName;
121
122
if (TableType* ttv = getMutable<TableType>(ty))
123
{
124
for (auto& [name, prop] : ttv->props)
125
{
126
std::string n;
127
n.reserve(rootName.size() + 1 + name.size());
128
n += rootName;
129
n += ".";
130
n += name;
131
prop.documentationSymbol = std::move(n);
132
}
133
}
134
else if (ExternType* etv = getMutable<ExternType>(ty))
135
{
136
for (auto& [name, prop] : etv->props)
137
{
138
std::string n;
139
n.reserve(rootName.size() + 1 + name.size());
140
n += rootName;
141
n += ".";
142
n += name;
143
prop.documentationSymbol = std::move(n);
144
}
145
}
146
}
147
148
static ParseResult parseSourceForModule(std::string_view source, Luau::SourceModule& sourceModule, bool captureComments)
149
{
150
ParseOptions options;
151
options.allowDeclarationSyntax = true;
152
options.captureComments = captureComments;
153
154
Luau::ParseResult parseResult = Luau::Parser::parse(source.data(), source.size(), *sourceModule.names, *sourceModule.allocator, options);
155
sourceModule.root = parseResult.root;
156
sourceModule.mode = Mode::Definition;
157
158
if (options.captureComments)
159
{
160
sourceModule.hotcomments = parseResult.hotcomments;
161
sourceModule.commentLocations = parseResult.commentLocations;
162
}
163
164
return parseResult;
165
}
166
167
static void persistCheckedTypes(ModulePtr checkedModule, GlobalTypes& globals, ScopePtr targetScope, const std::string& packageName)
168
{
169
CloneState cloneState{globals.builtinTypes};
170
171
std::vector<TypeId> typesToPersist;
172
typesToPersist.reserve(checkedModule->declaredGlobals.size() + checkedModule->exportedTypeBindings.size());
173
174
for (const auto& [name, ty] : checkedModule->declaredGlobals)
175
{
176
TypeId globalTy = clone(ty, globals.globalTypes, cloneState);
177
178
static constexpr const char infix[] = "/global/";
179
constexpr int infixLength = sizeof(infix) - 1; // exclude the null terminator
180
std::string documentationSymbol;
181
documentationSymbol.reserve(packageName.size() + infixLength + name.size());
182
documentationSymbol += packageName;
183
documentationSymbol += infix;
184
documentationSymbol += name;
185
186
generateDocumentationSymbols(globalTy, documentationSymbol);
187
targetScope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
188
189
typesToPersist.push_back(globalTy);
190
}
191
192
for (const auto& [name, ty] : checkedModule->exportedTypeBindings)
193
{
194
TypeFun globalTy = clone(ty, globals.globalTypes, cloneState);
195
196
static constexpr const char infix[] = "/globaltype/";
197
constexpr int infixLength = sizeof(infix) - 1; // exclude the null terminator
198
std::string documentationSymbol;
199
documentationSymbol.reserve(packageName.size() + infixLength + name.size());
200
documentationSymbol += packageName;
201
documentationSymbol += infix;
202
documentationSymbol += name;
203
204
generateDocumentationSymbols(globalTy.type, documentationSymbol);
205
targetScope->exportedTypeBindings[name] = globalTy;
206
207
typesToPersist.push_back(globalTy.type);
208
}
209
210
for (TypeId ty : typesToPersist)
211
{
212
persist(ty);
213
}
214
}
215
216
LoadDefinitionFileResult Frontend::loadDefinitionFile(
217
GlobalTypes& globals,
218
ScopePtr targetScope,
219
std::string_view source,
220
const std::string& packageName,
221
bool captureComments,
222
bool typeCheckForAutocomplete
223
)
224
{
225
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
226
227
Luau::SourceModule sourceModule;
228
sourceModule.name = packageName;
229
sourceModule.humanReadableName = packageName;
230
231
Luau::ParseResult parseResult = parseSourceForModule(source, sourceModule, captureComments);
232
if (parseResult.errors.size() > 0)
233
return LoadDefinitionFileResult{false, std::move(parseResult), std::move(sourceModule), nullptr};
234
235
Frontend::Stats dummyStats;
236
ModulePtr checkedModule =
237
check(sourceModule, Mode::Definition, {}, std::nullopt, /*forAutocomplete*/ false, /*recordJsonLog*/ false, dummyStats, {});
238
239
if (checkedModule->errors.size() > 0)
240
return LoadDefinitionFileResult{false, std::move(parseResult), std::move(sourceModule), std::move(checkedModule)};
241
242
persistCheckedTypes(checkedModule, globals, std::move(targetScope), packageName);
243
244
return LoadDefinitionFileResult{true, std::move(parseResult), std::move(sourceModule), std::move(checkedModule)};
245
}
246
247
namespace
248
{
249
250
ErrorVec accumulateErrors(
251
const std::unordered_map<ModuleName, std::shared_ptr<SourceNode>>& sourceNodes,
252
ModuleResolver& moduleResolver,
253
const ModuleName& name
254
)
255
{
256
DenseHashSet<ModuleName> seen{{}};
257
std::vector<ModuleName> queue{name};
258
259
ErrorVec result;
260
261
while (!queue.empty())
262
{
263
ModuleName next = std::move(queue.back());
264
queue.pop_back();
265
266
if (seen.contains(next))
267
continue;
268
seen.insert(next);
269
270
auto it = sourceNodes.find(next);
271
if (it == sourceNodes.end())
272
continue;
273
274
const SourceNode& sourceNode = *it->second;
275
queue.insert(queue.end(), sourceNode.requireSet.begin(), sourceNode.requireSet.end());
276
277
// FIXME: If a module has a syntax error, we won't be able to re-report it here.
278
// The solution is probably to move errors from Module to SourceNode
279
280
auto modulePtr = moduleResolver.getModule(next);
281
if (!modulePtr)
282
continue;
283
284
Module& module = *modulePtr;
285
size_t prevSize = result.size();
286
287
// Append module errors in reverse order
288
result.insert(result.end(), module.errors.rbegin(), module.errors.rend());
289
290
// Sort them in the reverse order as well
291
std::stable_sort(
292
result.begin() + prevSize,
293
result.end(),
294
[](const TypeError& e1, const TypeError& e2) -> bool
295
{
296
return e1.location.begin > e2.location.begin;
297
}
298
);
299
}
300
301
// Now we reverse errors from all modules and since they were inserted and sorted in reverse, it should be in order
302
std::reverse(result.begin(), result.end());
303
304
return result;
305
}
306
307
void filterLintOptions(LintOptions& lintOptions, const std::vector<HotComment>& hotcomments, Mode mode)
308
{
309
uint64_t ignoreLints = LintWarning::parseMask(hotcomments);
310
311
lintOptions.warningMask &= ~ignoreLints;
312
313
if (mode != Mode::NoCheck)
314
{
315
lintOptions.disableWarning(Luau::LintWarning::Code_UnknownGlobal);
316
}
317
318
if (mode == Mode::Strict)
319
{
320
lintOptions.disableWarning(Luau::LintWarning::Code_ImplicitReturn);
321
}
322
}
323
324
// Given a source node (start), find all requires that start a transitive dependency path that ends back at start
325
// For each such path, record the full path and the location of the require in the starting module.
326
// Note that this is O(V^2) for a fully connected graph and produces O(V) paths of length O(V)
327
// However, when the graph is acyclic, this is O(V), as well as when only the first cycle is needed (stopAtFirst=true)
328
std::vector<RequireCycle> getRequireCycles(
329
const FileResolver* resolver,
330
const std::unordered_map<ModuleName, std::shared_ptr<SourceNode>>& sourceNodes,
331
const SourceNode* start
332
)
333
{
334
std::vector<RequireCycle> result;
335
336
DenseHashSet<const SourceNode*> seen(nullptr);
337
std::vector<const SourceNode*> stack;
338
std::vector<const SourceNode*> path;
339
340
for (const auto& [depName, depLocation] : start->requireLocations)
341
{
342
std::vector<ModuleName> cycle;
343
344
auto dit = sourceNodes.find(depName);
345
if (dit == sourceNodes.end())
346
continue;
347
348
stack.push_back(dit->second.get());
349
350
while (!stack.empty())
351
{
352
const SourceNode* top = stack.back();
353
stack.pop_back();
354
355
if (top == nullptr)
356
{
357
// special marker for post-order processing
358
LUAU_ASSERT(!path.empty());
359
top = path.back();
360
path.pop_back();
361
362
// we reached the node! path must form a cycle now
363
if (top == start)
364
{
365
for (const SourceNode* node : path)
366
cycle.push_back(node->name);
367
368
cycle.push_back(top->name);
369
break;
370
}
371
}
372
else if (!seen.contains(top))
373
{
374
seen.insert(top);
375
376
// push marker for post-order processing
377
path.push_back(top);
378
stack.push_back(nullptr);
379
380
// note: we push require edges in the opposite order
381
// because it's a stack, the last edge to be pushed gets processed first
382
// this ensures that the cyclic path we report is the first one in DFS order
383
for (size_t i = top->requireLocations.size(); i > 0; --i)
384
{
385
const ModuleName& reqName = top->requireLocations[i - 1].first;
386
387
auto rit = sourceNodes.find(reqName);
388
if (rit != sourceNodes.end())
389
stack.push_back(rit->second.get());
390
}
391
}
392
}
393
394
path.clear();
395
stack.clear();
396
397
if (!cycle.empty())
398
{
399
result.emplace_back(
400
RequireCycle{depLocation, std::move(cycle)}
401
); // note: if we didn't find a cycle, all nodes that we've seen don't depend [transitively] on start
402
// so it's safe to *only* clear seen vector when we find a cycle
403
// if we don't do it, we will not have correct reporting for some cycles
404
seen.clear();
405
}
406
}
407
408
return result;
409
}
410
411
double getTimestamp()
412
{
413
using namespace std::chrono;
414
return double(duration_cast<nanoseconds>(high_resolution_clock::now().time_since_epoch()).count()) / 1e9;
415
}
416
417
} // namespace
418
419
static TypeCheckLimits makeTypeCheckLimits(const FrontendOptions& options)
420
{
421
TypeCheckLimits limits;
422
423
if (options.moduleTimeLimitSec)
424
limits.finishTime = TimeTrace::getClock() + *options.moduleTimeLimitSec;
425
else
426
limits.finishTime = std::nullopt;
427
428
limits.cancellationToken = options.cancellationToken;
429
430
return limits;
431
}
432
433
Frontend::Frontend(SolverMode mode, FileResolver* fileResolver, ConfigResolver* configResolver, FrontendOptions options)
434
: useNewLuauSolver(mode)
435
, builtinTypes(NotNull{&builtinTypes_})
436
, fileResolver(fileResolver)
437
, moduleResolver(this)
438
, moduleResolverForAutocomplete(this)
439
, globals(builtinTypes, getLuauSolverMode())
440
, globalsForAutocomplete(builtinTypes, getLuauSolverMode())
441
, configResolver(configResolver)
442
, options(std::move(options))
443
{
444
}
445
446
Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options)
447
: useNewLuauSolver(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old)
448
, builtinTypes(NotNull{&builtinTypes_})
449
, fileResolver(fileResolver)
450
, moduleResolver(this)
451
, moduleResolverForAutocomplete(this)
452
, globals(builtinTypes, getLuauSolverMode())
453
, globalsForAutocomplete(builtinTypes, getLuauSolverMode())
454
, configResolver(configResolver)
455
, options(options)
456
{
457
}
458
459
void Frontend::setLuauSolverMode(SolverMode mode)
460
{
461
useNewLuauSolver.store(mode);
462
}
463
464
SolverMode Frontend::getLuauSolverMode() const
465
{
466
return useNewLuauSolver.load();
467
}
468
469
void Frontend::parse(const ModuleName& name)
470
{
471
LUAU_TIMETRACE_SCOPE("Frontend::parse", "Frontend");
472
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
473
474
if (getCheckResult(name, false, false))
475
return;
476
477
std::vector<ModuleName> buildQueue;
478
parseGraph(buildQueue, name, {}, false);
479
}
480
481
void Frontend::parseModules(const std::vector<ModuleName>& names)
482
{
483
LUAU_TIMETRACE_SCOPE("Frontend::parseModules", "Frontend");
484
485
DenseHashSet<Luau::ModuleName> seen{{}};
486
487
for (const ModuleName& name : names)
488
{
489
if (seen.contains(name))
490
continue;
491
492
if (auto it = sourceNodes.find(name); it != sourceNodes.end() && !it->second->hasDirtySourceModule())
493
{
494
seen.insert(name);
495
continue;
496
}
497
498
std::vector<ModuleName> queue;
499
parseGraph(
500
queue,
501
name,
502
{},
503
false,
504
[&seen](const ModuleName& name)
505
{
506
return seen.contains(name);
507
}
508
);
509
510
seen.insert(name);
511
}
512
}
513
514
CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOptions> optionOverride)
515
{
516
LUAU_TIMETRACE_SCOPE("Frontend::check", "Frontend");
517
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
518
519
FrontendOptions frontendOptions = optionOverride.value_or(options);
520
if (getLuauSolverMode() == SolverMode::New)
521
frontendOptions.forAutocomplete = false;
522
523
if (std::optional<CheckResult> result = getCheckResult(name, true, frontendOptions.forAutocomplete))
524
return std::move(*result);
525
526
std::vector<ModuleName> buildQueue;
527
bool cycleDetected = parseGraph(buildQueue, name, makeTypeCheckLimits(frontendOptions), frontendOptions.forAutocomplete);
528
529
DenseHashSet<Luau::ModuleName> seen{{}};
530
std::vector<BuildQueueItem> buildQueueItems;
531
addBuildQueueItems(buildQueueItems, buildQueue, cycleDetected, seen, frontendOptions);
532
LUAU_ASSERT(!buildQueueItems.empty());
533
534
if (FFlag::DebugLuauLogSolverToJson)
535
{
536
LUAU_ASSERT(buildQueueItems.back().name == name);
537
buildQueueItems.back().recordJsonLog = true;
538
}
539
540
checkBuildQueueItems(buildQueueItems);
541
542
// Collect results only for checked modules, 'getCheckResult' produces a different result
543
CheckResult checkResult;
544
545
for (const BuildQueueItem& item : buildQueueItems)
546
{
547
if (item.module->timeout)
548
checkResult.timeoutHits.push_back(item.name);
549
550
// If check was manually cancelled, do not return partial results
551
if (item.module->cancelled)
552
return {};
553
554
checkResult.errors.insert(checkResult.errors.end(), item.module->errors.begin(), item.module->errors.end());
555
556
if (item.name == name)
557
checkResult.lintResult = item.module->lintResult;
558
}
559
560
return checkResult;
561
}
562
563
void Frontend::queueModuleCheck(const std::vector<ModuleName>& names)
564
{
565
moduleQueue.insert(moduleQueue.end(), names.begin(), names.end());
566
}
567
568
void Frontend::queueModuleCheck(const ModuleName& name)
569
{
570
moduleQueue.push_back(name);
571
}
572
573
std::vector<ModuleName> Frontend::checkQueuedModules(
574
std::optional<FrontendOptions> optionOverride,
575
std::function<void(std::vector<std::function<void()>> tasks)> executeTasks,
576
std::function<bool(size_t done, size_t total)> progress
577
)
578
{
579
FrontendOptions frontendOptions = optionOverride.value_or(options);
580
if (getLuauSolverMode() == SolverMode::New)
581
frontendOptions.forAutocomplete = false;
582
583
// By taking data into locals, we make sure queue is cleared at the end, even if an ICE or a different exception is thrown
584
std::vector<ModuleName> currModuleQueue;
585
std::swap(currModuleQueue, moduleQueue);
586
587
DenseHashSet<Luau::ModuleName> seen{{}};
588
589
std::shared_ptr<BuildQueueWorkState> state = std::make_shared<BuildQueueWorkState>();
590
591
for (const ModuleName& name : currModuleQueue)
592
{
593
if (seen.contains(name))
594
continue;
595
596
if (!isDirty(name, frontendOptions.forAutocomplete))
597
{
598
seen.insert(name);
599
continue;
600
}
601
602
std::vector<ModuleName> queue;
603
bool cycleDetected = parseGraph(
604
queue,
605
name,
606
makeTypeCheckLimits(frontendOptions),
607
frontendOptions.forAutocomplete,
608
[&seen](const ModuleName& name)
609
{
610
return seen.contains(name);
611
}
612
);
613
614
addBuildQueueItems(state->buildQueueItems, queue, cycleDetected, seen, frontendOptions);
615
}
616
617
if (state->buildQueueItems.empty())
618
return {};
619
620
// We need a mapping from modules to build queue slots
621
std::unordered_map<ModuleName, size_t> moduleNameToQueue;
622
623
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
624
{
625
BuildQueueItem& item = state->buildQueueItems[i];
626
moduleNameToQueue[item.name] = i;
627
}
628
629
// Default task execution is single-threaded and immediate
630
if (!executeTasks)
631
{
632
executeTasks = [](std::vector<std::function<void()>> tasks)
633
{
634
for (auto& task : tasks)
635
task();
636
};
637
}
638
639
state->executeTasks = executeTasks;
640
state->remaining = state->buildQueueItems.size();
641
642
// Record dependencies between modules
643
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
644
{
645
BuildQueueItem& item = state->buildQueueItems[i];
646
647
for (const ModuleName& dep : item.sourceNode->requireSet)
648
{
649
if (auto it = sourceNodes.find(dep); it != sourceNodes.end())
650
{
651
if (it->second->hasDirtyModule(frontendOptions.forAutocomplete))
652
{
653
item.dirtyDependencies++;
654
655
state->buildQueueItems[moduleNameToQueue[dep]].reverseDeps.push_back(i);
656
}
657
}
658
}
659
}
660
661
std::vector<size_t> nextItems;
662
663
// In the first pass, check all modules with no pending dependencies
664
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
665
{
666
if (state->buildQueueItems[i].dirtyDependencies == 0)
667
nextItems.push_back(i);
668
}
669
670
if (!nextItems.empty())
671
{
672
sendQueueItemTasks(state, nextItems);
673
nextItems.clear();
674
}
675
676
// If not a single item was found, a cycle in the graph was hit
677
if (state->processing == 0)
678
sendQueueCycleItemTask(state);
679
680
std::optional<size_t> itemWithException;
681
bool cancelled = false;
682
683
while (state->remaining != 0)
684
{
685
{
686
std::unique_lock guard(state->mtx);
687
688
// If nothing is ready yet, wait
689
state->cv.wait(
690
guard,
691
[state]
692
{
693
return !state->readyQueueItems.empty();
694
}
695
);
696
697
// Handle checked items
698
for (size_t i : state->readyQueueItems)
699
{
700
const BuildQueueItem& item = state->buildQueueItems[i];
701
702
// If exception was thrown, stop adding new items and wait for processing items to complete
703
if (item.exception)
704
itemWithException = i;
705
706
if (item.module && item.module->cancelled)
707
cancelled = true;
708
709
if (itemWithException || cancelled)
710
break;
711
712
recordItemResult(item);
713
714
// Notify items that were waiting for this dependency
715
for (size_t reverseDep : item.reverseDeps)
716
{
717
BuildQueueItem& reverseDepItem = state->buildQueueItems[reverseDep];
718
719
LUAU_ASSERT(reverseDepItem.dirtyDependencies != 0);
720
reverseDepItem.dirtyDependencies--;
721
722
// In case of a module cycle earlier, check if unlocked an item that was already processed
723
if (!reverseDepItem.processing && reverseDepItem.dirtyDependencies == 0)
724
nextItems.push_back(reverseDep);
725
}
726
}
727
728
LUAU_ASSERT(state->processing >= state->readyQueueItems.size());
729
state->processing -= state->readyQueueItems.size();
730
731
LUAU_ASSERT(state->remaining >= state->readyQueueItems.size());
732
state->remaining -= state->readyQueueItems.size();
733
state->readyQueueItems.clear();
734
}
735
736
if (progress)
737
{
738
if (!progress(state->buildQueueItems.size() - state->remaining, state->buildQueueItems.size()))
739
cancelled = true;
740
}
741
742
// Items cannot be submitted while holding the lock
743
if (!nextItems.empty())
744
{
745
sendQueueItemTasks(state, nextItems);
746
nextItems.clear();
747
}
748
749
if (state->processing == 0)
750
{
751
// Typechecking might have been cancelled by user, don't return partial results
752
if (cancelled)
753
return {};
754
755
// We might have stopped because of a pending exception
756
if (itemWithException)
757
recordItemResult(state->buildQueueItems[*itemWithException]);
758
}
759
760
// If we aren't done, but don't have anything processing, we hit a cycle
761
if (state->remaining != 0 && state->processing == 0)
762
sendQueueCycleItemTask(state);
763
}
764
765
std::vector<ModuleName> checkedModules;
766
checkedModules.reserve(state->buildQueueItems.size());
767
768
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
769
checkedModules.push_back(std::move(state->buildQueueItems[i].name));
770
771
return checkedModules;
772
}
773
774
std::optional<CheckResult> Frontend::getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete)
775
{
776
if (getLuauSolverMode() == SolverMode::New)
777
forAutocomplete = false;
778
779
auto it = sourceNodes.find(name);
780
781
if (it == sourceNodes.end() || it->second->hasDirtyModule(forAutocomplete))
782
return std::nullopt;
783
784
auto& resolver = forAutocomplete ? moduleResolverForAutocomplete : moduleResolver;
785
786
ModulePtr module = resolver.getModule(name);
787
788
if (module == nullptr)
789
throw InternalCompilerError("Frontend does not have module: " + name, name);
790
791
CheckResult checkResult;
792
793
if (module->timeout)
794
checkResult.timeoutHits.push_back(name);
795
796
if (accumulateNested)
797
checkResult.errors = accumulateErrors(sourceNodes, resolver, name);
798
else
799
checkResult.errors.insert(checkResult.errors.end(), module->errors.begin(), module->errors.end());
800
801
// Get lint result only for top checked module
802
checkResult.lintResult = module->lintResult;
803
804
return checkResult;
805
}
806
807
std::vector<ModuleName> Frontend::getRequiredScripts(const ModuleName& name, const TypeCheckLimits& limits)
808
{
809
RequireTraceResult require = requireTrace[name];
810
if (isDirty(name))
811
{
812
std::optional<SourceCode> source = fileResolver->readSource(name);
813
if (!source)
814
{
815
return {};
816
}
817
const Config& config = configResolver->getConfig(name, limits);
818
ParseOptions opts = config.parseOptions;
819
opts.captureComments = true;
820
SourceModule result = parse(name, source->source, opts);
821
result.type = source->type;
822
require = traceRequires(fileResolver, result.root, name, limits);
823
}
824
std::vector<std::string> requiredModuleNames;
825
requiredModuleNames.reserve(require.requireList.size());
826
for (const auto& [moduleName, _] : require.requireList)
827
{
828
requiredModuleNames.push_back(moduleName);
829
}
830
return requiredModuleNames;
831
}
832
833
bool Frontend::parseGraph(
834
std::vector<ModuleName>& buildQueue,
835
const ModuleName& root,
836
const TypeCheckLimits& limits,
837
bool forAutocomplete,
838
std::function<bool(const ModuleName&)> canSkip
839
)
840
{
841
LUAU_TIMETRACE_SCOPE("Frontend::parseGraph", "Frontend");
842
LUAU_TIMETRACE_ARGUMENT("root", root.c_str());
843
844
// https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
845
enum Mark
846
{
847
None,
848
Temporary,
849
Permanent
850
};
851
852
DenseHashMap<SourceNode*, Mark> seen(nullptr);
853
std::vector<SourceNode*> stack;
854
std::vector<SourceNode*> path;
855
bool cyclic = false;
856
857
{
858
auto [sourceNode, _] = getSourceNode(root, limits);
859
if (sourceNode)
860
stack.push_back(sourceNode);
861
}
862
863
while (!stack.empty())
864
{
865
SourceNode* top = stack.back();
866
stack.pop_back();
867
868
if (top == nullptr)
869
{
870
// special marker for post-order processing
871
LUAU_ASSERT(!path.empty());
872
873
top = path.back();
874
path.pop_back();
875
876
// note: topseen ref gets invalidated in any seen[] access, beware - only one seen[] access per iteration!
877
Mark& topseen = seen[top];
878
LUAU_ASSERT(topseen == Temporary);
879
topseen = Permanent;
880
881
buildQueue.push_back(top->name);
882
883
// at this point we know all valid dependencies are processed into SourceNodes
884
for (const ModuleName& dep : top->requireSet)
885
{
886
if (auto it = sourceNodes.find(dep); it != sourceNodes.end())
887
it->second->dependents.insert(top->name);
888
}
889
}
890
else
891
{
892
// note: topseen ref gets invalidated in any seen[] access, beware - only one seen[] access per iteration!
893
Mark& topseen = seen[top];
894
895
if (topseen != None)
896
{
897
cyclic |= topseen == Temporary;
898
continue;
899
}
900
901
topseen = Temporary;
902
903
// push marker for post-order processing
904
stack.push_back(nullptr);
905
path.push_back(top);
906
907
// push children
908
for (const ModuleName& dep : top->requireSet)
909
{
910
auto it = sourceNodes.find(dep);
911
if (it != sourceNodes.end())
912
{
913
// this is a critical optimization: we do *not* traverse non-dirty subtrees.
914
// this relies on the fact that markDirty marks reverse-dependencies dirty as well
915
// thus if a node is not dirty, all its transitive deps aren't dirty, which means that they won't ever need
916
// to be built, *and* can't form a cycle with any nodes we did process.
917
if (!it->second->hasDirtyModule(forAutocomplete))
918
continue;
919
920
// This module might already be in the outside build queue
921
if (canSkip && canSkip(dep))
922
continue;
923
924
// note: this check is technically redundant *except* that getSourceNode has somewhat broken memoization
925
// calling getSourceNode twice in succession will reparse the file, since getSourceNode leaves dirty flag set
926
if (seen.contains(it->second.get()))
927
{
928
stack.push_back(it->second.get());
929
continue;
930
}
931
}
932
933
auto [sourceNode, _] = getSourceNode(dep, limits);
934
if (sourceNode)
935
{
936
stack.push_back(sourceNode);
937
938
// note: this assignment is paired with .contains() check above and effectively deduplicates getSourceNode()
939
seen[sourceNode] = None;
940
}
941
}
942
}
943
}
944
945
return cyclic;
946
}
947
948
void Frontend::addBuildQueueItems(
949
std::vector<BuildQueueItem>& items,
950
std::vector<ModuleName>& buildQueue,
951
bool cycleDetected,
952
DenseHashSet<Luau::ModuleName>& seen,
953
const FrontendOptions& frontendOptions
954
)
955
{
956
for (const ModuleName& moduleName : buildQueue)
957
{
958
if (seen.contains(moduleName))
959
continue;
960
seen.insert(moduleName);
961
962
LUAU_ASSERT(sourceNodes.count(moduleName));
963
std::shared_ptr<SourceNode>& sourceNode = sourceNodes[moduleName];
964
965
if (!sourceNode->hasDirtyModule(frontendOptions.forAutocomplete))
966
continue;
967
968
LUAU_ASSERT(sourceModules.count(moduleName));
969
std::shared_ptr<SourceModule>& sourceModule = sourceModules[moduleName];
970
971
BuildQueueItem data{moduleName, fileResolver->getHumanReadableModuleName(moduleName), sourceNode, sourceModule};
972
973
data.config = configResolver->getConfig(moduleName, makeTypeCheckLimits(frontendOptions));
974
data.environmentScope = getModuleEnvironment(*sourceModule, data.config, frontendOptions.forAutocomplete);
975
data.recordJsonLog = FFlag::DebugLuauLogSolverToJson;
976
977
// in the future we could replace toposort with an algorithm that can flag cyclic nodes by itself
978
// however, for now getRequireCycles isn't expensive in practice on the cases we care about, and long term
979
// all correct programs must be acyclic so this code triggers rarely
980
if (cycleDetected)
981
data.requireCycles = getRequireCycles(fileResolver, sourceNodes, sourceNode.get());
982
983
data.options = frontendOptions;
984
985
// This is used by the type checker to replace the resulting type of cyclic modules with any
986
sourceModule->cyclic = !data.requireCycles.empty();
987
988
items.push_back(std::move(data));
989
}
990
}
991
992
static void applyInternalLimitScaling(SourceNode& sourceNode, const ModulePtr module, double limit)
993
{
994
if (module->timeout)
995
sourceNode.autocompleteLimitsMult = sourceNode.autocompleteLimitsMult / 2.0;
996
else if (module->checkDurationSec < limit / 2.0)
997
sourceNode.autocompleteLimitsMult = std::min(sourceNode.autocompleteLimitsMult * 2.0, 1.0);
998
}
999
1000
void Frontend::checkBuildQueueItem(BuildQueueItem& item)
1001
{
1002
SourceNode& sourceNode = *item.sourceNode;
1003
const SourceModule& sourceModule = *item.sourceModule;
1004
const Config& config = item.config;
1005
Mode mode;
1006
if (FFlag::DebugLuauForceStrictMode)
1007
mode = Mode::Strict;
1008
else if (FFlag::DebugLuauForceNonStrictMode)
1009
mode = Mode::Nonstrict;
1010
else
1011
mode = sourceModule.mode.value_or(config.mode);
1012
1013
item.sourceModule->mode = {mode};
1014
ScopePtr environmentScope = item.environmentScope;
1015
double timestamp = getTimestamp();
1016
const std::vector<RequireCycle>& requireCycles = item.requireCycles;
1017
1018
TypeCheckLimits typeCheckLimits = makeTypeCheckLimits(item.options);
1019
1020
// TODO: This is a dirty ad hoc solution for autocomplete timeouts
1021
// We are trying to dynamically adjust our existing limits to lower total typechecking time under the limit
1022
// so that we'll have type information for the whole file at lower quality instead of a full abort in the middle
1023
if (item.options.applyInternalLimitScaling)
1024
{
1025
if (FInt::LuauTarjanChildLimit > 0)
1026
typeCheckLimits.instantiationChildLimit = std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult));
1027
else
1028
typeCheckLimits.instantiationChildLimit = std::nullopt;
1029
1030
if (FInt::LuauTypeInferIterationLimit > 0)
1031
typeCheckLimits.unifierIterationLimit = std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult));
1032
else
1033
typeCheckLimits.unifierIterationLimit = std::nullopt;
1034
}
1035
1036
if (item.options.forAutocomplete)
1037
{
1038
// The autocomplete typecheck is always in strict mode with DM awareness to provide better type information for IDE features
1039
ModulePtr moduleForAutocomplete = check(
1040
sourceModule,
1041
Mode::Strict,
1042
requireCycles,
1043
environmentScope,
1044
/*forAutocomplete*/ true,
1045
/*recordJsonLog*/ false,
1046
item.stats,
1047
std::move(typeCheckLimits)
1048
);
1049
1050
double duration = getTimestamp() - timestamp;
1051
1052
moduleForAutocomplete->checkDurationSec = duration;
1053
1054
if (item.options.moduleTimeLimitSec && item.options.applyInternalLimitScaling)
1055
applyInternalLimitScaling(sourceNode, moduleForAutocomplete, *item.options.moduleTimeLimitSec);
1056
1057
item.stats.timeCheck += duration;
1058
item.stats.filesStrict += 1;
1059
1060
if (item.options.collectTypeAllocationStats)
1061
{
1062
item.stats.typesAllocated += moduleForAutocomplete->internalTypes.types.size();
1063
item.stats.typePacksAllocated += moduleForAutocomplete->internalTypes.typePacks.size();
1064
item.stats.boolSingletonsMinted += moduleForAutocomplete->internalTypes.boolSingletonsMinted;
1065
item.stats.strSingletonsMinted += moduleForAutocomplete->internalTypes.strSingletonsMinted;
1066
item.stats.uniqueStrSingletonsMinted += moduleForAutocomplete->internalTypes.uniqueStrSingletonsMinted.size();
1067
}
1068
1069
if (item.options.customModuleCheck)
1070
item.options.customModuleCheck(sourceModule, *moduleForAutocomplete);
1071
1072
item.module = moduleForAutocomplete;
1073
return;
1074
}
1075
1076
ModulePtr module = check(
1077
sourceModule, mode, requireCycles, environmentScope, /*forAutocomplete*/ false, item.recordJsonLog, item.stats, std::move(typeCheckLimits)
1078
);
1079
1080
double duration = getTimestamp() - timestamp;
1081
1082
module->checkDurationSec = duration;
1083
1084
if (item.options.moduleTimeLimitSec && item.options.applyInternalLimitScaling)
1085
applyInternalLimitScaling(sourceNode, module, *item.options.moduleTimeLimitSec);
1086
1087
item.stats.timeCheck += duration;
1088
item.stats.filesStrict += (mode == Mode::Strict) ? 1 : 0;
1089
item.stats.filesNonstrict += (mode == Mode::Nonstrict) ? 1 : 0;
1090
1091
if (item.options.collectTypeAllocationStats)
1092
{
1093
item.stats.typesAllocated += module->internalTypes.types.size();
1094
item.stats.typePacksAllocated += module->internalTypes.typePacks.size();
1095
item.stats.boolSingletonsMinted += module->internalTypes.boolSingletonsMinted;
1096
item.stats.strSingletonsMinted += module->internalTypes.strSingletonsMinted;
1097
item.stats.uniqueStrSingletonsMinted += module->internalTypes.uniqueStrSingletonsMinted.size();
1098
}
1099
1100
if (item.options.customModuleCheck)
1101
item.options.customModuleCheck(sourceModule, *module);
1102
1103
if ((getLuauSolverMode() == SolverMode::New) && mode == Mode::NoCheck)
1104
module->errors.clear();
1105
1106
if (item.options.runLintChecks)
1107
{
1108
LUAU_TIMETRACE_SCOPE("lint", "Frontend");
1109
1110
LintOptions lintOptions = item.options.enabledLintWarnings.value_or(config.enabledLint);
1111
filterLintOptions(lintOptions, sourceModule.hotcomments, mode);
1112
1113
double timestamp = getTimestamp();
1114
1115
std::vector<LintWarning> warnings =
1116
Luau::lint(sourceModule.root, *sourceModule.names, environmentScope, module.get(), sourceModule.hotcomments, lintOptions);
1117
1118
item.stats.timeLint += getTimestamp() - timestamp;
1119
1120
module->lintResult = classifyLints(warnings, config);
1121
}
1122
1123
if (!item.options.retainFullTypeGraphs)
1124
{
1125
// copyErrors needs to allocate into interfaceTypes as it copies
1126
// types out of internalTypes, so we unfreeze it here.
1127
unfreeze(module->interfaceTypes);
1128
copyErrors(module->errors, module->interfaceTypes, builtinTypes);
1129
freeze(module->interfaceTypes);
1130
1131
module->internalTypes.clear();
1132
module->defArena.allocator.clear();
1133
module->keyArena.allocator.clear();
1134
1135
module->astTypes.clear();
1136
module->astTypePacks.clear();
1137
module->astExpectedTypes.clear();
1138
module->astOriginalCallTypes.clear();
1139
module->astOverloadResolvedTypes.clear();
1140
module->astForInNextTypes.clear();
1141
module->astResolvedTypes.clear();
1142
module->astResolvedTypePacks.clear();
1143
module->astCompoundAssignResultTypes.clear();
1144
module->astScopes.clear();
1145
module->upperBoundContributors.clear();
1146
module->scopes.clear();
1147
}
1148
1149
if (mode != Mode::NoCheck)
1150
{
1151
for (const RequireCycle& cyc : requireCycles)
1152
{
1153
TypeError te{cyc.location, item.name, ModuleHasCyclicDependency{cyc.path}};
1154
1155
module->errors.push_back(te);
1156
}
1157
}
1158
1159
ErrorVec parseErrors;
1160
1161
for (const ParseError& pe : sourceModule.parseErrors)
1162
parseErrors.emplace_back(pe.getLocation(), item.name, SyntaxError{pe.what()});
1163
module->errors.insert(module->errors.begin(), parseErrors.begin(), parseErrors.end());
1164
1165
item.module = module;
1166
}
1167
1168
void Frontend::checkBuildQueueItems(std::vector<BuildQueueItem>& items)
1169
{
1170
for (BuildQueueItem& item : items)
1171
{
1172
checkBuildQueueItem(item);
1173
1174
if (item.module && item.module->cancelled)
1175
break;
1176
1177
recordItemResult(item);
1178
}
1179
}
1180
1181
void Frontend::recordItemResult(const BuildQueueItem& item)
1182
{
1183
if (item.exception)
1184
std::rethrow_exception(item.exception);
1185
1186
bool replacedModule = false;
1187
if (item.options.forAutocomplete)
1188
{
1189
replacedModule = moduleResolverForAutocomplete.setModule(item.name, item.module);
1190
item.sourceNode->dirtyModuleForAutocomplete = false;
1191
}
1192
else
1193
{
1194
replacedModule = moduleResolver.setModule(item.name, item.module);
1195
item.sourceNode->dirtyModule = false;
1196
}
1197
1198
if (replacedModule)
1199
{
1200
LUAU_TIMETRACE_SCOPE("Frontend::invalidateDependentModules", "Frontend");
1201
LUAU_TIMETRACE_ARGUMENT("name", item.name.c_str());
1202
traverseDependents(
1203
item.name,
1204
[forAutocomplete = item.options.forAutocomplete](SourceNode& sourceNode)
1205
{
1206
bool traverseSubtree = !sourceNode.hasInvalidModuleDependency(forAutocomplete);
1207
sourceNode.setInvalidModuleDependency(true, forAutocomplete);
1208
return traverseSubtree;
1209
}
1210
);
1211
}
1212
1213
item.sourceNode->setInvalidModuleDependency(false, item.options.forAutocomplete);
1214
1215
stats.timeCheck += item.stats.timeCheck;
1216
stats.timeLint += item.stats.timeLint;
1217
1218
stats.filesStrict += item.stats.filesStrict;
1219
stats.filesNonstrict += item.stats.filesNonstrict;
1220
1221
if (item.options.collectTypeAllocationStats)
1222
{
1223
stats.typesAllocated += item.stats.typesAllocated;
1224
stats.typePacksAllocated += item.stats.typePacksAllocated;
1225
1226
stats.boolSingletonsMinted += item.stats.boolSingletonsMinted;
1227
stats.strSingletonsMinted += item.stats.strSingletonsMinted;
1228
stats.uniqueStrSingletonsMinted += item.stats.uniqueStrSingletonsMinted;
1229
}
1230
1231
stats.dynamicConstraintsCreated += item.stats.dynamicConstraintsCreated;
1232
}
1233
1234
void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos)
1235
{
1236
BuildQueueItem& item = state->buildQueueItems[itemPos];
1237
1238
try
1239
{
1240
checkBuildQueueItem(item);
1241
}
1242
catch (const Luau::InternalCompilerError&)
1243
{
1244
item.exception = std::current_exception();
1245
}
1246
1247
{
1248
std::unique_lock guard(state->mtx);
1249
state->readyQueueItems.push_back(itemPos);
1250
}
1251
1252
state->cv.notify_one();
1253
}
1254
1255
void Frontend::sendQueueItemTasks(std::shared_ptr<BuildQueueWorkState> state, const std::vector<size_t>& items)
1256
{
1257
std::vector<std::function<void()>> tasks;
1258
tasks.reserve(items.size());
1259
1260
for (size_t itemPos : items)
1261
{
1262
BuildQueueItem& item = state->buildQueueItems[itemPos];
1263
1264
LUAU_ASSERT(!item.processing);
1265
item.processing = true;
1266
1267
tasks.emplace_back(
1268
[this, state, itemPos]()
1269
{
1270
performQueueItemTask(state, itemPos);
1271
}
1272
);
1273
}
1274
1275
state->processing += items.size();
1276
state->executeTasks(std::move(tasks));
1277
}
1278
1279
void Frontend::sendQueueCycleItemTask(std::shared_ptr<BuildQueueWorkState> state)
1280
{
1281
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
1282
{
1283
BuildQueueItem& item = state->buildQueueItems[i];
1284
1285
if (!item.processing)
1286
{
1287
sendQueueItemTasks(std::move(state), {i});
1288
break;
1289
}
1290
}
1291
}
1292
1293
ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const
1294
{
1295
ScopePtr result;
1296
if (forAutocomplete)
1297
result = globalsForAutocomplete.globalScope;
1298
else
1299
result = globals.globalScope;
1300
1301
if (module.environmentName)
1302
result = getEnvironmentScope(*module.environmentName);
1303
1304
if (!config.globals.empty())
1305
{
1306
result = std::make_shared<Scope>(result);
1307
1308
for (const std::string& global : config.globals)
1309
{
1310
AstName name = module.names->get(global.c_str());
1311
1312
if (name.value)
1313
result->bindings[name].typeId = builtinTypes->anyType;
1314
}
1315
}
1316
1317
return result;
1318
}
1319
1320
bool Frontend::allModuleDependenciesValid(const ModuleName& name, bool forAutocomplete) const
1321
{
1322
auto it = sourceNodes.find(name);
1323
return it != sourceNodes.end() && !it->second->hasInvalidModuleDependency(forAutocomplete);
1324
}
1325
1326
bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
1327
{
1328
auto it = sourceNodes.find(name);
1329
return it == sourceNodes.end() || it->second->hasDirtyModule(forAutocomplete);
1330
}
1331
1332
/*
1333
* Mark a file as requiring rechecking before its type information can be safely used again.
1334
*
1335
* I am not particularly pleased with the way each dirty() operation involves a BFS on reverse dependencies.
1336
* It would be nice for this function to be O(1)
1337
*/
1338
void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty)
1339
{
1340
LUAU_TIMETRACE_SCOPE("Frontend::markDirty", "Frontend");
1341
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
1342
1343
traverseDependents(
1344
name,
1345
[markedDirty](SourceNode& sourceNode)
1346
{
1347
if (markedDirty)
1348
markedDirty->push_back(sourceNode.name);
1349
1350
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
1351
return false;
1352
1353
sourceNode.dirtySourceModule = true;
1354
sourceNode.dirtyModule = true;
1355
sourceNode.dirtyModuleForAutocomplete = true;
1356
1357
return true;
1358
}
1359
);
1360
}
1361
1362
void Frontend::traverseDependents(const ModuleName& name, std::function<bool(SourceNode&)> processSubtree)
1363
{
1364
LUAU_TIMETRACE_SCOPE("Frontend::traverseDependents", "Frontend");
1365
1366
if (sourceNodes.count(name) == 0)
1367
return;
1368
1369
std::vector<ModuleName> queue{name};
1370
1371
while (!queue.empty())
1372
{
1373
ModuleName next = std::move(queue.back());
1374
queue.pop_back();
1375
1376
LUAU_ASSERT(sourceNodes.count(next) > 0);
1377
SourceNode& sourceNode = *sourceNodes[next];
1378
1379
if (!processSubtree(sourceNode))
1380
continue;
1381
1382
const Set<ModuleName>& dependents = sourceNode.dependents;
1383
queue.insert(queue.end(), dependents.begin(), dependents.end());
1384
}
1385
}
1386
1387
SourceModule* Frontend::getSourceModule(const ModuleName& moduleName)
1388
{
1389
auto it = sourceModules.find(moduleName);
1390
if (it != sourceModules.end())
1391
return it->second.get();
1392
else
1393
return nullptr;
1394
}
1395
1396
const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) const
1397
{
1398
return const_cast<Frontend*>(this)->getSourceModule(moduleName); // NOLINT(cppcoreguidelines-pro-type-const-cast)
1399
}
1400
1401
struct InternalTypeFinder : TypeOnceVisitor
1402
{
1403
InternalTypeFinder()
1404
: TypeOnceVisitor("InternalTypeFinder", /* skipBoundTypes */ true)
1405
{
1406
}
1407
1408
bool visit(TypeId, const ExternType&) override
1409
{
1410
return false;
1411
}
1412
1413
bool visit(TypeId, const BlockedType&) override
1414
{
1415
LUAU_ASSERT(false);
1416
return false;
1417
}
1418
1419
bool visit(TypeId, const FreeType&) override
1420
{
1421
LUAU_ASSERT(false);
1422
return false;
1423
}
1424
1425
bool visit(TypeId, const PendingExpansionType&) override
1426
{
1427
LUAU_ASSERT(false);
1428
return false;
1429
}
1430
1431
bool visit(TypePackId, const BlockedTypePack&) override
1432
{
1433
LUAU_ASSERT(false);
1434
return false;
1435
}
1436
1437
bool visit(TypePackId, const FreeTypePack&) override
1438
{
1439
LUAU_ASSERT(false);
1440
return false;
1441
}
1442
1443
bool visit(TypePackId, const TypeFunctionInstanceTypePack&) override
1444
{
1445
LUAU_ASSERT(false);
1446
return false;
1447
}
1448
};
1449
1450
ModulePtr check(
1451
const SourceModule& sourceModule,
1452
Mode mode,
1453
const std::vector<RequireCycle>& requireCycles,
1454
NotNull<BuiltinTypes> builtinTypes,
1455
NotNull<InternalErrorReporter> iceHandler,
1456
NotNull<ModuleResolver> moduleResolver,
1457
NotNull<FileResolver> fileResolver,
1458
const ScopePtr& parentScope,
1459
const ScopePtr& typeFunctionScope,
1460
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
1461
FrontendOptions options,
1462
TypeCheckLimits limits,
1463
bool recordJsonLog,
1464
Frontend::Stats& stats,
1465
std::function<void(const ModuleName&, std::string)> writeJsonLog
1466
)
1467
{
1468
LUAU_TIMETRACE_SCOPE("Frontend::check", "Typechecking");
1469
LUAU_TIMETRACE_ARGUMENT("module", sourceModule.name.c_str());
1470
LUAU_TIMETRACE_ARGUMENT("name", sourceModule.humanReadableName.c_str());
1471
1472
ModulePtr module = std::make_shared<Module>();
1473
module->checkedInNewSolver = true;
1474
module->name = sourceModule.name;
1475
module->humanReadableName = sourceModule.humanReadableName;
1476
module->mode = mode;
1477
module->internalTypes.owningModule = module.get();
1478
module->interfaceTypes.owningModule = module.get();
1479
module->internalTypes.collectSingletonStats = options.collectTypeAllocationStats;
1480
module->allocator = sourceModule.allocator;
1481
module->names = sourceModule.names;
1482
module->root = sourceModule.root;
1483
1484
iceHandler->moduleName = sourceModule.name;
1485
1486
std::unique_ptr<DcrLogger> logger;
1487
if (recordJsonLog)
1488
{
1489
logger = std::make_unique<DcrLogger>();
1490
std::optional<SourceCode> source = fileResolver->readSource(module->name);
1491
if (source)
1492
{
1493
logger->captureSource(source->source);
1494
}
1495
}
1496
1497
DataFlowGraph dfg = DataFlowGraphBuilder::build(sourceModule.root, NotNull{&module->defArena}, NotNull{&module->keyArena}, iceHandler);
1498
1499
UnifierSharedState unifierState{iceHandler};
1500
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
1501
unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit);
1502
1503
Normalizer normalizer{&module->internalTypes, builtinTypes, NotNull{&unifierState}, SolverMode::New};
1504
TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}};
1505
1506
typeFunctionRuntime.allowEvaluation = true;
1507
1508
ConstraintGenerator cg{
1509
module,
1510
NotNull{&normalizer},
1511
NotNull{&typeFunctionRuntime},
1512
moduleResolver,
1513
builtinTypes,
1514
iceHandler,
1515
parentScope,
1516
typeFunctionScope,
1517
std::move(prepareModuleScope),
1518
logger.get(),
1519
NotNull{&dfg},
1520
requireCycles
1521
};
1522
1523
ConstraintSet constraintSet = cg.run(sourceModule.root);
1524
module->errors = std::move(constraintSet.errors);
1525
module->constraintGenerationDidNotComplete = cg.recursionLimitMet;
1526
1527
ConstraintSolver cs{
1528
NotNull{&normalizer},
1529
NotNull{&typeFunctionRuntime},
1530
module,
1531
moduleResolver,
1532
requireCycles,
1533
logger.get(),
1534
NotNull{&dfg},
1535
limits,
1536
std::move(constraintSet)
1537
};
1538
1539
if (options.randomizeConstraintResolutionSeed)
1540
cs.randomize(*options.randomizeConstraintResolutionSeed);
1541
1542
try
1543
{
1544
cs.run();
1545
}
1546
catch (const TimeLimitError&)
1547
{
1548
module->timeout = true;
1549
}
1550
catch (const UserCancelError&)
1551
{
1552
module->cancelled = true;
1553
}
1554
1555
stats.dynamicConstraintsCreated += cs.solverConstraints.size();
1556
1557
if (recordJsonLog)
1558
{
1559
std::string output = logger->compileOutput();
1560
if (FFlag::DebugLuauLogSolverToJsonFile && writeJsonLog)
1561
writeJsonLog(sourceModule.name, std::move(output));
1562
else
1563
printf("%s\n", output.c_str());
1564
}
1565
1566
for (TypeError& e : cs.errors)
1567
module->errors.emplace_back(std::move(e));
1568
1569
module->scopes = std::move(cg.scopes);
1570
module->type = sourceModule.type;
1571
module->upperBoundContributors = std::move(cs.upperBoundContributors);
1572
1573
if (module->timeout || module->cancelled)
1574
{
1575
// If solver was interrupted, skip typechecking and replace all module results with error-supressing types to avoid leaking blocked/pending
1576
// types
1577
ScopePtr moduleScope = module->getModuleScope();
1578
moduleScope->returnType = builtinTypes->errorTypePack;
1579
1580
for (auto& [name, ty] : module->declaredGlobals)
1581
ty = builtinTypes->errorType;
1582
1583
for (auto& [name, tf] : module->exportedTypeBindings)
1584
tf.type = builtinTypes->errorType;
1585
}
1586
else
1587
{
1588
try
1589
{
1590
switch (mode)
1591
{
1592
case Mode::Nonstrict:
1593
Luau::checkNonStrict(
1594
builtinTypes,
1595
NotNull{&typeFunctionRuntime},
1596
iceHandler,
1597
NotNull{&unifierState},
1598
NotNull{&dfg},
1599
NotNull{&limits},
1600
sourceModule,
1601
module.get()
1602
);
1603
break;
1604
case Mode::Definition:
1605
// fallthrough intentional
1606
case Mode::Strict:
1607
Luau::check(
1608
builtinTypes, NotNull{&typeFunctionRuntime}, NotNull{&unifierState}, NotNull{&limits}, logger.get(), sourceModule, module.get()
1609
);
1610
break;
1611
case Mode::NoCheck:
1612
break;
1613
};
1614
}
1615
catch (const TimeLimitError&)
1616
{
1617
module->timeout = true;
1618
}
1619
catch (const UserCancelError&)
1620
{
1621
module->cancelled = true;
1622
}
1623
}
1624
1625
// if the only error we're producing is one about constraint solving being incomplete, we can silence it.
1626
// this means we won't give this warning if types seem totally nonsensical, but there are no other errors.
1627
// this is probably, on the whole, a good decision to not annoy users though.
1628
if (module->errors.size() == 1 && get<ConstraintSolvingIncompleteError>(module->errors[0]) &&
1629
!FFlag::DebugLuauAlwaysShowConstraintSolvingIncomplete)
1630
module->errors.clear();
1631
1632
if (FFlag::LuauOverloadGetsInstantiated)
1633
{
1634
ExpectedTypeVisitor etv{
1635
NotNull{&module->astTypes},
1636
NotNull{&module->astExpectedTypes},
1637
NotNull{&module->astResolvedTypes},
1638
NotNull{&module->astOverloadResolvedTypes},
1639
NotNull{&module->internalTypes},
1640
builtinTypes,
1641
NotNull{parentScope.get()}
1642
};
1643
sourceModule.root->visit(&etv);
1644
}
1645
else
1646
{
1647
1648
ExpectedTypeVisitor etv{
1649
NotNull{&module->astTypes},
1650
NotNull{&module->astExpectedTypes},
1651
NotNull{&module->astResolvedTypes},
1652
NotNull{&module->internalTypes},
1653
builtinTypes,
1654
NotNull{parentScope.get()}
1655
};
1656
sourceModule.root->visit(&etv);
1657
}
1658
1659
// NOTE: This used to be done prior to cloning the public interface, but
1660
// we now replace "internal" types with `*error-type*`.
1661
if (FFlag::DebugLuauForbidInternalTypes)
1662
{
1663
InternalTypeFinder finder;
1664
1665
// `result->returnType` is not filled in yet, so we
1666
// traverse the return type of the root module.
1667
finder.traverse(module->getModuleScope()->returnType);
1668
1669
for (const auto& [_, binding] : module->exportedTypeBindings)
1670
finder.traverse(binding.type);
1671
1672
for (const auto& [_, ty] : module->astTypes)
1673
finder.traverse(ty);
1674
1675
for (const auto& [_, ty] : module->astExpectedTypes)
1676
finder.traverse(ty);
1677
1678
for (const auto& [_, tp] : module->astTypePacks)
1679
finder.traverse(tp);
1680
1681
for (const auto& [_, ty] : module->astResolvedTypes)
1682
finder.traverse(ty);
1683
1684
for (const auto& [_, ty] : module->astOverloadResolvedTypes)
1685
finder.traverse(ty);
1686
1687
for (const auto& [_, tp] : module->astResolvedTypePacks)
1688
finder.traverse(tp);
1689
}
1690
1691
1692
unfreeze(module->interfaceTypes);
1693
module->clonePublicInterface(builtinTypes, *iceHandler, SolverMode::New);
1694
1695
// It would be nice if we could freeze the arenas before doing type
1696
// checking, but we'll have to do some work to get there.
1697
//
1698
// TypeChecker2 sometimes needs to allocate TypePacks via extendTypePack()
1699
// in order to do its thing. We can rework that code to instead allocate
1700
// into a temporary arena as long as we can prove that the allocated types
1701
// and packs can never find their way into an error.
1702
//
1703
// Notably, we would first need to get to a place where TypeChecker2 is
1704
// never in the position of dealing with a FreeType. They should all be
1705
// bound to something by the time constraints are solved.
1706
freeze(module->internalTypes);
1707
freeze(module->interfaceTypes);
1708
1709
return module;
1710
}
1711
1712
ModulePtr Frontend::check(
1713
const SourceModule& sourceModule,
1714
Mode mode,
1715
std::vector<RequireCycle> requireCycles,
1716
std::optional<ScopePtr> environmentScope,
1717
bool forAutocomplete,
1718
bool recordJsonLog,
1719
Frontend::Stats& stats,
1720
TypeCheckLimits typeCheckLimits
1721
)
1722
{
1723
if (getLuauSolverMode() == SolverMode::New)
1724
{
1725
auto prepareModuleScopeWrap = [this, forAutocomplete](const ModuleName& name, const ScopePtr& scope)
1726
{
1727
if (prepareModuleScope)
1728
prepareModuleScope(name, scope, forAutocomplete);
1729
};
1730
1731
try
1732
{
1733
return Luau::check(
1734
sourceModule,
1735
mode,
1736
requireCycles,
1737
builtinTypes,
1738
NotNull{&iceHandler},
1739
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver},
1740
NotNull{fileResolver},
1741
environmentScope ? *environmentScope : globals.globalScope,
1742
globals.globalTypeFunctionScope,
1743
prepareModuleScopeWrap,
1744
options,
1745
std::move(typeCheckLimits),
1746
recordJsonLog,
1747
stats,
1748
writeJsonLog
1749
);
1750
}
1751
catch (const InternalCompilerError& err)
1752
{
1753
InternalCompilerError augmented = err.location.has_value() ? InternalCompilerError{err.message, sourceModule.name, *err.location}
1754
: InternalCompilerError{err.message, sourceModule.name};
1755
throw augmented;
1756
}
1757
}
1758
else
1759
{
1760
TypeChecker typeChecker(
1761
forAutocomplete ? globalsForAutocomplete.globalScope : globals.globalScope,
1762
forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver,
1763
builtinTypes,
1764
&iceHandler
1765
);
1766
1767
if (prepareModuleScope)
1768
{
1769
typeChecker.prepareModuleScope = [this, forAutocomplete](const ModuleName& name, const ScopePtr& scope)
1770
{
1771
prepareModuleScope(name, scope, forAutocomplete);
1772
};
1773
}
1774
1775
typeChecker.requireCycles = requireCycles;
1776
typeChecker.finishTime = typeCheckLimits.finishTime;
1777
typeChecker.instantiationChildLimit = typeCheckLimits.instantiationChildLimit;
1778
typeChecker.unifierIterationLimit = typeCheckLimits.unifierIterationLimit;
1779
typeChecker.cancellationToken = typeCheckLimits.cancellationToken;
1780
1781
return typeChecker.check(sourceModule, mode, std::move(environmentScope));
1782
}
1783
}
1784
1785
// Read AST into sourceModules if necessary. Trace require()s. Report parse errors.
1786
std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(const ModuleName& name, const TypeCheckLimits& limits)
1787
{
1788
auto it = sourceNodes.find(name);
1789
if (it != sourceNodes.end() && !it->second->hasDirtySourceModule())
1790
{
1791
auto moduleIt = sourceModules.find(name);
1792
if (moduleIt != sourceModules.end())
1793
return {it->second.get(), moduleIt->second.get()};
1794
else
1795
{
1796
LUAU_ASSERT(!"Everything in sourceNodes should also be in sourceModules");
1797
return {it->second.get(), nullptr};
1798
}
1799
}
1800
1801
LUAU_TIMETRACE_SCOPE("Frontend::getSourceNode", "Frontend");
1802
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
1803
1804
double timestamp = getTimestamp();
1805
1806
std::optional<SourceCode> source = fileResolver->readSource(name);
1807
std::optional<std::string> environmentName = fileResolver->getEnvironmentForModule(name);
1808
1809
stats.timeRead += getTimestamp() - timestamp;
1810
1811
if (!source)
1812
{
1813
sourceModules.erase(name);
1814
return {nullptr, nullptr};
1815
}
1816
1817
const Config& config = configResolver->getConfig(name, limits);
1818
ParseOptions opts = config.parseOptions;
1819
opts.captureComments = true;
1820
SourceModule result = parse(name, source->source, opts);
1821
result.type = source->type;
1822
1823
RequireTraceResult& require = requireTrace[name];
1824
require = traceRequires(fileResolver, result.root, name, limits);
1825
1826
std::shared_ptr<SourceNode>& sourceNode = sourceNodes[name];
1827
1828
if (!sourceNode)
1829
sourceNode = std::make_shared<SourceNode>();
1830
1831
std::shared_ptr<SourceModule>& sourceModule = sourceModules[name];
1832
1833
if (!sourceModule)
1834
sourceModule = std::make_shared<SourceModule>();
1835
1836
*sourceModule = std::move(result);
1837
sourceModule->environmentName = environmentName;
1838
1839
sourceNode->name = sourceModule->name;
1840
sourceNode->humanReadableName = sourceModule->humanReadableName;
1841
1842
// clear all prior dependents. we will re-add them after parsing the rest of the graph
1843
for (const auto& [moduleName, _] : sourceNode->requireLocations)
1844
{
1845
if (auto depIt = sourceNodes.find(moduleName); depIt != sourceNodes.end())
1846
depIt->second->dependents.erase(sourceNode->name);
1847
}
1848
1849
sourceNode->requireSet.clear();
1850
sourceNode->requireLocations.clear();
1851
sourceNode->dirtySourceModule = false;
1852
1853
if (it == sourceNodes.end())
1854
{
1855
sourceNode->dirtyModule = true;
1856
sourceNode->dirtyModuleForAutocomplete = true;
1857
}
1858
1859
for (const auto& [moduleName, location] : require.requireList)
1860
sourceNode->requireSet.insert(moduleName);
1861
1862
sourceNode->requireLocations = require.requireList;
1863
1864
return {sourceNode.get(), sourceModule.get()};
1865
}
1866
1867
/** Try to parse a source file into a SourceModule.
1868
*
1869
* The logic here is a little bit more complicated than we'd like it to be.
1870
*
1871
* If a file does not exist, we return none to prevent the Frontend from creating knowledge that this module exists.
1872
* If the Frontend thinks that the file exists, it will not produce an "Unknown require" error.
1873
*
1874
* If the file has syntax errors, we report them and synthesize an empty AST if it's not available.
1875
* This suppresses the Unknown require error and allows us to make a best effort to typecheck code that require()s
1876
* something that has broken syntax.
1877
* We also translate Luau::ParseError into a Luau::TypeError so that we can use a vector<TypeError> to describe the
1878
* result of the check()
1879
*/
1880
SourceModule Frontend::parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions)
1881
{
1882
LUAU_TIMETRACE_SCOPE("Frontend::parse", "Frontend");
1883
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
1884
1885
SourceModule sourceModule;
1886
1887
double timestamp = getTimestamp();
1888
1889
Luau::ParseResult parseResult = Luau::Parser::parse(src.data(), src.size(), *sourceModule.names, *sourceModule.allocator, parseOptions);
1890
1891
stats.timeParse += getTimestamp() - timestamp;
1892
stats.files++;
1893
stats.lines += parseResult.lines;
1894
1895
if (!parseResult.errors.empty())
1896
sourceModule.parseErrors.insert(sourceModule.parseErrors.end(), parseResult.errors.begin(), parseResult.errors.end());
1897
1898
if (parseResult.errors.empty() || parseResult.root)
1899
{
1900
sourceModule.root = parseResult.root;
1901
sourceModule.mode = parseMode(parseResult.hotcomments);
1902
}
1903
else
1904
{
1905
sourceModule.root = sourceModule.allocator->alloc<AstStatBlock>(Location{}, AstArray<AstStat*>{nullptr, 0});
1906
sourceModule.mode = Mode::NoCheck;
1907
}
1908
1909
sourceModule.name = name;
1910
sourceModule.humanReadableName = fileResolver->getHumanReadableModuleName(name);
1911
1912
if (parseOptions.captureComments)
1913
{
1914
sourceModule.commentLocations = std::move(parseResult.commentLocations);
1915
sourceModule.hotcomments = std::move(parseResult.hotcomments);
1916
}
1917
1918
return sourceModule;
1919
}
1920
1921
1922
FrontendModuleResolver::FrontendModuleResolver(Frontend* frontend)
1923
: frontend(frontend)
1924
{
1925
}
1926
1927
std::optional<ModuleInfo> FrontendModuleResolver::resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr)
1928
{
1929
// FIXME I think this can be pushed into the FileResolver.
1930
auto it = frontend->requireTrace.find(currentModuleName);
1931
if (it == frontend->requireTrace.end())
1932
{
1933
// CLI-43699
1934
// If we can't find the current module name, that's because we bypassed the frontend's initializer
1935
// and called typeChecker.check directly.
1936
// In that case, requires will always fail.
1937
return std::nullopt;
1938
}
1939
1940
const auto& exprs = it->second.exprs;
1941
1942
const ModuleInfo* info = exprs.find(&pathExpr);
1943
if (!info)
1944
return std::nullopt;
1945
1946
return *info;
1947
}
1948
1949
const ModulePtr FrontendModuleResolver::getModule(const ModuleName& moduleName) const
1950
{
1951
std::scoped_lock lock(moduleMutex);
1952
1953
auto it = modules.find(moduleName);
1954
if (it != modules.end())
1955
return it->second;
1956
else
1957
return nullptr;
1958
}
1959
1960
bool FrontendModuleResolver::moduleExists(const ModuleName& moduleName) const
1961
{
1962
return frontend->sourceNodes.count(moduleName) != 0;
1963
}
1964
1965
std::string FrontendModuleResolver::getHumanReadableModuleName(const ModuleName& moduleName) const
1966
{
1967
return frontend->fileResolver->getHumanReadableModuleName(moduleName);
1968
}
1969
1970
bool FrontendModuleResolver::setModule(const ModuleName& moduleName, ModulePtr module)
1971
{
1972
std::scoped_lock lock(moduleMutex);
1973
1974
bool replaced = modules.count(moduleName) > 0;
1975
modules[moduleName] = std::move(module);
1976
return replaced;
1977
}
1978
1979
void FrontendModuleResolver::clearModules()
1980
{
1981
std::scoped_lock lock(moduleMutex);
1982
1983
modules.clear();
1984
}
1985
1986
ScopePtr Frontend::addEnvironment(const std::string& environmentName)
1987
{
1988
LUAU_ASSERT(environments.count(environmentName) == 0);
1989
1990
if (environments.count(environmentName) == 0)
1991
{
1992
ScopePtr scope = std::make_shared<Scope>(globals.globalScope);
1993
environments[environmentName] = scope;
1994
return scope;
1995
}
1996
else
1997
return environments[environmentName];
1998
}
1999
2000
ScopePtr Frontend::getEnvironmentScope(const std::string& environmentName) const
2001
{
2002
if (auto it = environments.find(environmentName); it != environments.end())
2003
return it->second;
2004
2005
LUAU_ASSERT(!"environment doesn't exist");
2006
return {};
2007
}
2008
2009
void Frontend::registerBuiltinDefinition(const std::string& name, std::function<void(Frontend&, GlobalTypes&, ScopePtr)> applicator)
2010
{
2011
LUAU_ASSERT(builtinDefinitions.count(name) == 0);
2012
2013
if (builtinDefinitions.count(name) == 0)
2014
builtinDefinitions[name] = applicator;
2015
}
2016
2017
void Frontend::applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName)
2018
{
2019
LUAU_ASSERT(builtinDefinitions.count(definitionName) > 0);
2020
2021
if (builtinDefinitions.count(definitionName) > 0)
2022
builtinDefinitions[definitionName](*this, globals, getEnvironmentScope(environmentName));
2023
}
2024
2025
LintResult Frontend::classifyLints(const std::vector<LintWarning>& warnings, const Config& config)
2026
{
2027
LintResult result;
2028
for (const auto& w : warnings)
2029
{
2030
if (config.lintErrors || config.fatalLint.isEnabled(w.code))
2031
result.errors.push_back(w);
2032
else
2033
result.warnings.push_back(w);
2034
}
2035
2036
return result;
2037
}
2038
2039
void Frontend::clearStats()
2040
{
2041
stats = {};
2042
}
2043
2044
void Frontend::clear()
2045
{
2046
sourceNodes.clear();
2047
sourceModules.clear();
2048
moduleResolver.clearModules();
2049
moduleResolverForAutocomplete.clearModules();
2050
requireTrace.clear();
2051
}
2052
2053
void Frontend::clearBuiltinEnvironments()
2054
{
2055
environments.clear();
2056
builtinDefinitions.clear();
2057
}
2058
2059
TypeId Frontend::parseType(
2060
NotNull<Allocator> allocator,
2061
NotNull<AstNameTable> nameTable,
2062
NotNull<InternalErrorReporter> iceHandler,
2063
TypeCheckLimits limits,
2064
NotNull<TypeArena> arena,
2065
std::string_view source
2066
)
2067
{
2068
ParseNodeResult<AstType> parseResult = Parser::parseType(source.data(), source.size(), *nameTable, *allocator);
2069
2070
if (!parseResult.root)
2071
iceHandler->ice("Frontend::parseType was given an unparseable type");
2072
2073
if (!parseResult.errors.empty())
2074
iceHandler->ice("Frontend::parseType error: " + parseResult.errors.front().getMessage());
2075
2076
ModulePtr module = std::make_shared<Module>();
2077
2078
UnifierSharedState unifierState{iceHandler};
2079
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
2080
unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit);
2081
2082
Normalizer normalizer{arena, builtinTypes, NotNull{&unifierState}, SolverMode::New};
2083
2084
TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}};
2085
typeFunctionRuntime.allowEvaluation = true;
2086
2087
NullModuleResolver moduleResolver;
2088
2089
DataFlowGraph dfg = DataFlowGraphBuilder::empty(NotNull{&module->defArena}, NotNull{&module->keyArena});
2090
2091
ConstraintGenerator cg{
2092
module,
2093
NotNull{&normalizer},
2094
NotNull{&typeFunctionRuntime},
2095
NotNull{&moduleResolver},
2096
builtinTypes,
2097
iceHandler,
2098
globals.globalScope,
2099
globals.globalScope,
2100
nullptr,
2101
nullptr,
2102
NotNull{&dfg},
2103
{}
2104
};
2105
2106
TypeId t = cg.resolveType(globals.globalScope, parseResult.root, false);
2107
2108
if (!cg.constraints.empty())
2109
{
2110
iceHandler->ice("Not yet implemented: parseType cannot reduce other type aliases");
2111
}
2112
2113
return t;
2114
}
2115
2116
} // namespace Luau
2117
2118