Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/Analysis/src/RequireTracer.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/RequireTracer.h"
3
4
#include "Luau/Ast.h"
5
#include "Luau/Module.h"
6
7
namespace Luau
8
{
9
10
struct RequireTracer : AstVisitor
11
{
12
RequireTracer(RequireTraceResult& result, FileResolver* fileResolver, const ModuleName& currentModuleName)
13
: result(result)
14
, fileResolver(fileResolver)
15
, currentModuleName(currentModuleName)
16
, locals(nullptr)
17
{
18
}
19
20
bool visit(AstExprTypeAssertion* expr) override
21
{
22
// suppress `require() :: any`
23
return false;
24
}
25
26
bool visit(AstExprCall* expr) override
27
{
28
AstExprGlobal* global = expr->func->as<AstExprGlobal>();
29
30
if (global && global->name == "require" && expr->args.size >= 1)
31
requireCalls.push_back(expr);
32
33
return true;
34
}
35
36
bool visit(AstStatLocal* stat) override
37
{
38
for (size_t i = 0; i < stat->vars.size && i < stat->values.size; ++i)
39
{
40
AstLocal* local = stat->vars.data[i];
41
AstExpr* expr = stat->values.data[i];
42
43
// track initializing expression to be able to trace modules through locals
44
locals[local] = expr;
45
}
46
47
return true;
48
}
49
50
bool visit(AstStatAssign* stat) override
51
{
52
for (size_t i = 0; i < stat->vars.size; ++i)
53
{
54
// locals that are assigned don't have a known expression
55
if (AstExprLocal* expr = stat->vars.data[i]->as<AstExprLocal>())
56
locals[expr->local] = nullptr;
57
}
58
59
return true;
60
}
61
62
bool visit(AstType* node) override
63
{
64
// allow resolving require inside `typeof` annotations
65
return true;
66
}
67
68
bool visit(AstTypePack* node) override
69
{
70
// allow resolving require inside `typeof` annotations
71
return true;
72
}
73
74
AstNode* getDependent(AstNode* node)
75
{
76
if (AstExprLocal* expr = node->as<AstExprLocal>())
77
return locals[expr->local];
78
else if (AstExprIndexName* expr = node->as<AstExprIndexName>())
79
return expr->expr;
80
else if (AstExprIndexExpr* expr = node->as<AstExprIndexExpr>())
81
return expr->expr;
82
else if (AstExprCall* expr = node->as<AstExprCall>(); expr && expr->self)
83
return expr->func->as<AstExprIndexName>()->expr;
84
else if (AstExprGroup* expr = node->as<AstExprGroup>())
85
return expr->expr;
86
else if (AstExprTypeAssertion* expr = node->as<AstExprTypeAssertion>())
87
return expr->annotation;
88
else if (AstTypeGroup* expr = node->as<AstTypeGroup>())
89
return expr->type;
90
else if (AstTypeTypeof* expr = node->as<AstTypeTypeof>())
91
return expr->expr;
92
else
93
return nullptr;
94
}
95
96
void process(const TypeCheckLimits& limits)
97
{
98
ModuleInfo moduleContext{currentModuleName};
99
100
// seed worklist with require arguments
101
work.reserve(requireCalls.size());
102
103
for (AstExprCall* require : requireCalls)
104
work.push_back(require->args.data[0]);
105
106
// push all dependent expressions to the work stack; note that the vector is modified during traversal
107
for (size_t i = 0; i < work.size(); ++i)
108
{
109
if (AstNode* dep = getDependent(work[i]))
110
work.push_back(dep);
111
}
112
113
// resolve all expressions to a module info
114
for (size_t i = work.size(); i > 0; --i)
115
{
116
AstNode* expr = work[i - 1];
117
118
// when multiple expressions depend on the same one we push it to work queue multiple times
119
if (result.exprs.contains(expr))
120
continue;
121
122
std::optional<ModuleInfo> info;
123
124
if (AstNode* dep = getDependent(expr))
125
{
126
const ModuleInfo* context = result.exprs.find(dep);
127
128
if (context && expr->is<AstExprLocal>())
129
info = *context; // locals just inherit their dependent context, no resolution required
130
else if (context && (expr->is<AstExprGroup>() || expr->is<AstTypeGroup>()))
131
info = *context; // simple group nodes propagate their value
132
else if (context && (expr->is<AstTypeTypeof>() || expr->is<AstExprTypeAssertion>()))
133
info = *context; // typeof type annotations will resolve to the typeof content
134
else if (AstExpr* asExpr = expr->asExpr())
135
info = fileResolver->resolveModule(context, asExpr, limits);
136
}
137
else if (AstExpr* asExpr = expr->asExpr())
138
{
139
info = fileResolver->resolveModule(&moduleContext, asExpr, limits);
140
}
141
142
if (info)
143
result.exprs[expr] = std::move(*info);
144
}
145
146
// resolve all requires according to their argument
147
result.requireList.reserve(requireCalls.size());
148
149
for (AstExprCall* require : requireCalls)
150
{
151
AstExpr* arg = require->args.data[0];
152
153
if (const ModuleInfo* info = result.exprs.find(arg))
154
{
155
result.requireList.push_back({info->name, require->location});
156
157
ModuleInfo infoCopy = *info; // copy *info out since next line invalidates info!
158
result.exprs[require] = std::move(infoCopy);
159
}
160
else
161
{
162
result.exprs[require] = {}; // mark require as unresolved
163
}
164
}
165
}
166
167
RequireTraceResult& result;
168
FileResolver* fileResolver;
169
ModuleName currentModuleName;
170
171
DenseHashMap<AstLocal*, AstExpr*> locals;
172
std::vector<AstNode*> work;
173
std::vector<AstExprCall*> requireCalls;
174
};
175
176
RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root, const ModuleName& currentModuleName, const TypeCheckLimits& limits)
177
{
178
RequireTraceResult result;
179
RequireTracer tracer{result, fileResolver, currentModuleName};
180
root->visit(&tracer);
181
tracer.process(limits);
182
return result;
183
}
184
185
} // namespace Luau
186
187