Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/Analysis/src/Instantiation2.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/Common.h"
3
#include "Luau/Polarity.h"
4
#include "Luau/Scope.h"
5
#include "Luau/Instantiation2.h"
6
7
LUAU_FASTFLAG(LuauReplacerRespectsReboundGenerics)
8
9
namespace Luau
10
{
11
12
Replacer::Replacer(
13
NotNull<TypeArena> arena,
14
NotNull<DenseHashMap<TypeId, TypeId>> replacements,
15
NotNull<DenseHashMap<TypePackId, TypePackId>> replacementPacks
16
)
17
: Substitution(TxnLog::empty(), arena)
18
, replacements(replacements)
19
, replacementPacks(replacementPacks)
20
{
21
LUAU_ASSERT(FFlag::LuauReplacerRespectsReboundGenerics);
22
LUAU_ASSERT(checkReplacementKeys());
23
}
24
25
bool Replacer::isDirty(TypeId ty)
26
{
27
return replacements->contains(ty);
28
}
29
30
bool Replacer::isDirty(TypePackId tp)
31
{
32
return replacementPacks->contains(tp);
33
}
34
35
TypeId Replacer::clean(TypeId ty)
36
{
37
const auto res = replacements->find(ty);
38
LUAU_ASSERT(res);
39
dontTraverseInto(*res);
40
return *res;
41
}
42
43
TypePackId Replacer::clean(TypePackId tp)
44
{
45
const auto res = replacementPacks->find(tp);
46
LUAU_ASSERT(res);
47
dontTraverseInto(*res);
48
return *res;
49
}
50
51
bool Replacer::ignoreChildren(TypeId ty)
52
{
53
if (get<ExternType>(ty))
54
return true;
55
56
if (auto ftv = get<FunctionType>(ty))
57
{
58
if (ftv->hasNoFreeOrGenericTypes)
59
return false;
60
61
// If this function type quantifies over these generics, we don't want substitution to
62
// go any further into them because it's being shadowed in this case.
63
for (auto generic : ftv->generics)
64
if (replacements->contains(generic))
65
return true;
66
67
for (auto generic : ftv->genericPacks)
68
if (replacementPacks->contains(generic))
69
return true;
70
}
71
72
return false;
73
}
74
75
bool Replacer::checkReplacementKeys() const
76
{
77
for (const auto& [k, _] : *replacements)
78
{
79
if (k != follow(k))
80
return false;
81
}
82
83
for (const auto& [k, _] : *replacementPacks)
84
{
85
if (k != follow(k))
86
return false;
87
}
88
89
return true;
90
}
91
92
93
bool Instantiation2::ignoreChildren(TypeId ty)
94
{
95
if (get<ExternType>(ty))
96
return true;
97
98
if (auto ftv = get<FunctionType>(ty))
99
{
100
if (ftv->hasNoFreeOrGenericTypes)
101
return false;
102
103
// If this function type quantifies over these generics, we don't want substitution to
104
// go any further into them because it's being shadowed in this case.
105
for (auto generic : ftv->generics)
106
if (genericSubstitutions.contains(generic))
107
return true;
108
109
for (auto generic : ftv->genericPacks)
110
if (genericPackSubstitutions.contains(generic))
111
return true;
112
}
113
114
return false;
115
}
116
117
bool Instantiation2::isDirty(TypeId ty)
118
{
119
return get<GenericType>(ty) && genericSubstitutions.contains(ty);
120
}
121
122
bool Instantiation2::isDirty(TypePackId tp)
123
{
124
return get<GenericTypePack>(tp) && genericPackSubstitutions.contains(tp);
125
}
126
127
TypeId Instantiation2::clean(TypeId ty)
128
{
129
LUAU_ASSERT(subtyping && scope);
130
auto generic = get<GenericType>(ty);
131
LUAU_ASSERT(generic);
132
TypeId substTy = follow(genericSubstitutions[ty]);
133
const FreeType* ft = get<FreeType>(substTy);
134
135
// violation of the substitution invariant if this is not a free type.
136
LUAU_ASSERT(ft);
137
138
TypeId res;
139
if (is<NeverType>(follow(ft->lowerBound)))
140
{
141
// If the lower bound is never, assume that we can pick the
142
// upper bound, and that this will provide a reasonable type.
143
//
144
// If we have a mixed generic who's free type is totally
145
// unbound (the upper bound is `unknown` and the lower
146
// bound is `never`), then we instantiate it to `unknown`.
147
// This seems ... fine.
148
res = ft->upperBound;
149
}
150
else if (is<UnknownType>(follow(ft->upperBound)))
151
{
152
// If the upper bound is unknown, assume we can pick the
153
// lower bound, and that this will provide a reasonable
154
// type.
155
res = ft->lowerBound;
156
}
157
else
158
{
159
// Imagine that we have some set of bounds on a free type:
160
//
161
// Q <: 'a <: Z
162
//
163
// If we have a mixed generic, then the upper and lower bounds
164
// should inform what type to instantiate. In fact, we should
165
// pick the intersection between the two. If our bounds are
166
// coherent, then Q <: Z, meaning that Q & Z == Q.
167
//
168
// If `Q </: Z`, then try the upper bound. We _might_ error
169
// later.
170
auto r = subtyping->isSubtype(ft->lowerBound, ft->upperBound, NotNull{scope});
171
res = r.isSubtype ? ft->lowerBound : ft->upperBound;
172
}
173
174
// Instantiation should not traverse into the type that we are substituting for.
175
dontTraverseInto(res);
176
177
return res;
178
}
179
180
TypePackId Instantiation2::clean(TypePackId tp)
181
{
182
TypePackId res = genericPackSubstitutions[tp];
183
dontTraverseInto(res);
184
return res;
185
}
186
187
std::optional<TypeId> instantiate2(
188
TypeArena* arena,
189
DenseHashMap<TypeId, TypeId> genericSubstitutions,
190
DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions,
191
NotNull<Subtyping> subtyping,
192
NotNull<Scope> scope,
193
TypeId ty
194
)
195
{
196
Instantiation2 instantiation{arena, std::move(genericSubstitutions), std::move(genericPackSubstitutions), subtyping, scope};
197
return instantiation.substitute(ty);
198
}
199
200
std::optional<TypePackId> instantiate2(
201
TypeArena* arena,
202
DenseHashMap<TypeId, TypeId> genericSubstitutions,
203
DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions,
204
NotNull<Subtyping> subtyping,
205
NotNull<Scope> scope,
206
TypePackId tp
207
)
208
{
209
Instantiation2 instantiation{arena, std::move(genericSubstitutions), std::move(genericPackSubstitutions), subtyping, scope};
210
return instantiation.substitute(tp);
211
}
212
213
} // namespace Luau
214
215