Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/Analysis/src/Subtyping.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
3
#include "Luau/Subtyping.h"
4
5
#include "Luau/Common.h"
6
#include "Luau/Error.h"
7
#include "Luau/Normalize.h"
8
#include "Luau/RecursionCounter.h"
9
#include "Luau/Scope.h"
10
#include "Luau/ScopedSeenSet.h"
11
#include "Luau/Substitution.h"
12
#include "Luau/TxnLog.h"
13
#include "Luau/Type.h"
14
#include "Luau/TypeArena.h"
15
#include "Luau/TypeFunction.h"
16
#include "Luau/TypeIds.h"
17
#include "Luau/TypePack.h"
18
#include "Luau/TypePath.h"
19
#include "Luau/TypeUtils.h"
20
21
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSubtypingRecursionLimit, 100)
22
23
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
24
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
25
LUAU_FASTFLAGVARIABLE(LuauMorePreciseErrorSuppression)
26
LUAU_FASTFLAGVARIABLE(LuauSubtypingMissingPropertiesAsNil)
27
LUAU_FASTFLAG(LuauTableFreezeCheckIsSubtype)
28
LUAU_FASTFLAG(LuauUnifyWithSubtyping2)
29
LUAU_FASTINTVARIABLE(LuauSubtypingIterationLimit, 20000)
30
LUAU_FASTFLAGVARIABLE(LuauSubtypingReplaceBounds)
31
LUAU_FASTFLAG(LuauOverloadGetsInstantiated)
32
LUAU_FASTFLAGVARIABLE(LuauFollowGenericBeforeCheckingIfMapped)
33
34
namespace Luau
35
{
36
37
bool SubtypingReasoning::operator==(const SubtypingReasoning& other) const
38
{
39
return subPath == other.subPath && superPath == other.superPath && variance == other.variance;
40
}
41
42
size_t SubtypingReasoningHash::operator()(const SubtypingReasoning& r) const
43
{
44
return TypePath::PathHash()(r.subPath) ^ (TypePath::PathHash()(r.superPath) << 1) ^ (static_cast<size_t>(r.variance) << 1);
45
}
46
47
MappedGenericEnvironment::MappedGenericFrame::MappedGenericFrame(
48
DenseHashMap<TypePackId, std::optional<TypePackId>> mappings,
49
const std::optional<size_t> parentScopeIndex
50
)
51
: mappings(std::move(mappings))
52
, parentScopeIndex(parentScopeIndex)
53
{
54
}
55
56
MappedGenericEnvironment::LookupResult MappedGenericEnvironment::lookupGenericPack(TypePackId genericTp) const
57
{
58
genericTp = follow(genericTp);
59
60
std::optional<size_t> currentFrameIndex = currentScopeIndex;
61
62
while (currentFrameIndex)
63
{
64
const MappedGenericFrame& currentFrame = frames[*currentFrameIndex];
65
if (const auto mappedPack = currentFrame.mappings.find(genericTp))
66
{
67
if (*mappedPack)
68
return mappedPack->value();
69
else
70
return Unmapped{*currentFrameIndex};
71
}
72
73
currentFrameIndex = currentFrame.parentScopeIndex;
74
}
75
76
// Do a DFS of children to see if any enclosed scope mentions this generic pack
77
if (currentScopeIndex)
78
{
79
const MappedGenericFrame& baseFrame = frames[*currentScopeIndex];
80
std::vector<size_t> toCheck = std::vector(baseFrame.children.begin(), baseFrame.children.end());
81
82
while (!toCheck.empty())
83
{
84
const size_t currIndex = toCheck.back();
85
toCheck.pop_back();
86
87
const MappedGenericFrame& currentFrame = frames[currIndex];
88
if (const auto mappedPack = currentFrame.mappings.find(genericTp))
89
{
90
if (*mappedPack)
91
return mappedPack->value();
92
else
93
return Unmapped{currIndex};
94
}
95
96
toCheck.insert(toCheck.end(), currentFrame.children.begin(), currentFrame.children.end());
97
}
98
}
99
100
return NotBindable{};
101
}
102
103
void MappedGenericEnvironment::pushFrame(const std::vector<TypePackId>& genericTps)
104
{
105
DenseHashMap<TypePackId, std::optional<TypePackId>> mappings{nullptr};
106
107
for (TypePackId tp : genericTps)
108
mappings[tp] = std::nullopt;
109
110
frames.emplace_back(std::move(mappings), currentScopeIndex);
111
112
const size_t newFrameIndex = frames.size() - 1;
113
114
if (currentScopeIndex)
115
frames[*currentScopeIndex].children.insert(newFrameIndex);
116
117
currentScopeIndex = newFrameIndex;
118
}
119
120
void MappedGenericEnvironment::popFrame()
121
{
122
LUAU_ASSERT(currentScopeIndex);
123
if (currentScopeIndex)
124
{
125
const std::optional<size_t> newFrameIndex = frames[*currentScopeIndex].parentScopeIndex;
126
currentScopeIndex = newFrameIndex.value_or(0);
127
}
128
}
129
130
bool MappedGenericEnvironment::bindGeneric(TypePackId genericTp, TypePackId bindeeTp)
131
{
132
// We shouldn't bind generic type packs to themselves
133
if (genericTp == bindeeTp)
134
return true;
135
136
if (!get<GenericTypePack>(genericTp))
137
{
138
LUAU_ASSERT(!"bindGeneric should not be called with a non-generic type pack");
139
return false;
140
}
141
142
const LookupResult lookupResult = lookupGenericPack(genericTp);
143
if (const Unmapped* unmapped = get_if<Unmapped>(&lookupResult))
144
{
145
frames[unmapped->scopeIndex].mappings[genericTp] = bindeeTp;
146
return true;
147
}
148
else
149
{
150
return false;
151
}
152
}
153
154
template<typename TID>
155
static void assertReasoningValid_DEPRECATED(TID subTy, TID superTy, const SubtypingResult& result, NotNull<BuiltinTypes> builtinTypes)
156
{
157
if (!FFlag::DebugLuauSubtypingCheckPathValidity)
158
return;
159
160
for (const SubtypingReasoning& reasoning : result.reasoning)
161
{
162
LUAU_ASSERT(traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes));
163
LUAU_ASSERT(traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes));
164
}
165
}
166
167
template<typename TID>
168
static void assertReasoningValid(TID subTy, TID superTy, const SubtypingResult& result, NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena)
169
{
170
if (!FFlag::DebugLuauSubtypingCheckPathValidity)
171
return;
172
173
for (const SubtypingReasoning& reasoning : result.reasoning)
174
{
175
LUAU_ASSERT(traverse(subTy, reasoning.subPath, builtinTypes, arena));
176
LUAU_ASSERT(traverse(superTy, reasoning.superPath, builtinTypes, arena));
177
}
178
}
179
180
template<>
181
void assertReasoningValid<TableIndexer>(TableIndexer, TableIndexer, const SubtypingResult&, NotNull<BuiltinTypes>, NotNull<TypeArena>)
182
{
183
// This specialization exists so that we can invoke methods like
184
// isInvariantWith() on a pair of TableIndexers.
185
}
186
187
static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const SubtypingReasonings& b)
188
{
189
SubtypingReasonings result{kEmptyReasoning};
190
191
for (const SubtypingReasoning& r : a)
192
{
193
if (r.variance == SubtypingVariance::Invariant)
194
result.insert(r);
195
else if (r.variance == SubtypingVariance::Covariant || r.variance == SubtypingVariance::Contravariant)
196
{
197
SubtypingReasoning inverseReasoning = SubtypingReasoning{
198
r.subPath, r.superPath, r.variance == SubtypingVariance::Covariant ? SubtypingVariance::Contravariant : SubtypingVariance::Covariant
199
};
200
if (b.contains(inverseReasoning))
201
result.insert(SubtypingReasoning{r.subPath, r.superPath, SubtypingVariance::Invariant});
202
else
203
result.insert(r);
204
}
205
206
if (result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
207
return result;
208
}
209
210
for (const SubtypingReasoning& r : b)
211
{
212
if (r.variance == SubtypingVariance::Invariant)
213
result.insert(r);
214
else if (r.variance == SubtypingVariance::Covariant || r.variance == SubtypingVariance::Contravariant)
215
{
216
SubtypingReasoning inverseReasoning = SubtypingReasoning{
217
r.subPath, r.superPath, r.variance == SubtypingVariance::Covariant ? SubtypingVariance::Contravariant : SubtypingVariance::Covariant
218
};
219
if (a.contains(inverseReasoning))
220
result.insert(SubtypingReasoning{r.subPath, r.superPath, SubtypingVariance::Invariant});
221
else
222
result.insert(r);
223
}
224
225
if (result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
226
return result;
227
}
228
229
return result;
230
}
231
232
SubtypingResult& SubtypingResult::andAlso(SubtypingResult other, SubtypingSuppressionPolicy policy)
233
{
234
// If the other result is not a subtype, we want to join all of its
235
// reasonings to this one. If this result already has reasonings of its own,
236
// those need to be attributed here whenever this _also_ failed.
237
if (!other.isSubtype)
238
{
239
if (isSubtype)
240
reasoning = std::move(other.reasoning);
241
else
242
// NOTE: This probably doesn't need to be two copies.
243
reasoning = mergeReasonings(reasoning, other.reasoning);
244
}
245
246
isSubtype &= other.isSubtype;
247
if (FFlag::LuauMorePreciseErrorSuppression)
248
{
249
if (policy == SubtypingSuppressionPolicy::All)
250
isErrorSuppressing &= other.isErrorSuppressing;
251
else
252
isErrorSuppressing |= other.isErrorSuppressing;
253
}
254
normalizationTooComplex |= other.normalizationTooComplex;
255
isCacheable &= other.isCacheable;
256
errors.insert(errors.end(), other.errors.begin(), other.errors.end());
257
genericBoundsMismatches.insert(genericBoundsMismatches.end(), other.genericBoundsMismatches.begin(), other.genericBoundsMismatches.end());
258
assumedConstraints.insert(assumedConstraints.end(), other.assumedConstraints.begin(), other.assumedConstraints.end());
259
260
return *this;
261
}
262
263
SubtypingResult& SubtypingResult::orElse(SubtypingResult other)
264
{
265
// If this result is a subtype, we do not join the reasoning lists. If this
266
// result is not a subtype, but the other is a subtype, we want to _clear_
267
// our reasoning list. If both results are not subtypes, we join the
268
// reasoning lists.
269
if (!isSubtype)
270
{
271
if (other.isSubtype)
272
{
273
reasoning.clear();
274
assumedConstraints = std::move(other.assumedConstraints);
275
}
276
else
277
{
278
reasoning = mergeReasonings(reasoning, other.reasoning);
279
if (FFlag::LuauMorePreciseErrorSuppression)
280
isErrorSuppressing |= other.isErrorSuppressing;
281
}
282
}
283
else if (FFlag::LuauUnifyWithSubtyping2 && other.isSubtype)
284
{
285
// If the other result has assumed constraints, we drop ours (given
286
// we represent a failed subtype) and then take the constraints of
287
// the other check.
288
assumedConstraints = std::move(other.assumedConstraints);
289
}
290
291
isSubtype |= other.isSubtype;
292
normalizationTooComplex |= other.normalizationTooComplex;
293
isCacheable &= other.isCacheable;
294
errors.insert(errors.end(), other.errors.begin(), other.errors.end());
295
genericBoundsMismatches.insert(genericBoundsMismatches.end(), other.genericBoundsMismatches.begin(), other.genericBoundsMismatches.end());
296
297
return *this;
298
}
299
300
SubtypingResult& SubtypingResult::withBothComponent(TypePath::Component component)
301
{
302
return withSubComponent(component).withSuperComponent(std::move(component));
303
}
304
305
SubtypingResult& SubtypingResult::withSubComponent(TypePath::Component component)
306
{
307
if (reasoning.empty())
308
reasoning.insert(SubtypingReasoning{Path(std::move(component)), TypePath::kEmpty});
309
else
310
{
311
for (auto& r : reasoning)
312
r.subPath = r.subPath.push_front(component);
313
}
314
315
return *this;
316
}
317
318
SubtypingResult& SubtypingResult::withSuperComponent(TypePath::Component component)
319
{
320
if (reasoning.empty())
321
reasoning.insert(SubtypingReasoning{TypePath::kEmpty, Path(std::move(component))});
322
else
323
{
324
for (auto& r : reasoning)
325
r.superPath = r.superPath.push_front(component);
326
}
327
328
return *this;
329
}
330
331
SubtypingResult& SubtypingResult::withBothPath(TypePath::Path path)
332
{
333
return withSubPath(path).withSuperPath(std::move(path));
334
}
335
336
SubtypingResult& SubtypingResult::withSubPath(TypePath::Path path)
337
{
338
if (reasoning.empty())
339
reasoning.insert(SubtypingReasoning{std::move(path), TypePath::kEmpty});
340
else
341
{
342
for (auto& r : reasoning)
343
r.subPath = path.append(r.subPath);
344
}
345
346
return *this;
347
}
348
349
SubtypingResult& SubtypingResult::withSuperPath(TypePath::Path path)
350
{
351
if (reasoning.empty())
352
reasoning.insert(SubtypingReasoning{TypePath::kEmpty, std::move(path)});
353
else
354
{
355
for (auto& r : reasoning)
356
r.superPath = path.append(r.superPath);
357
}
358
359
return *this;
360
}
361
362
SubtypingResult& SubtypingResult::withErrors(ErrorVec& err)
363
{
364
for (TypeError& e : err)
365
errors.emplace_back(e);
366
return *this;
367
}
368
369
SubtypingResult& SubtypingResult::withError(TypeError err)
370
{
371
errors.push_back(std::move(err));
372
return *this;
373
}
374
375
SubtypingResult& SubtypingResult::withAssumedConstraint(ConstraintV constraint)
376
{
377
assumedConstraints.push_back(std::move(constraint));
378
return *this;
379
}
380
381
SubtypingResult SubtypingResult::negate(const SubtypingResult& result)
382
{
383
return SubtypingResult{
384
!result.isSubtype,
385
result.normalizationTooComplex,
386
};
387
}
388
389
struct ApplyMappedGenerics : Substitution
390
{
391
NotNull<BuiltinTypes> builtinTypes;
392
NotNull<TypeArena> arena;
393
NotNull<InternalErrorReporter> iceReporter;
394
395
NotNull<SubtypingEnvironment> env;
396
397
ApplyMappedGenerics(
398
NotNull<BuiltinTypes> builtinTypes,
399
NotNull<TypeArena> arena,
400
SubtypingEnvironment& env,
401
NotNull<InternalErrorReporter> iceReporter
402
)
403
: Substitution(TxnLog::empty(), arena)
404
, builtinTypes(builtinTypes)
405
, arena(arena)
406
, iceReporter(iceReporter.get())
407
, env(NotNull{&env})
408
{
409
}
410
411
bool isDirty(TypeId ty) override
412
{
413
return env->containsMappedType(ty);
414
}
415
416
bool isDirty(TypePackId tp) override
417
{
418
return env->containsMappedPack(tp);
419
}
420
421
TypeId clean(TypeId ty) override
422
{
423
const auto& [lowerBound, upperBound] = env->getMappedTypeBounds(ty, NotNull{iceReporter});
424
425
if (upperBound.empty() && lowerBound.empty())
426
{
427
// No bounds for the generic we're mapping.
428
// In this case, unknown vs never is an arbitrary choice:
429
// ie, does it matter if we map add<A, A> to add<unknown, unknown> or add<never, never> in the context of subtyping?
430
// We choose unknown here, since it's closest to the original behavior.
431
return builtinTypes->unknownType;
432
}
433
else if (!upperBound.empty())
434
{
435
if (FFlag::LuauSubtypingReplaceBounds)
436
{
437
IntersectionBuilder ib{arena, builtinTypes};
438
for (TypeId ub : upperBound)
439
{
440
// NOTE: The original implementation skips over generic
441
// types, but that seems incorrect to me.
442
if (!get<GenericType>(ub))
443
ib.add(ub);
444
}
445
return ib.build();
446
}
447
else
448
{
449
TypeIds boundsToUse;
450
451
for (TypeId ub : upperBound)
452
{
453
// quick and dirty check to avoid adding generic types
454
if (!get<GenericType>(ub))
455
boundsToUse.insert(ub);
456
}
457
458
if (boundsToUse.empty())
459
{
460
// This case happens when we've collected no bounds for the generic we're mapping.
461
// In this case, unknown vs never is an arbitrary choice:
462
// ie, does it matter if we map add<A, A> to add<unknown, unknown> or add<never, never> in the context of subtyping?
463
// We choose unknown here, since it's closest to the original behavior.
464
return builtinTypes->unknownType;
465
}
466
if (boundsToUse.size() == 1)
467
return *boundsToUse.begin();
468
469
return arena->addType(IntersectionType{boundsToUse.take()});
470
}
471
}
472
else if (!lowerBound.empty())
473
{
474
if (FFlag::LuauSubtypingReplaceBounds)
475
{
476
UnionBuilder ub{arena, builtinTypes};
477
for (TypeId lb : lowerBound)
478
{
479
// NOTE: The original implementation skips over generic
480
// types, but that seems incorrect to me.
481
if (!get<GenericType>(lb))
482
ub.add(lb);
483
}
484
return ub.build();
485
}
486
else
487
{
488
TypeIds boundsToUse;
489
490
for (TypeId lb : lowerBound)
491
{
492
// quick and dirty check to avoid adding generic types
493
if (!get<GenericType>(lb))
494
boundsToUse.insert(lb);
495
}
496
497
if (boundsToUse.empty())
498
{
499
// This case happens when we've collected no bounds for the generic we're mapping.
500
// In this case, unknown vs never is an arbitrary choice:
501
// ie, does it matter if we map add<A, A> to add<unknown, unknown> or add<never, never> in the context of subtyping?
502
// We choose unknown here, since it's closest to the original behavior.
503
return builtinTypes->unknownType;
504
}
505
else if (lowerBound.size() == 1)
506
return *boundsToUse.begin();
507
else
508
return arena->addType(UnionType{boundsToUse.take()});
509
}
510
}
511
else
512
{
513
LUAU_ASSERT(!"Unreachable path");
514
return builtinTypes->unknownType;
515
}
516
}
517
518
TypePackId clean(TypePackId tp) override
519
{
520
const MappedGenericEnvironment::LookupResult result = env->lookupGenericPack(tp);
521
if (const TypePackId* mappedGen = get_if<TypePackId>(&result))
522
return *mappedGen;
523
// Clean is only called when isDirty found a pack bound
524
LUAU_ASSERT(!"Unreachable");
525
return builtinTypes->anyTypePack;
526
}
527
528
bool ignoreChildren(TypeId ty) override
529
{
530
if (get<ExternType>(ty))
531
return true;
532
533
if (const FunctionType* f = get<FunctionType>(ty))
534
{
535
for (TypeId g : f->generics)
536
{
537
if (FFlag::LuauFollowGenericBeforeCheckingIfMapped)
538
g = follow(g);
539
if (const std::vector<SubtypingEnvironment::GenericBounds>* bounds = env->mappedGenerics.find(g); bounds && !bounds->empty())
540
// We don't want to mutate the generics of a function that's being subtyped
541
return true;
542
}
543
}
544
545
return ty->persistent;
546
}
547
bool ignoreChildren(TypePackId ty) override
548
{
549
return ty->persistent;
550
}
551
};
552
553
std::optional<TypeId> SubtypingEnvironment::applyMappedGenerics(
554
NotNull<BuiltinTypes> builtinTypes,
555
NotNull<TypeArena> arena,
556
TypeId ty,
557
NotNull<InternalErrorReporter> iceReporter
558
)
559
{
560
ApplyMappedGenerics amg{builtinTypes, arena, *this, iceReporter};
561
return amg.substitute(ty);
562
}
563
564
std::optional<TypeId> SubtypingEnvironment::tryFindSubstitution(TypeId ty) const
565
{
566
if (const TypeId* it = substitutions.find(ty))
567
return *it;
568
569
if (parent)
570
return parent->tryFindSubstitution(ty);
571
572
return std::nullopt;
573
}
574
575
const SubtypingResult* SubtypingEnvironment::tryFindSubtypingResult(std::pair<TypeId, TypeId> subAndSuper) const
576
{
577
if (const auto it = seenSetCache.find(subAndSuper))
578
return it;
579
580
if (parent)
581
return parent->tryFindSubtypingResult(subAndSuper);
582
583
return nullptr;
584
}
585
586
bool SubtypingEnvironment::containsMappedType(TypeId ty) const
587
{
588
ty = follow(ty);
589
if (const auto bounds = mappedGenerics.find(ty); bounds && !bounds->empty())
590
return true;
591
592
if (parent)
593
return parent->containsMappedType(ty);
594
595
return false;
596
}
597
598
bool SubtypingEnvironment::containsMappedPack(TypePackId tp) const
599
{
600
if (const MappedGenericEnvironment::LookupResult lookupResult = mappedGenericPacks.lookupGenericPack(tp); get_if<TypePackId>(&lookupResult))
601
return true;
602
603
if (parent)
604
return parent->containsMappedPack(tp);
605
606
return false;
607
}
608
609
SubtypingEnvironment::GenericBounds& SubtypingEnvironment::getMappedTypeBounds(TypeId ty, NotNull<InternalErrorReporter> iceReporter)
610
{
611
ty = follow(ty);
612
std::vector<GenericBounds>* bounds = mappedGenerics.find(ty);
613
if (bounds && !bounds->empty())
614
return bounds->back();
615
616
if (parent)
617
return parent->getMappedTypeBounds(ty, iceReporter);
618
619
LUAU_ASSERT(!"Use containsMappedType before asking for bounds!");
620
iceReporter->ice("Trying to access bounds for a type with no in-scope bounds");
621
}
622
623
MappedGenericEnvironment::LookupResult SubtypingEnvironment::lookupGenericPack(TypePackId tp) const
624
{
625
MappedGenericEnvironment::LookupResult result = mappedGenericPacks.lookupGenericPack(tp);
626
if (get_if<TypePackId>(&result))
627
return result;
628
else if (parent)
629
return parent->lookupGenericPack(tp);
630
else
631
return result;
632
}
633
634
Subtyping::Subtyping(
635
NotNull<BuiltinTypes> builtinTypes,
636
NotNull<TypeArena> typeArena,
637
NotNull<Normalizer> normalizer,
638
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
639
NotNull<InternalErrorReporter> iceReporter
640
)
641
: builtinTypes(builtinTypes)
642
, arena(typeArena)
643
, normalizer(normalizer)
644
, typeFunctionRuntime(typeFunctionRuntime)
645
, iceReporter(iceReporter)
646
{
647
}
648
649
SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope)
650
{
651
SubtypingEnvironment env;
652
653
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope);
654
655
if (result.normalizationTooComplex)
656
{
657
if (result.isCacheable)
658
resultCache[{subTy, superTy}] = result;
659
660
return result;
661
}
662
663
for (const auto& [_, bounds] : env.mappedGenerics)
664
LUAU_ASSERT(bounds.empty());
665
666
/* TODO: We presently don't store subtype test results in the persistent
667
* cache if the left-side type is a generic function.
668
*
669
* The implementation would be a bit tricky and we haven't seen any material
670
* impact on benchmarks.
671
*
672
* What we would want to do is to remember points within the type where
673
* mapped generics are introduced. When all the contingent generics are
674
* introduced at which we're doing the test, we can mark the result as
675
* cacheable.
676
*/
677
678
if (result.isCacheable)
679
resultCache[{subTy, superTy}] = result;
680
681
return result;
682
}
683
684
SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp, NotNull<Scope> scope, const std::vector<TypeId>& bindableGenerics)
685
{
686
const std::vector<TypePackId> bindableGenericPacks;
687
return isSubtype(subTp, superTp, scope, bindableGenerics, bindableGenericPacks);
688
}
689
690
SubtypingResult Subtyping::isSubtype(
691
TypePackId subTp,
692
TypePackId superTp,
693
NotNull<Scope> scope,
694
const std::vector<TypeId>& bindableGenerics,
695
const std::vector<TypePackId>& bindableGenericPacks
696
)
697
{
698
SubtypingEnvironment env;
699
for (TypeId g : bindableGenerics)
700
env.mappedGenerics[follow(g)] = {SubtypingEnvironment::GenericBounds{}};
701
702
env.mappedGenericPacks.pushFrame(bindableGenericPacks);
703
704
SubtypingResult result = isCovariantWith(env, subTp, superTp, scope);
705
706
for (TypeId bg : bindableGenerics)
707
{
708
bg = follow(bg);
709
710
LUAU_ASSERT(env.mappedGenerics.contains(bg));
711
712
if (const std::vector<SubtypingEnvironment::GenericBounds>* bounds = env.mappedGenerics.find(bg))
713
{
714
// Bounds should have exactly one entry
715
LUAU_ASSERT(bounds->size() == 1);
716
if (bounds->empty())
717
continue;
718
if (const GenericType* gen = get<GenericType>(bg))
719
result.andAlso(checkGenericBounds(bounds->back(), env, scope, gen->name));
720
}
721
}
722
723
return result;
724
}
725
726
SubtypingResult Subtyping::cache(SubtypingEnvironment& env, SubtypingResult result, TypeId subTy, TypeId superTy)
727
{
728
const std::pair<TypeId, TypeId> p{subTy, superTy};
729
730
if (result.isCacheable)
731
resultCache[p] = result;
732
733
return result;
734
}
735
736
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy, NotNull<Scope> scope)
737
{
738
NonExceptionalRecursionLimiter nerl(&normalizer->sharedState->counters.recursionCount);
739
if (!nerl.isOk(DFInt::LuauSubtypingRecursionLimit))
740
return SubtypingResult{false, true};
741
742
if (FFlag::LuauUnifyWithSubtyping2)
743
{
744
env.iterationCount++;
745
if (FInt::LuauSubtypingIterationLimit > 0 && env.iterationCount >= FInt::LuauSubtypingIterationLimit)
746
return SubtypingResult{false, true};
747
}
748
749
subTy = follow(subTy);
750
superTy = follow(superTy);
751
752
if (std::optional<TypeId> subIt = env.tryFindSubstitution(subTy); subIt && *subIt)
753
subTy = *subIt;
754
755
if (std::optional<TypeId> superIt = env.tryFindSubstitution(superTy); superIt && *superIt)
756
subTy = *superIt;
757
758
const SubtypingResult* cachedResult = resultCache.find({subTy, superTy});
759
if (cachedResult)
760
{
761
return *cachedResult;
762
}
763
764
cachedResult = env.tryFindSubtypingResult({subTy, superTy});
765
if (cachedResult)
766
{
767
return *cachedResult;
768
}
769
770
// TODO: Do we care about returning a proof that this is error-suppressing?
771
// e.g. given `a | error <: a | error` where both operands are pointer equal,
772
// then should it also carry the information that it's error-suppressing?
773
// If it should, then `error <: error` should also do the same.
774
if (subTy == superTy)
775
return {true};
776
777
std::pair<TypeId, TypeId> typePair{subTy, superTy};
778
if (!seenTypes.insert(typePair))
779
{
780
/* TODO: Caching results for recursive types is really tricky to think
781
* about.
782
*
783
* We'd like to cache at the outermost level where we encounter the
784
* recursive type, but we do not want to cache interior results that
785
* involve the cycle.
786
*
787
* Presently, we stop at cycles and assume that the subtype check will
788
* succeed because we'll eventually get there if it won't. However, if
789
* that cyclic type turns out not to have the asked-for subtyping
790
* relation, then all the intermediate cached results that were
791
* contingent on that assumption need to be evicted from the cache, or
792
* not entered into the cache, or something.
793
*
794
* For now, we do the conservative thing and refuse to cache anything
795
* that touches a cycle.
796
*/
797
SubtypingResult res;
798
res.isSubtype = true;
799
res.isCacheable = false;
800
801
env.seenSetCache[typePair] = res;
802
803
return res;
804
}
805
806
ScopedSeenSet ssp{seenTypes, typePair};
807
808
// Within the scope to which a generic belongs, that generic should be
809
// tested as though it were its upper bounds. We do not yet support bounded
810
// generics, so the upper bound is always unknown.
811
if (!FFlag::LuauUnifyWithSubtyping2)
812
{
813
if (auto subGeneric = get<GenericType>(subTy); subGeneric && subsumes(subGeneric->scope, scope))
814
return isCovariantWith(env, builtinTypes->neverType, superTy, scope);
815
if (auto superGeneric = get<GenericType>(superTy); superGeneric && subsumes(superGeneric->scope, scope))
816
return isCovariantWith(env, subTy, builtinTypes->unknownType, scope);
817
}
818
819
SubtypingResult result;
820
821
if (FFlag::LuauUnifyWithSubtyping2 && get2<FreeType, FreeType>(subTy, superTy))
822
{
823
// Any two free types are potentially subtypes of one another because
824
// both of them could be narrowed to never.
825
result = {true};
826
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
827
}
828
else if (auto superFree = get<FreeType>(superTy); FFlag::LuauUnifyWithSubtyping2 && superFree)
829
{
830
// FIXME CLI-185582: See comment below with the same ticket number.
831
832
// Given SubTy <: (LB <: SuperTy <: UB)
833
//
834
// If SubTy <: UB, then it is possible that SubTy <: SuperTy.
835
// If SubTy </: UB, then it is definitely the case that SubTy </: SuperTy.
836
//
837
// It's always possible for SuperTy's upper bound to later be
838
// constrained, so this relation may not actually hold.
839
840
result = isCovariantWith(env, subTy, superFree->upperBound, scope);
841
842
if (result.isSubtype)
843
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
844
}
845
else if (auto subFree = get<FreeType>(subTy); FFlag::LuauUnifyWithSubtyping2 && subFree)
846
{
847
// FIXME CLI-185582: When combined with unification via subtyping, this
848
// can allow generics to appear upper bounds, but unless this is used
849
// to resolve function calls (which the linked ticket is for) said
850
// generics will be from within the scope of said generic, for example:
851
//
852
// local function foo<T>(x): T
853
// -- `T` will appear in the upper bound of `x`, but that's
854
// -- fine, as we are within the scope of T
855
// return x
856
// end
857
858
// Given (LB <: SubTy <: UB) <: SuperTy
859
//
860
// If UB <: SuperTy, then it is certainly the case that SubTy <: SuperTy.
861
// If SuperTy <: UB and LB <: SuperTy, then it is possible that UB will later be narrowed such that SubTy <: SuperTy.
862
// If LB </: SuperTy, then SubTy </: SuperTy
863
864
if (isCovariantWith(env, subFree->lowerBound, superTy, scope).isSubtype)
865
{
866
result = {true};
867
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
868
}
869
else
870
result = {false};
871
}
872
else if (FFlag::LuauUnifyWithSubtyping2 && (is<BlockedType>(subTy) || is<BlockedType>(superTy)))
873
{
874
result = {true};
875
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
876
}
877
else if (auto subGeneric = get<GenericType>(subTy); FFlag::LuauUnifyWithSubtyping2 && subGeneric && subsumes(subGeneric->scope, scope))
878
return isCovariantWith(env, builtinTypes->neverType, superTy, scope);
879
else if (auto superGeneric = get<GenericType>(superTy); FFlag::LuauUnifyWithSubtyping2 && superGeneric && subsumes(superGeneric->scope, scope))
880
return isCovariantWith(env, subTy, builtinTypes->unknownType, scope);
881
else if (get<AnyType>(superTy))
882
result = {true};
883
884
// We have added this as an exception - the set of inhabitants of any is exactly the set of inhabitants of unknown (since error has no
885
// inhabitants). any = err | unknown, so under semantic subtyping, {} U unknown = unknown
886
else if (get<AnyType>(subTy) && get<UnknownType>(superTy))
887
result = {true};
888
else if (get<AnyType>(subTy))
889
{
890
// any = unknown | error, so we rewrite this to match.
891
// As per TAPL: A | B <: T iff A <: T && B <: T
892
result =
893
isCovariantWith(env, builtinTypes->unknownType, superTy, scope).andAlso(isCovariantWith(env, builtinTypes->errorType, superTy, scope));
894
if (FFlag::LuauMorePreciseErrorSuppression)
895
result.isErrorSuppressing = true;
896
}
897
else if (get<UnknownType>(superTy) && !get<UnionType>(subTy) && !get<IntersectionType>(subTy))
898
{
899
LUAU_ASSERT(!get<AnyType>(subTy)); // TODO: replace with ice.
900
LUAU_ASSERT(!get<UnionType>(subTy)); // TODO: replace with ice.
901
LUAU_ASSERT(!get<IntersectionType>(subTy)); // TODO: replace with ice.
902
903
bool errorSuppressing = nullptr != get<ErrorType>(subTy);
904
if (FFlag::LuauMorePreciseErrorSuppression)
905
{
906
result.isSubtype = !errorSuppressing;
907
result.isErrorSuppressing = errorSuppressing;
908
}
909
else
910
result = {!errorSuppressing};
911
}
912
else if (get<NeverType>(subTy))
913
result = {true};
914
else if (get<ErrorType>(superTy))
915
result = {false};
916
else if (get<ErrorType>(subTy))
917
{
918
result = {true};
919
if (FFlag::LuauMorePreciseErrorSuppression)
920
result.isErrorSuppressing = true;
921
}
922
else if (auto subTypeFunctionInstance = get<TypeFunctionInstanceType>(subTy))
923
{
924
bool mappedGenericsApplied = false;
925
if (auto substSubTy = env.applyMappedGenerics(builtinTypes, arena, subTy, iceReporter))
926
{
927
mappedGenericsApplied = *substSubTy != subTy;
928
subTypeFunctionInstance = get<TypeFunctionInstanceType>(*substSubTy);
929
}
930
931
result = isCovariantWith(env, subTypeFunctionInstance, superTy, scope);
932
result.isCacheable = !mappedGenericsApplied;
933
}
934
else if (auto superTypeFunctionInstance = get<TypeFunctionInstanceType>(superTy))
935
{
936
bool mappedGenericsApplied = false;
937
if (auto substSuperTy = env.applyMappedGenerics(builtinTypes, arena, superTy, iceReporter))
938
{
939
mappedGenericsApplied = *substSuperTy != superTy;
940
superTypeFunctionInstance = get<TypeFunctionInstanceType>(*substSuperTy);
941
}
942
943
result = isCovariantWith(env, subTy, superTypeFunctionInstance, scope);
944
result.isCacheable = !mappedGenericsApplied;
945
}
946
else if (get<GenericType>(subTy) || get<GenericType>(superTy))
947
{
948
if (const auto subBounds = env.mappedGenerics.find(subTy); subBounds && !subBounds->empty())
949
{
950
bool ok = bindGeneric(env, subTy, superTy);
951
result.isSubtype = ok;
952
result.isCacheable = false;
953
}
954
else if (const auto superBounds = env.mappedGenerics.find(superTy); superBounds && !superBounds->empty())
955
{
956
bool ok = bindGeneric(env, subTy, superTy);
957
result.isSubtype = ok;
958
result.isCacheable = false;
959
}
960
}
961
else if (auto subUnion = get<UnionType>(subTy))
962
result = isCovariantWith(env, subUnion, superTy, scope);
963
else if (auto superUnion = get<UnionType>(superTy))
964
{
965
result = isCovariantWith(env, subTy, superUnion, scope);
966
if (!result.isSubtype && !result.normalizationTooComplex)
967
result = trySemanticSubtyping(env, subTy, superTy, scope, result);
968
}
969
else if (auto superIntersection = get<IntersectionType>(superTy))
970
result = isCovariantWith(env, subTy, superIntersection, scope);
971
else if (auto subIntersection = get<IntersectionType>(subTy))
972
{
973
result = isCovariantWith(env, subIntersection, superTy, scope);
974
if (!result.isSubtype && !result.normalizationTooComplex)
975
result = trySemanticSubtyping(env, subTy, superTy, scope, result);
976
}
977
else if (auto pair = get2<FreeType, FreeType>(subTy, superTy); !FFlag::LuauUnifyWithSubtyping2 && pair)
978
{
979
// Any two free types are potentially subtypes of one another because
980
// both of them could be narrowed to never.
981
result = {true};
982
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
983
}
984
else if (auto superFree = get<FreeType>(superTy); !FFlag::LuauUnifyWithSubtyping2 && superFree)
985
{
986
// Given SubTy <: (LB <: SuperTy <: UB)
987
//
988
// If SubTy <: UB, then it is possible that SubTy <: SuperTy.
989
// If SubTy </: UB, then it is definitely the case that SubTy </: SuperTy.
990
//
991
// It's always possible for SuperTy's upper bound to later be
992
// constrained, so this relation may not actually hold.
993
994
result = isCovariantWith(env, subTy, superFree->upperBound, scope);
995
996
if (result.isSubtype)
997
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
998
}
999
else if (auto subFree = get<FreeType>(subTy); !FFlag::LuauUnifyWithSubtyping2 && subFree)
1000
{
1001
// Given (LB <: SubTy <: UB) <: SuperTy
1002
//
1003
// If UB <: SuperTy, then it is certainly the case that SubTy <: SuperTy.
1004
// If SuperTy <: UB and LB <: SuperTy, then it is possible that UB will later be narrowed such that SubTy <: SuperTy.
1005
// If LB </: SuperTy, then SubTy </: SuperTy
1006
1007
if (FFlag::LuauMorePreciseErrorSuppression)
1008
{
1009
SubtypingResult r = isCovariantWith(env, subFree->lowerBound, superTy, scope);
1010
result.isSubtype = r.isSubtype;
1011
result.isErrorSuppressing = r.isErrorSuppressing;
1012
if (r.isSubtype)
1013
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
1014
}
1015
else
1016
{
1017
if (isCovariantWith(env, subFree->lowerBound, superTy, scope).isSubtype)
1018
{
1019
result = {true};
1020
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
1021
}
1022
else
1023
result = {false};
1024
}
1025
}
1026
else if (auto p = get2<NegationType, NegationType>(subTy, superTy))
1027
{
1028
// We use `isContravariantWith` here in order to make sure that the
1029
// type paths still look coherent.
1030
result = isContravariantWith(env, p.first->ty, p.second->ty, scope).withBothComponent(TypePath::TypeField::Negated);
1031
}
1032
else if (auto subNegation = get<NegationType>(subTy))
1033
{
1034
result = isCovariantWith(env, subNegation, superTy, scope);
1035
if (!result.isSubtype && !result.normalizationTooComplex)
1036
result = trySemanticSubtyping(env, subTy, superTy, scope, result);
1037
}
1038
else if (auto superNegation = get<NegationType>(superTy))
1039
{
1040
result = isCovariantWith(env, subTy, superNegation, scope);
1041
if (!result.isSubtype && !result.normalizationTooComplex)
1042
result = trySemanticSubtyping(env, subTy, superTy, scope, result);
1043
}
1044
else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, superTy))
1045
result = isCovariantWith(env, p, scope);
1046
else if (auto p = get2<SingletonType, PrimitiveType>(subTy, superTy))
1047
result = isCovariantWith(env, p, scope);
1048
else if (auto p = get2<SingletonType, SingletonType>(subTy, superTy))
1049
result = isCovariantWith(env, p, scope);
1050
else if (auto p = get2<FunctionType, PrimitiveType>(subTy, superTy))
1051
{
1052
auto [subFunction, superPrimitive] = p;
1053
result.isSubtype = superPrimitive->type == PrimitiveType::Function;
1054
}
1055
else if (auto p = get2<FunctionType, FunctionType>(subTy, superTy))
1056
result = isCovariantWith(env, p, scope);
1057
else if (auto p = get2<TableType, TableType>(subTy, superTy))
1058
{
1059
const bool forceCovariantTest = uniqueTypes != nullptr && uniqueTypes->contains(subTy);
1060
result = isCovariantWith(env, p.first, p.second, forceCovariantTest, scope);
1061
if (FFlag::LuauUnifyWithSubtyping2)
1062
{
1063
if (result.isSubtype && !p.first->indexer && p.second->indexer && p.first->state != TableState::Sealed)
1064
{
1065
// FIXME CLI-182960
1066
//
1067
// Currently, unification is also the mechanism by which unsealed
1068
// tables may receive an indexer. If we've observed that this is
1069
// already a subtype and this is something of the form:
1070
//
1071
// {| ... |} <: { [A]: B ... }
1072
//
1073
// Then add an assumed constraint stating such.
1074
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
1075
}
1076
}
1077
}
1078
else if (auto p = get2<MetatableType, MetatableType>(subTy, superTy))
1079
result = isCovariantWith(env, p, scope);
1080
else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
1081
result = isCovariantWith(env, p, scope);
1082
else if (FFlag::LuauTableFreezeCheckIsSubtype && get2<MetatableType, PrimitiveType>(subTy, superTy))
1083
{
1084
// When FFlag::LuauTableFreezeCheckIsSubtype is clipped, will update the `if` to follow the same pattern of `auto p = get2<...>` as above.
1085
auto p = get2<MetatableType, PrimitiveType>(subTy, superTy);
1086
result = isCovariantWith(env, p, scope);
1087
}
1088
else if (auto p = get2<ExternType, ExternType>(subTy, superTy))
1089
result = isCovariantWith(env, p, scope);
1090
else if (auto p = get2<ExternType, TableType>(subTy, superTy))
1091
result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope);
1092
else if (auto p = get2<TableType, PrimitiveType>(subTy, superTy))
1093
result = isCovariantWith(env, p, scope);
1094
else if (auto p = get2<PrimitiveType, TableType>(subTy, superTy))
1095
result = isCovariantWith(env, p, scope);
1096
else if (auto p = get2<SingletonType, TableType>(subTy, superTy))
1097
result = isCovariantWith(env, p, scope);
1098
1099
assertReasoningValid(subTy, superTy, result, builtinTypes, arena);
1100
1101
return cache(env, std::move(result), subTy, superTy);
1102
}
1103
1104
/*
1105
* Subtyping of packs is fairly involved. There are three parts to the test.
1106
*
1107
* 1. If both packs have types at their heads, we do a pairwise test for each
1108
* pair of types.
1109
* 2. If the finite parts of the packs are of inequal length and the pack on the
1110
* opposite side has a tail, we test that. (eg
1111
* testing concrete types against variadics or a generic pack)
1112
* 3. Lastly, do a subtype test on non-finite tails. (eg between two generic
1113
* packs or variadics)
1114
*/
1115
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp, NotNull<Scope> scope)
1116
{
1117
NonExceptionalRecursionLimiter nerl{&normalizer->sharedState->counters.recursionCount};
1118
if (!nerl.isOk(DFInt::LuauSubtypingRecursionLimit))
1119
return SubtypingResult{false, true};
1120
1121
subTp = follow(subTp);
1122
superTp = follow(superTp);
1123
1124
std::pair<TypePackId, TypePackId> typePair = {subTp, superTp};
1125
if (!seenPacks.insert(typePair))
1126
return SubtypingResult{true, false, false};
1127
ScopedSeenSet<Subtyping::SeenTypePackSet, std::pair<TypePackId, TypePackId>> popper{seenPacks, std::move(typePair)};
1128
1129
auto [subHead, subTail] = flatten(subTp);
1130
auto [superHead, superTail] = flatten(superTp);
1131
1132
const size_t headSize = std::min(subHead.size(), superHead.size());
1133
1134
// SubtypingResult is pretty heavy, we keep it as a pointer for stack pressure reasons.
1135
std::unique_ptr<SubtypingResult> result = std::make_unique<SubtypingResult>();
1136
result->isSubtype = true;
1137
1138
if (subTp == superTp)
1139
return {true};
1140
1141
// Match head types pairwise
1142
1143
for (size_t i = 0; i < headSize; ++i)
1144
result->andAlso(isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i, TypePath::Index::Variant::Pack}));
1145
1146
// Handle mismatched head sizes
1147
1148
if (subHead.size() < superHead.size())
1149
{
1150
if (subTail)
1151
{
1152
auto earlyExit = isSubTailCovariantWith(env, *result, subTp, *subTail, superTp, headSize, superHead, superTail, scope);
1153
if (earlyExit == EarlyExit::Yes)
1154
return *result;
1155
}
1156
else
1157
{
1158
result->andAlso({false});
1159
return *result;
1160
}
1161
}
1162
else if (subHead.size() > superHead.size())
1163
{
1164
if (superTail)
1165
{
1166
auto earlyExit = isCovariantWithSuperTail(env, *result, subTp, headSize, subHead, subTail, superTp, *superTail, scope);
1167
if (earlyExit == EarlyExit::Yes)
1168
return *result;
1169
}
1170
else
1171
return {false};
1172
}
1173
1174
// Handle tails
1175
1176
if (subTail && superTail)
1177
{
1178
if (auto p = get2<VariadicTypePack, VariadicTypePack>(*subTail, *superTail))
1179
{
1180
result->andAlso(isTailCovariantWithTail(env, scope, *subTail, p.first, *superTail, p.second));
1181
}
1182
else if (auto p = get2<GenericTypePack, GenericTypePack>(*subTail, *superTail))
1183
{
1184
result->andAlso(isTailCovariantWithTail(env, scope, *subTail, p.first, *superTail, p.second));
1185
}
1186
else if (auto p = get2<VariadicTypePack, GenericTypePack>(*subTail, *superTail))
1187
{
1188
result->andAlso(isTailCovariantWithTail(env, scope, *subTail, p.first, *superTail, p.second));
1189
}
1190
else if (auto p = get2<GenericTypePack, VariadicTypePack>(*subTail, *superTail))
1191
{
1192
result->andAlso(isTailCovariantWithTail(env, scope, *subTail, p.first, *superTail, p.second));
1193
}
1194
else if (FFlag::LuauUnifyWithSubtyping2 && (is<FreeTypePack>(*subTail) || is<FreeTypePack>(*superTail)))
1195
{
1196
result->andAlso(
1197
SubtypingResult{true}.withBothComponent(TypePath::PackField::Tail).withAssumedConstraint(PackSubtypeConstraint{*subTail, *superTail})
1198
);
1199
}
1200
else if (get<ErrorTypePack>(*subTail) || get<ErrorTypePack>(*superTail))
1201
// error type is fine on either side
1202
result->andAlso(SubtypingResult{true}.withBothComponent(TypePath::PackField::Tail));
1203
else if (get<FreeTypePack>(*subTail) || get<FreeTypePack>(*superTail))
1204
{
1205
// This seems incorrect in the event that the heads don't match ...
1206
return SubtypingResult{true}
1207
.withBothComponent(TypePath::PackField::Tail)
1208
.withAssumedConstraint(PackSubtypeConstraint{*subTail, *superTail});
1209
}
1210
else
1211
return SubtypingResult{false}
1212
.withBothComponent(TypePath::PackField::Tail)
1213
.withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}})
1214
.withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}});
1215
}
1216
else if (subTail)
1217
{
1218
if (get<VariadicTypePack>(*subTail))
1219
{
1220
return SubtypingResult{false}.withSubComponent(TypePath::PackField::Tail);
1221
}
1222
else if (auto g = get<GenericTypePack>(*subTail))
1223
{
1224
return isTailCovariantWithTail(env, scope, *subTail, g, Nothing{});
1225
}
1226
else if (FFlag::LuauUnifyWithSubtyping2 && is<FreeTypePack>(*subTail))
1227
{
1228
// This is the case where:
1229
// 1. Both the `superTp` and `subTp` have the same number of types in the head
1230
// 2. `superTp` does not have a tail (as if it did we'd hit a different branch)
1231
// 3. `subTp` has a free tail
1232
// We can treat the lack of a tail as the empty type pack.
1233
return SubtypingResult{true}
1234
.withBothComponent(TypePath::PackField::Tail)
1235
.withAssumedConstraint(PackSubtypeConstraint{*subTail, builtinTypes->emptyTypePack});
1236
}
1237
else
1238
return SubtypingResult{false}
1239
.withSubComponent(TypePath::PackField::Tail)
1240
.withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}});
1241
}
1242
else if (superTail)
1243
{
1244
if (get<VariadicTypePack>(*superTail))
1245
{
1246
/*
1247
* A variadic type pack ...T can be thought of as an infinite union of finite type packs.
1248
* () | (T) | (T, T) | (T, T, T) | ...
1249
*
1250
* And, per TAPL:
1251
* T <: A | B iff T <: A or T <: B
1252
*
1253
* All variadic type packs are therefore supertypes of the empty type pack.
1254
*/
1255
}
1256
else if (auto g = get<GenericTypePack>(*superTail))
1257
{
1258
result->andAlso(isTailCovariantWithTail(env, scope, Nothing{}, *superTail, g));
1259
}
1260
else if (FFlag::LuauUnifyWithSubtyping2 && is<FreeTypePack>(*superTail))
1261
{
1262
// This is the case where:
1263
// 1. Both the `superTp` and `subTp` have the same number of types in the head
1264
// 2. `subTp` does not have a tail
1265
// 3. `superTp` has a free tail
1266
result->andAlso(
1267
SubtypingResult{true}
1268
.withBothComponent(TypePath::PackField::Tail)
1269
.withAssumedConstraint(PackSubtypeConstraint{builtinTypes->emptyTypePack, *superTail})
1270
);
1271
}
1272
else
1273
return SubtypingResult{false}
1274
.withSuperComponent(TypePath::PackField::Tail)
1275
.withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}});
1276
}
1277
1278
assertReasoningValid(subTp, superTp, *result, builtinTypes, arena);
1279
1280
return *result;
1281
}
1282
1283
/* Check the tail of the subtype pack against a slice of the finite part of the
1284
* pack in supertype position. For example, in the following type, the head of
1285
* the pack in supertype position is longer than that of the subtype head:
1286
*
1287
* (number, string, any...) <: (number, string, boolean, thread, any...),
1288
*
1289
* This function handles the mismatched heads: any... <: (boolean, thread)
1290
*
1291
* Notably, this function does _not_ handle the test between the actual tail
1292
* packs.
1293
*
1294
* The contract on this function is a bit strange. If the function returns a
1295
* SubtypingResult, it should be considered to be the result for the entire pack
1296
* subtyping relation. It is not necessary to further check the tails.
1297
*/
1298
Subtyping::EarlyExit Subtyping::isSubTailCovariantWith(
1299
SubtypingEnvironment& env,
1300
SubtypingResult& outputResult,
1301
TypePackId subTp,
1302
TypePackId subTail,
1303
TypePackId superTp,
1304
size_t superHeadStartIndex,
1305
const std::vector<TypeId>& superHead,
1306
std::optional<TypePackId> superTail,
1307
NotNull<Scope> scope
1308
)
1309
{
1310
if (auto vt = get<VariadicTypePack>(subTail))
1311
{
1312
for (size_t i = superHeadStartIndex; i < superHead.size(); ++i)
1313
outputResult.andAlso(isCovariantWith(env, vt->ty, superHead[i], scope)
1314
.withSubPath(TypePath::PathBuilder().tail().variadic().build())
1315
.withSuperComponent(TypePath::Index{i, TypePath::Index::Variant::Pack}));
1316
return EarlyExit::No;
1317
}
1318
else if (get<GenericTypePack>(subTail))
1319
{
1320
MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(subTail);
1321
SubtypingResult result;
1322
if (get_if<MappedGenericEnvironment::NotBindable>(&lookupResult))
1323
result = SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}
1324
.withSubComponent(TypePath::PackField::Tail)
1325
.withSuperComponent(TypePath::PackSlice{superHeadStartIndex});
1326
else
1327
{
1328
TypePackId superTailPack = sliceTypePack(superHeadStartIndex, superTp, superHead, superTail, builtinTypes, arena);
1329
1330
if (const TypePackId* mappedGen = get_if<TypePackId>(&lookupResult))
1331
{
1332
// Subtype against the mapped generic pack.
1333
TypePackId subTpToCompare = *mappedGen;
1334
1335
// If mappedGen has a hidden variadic tail, we clip it for better arity mismatch reporting.
1336
const TypePack* tp = get<TypePack>(*mappedGen);
1337
if (const VariadicTypePack* vtp = tp ? get<VariadicTypePack>(follow(tp->tail)) : nullptr; vtp && vtp->hidden)
1338
subTpToCompare = arena->addTypePack(tp->head);
1339
1340
result = isCovariantWith(env, subTpToCompare, superTailPack, scope)
1341
.withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*mappedGen}}))
1342
.withSuperComponent(TypePath::PackSlice{superHeadStartIndex});
1343
}
1344
else
1345
{
1346
LUAU_ASSERT(get_if<MappedGenericEnvironment::Unmapped>(&lookupResult));
1347
bool ok = env.mappedGenericPacks.bindGeneric(subTail, superTailPack);
1348
result = SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}
1349
.withSubComponent(TypePath::PackField::Tail)
1350
.withSuperComponent(TypePath::PackSlice{superHeadStartIndex});
1351
}
1352
}
1353
1354
outputResult.andAlso(result);
1355
return EarlyExit::Yes;
1356
}
1357
else if (get<ErrorTypePack>(subTail))
1358
{
1359
outputResult = SubtypingResult{true}.withSubComponent(TypePath::PackField::Tail);
1360
return EarlyExit::Yes;
1361
}
1362
else if (FFlag::LuauUnifyWithSubtyping2 && get<FreeTypePack>(subTail))
1363
{
1364
TypePackId superTailPack = sliceTypePack(superHeadStartIndex, superTp, superHead, superTail, builtinTypes, arena);
1365
outputResult.andAlso(
1366
SubtypingResult{true}.withSubComponent(TypePath::PackField::Tail).withAssumedConstraint(PackSubtypeConstraint{subTail, superTailPack})
1367
);
1368
return EarlyExit::Yes;
1369
}
1370
else
1371
{
1372
outputResult =
1373
SubtypingResult{false}.withSubComponent(TypePath::PackField::Tail).withError({scope->location, UnexpectedTypePackInSubtyping{subTail}});
1374
return EarlyExit::Yes;
1375
}
1376
}
1377
1378
Subtyping::EarlyExit Subtyping::isCovariantWithSuperTail(
1379
SubtypingEnvironment& env,
1380
SubtypingResult& outputResult,
1381
TypePackId subTp,
1382
size_t subHeadStartIndex,
1383
const std::vector<TypeId>& subHead,
1384
std::optional<TypePackId> subTail,
1385
TypePackId superTp,
1386
TypePackId superTail,
1387
NotNull<Scope> scope
1388
)
1389
{
1390
if (auto vt = get<VariadicTypePack>(superTail))
1391
{
1392
for (size_t i = subHeadStartIndex; i < subHead.size(); ++i)
1393
outputResult.andAlso(isCovariantWith(env, subHead[i], vt->ty, scope)
1394
.withSubComponent(TypePath::Index{i, TypePath::Index::Variant::Pack})
1395
.withSuperPath(TypePath::PathBuilder().tail().variadic().build()));
1396
return EarlyExit::No;
1397
}
1398
else if (get<GenericTypePack>(superTail))
1399
{
1400
MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(superTail);
1401
SubtypingResult result;
1402
if (get_if<MappedGenericEnvironment::NotBindable>(&lookupResult))
1403
result = SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}
1404
.withSubComponent(TypePath::PackSlice{subHeadStartIndex})
1405
.withSuperComponent(TypePath::PackField::Tail);
1406
else
1407
{
1408
TypePackId subTailPack = sliceTypePack(subHeadStartIndex, subTp, subHead, subTail, builtinTypes, arena);
1409
1410
if (const TypePackId* mappedGen = get_if<TypePackId>(&lookupResult))
1411
{
1412
TypePackId superTpToCompare = *mappedGen;
1413
1414
// Subtype against the mapped generic pack.
1415
const TypePack* tp = get<TypePack>(*mappedGen);
1416
if (const VariadicTypePack* vtp = tp ? get<VariadicTypePack>(follow(tp->tail)) : nullptr; vtp && vtp->hidden)
1417
superTpToCompare = arena->addTypePack(tp->head);
1418
1419
result = isCovariantWith(env, subTailPack, superTpToCompare, scope)
1420
.withSubComponent(TypePath::PackSlice{subHeadStartIndex})
1421
.withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*mappedGen}}));
1422
}
1423
else
1424
{
1425
LUAU_ASSERT(get_if<MappedGenericEnvironment::Unmapped>(&lookupResult));
1426
bool ok = env.mappedGenericPacks.bindGeneric(superTail, subTailPack);
1427
result = SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}
1428
.withSubComponent(TypePath::PackSlice{subHeadStartIndex})
1429
.withSuperComponent(TypePath::PackField::Tail);
1430
}
1431
}
1432
1433
outputResult.andAlso(result);
1434
return EarlyExit::Yes;
1435
}
1436
else if (get<ErrorTypePack>(superTail))
1437
{
1438
outputResult = SubtypingResult{true}.withSuperComponent(TypePath::PackField::Tail);
1439
return EarlyExit::Yes;
1440
}
1441
else if (FFlag::LuauUnifyWithSubtyping2 && is<FreeTypePack>(superTail))
1442
{
1443
TypePackId subTailPack = sliceTypePack(subHeadStartIndex, subTp, subHead, subTail, builtinTypes, arena);
1444
outputResult.andAlso(
1445
SubtypingResult{true}.withSuperComponent(TypePath::PackField::Tail).withAssumedConstraint({PackSubtypeConstraint{subTailPack, superTail}})
1446
);
1447
return EarlyExit::Yes;
1448
}
1449
else
1450
{
1451
outputResult = SubtypingResult{false}
1452
.withSuperComponent(TypePath::PackField::Tail)
1453
.withError({scope->location, UnexpectedTypePackInSubtyping{superTail}});
1454
return EarlyExit::Yes;
1455
}
1456
}
1457
1458
SubtypingResult Subtyping::isTailCovariantWithTail(
1459
SubtypingEnvironment& env,
1460
NotNull<Scope> scope,
1461
TypePackId subTp,
1462
const VariadicTypePack* sub,
1463
TypePackId superTp,
1464
const VariadicTypePack* super
1465
)
1466
{
1467
// A variadic type pack is a subtype of another variadic type pack if their
1468
// respective element types have the same subtyping relationship.
1469
return isCovariantWith(env, sub->ty, super->ty, scope)
1470
.withBothComponent(TypePath::TypeField::Variadic)
1471
.withBothComponent(TypePath::PackField::Tail);
1472
}
1473
1474
SubtypingResult Subtyping::isTailCovariantWithTail(
1475
SubtypingEnvironment& env,
1476
NotNull<Scope> scope,
1477
TypePackId subTp,
1478
const GenericTypePack* sub,
1479
TypePackId superTp,
1480
const GenericTypePack* super
1481
)
1482
{
1483
MappedGenericEnvironment::LookupResult subLookupResult = env.lookupGenericPack(subTp);
1484
MappedGenericEnvironment::LookupResult superLookupResult = env.lookupGenericPack(superTp);
1485
1486
// match (subLookup, superLookupResult) {
1487
// (TypePackId, _) => do covariant test
1488
// (Unmapped, _) => bind the generic
1489
// (_, TypePackId) => do covariant test
1490
// (_, Unmapped) => bind the generic
1491
// (_, _) => subtyping succeeds if the two generics are pointer-identical
1492
// }
1493
if (const TypePackId* currMapping = get_if<TypePackId>(&subLookupResult))
1494
{
1495
return isCovariantWith(env, *currMapping, superTp, scope)
1496
.withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}))
1497
.withSuperComponent(TypePath::PackField::Tail);
1498
}
1499
else if (get_if<MappedGenericEnvironment::Unmapped>(&subLookupResult))
1500
{
1501
bool ok = env.mappedGenericPacks.bindGeneric(subTp, superTp);
1502
return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail);
1503
}
1504
else if (const TypePackId* currMapping = get_if<TypePackId>(&superLookupResult))
1505
{
1506
return isCovariantWith(env, subTp, *currMapping, scope)
1507
.withSubComponent(TypePath::PackField::Tail)
1508
.withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}));
1509
}
1510
else if (get_if<MappedGenericEnvironment::Unmapped>(&superLookupResult))
1511
{
1512
bool ok = env.mappedGenericPacks.bindGeneric(superTp, subTp);
1513
return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail);
1514
}
1515
else
1516
{
1517
// Sometimes, we compare generic packs inside the functions which are quantifying them. They're not bindable, but should still
1518
// subtype against themselves.
1519
return SubtypingResult{subTp == superTp, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(
1520
TypePath::PackField::Tail
1521
);
1522
}
1523
}
1524
1525
SubtypingResult Subtyping::isTailCovariantWithTail(
1526
SubtypingEnvironment& env,
1527
NotNull<Scope> scope,
1528
TypePackId subTp,
1529
const VariadicTypePack* sub,
1530
TypePackId superTp,
1531
const GenericTypePack* super
1532
)
1533
{
1534
MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(superTp);
1535
if (const TypePackId* currMapping = get_if<TypePackId>(&lookupResult))
1536
{
1537
return isCovariantWith(env, subTp, *currMapping, scope)
1538
.withSubComponent(TypePath::PackField::Tail)
1539
.withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}));
1540
}
1541
else if (get_if<MappedGenericEnvironment::Unmapped>(&lookupResult))
1542
{
1543
bool ok = env.mappedGenericPacks.bindGeneric(superTp, subTp);
1544
return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail);
1545
}
1546
else
1547
{
1548
LUAU_ASSERT(get_if<MappedGenericEnvironment::NotBindable>(&lookupResult));
1549
return SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail);
1550
}
1551
}
1552
1553
SubtypingResult Subtyping::isTailCovariantWithTail(
1554
SubtypingEnvironment& env,
1555
NotNull<Scope> scope,
1556
TypePackId subTp,
1557
const GenericTypePack* sub,
1558
TypePackId superTp,
1559
const VariadicTypePack* super
1560
)
1561
{
1562
if (TypeId t = follow(super->ty); get<AnyType>(t) || get<UnknownType>(t))
1563
{
1564
// Extra magic rule:
1565
// T... <: ...any
1566
// T... <: ...unknown
1567
//
1568
// See https://github.com/luau-lang/luau/issues/767
1569
return SubtypingResult{true};
1570
}
1571
else
1572
{
1573
MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(subTp);
1574
if (const TypePackId* currMapping = get_if<TypePackId>(&lookupResult))
1575
{
1576
return isCovariantWith(env, *currMapping, superTp, scope)
1577
.withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}))
1578
.withSuperComponent(TypePath::PackField::Tail);
1579
}
1580
else if (get_if<MappedGenericEnvironment::Unmapped>(&lookupResult))
1581
{
1582
bool ok = env.mappedGenericPacks.bindGeneric(subTp, superTp);
1583
return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail);
1584
}
1585
else
1586
{
1587
LUAU_ASSERT(get_if<MappedGenericEnvironment::NotBindable>(&lookupResult));
1588
return SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail);
1589
}
1590
}
1591
}
1592
1593
SubtypingResult Subtyping::isTailCovariantWithTail(
1594
SubtypingEnvironment& env,
1595
NotNull<Scope> scope,
1596
TypePackId subTp,
1597
const GenericTypePack* sub,
1598
Nothing
1599
)
1600
{
1601
MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(subTp);
1602
if (const TypePackId* currMapping = get_if<TypePackId>(&lookupResult))
1603
return isCovariantWith(env, *currMapping, builtinTypes->emptyTypePack, scope)
1604
.withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}));
1605
else if (get_if<MappedGenericEnvironment::Unmapped>(&lookupResult))
1606
{
1607
bool ok = env.mappedGenericPacks.bindGeneric(subTp, builtinTypes->emptyTypePack);
1608
return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSubComponent(TypePath::PackField::Tail);
1609
}
1610
else
1611
{
1612
LUAU_ASSERT(get_if<MappedGenericEnvironment::NotBindable>(&lookupResult));
1613
return SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSubComponent(TypePath::PackField::Tail);
1614
}
1615
}
1616
1617
SubtypingResult Subtyping::isTailCovariantWithTail(
1618
SubtypingEnvironment& env,
1619
NotNull<Scope> scope,
1620
Nothing,
1621
TypePackId superTp,
1622
const GenericTypePack* super
1623
)
1624
{
1625
MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(superTp);
1626
if (const TypePackId* currMapping = get_if<TypePackId>(&lookupResult))
1627
return isCovariantWith(env, builtinTypes->emptyTypePack, *currMapping, scope)
1628
.withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}));
1629
else if (get_if<MappedGenericEnvironment::Unmapped>(&lookupResult))
1630
{
1631
bool ok = env.mappedGenericPacks.bindGeneric(superTp, builtinTypes->emptyTypePack);
1632
return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSuperComponent(TypePath::PackField::Tail);
1633
}
1634
else
1635
{
1636
LUAU_ASSERT(get_if<MappedGenericEnvironment::NotBindable>(&lookupResult));
1637
return SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSuperComponent(TypePath::PackField::Tail);
1638
}
1639
}
1640
1641
1642
template<typename SubTy, typename SuperTy>
1643
SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy subTy, SuperTy superTy, NotNull<Scope> scope)
1644
{
1645
SubtypingResult result = isCovariantWith(env, superTy, subTy, scope);
1646
if (result.reasoning.empty())
1647
result.reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Contravariant});
1648
else
1649
{
1650
// If we don't swap the paths here, we will end up producing an invalid path
1651
// whenever we involve contravariance. We'll end up appending path
1652
// components that should belong to the supertype to the subtype, and vice
1653
// versa.
1654
for (auto& reasoning : result.reasoning)
1655
{
1656
std::swap(reasoning.subPath, reasoning.superPath);
1657
1658
// Also swap covariant/contravariant, since those are also the other way
1659
// around.
1660
if (reasoning.variance == SubtypingVariance::Covariant)
1661
reasoning.variance = SubtypingVariance::Contravariant;
1662
else if (reasoning.variance == SubtypingVariance::Contravariant)
1663
reasoning.variance = SubtypingVariance::Covariant;
1664
}
1665
}
1666
1667
assertReasoningValid(subTy, superTy, result, builtinTypes, arena);
1668
1669
return result;
1670
}
1671
1672
template<typename SubTy, typename SuperTy>
1673
SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy subTy, SuperTy superTy, NotNull<Scope> scope)
1674
{
1675
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope);
1676
result.andAlso(isContravariantWith(env, subTy, superTy, scope));
1677
1678
if (result.reasoning.empty())
1679
result.reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invariant});
1680
else
1681
{
1682
for (auto& reasoning : result.reasoning)
1683
reasoning.variance = SubtypingVariance::Invariant;
1684
}
1685
1686
assertReasoningValid(subTy, superTy, result, builtinTypes, arena);
1687
1688
return result;
1689
}
1690
1691
template<typename SubTy, typename SuperTy>
1692
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair, NotNull<Scope> scope)
1693
{
1694
return isCovariantWith(env, pair.first, pair.second, scope);
1695
}
1696
1697
template<typename SubTy, typename SuperTy>
1698
SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair, NotNull<Scope> scope)
1699
{
1700
return isContravariantWith(env, pair.first, pair.second, scope);
1701
}
1702
1703
template<typename SubTy, typename SuperTy>
1704
SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair, NotNull<Scope> scope)
1705
{
1706
return isInvariantWith(env, pair.first, pair.second);
1707
}
1708
1709
/*
1710
* This is much simpler than the Unifier implementation because we don't
1711
* actually care about potential "cross-talk" between union parts that match the
1712
* left side.
1713
*
1714
* In fact, we're very limited in what we can do: If multiple choices match, but
1715
* all of them have non-overlapping constraints, then we're stuck with an "or"
1716
* conjunction of constraints. Solving this in the general case is quite
1717
* difficult.
1718
*
1719
* For example, we cannot dispatch anything from this constraint:
1720
*
1721
* {x: number, y: string} <: {x: number, y: 'a} | {x: 'b, y: string}
1722
*
1723
* From this constraint, we can know that either string <: 'a or number <: 'b,
1724
* but we don't know which!
1725
*
1726
* However:
1727
*
1728
* {x: number, y: string} <: {x: number, y: 'a} | {x: number, y: string}
1729
*
1730
* We can dispatch this constraint because there is no 'or' conjunction. One of
1731
* the arms requires 0 matches.
1732
*
1733
* {x: number, y: string, z: boolean} | {x: number, y: 'a, z: 'b} | {x: number,
1734
* y: string, z: 'b}
1735
*
1736
* Here, we have two matches. One asks for string ~ 'a and boolean ~ 'b. The
1737
* other just asks for boolean ~ 'b. We can dispatch this and only commit
1738
* boolean ~ 'b. This constraint does not teach us anything about 'a.
1739
*/
1740
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const UnionType* superUnion, NotNull<Scope> scope)
1741
{
1742
// As per TAPL: T <: A | B iff T <: A || T <: B
1743
1744
SubtypingResult result{false};
1745
1746
size_t index = 0;
1747
for (TypeId ty : superUnion)
1748
{
1749
SubtypingResult next = isCovariantWith(env, subTy, ty, scope);
1750
1751
if (next.normalizationTooComplex)
1752
return SubtypingResult{false, /* normalizationTooComplex */ true};
1753
1754
if (next.isSubtype)
1755
return FFlag::LuauUnifyWithSubtyping2 ? next : SubtypingResult{true};
1756
1757
if (FFlag::LuauMorePreciseErrorSuppression)
1758
{
1759
result.andAlso(next.withSuperComponent(TypePath::Index{index, TypePath::Index::Variant::Union}));
1760
++index;
1761
}
1762
}
1763
1764
return result;
1765
}
1766
1767
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const UnionType* subUnion, TypeId superTy, NotNull<Scope> scope)
1768
{
1769
// As per TAPL: A | B <: T iff A <: T && B <: T
1770
// Keep in the heap for stack pressure reasons.
1771
std::unique_ptr<SubtypingResult> result = std::make_unique<SubtypingResult>();
1772
result->isSubtype = true;
1773
size_t i = 0;
1774
for (TypeId ty : subUnion)
1775
{
1776
result->andAlso(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Union}));
1777
1778
if (result->normalizationTooComplex)
1779
return SubtypingResult{false, /* normalizationTooComplex */ true};
1780
}
1781
1782
return *result;
1783
}
1784
1785
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const IntersectionType* superIntersection, NotNull<Scope> scope)
1786
{
1787
// As per TAPL: T <: A & B iff T <: A && T <: B
1788
std::unique_ptr<SubtypingResult> result = std::make_unique<SubtypingResult>();
1789
result->isSubtype = true;
1790
size_t i = 0;
1791
for (TypeId ty : superIntersection)
1792
{
1793
result->andAlso(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
1794
1795
if (result->normalizationTooComplex)
1796
return SubtypingResult{false, /* normalizationTooComplex */ true};
1797
}
1798
1799
return *result;
1800
}
1801
1802
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const IntersectionType* subIntersection, TypeId superTy, NotNull<Scope> scope)
1803
{
1804
// As per TAPL: A & B <: T iff A <: T || B <: T
1805
std::unique_ptr<SubtypingResult> result = std::make_unique<SubtypingResult>();
1806
result->isSubtype = false;
1807
size_t i = 0;
1808
for (TypeId ty : subIntersection)
1809
{
1810
result->orElse(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
1811
1812
if (result->normalizationTooComplex)
1813
return SubtypingResult{false, /* normalizationTooComplex */ true};
1814
}
1815
1816
return *result;
1817
}
1818
1819
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NegationType* subNegation, TypeId superTy, NotNull<Scope> scope)
1820
{
1821
TypeId negatedTy = follow(subNegation->ty);
1822
1823
SubtypingResult result;
1824
1825
// In order to follow a consistent codepath, rather than folding the
1826
// isCovariantWith test down to its conclusion here, we test the subtyping test
1827
// of the result of negating the type for never, unknown, any, and error.
1828
if (is<NeverType>(negatedTy))
1829
{
1830
// ¬never ~ unknown
1831
result = isCovariantWith(env, builtinTypes->unknownType, superTy, scope).withSubComponent(TypePath::TypeField::Negated);
1832
}
1833
else if (is<UnknownType>(negatedTy))
1834
{
1835
// ¬unknown ~ never
1836
result = isCovariantWith(env, builtinTypes->neverType, superTy, scope).withSubComponent(TypePath::TypeField::Negated);
1837
}
1838
else if (is<AnyType>(negatedTy))
1839
{
1840
// ¬any ~ any
1841
result = isCovariantWith(env, negatedTy, superTy, scope).withSubComponent(TypePath::TypeField::Negated);
1842
}
1843
else if (auto u = get<UnionType>(negatedTy))
1844
{
1845
// ¬(A ∪ B) ~ ¬A ∩ ¬B
1846
// follow intersection rules: A & B <: T iff A <: T && B <: T
1847
result = {true};
1848
1849
for (TypeId ty : u)
1850
{
1851
if (auto negatedPart = get<NegationType>(follow(ty)))
1852
result.andAlso(isCovariantWith(env, negatedPart->ty, superTy, scope).withSubComponent(TypePath::TypeField::Negated));
1853
else
1854
{
1855
NegationType negatedTmp{ty};
1856
result.andAlso(isCovariantWith(env, &negatedTmp, superTy, scope));
1857
}
1858
}
1859
}
1860
else if (auto i = get<IntersectionType>(negatedTy))
1861
{
1862
// ¬(A ∩ B) ~ ¬A ∪ ¬B
1863
// follow union rules: A | B <: T iff A <: T || B <: T
1864
result = {false};
1865
1866
for (TypeId ty : i)
1867
{
1868
if (auto negatedPart = get<NegationType>(follow(ty)))
1869
result.orElse(isCovariantWith(env, negatedPart->ty, superTy, scope).withSubComponent(TypePath::TypeField::Negated));
1870
else
1871
{
1872
NegationType negatedTmp{ty};
1873
result.orElse(isCovariantWith(env, &negatedTmp, superTy, scope));
1874
}
1875
}
1876
}
1877
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
1878
{
1879
iceReporter->ice("attempting to negate a non-testable type");
1880
}
1881
// negating a different subtype will get you a very wide type that's not a
1882
// subtype of other stuff.
1883
else
1884
{
1885
result = SubtypingResult{false}.withSubComponent(TypePath::TypeField::Negated);
1886
}
1887
1888
return result;
1889
}
1890
1891
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const NegationType* superNegation, NotNull<Scope> scope)
1892
{
1893
TypeId negatedTy = follow(superNegation->ty);
1894
1895
SubtypingResult result;
1896
1897
if (is<NeverType>(negatedTy))
1898
{
1899
// ¬never ~ unknown
1900
result = isCovariantWith(env, subTy, builtinTypes->unknownType, scope);
1901
}
1902
else if (is<UnknownType>(negatedTy))
1903
{
1904
// ¬unknown ~ never
1905
result = isCovariantWith(env, subTy, builtinTypes->neverType, scope);
1906
}
1907
else if (is<AnyType>(negatedTy))
1908
{
1909
// ¬any ~ any
1910
result = FFlag::LuauUnifyWithSubtyping2 ? isCovariantWith(env, subTy, negatedTy, scope) : isSubtype(subTy, negatedTy, scope);
1911
}
1912
else if (auto u = get<UnionType>(negatedTy))
1913
{
1914
// ¬(A ∪ B) ~ ¬A ∩ ¬B
1915
// follow intersection rules: A & B <: T iff A <: T && B <: T
1916
std::vector<SubtypingResult> subtypings;
1917
result = {true};
1918
1919
for (TypeId ty : u)
1920
{
1921
if (auto negatedPart = get<NegationType>(follow(ty)))
1922
result.andAlso(isCovariantWith(env, subTy, negatedPart->ty, scope));
1923
else
1924
{
1925
NegationType negatedTmp{ty};
1926
result.andAlso(isCovariantWith(env, subTy, &negatedTmp, scope));
1927
}
1928
}
1929
}
1930
else if (auto i = get<IntersectionType>(negatedTy))
1931
{
1932
// ¬(A ∩ B) ~ ¬A ∪ ¬B
1933
// follow union rules: A | B <: T iff A <: T || B <: T
1934
result = {false};
1935
1936
for (TypeId ty : i)
1937
{
1938
if (auto negatedPart = get<NegationType>(follow(ty)))
1939
result.orElse(isCovariantWith(env, subTy, negatedPart->ty, scope));
1940
else
1941
{
1942
NegationType negatedTmp{ty};
1943
result.orElse(isCovariantWith(env, subTy, &negatedTmp, scope));
1944
}
1945
}
1946
1947
}
1948
else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, negatedTy))
1949
{
1950
// number <: ¬boolean
1951
// number </: ¬number
1952
result = {p.first->type != p.second->type};
1953
}
1954
else if (auto p = get2<SingletonType, PrimitiveType>(subTy, negatedTy))
1955
{
1956
// "foo" </: ¬string
1957
if (get<StringSingleton>(p.first) && p.second->type == PrimitiveType::String)
1958
result = {false};
1959
// false </: ¬boolean
1960
else if (get<BooleanSingleton>(p.first) && p.second->type == PrimitiveType::Boolean)
1961
result = {false};
1962
// other cases are true
1963
else
1964
result = {true};
1965
}
1966
else if (auto p = get2<PrimitiveType, SingletonType>(subTy, negatedTy))
1967
{
1968
if (p.first->type == PrimitiveType::String && get<StringSingleton>(p.second))
1969
result = {false};
1970
else if (p.first->type == PrimitiveType::Boolean && get<BooleanSingleton>(p.second))
1971
result = {false};
1972
else
1973
result = {true};
1974
}
1975
// the top class type is not actually a primitive type, so the negation of
1976
// any one of them includes the top class type.
1977
else if (auto p = get2<ExternType, PrimitiveType>(subTy, negatedTy))
1978
result = {true};
1979
else if (auto p = get<PrimitiveType>(negatedTy); p && is<TableType, MetatableType>(subTy))
1980
result = {p->type != PrimitiveType::Table};
1981
else if (auto p = get2<FunctionType, PrimitiveType>(subTy, negatedTy))
1982
result = {p.second->type != PrimitiveType::Function};
1983
else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy))
1984
result = {*p.first != *p.second};
1985
else if (auto p = get2<ExternType, ExternType>(subTy, negatedTy))
1986
result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second, scope));
1987
else if (get2<FunctionType, ExternType>(subTy, negatedTy))
1988
result = {true};
1989
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
1990
iceReporter->ice("attempting to negate a non-testable type");
1991
else
1992
result = {false};
1993
1994
return result.withSuperComponent(TypePath::TypeField::Negated);
1995
}
1996
1997
SubtypingResult Subtyping::isCovariantWith(
1998
SubtypingEnvironment& env,
1999
const PrimitiveType* subPrim,
2000
const PrimitiveType* superPrim,
2001
NotNull<Scope> scope
2002
)
2003
{
2004
return {subPrim->type == superPrim->type};
2005
}
2006
2007
SubtypingResult Subtyping::isCovariantWith(
2008
SubtypingEnvironment& env,
2009
const SingletonType* subSingleton,
2010
const PrimitiveType* superPrim,
2011
NotNull<Scope> scope
2012
)
2013
{
2014
if (get<StringSingleton>(subSingleton) && superPrim->type == PrimitiveType::String)
2015
return {true};
2016
else if (get<BooleanSingleton>(subSingleton) && superPrim->type == PrimitiveType::Boolean)
2017
return {true};
2018
else
2019
return {false};
2020
}
2021
2022
SubtypingResult Subtyping::isCovariantWith(
2023
SubtypingEnvironment& env,
2024
const SingletonType* subSingleton,
2025
const SingletonType* superSingleton,
2026
NotNull<Scope> scope
2027
)
2028
{
2029
return {*subSingleton == *superSingleton};
2030
}
2031
2032
SubtypingResult Subtyping::isCovariantWith(
2033
SubtypingEnvironment& env,
2034
const TableType* subTable,
2035
const TableType* superTable,
2036
bool forceCovariantTest,
2037
NotNull<Scope> scope
2038
)
2039
{
2040
SubtypingResult result{true};
2041
2042
if (subTable->props.empty() && !subTable->indexer && subTable->state == TableState::Sealed && superTable->indexer)
2043
{
2044
// While it is certainly the case that {} </: {T}, the story is a little bit different for {| |} <: {T}
2045
// The shape of an unsealed table is still in flux, so it is probably the case that the unsealed table
2046
// will later gain the necessary indexer as type inference proceeds.
2047
//
2048
// Unsealed tables are always sealed by the time inference completes, so this should never affect the
2049
// type checking phase.
2050
return {false};
2051
}
2052
2053
for (const auto& [name, superProp] : superTable->props)
2054
{
2055
std::vector<SubtypingResult> results;
2056
if (auto subIter = subTable->props.find(name); subIter != subTable->props.end())
2057
results.push_back(isCovariantWith(env, subIter->second, superProp, name, forceCovariantTest, scope));
2058
else if (subTable->indexer)
2059
{
2060
if (isCovariantWith(env, builtinTypes->stringType, subTable->indexer->indexType, scope).isSubtype)
2061
{
2062
if (superProp.isShared())
2063
{
2064
results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, *superProp.readTy, scope)
2065
.withSubComponent(TypePath::TypeField::IndexResult)
2066
.withSuperComponent(TypePath::Property::read(name)));
2067
}
2068
else
2069
{
2070
if (superProp.readTy)
2071
results.push_back(isCovariantWith(env, subTable->indexer->indexResultType, *superProp.readTy, scope)
2072
.withSubComponent(TypePath::TypeField::IndexResult)
2073
.withSuperComponent(TypePath::Property::read(name)));
2074
if (superProp.writeTy)
2075
results.push_back(isContravariantWith(env, subTable->indexer->indexResultType, *superProp.writeTy, scope)
2076
.withSubComponent(TypePath::TypeField::IndexResult)
2077
.withSuperComponent(TypePath::Property::write(name)));
2078
}
2079
}
2080
}
2081
else if (FFlag::LuauSubtypingMissingPropertiesAsNil)
2082
{
2083
SubtypingResult result = isCovariantWith(env, Property::readonly(builtinTypes->nilType), superProp, name, forceCovariantTest, scope);
2084
// We must ignore the actual reasoning from here because the subtype doesn't have a property to traverse into later.
2085
// If there is a type error, we want to point at this spot as being responsible for it!
2086
result.reasoning.clear();
2087
results.push_back(result);
2088
}
2089
2090
if (results.empty())
2091
return SubtypingResult{false};
2092
2093
if (FFlag::LuauMorePreciseErrorSuppression)
2094
{
2095
bool isSubtype = true;
2096
for (const SubtypingResult& sr : results)
2097
isSubtype &= sr.isSubtype;
2098
2099
// If the first failed subtype test is a suppressing failure, then
2100
// we set the suppression bit in case there are no subsequent
2101
// non-suppressing failures.
2102
//
2103
// If we at any point encounter a non-suppressing failure, then this
2104
// whole subtype test is a non-suppressing failure.
2105
if (result.isSubtype && !isSubtype)
2106
{
2107
for (const SubtypingResult& sr : results)
2108
result.andAlso(sr, SubtypingSuppressionPolicy::Any);
2109
}
2110
else
2111
{
2112
for (const SubtypingResult& sr : results)
2113
result.andAlso(sr, SubtypingSuppressionPolicy::All);
2114
}
2115
}
2116
else
2117
{
2118
for (auto&& sr : results)
2119
result.andAlso(sr);
2120
}
2121
}
2122
2123
if (superTable->indexer)
2124
{
2125
if (subTable->indexer)
2126
result.andAlso(isInvariantWith(env, *subTable->indexer, *superTable->indexer, scope));
2127
else if (subTable->state != TableState::Sealed)
2128
{
2129
// As above, we assume that {| |} <: {T} because the unsealed table
2130
// on the left will eventually gain the necessary indexer.
2131
return {true};
2132
}
2133
else
2134
return {false};
2135
}
2136
2137
return result;
2138
}
2139
2140
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope)
2141
{
2142
return isCovariantWith(env, subMt->table, superMt->table, scope)
2143
.withBothComponent(TypePath::TypeField::Table)
2144
.andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable));
2145
}
2146
2147
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope)
2148
{
2149
if (auto subTable = get<TableType>(follow(subMt->table)))
2150
{
2151
auto doDefault = [&]()
2152
{
2153
return isCovariantWith(env, subTable, superTable, /* forceCovariantTest */ false, scope);
2154
};
2155
2156
// My kingdom for `do` notation.
2157
2158
// TODO CLI-169235: This logic is very mechanical and is,
2159
// effectively a repeat of the logic for the `index<_, _>`
2160
// type function. These should use similar logic. Otherwise
2161
// this all constantly falls over for the same reasons
2162
// structural subtyping falls over.
2163
//
2164
// Notably, this does not support `__index` as a function.
2165
2166
auto subMTTable = get<TableType>(follow(subMt->metatable));
2167
if (!subMTTable)
2168
return doDefault();
2169
2170
auto indexProp = subMTTable->props.find("__index");
2171
if (indexProp == subMTTable->props.end())
2172
return doDefault();
2173
2174
// `read`-only __index sounds reasonable, but write-only
2175
// or non-shared sounds weird.
2176
if (!indexProp->second.readTy)
2177
return doDefault();
2178
2179
auto indexTableProp = get<TableType>(follow(*indexProp->second.readTy));
2180
if (!indexTableProp)
2181
return doDefault();
2182
2183
// Consider the snippet:
2184
//
2185
// local ItemContainer = {}
2186
// ItemContainer.__index = ItemContainer
2187
//
2188
// function ItemContainer.new()
2189
// local self = {}
2190
// setmetatable(self, ItemContainer)
2191
// return self
2192
// end
2193
//
2194
// function ItemContainer:removeItem(itemId, itemType)
2195
// self:getItem(itemId, itemType)
2196
// end
2197
//
2198
// function ItemContainer:getItem(itemId, itemType) end
2199
//
2200
// local container = ItemContainer.new()
2201
// container:removeItem(0, "magic")
2202
//
2203
// When we go to check this, we're effectively asking whether
2204
// `container` is a subtype of the first argument of
2205
// `container.removeItem`. `container` has a metatable with the
2206
// `__index` metamethod, so we need to include those fields in the
2207
// subtype check.
2208
//
2209
// However, we need to include a read only view of those fields.
2210
// Consider:
2211
//
2212
// local Foobar = {}
2213
// Foobar.__index = Foobar
2214
// Foobar.const = 42
2215
//
2216
// local foobar = setmetatable({}, Foobar)
2217
//
2218
// local _: { const: number } = foobar
2219
//
2220
// This should error, as we cannot write to `const`.
2221
2222
TableType fauxSubTable{*subTable};
2223
for (auto& [name, prop] : indexTableProp->props)
2224
{
2225
if (prop.readTy && fauxSubTable.props.find(name) == fauxSubTable.props.end())
2226
fauxSubTable.props[name] = Property::readonly(*prop.readTy);
2227
}
2228
2229
return isCovariantWith(env, &fauxSubTable, superTable, /* forceCovariantTest */ false, scope);
2230
}
2231
else
2232
{
2233
// TODO: This may be a case we actually hit?
2234
return {false};
2235
}
2236
}
2237
2238
SubtypingResult Subtyping::isCovariantWith(
2239
SubtypingEnvironment& env,
2240
const MetatableType* subMt,
2241
const PrimitiveType* superPrim,
2242
NotNull<Scope> scope
2243
)
2244
{
2245
LUAU_ASSERT(FFlag::LuauTableFreezeCheckIsSubtype);
2246
2247
// Metatable types can be subtypes of primitive table types if their table component is a subtype of table.
2248
if (superPrim->type == PrimitiveType::Table)
2249
{
2250
if (auto subTable = get<TableType>(follow(subMt->table)))
2251
{
2252
return isCovariantWith(env, subTable, superPrim, scope);
2253
}
2254
else if (auto subNestedMt = get<MetatableType>(follow(subMt->table)))
2255
{
2256
return isCovariantWith(env, subNestedMt, superPrim, scope);
2257
}
2258
}
2259
2260
return {false};
2261
}
2262
2263
SubtypingResult Subtyping::isCovariantWith(
2264
SubtypingEnvironment& env,
2265
const ExternType* subExternType,
2266
const ExternType* superExternType,
2267
NotNull<Scope> scope
2268
)
2269
{
2270
return {isSubclass(subExternType, superExternType)};
2271
}
2272
2273
SubtypingResult Subtyping::isCovariantWith(
2274
SubtypingEnvironment& env,
2275
TypeId subTy,
2276
const ExternType* subExternType,
2277
TypeId superTy,
2278
const TableType* superTable,
2279
NotNull<Scope> scope
2280
)
2281
{
2282
SubtypingResult result{true};
2283
2284
env.substitutions[superTy] = subTy;
2285
2286
for (const auto& [name, prop] : superTable->props)
2287
{
2288
if (auto classProp = lookupExternTypeProp(subExternType, name))
2289
{
2290
result.andAlso(isCovariantWith(env, *classProp, prop, name, /*forceCovariantTest*/ false, scope));
2291
}
2292
else
2293
{
2294
result = {false};
2295
break;
2296
}
2297
}
2298
2299
if (superTable->indexer && subExternType->indexer)
2300
{
2301
// NOTE: despite the name, this will internally check that the two
2302
// indexers are invariant with one another.
2303
result.andAlso(isCovariantWith(env, *subExternType->indexer, *superTable->indexer, scope));
2304
}
2305
else if (superTable->indexer && !subExternType->indexer)
2306
{
2307
// If the super table has an indexer and the sub extern type does
2308
// not, claim this isn't a subtype. For example:
2309
//
2310
// declare extern type Vector3 with
2311
// X: number
2312
// Y: number
2313
// Z: number
2314
// end
2315
//
2316
// local function cast(v: Vector3): { [string]: number }
2317
// return v
2318
// end
2319
//
2320
// local function ohno(v: Vector3)
2321
// local v_prime = cast(v)
2322
// v_prime["well thats not good"] = 42
2323
// end
2324
result = {/* isSubtype */ false};
2325
}
2326
// The remaining cases are:
2327
// - The extern type has an indexer and the table does not: this is
2328
// fine under width subtyping (for now).
2329
// - Neither the extern type nor the table has an indexer, which is
2330
// also fine.
2331
2332
env.substitutions[superTy] = nullptr;
2333
2334
return result;
2335
}
2336
2337
SubtypingResult Subtyping::isCovariantWith(
2338
SubtypingEnvironment& env,
2339
const FunctionType* subFunction,
2340
const FunctionType* superFunction,
2341
NotNull<Scope> scope
2342
)
2343
{
2344
SubtypingResult result;
2345
2346
if (!subFunction->generics.empty())
2347
{
2348
for (TypeId g : subFunction->generics)
2349
{
2350
g = follow(g);
2351
if (get<GenericType>(g))
2352
{
2353
if (auto bounds = env.mappedGenerics.find(g))
2354
// g may shadow an existing generic, so push a fresh set of bounds
2355
bounds->emplace_back();
2356
else
2357
env.mappedGenerics[g] = {SubtypingEnvironment::GenericBounds{}};
2358
}
2359
}
2360
}
2361
2362
if (!subFunction->genericPacks.empty())
2363
{
2364
std::vector<TypePackId> packs;
2365
packs.reserve(subFunction->genericPacks.size());
2366
2367
for (TypePackId g : subFunction->genericPacks)
2368
{
2369
g = follow(g);
2370
if (get<GenericTypePack>(g))
2371
packs.emplace_back(g);
2372
}
2373
2374
env.mappedGenericPacks.pushFrame(packs);
2375
}
2376
2377
{
2378
result.orElse(
2379
isContravariantWith(env, subFunction->argTypes, superFunction->argTypes, scope).withBothComponent(TypePath::PackField::Arguments)
2380
);
2381
2382
// If subtyping failed in the argument packs, we should check if there's a hidden variadic tail and try ignoring it.
2383
// This might cause subtyping correctly because the sub type here may not have a hidden variadic tail or equivalent.
2384
if (!result.isSubtype)
2385
{
2386
auto [arguments, tail] = flatten(superFunction->argTypes);
2387
2388
if (auto variadic = get<VariadicTypePack>(tail); variadic && variadic->hidden)
2389
{
2390
result.orElse(isContravariantWith(env, subFunction->argTypes, arena->addTypePack(TypePack{arguments}), scope)
2391
.withBothComponent(TypePath::PackField::Arguments));
2392
}
2393
}
2394
}
2395
2396
result.andAlso(isCovariantWith(env, subFunction->retTypes, superFunction->retTypes, scope).withBothComponent(TypePath::PackField::Returns));
2397
2398
if (*subFunction->argTypes == *superFunction->argTypes && *subFunction->retTypes == *superFunction->retTypes)
2399
{
2400
if (FFlag::LuauOverloadGetsInstantiated)
2401
{
2402
// It's fine to upcast a function with generics to a function without, for example:
2403
//
2404
// local f: ({number}) -> number = (nil :: <T>({T}) -> T)
2405
//
2406
// ... or even ...
2407
//
2408
// local f: () -> () = (nil :: <T>() -> ())
2409
//
2410
// Intuitively: a generic function should always be a subtype of its instantiations.
2411
if (superFunction->generics.size() != subFunction->generics.size() && !superFunction->generics.empty())
2412
result.andAlso({false}).withError(
2413
TypeError{scope->location, GenericTypeCountMismatch{superFunction->generics.size(), subFunction->generics.size()}}
2414
);
2415
2416
if (superFunction->genericPacks.size() != subFunction->genericPacks.size() && !superFunction->genericPacks.empty())
2417
result.andAlso({false}).withError(
2418
TypeError{scope->location, GenericTypePackCountMismatch{superFunction->genericPacks.size(), subFunction->genericPacks.size()}}
2419
);
2420
}
2421
else
2422
{
2423
if (superFunction->generics.size() != subFunction->generics.size())
2424
result.andAlso({false}).withError(
2425
TypeError{scope->location, GenericTypeCountMismatch{superFunction->generics.size(), subFunction->generics.size()}}
2426
);
2427
if (superFunction->genericPacks.size() != subFunction->genericPacks.size())
2428
result.andAlso({false}).withError(
2429
TypeError{scope->location, GenericTypePackCountMismatch{superFunction->genericPacks.size(), subFunction->genericPacks.size()}}
2430
);
2431
}
2432
}
2433
2434
if (!subFunction->generics.empty())
2435
{
2436
for (TypeId g : subFunction->generics)
2437
{
2438
g = follow(g);
2439
if (const GenericType* gen = get<GenericType>(g))
2440
{
2441
auto bounds = env.mappedGenerics.find(g);
2442
LUAU_ASSERT(bounds && !bounds->empty());
2443
// Check the bounds are valid
2444
result.andAlso(checkGenericBounds(bounds->back(), env, scope, gen->name));
2445
2446
bounds->pop_back();
2447
}
2448
}
2449
}
2450
2451
if (!subFunction->genericPacks.empty())
2452
{
2453
env.mappedGenericPacks.popFrame();
2454
// This result isn't cacheable, because we may need it to populate the generic pack mapping environment again later
2455
result.isCacheable = false;
2456
}
2457
2458
return result;
2459
}
2460
2461
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const PrimitiveType* superPrim, NotNull<Scope> scope)
2462
{
2463
SubtypingResult result{false};
2464
if (superPrim->type == PrimitiveType::Table)
2465
result.isSubtype = true;
2466
2467
return result;
2468
}
2469
2470
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const TableType* superTable, NotNull<Scope> scope)
2471
{
2472
SubtypingResult result{false};
2473
if (subPrim->type == PrimitiveType::String)
2474
{
2475
if (auto metatable = getMetatable(builtinTypes->stringType, builtinTypes))
2476
{
2477
if (auto mttv = get<TableType>(follow(metatable)))
2478
{
2479
if (auto it = mttv->props.find("__index"); it != mttv->props.end())
2480
{
2481
// the `string` metatable should not have any write-only types.
2482
LUAU_ASSERT(*it->second.readTy);
2483
2484
if (auto stringTable = get<TableType>(*it->second.readTy))
2485
result.orElse(isCovariantWith(env, stringTable, superTable, /*forceCovariantTest*/ false, scope)
2486
.withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()));
2487
}
2488
}
2489
}
2490
}
2491
else if (subPrim->type == PrimitiveType::Table)
2492
{
2493
const bool isSubtype = superTable->props.empty() && (!superTable->indexer.has_value() || superTable->state == TableState::Generic);
2494
return {isSubtype};
2495
}
2496
2497
return result;
2498
}
2499
2500
SubtypingResult Subtyping::isCovariantWith(
2501
SubtypingEnvironment& env,
2502
const SingletonType* subSingleton,
2503
const TableType* superTable,
2504
NotNull<Scope> scope
2505
)
2506
{
2507
SubtypingResult result{false};
2508
if (get<StringSingleton>(subSingleton))
2509
{
2510
if (auto metatable = getMetatable(builtinTypes->stringType, builtinTypes))
2511
{
2512
if (auto mttv = get<TableType>(follow(metatable)))
2513
{
2514
if (auto it = mttv->props.find("__index"); it != mttv->props.end())
2515
{
2516
// the `string` metatable should not have any write-only types.
2517
LUAU_ASSERT(*it->second.readTy);
2518
2519
if (auto stringTable = get<TableType>(*it->second.readTy))
2520
result.orElse(isCovariantWith(env, stringTable, superTable, /*forceCovariantTest*/ false, scope)
2521
.withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()));
2522
}
2523
}
2524
}
2525
}
2526
return result;
2527
}
2528
2529
SubtypingResult Subtyping::isCovariantWith(
2530
SubtypingEnvironment& env,
2531
const TableIndexer& subIndexer,
2532
const TableIndexer& superIndexer,
2533
NotNull<Scope> scope
2534
)
2535
{
2536
return isInvariantWith(env, subIndexer.indexType, superIndexer.indexType, scope)
2537
.withBothComponent(TypePath::TypeField::IndexLookup)
2538
.andAlso(
2539
isInvariantWith(env, subIndexer.indexResultType, superIndexer.indexResultType, scope).withBothComponent(TypePath::TypeField::IndexResult)
2540
);
2541
}
2542
2543
SubtypingResult Subtyping::isCovariantWith(
2544
SubtypingEnvironment& env,
2545
const Property& subProp,
2546
const Property& superProp,
2547
const std::string& name,
2548
bool forceCovariantTest,
2549
NotNull<Scope> scope
2550
)
2551
{
2552
SubtypingResult res{true};
2553
2554
if (superProp.isShared() && subProp.isShared())
2555
{
2556
if (forceCovariantTest)
2557
res.andAlso(isCovariantWith(env, *subProp.readTy, *superProp.readTy, scope).withBothComponent(TypePath::Property::read(name)));
2558
else
2559
res.andAlso(isInvariantWith(env, *subProp.readTy, *superProp.readTy, scope).withBothComponent(TypePath::Property::read(name)));
2560
}
2561
else
2562
{
2563
if (superProp.readTy.has_value() && subProp.readTy.has_value())
2564
res.andAlso(isCovariantWith(env, *subProp.readTy, *superProp.readTy, scope).withBothComponent(TypePath::Property::read(name)));
2565
if (superProp.writeTy.has_value() && subProp.writeTy.has_value() && !forceCovariantTest)
2566
res.andAlso(isContravariantWith(env, *subProp.writeTy, *superProp.writeTy, scope).withBothComponent(TypePath::Property::write(name)));
2567
2568
if (superProp.isReadWrite())
2569
{
2570
if (subProp.isReadOnly())
2571
res.andAlso(SubtypingResult{false}.withBothComponent(TypePath::Property::read(name)));
2572
else if (subProp.isWriteOnly())
2573
res.andAlso(SubtypingResult{false}.withBothComponent(TypePath::Property::write(name)));
2574
}
2575
}
2576
2577
return res;
2578
}
2579
2580
SubtypingResult Subtyping::isCovariantWith(
2581
SubtypingEnvironment& env,
2582
const std::shared_ptr<const NormalizedType>& subNorm,
2583
const std::shared_ptr<const NormalizedType>& superNorm,
2584
NotNull<Scope> scope
2585
)
2586
{
2587
if (!subNorm || !superNorm)
2588
return {false, true};
2589
2590
SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope);
2591
result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope));
2592
result.andAlso(isCovariantWith(env, subNorm->externTypes, superNorm->externTypes, scope)
2593
.orElse(isCovariantWith(env, subNorm->externTypes, superNorm->tables, scope)));
2594
result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope));
2595
result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope));
2596
result.andAlso(isCovariantWith(env, subNorm->numbers, superNorm->numbers, scope));
2597
result.andAlso(isCovariantWith(env, subNorm->strings, superNorm->strings, scope));
2598
result.andAlso(isCovariantWith(env, subNorm->strings, superNorm->tables, scope));
2599
result.andAlso(isCovariantWith(env, subNorm->threads, superNorm->threads, scope));
2600
result.andAlso(isCovariantWith(env, subNorm->buffers, superNorm->buffers, scope));
2601
result.andAlso(isCovariantWith(env, subNorm->tables, superNorm->tables, scope));
2602
result.andAlso(isCovariantWith(env, subNorm->functions, superNorm->functions, scope));
2603
// isCovariantWith(subNorm->tyvars, superNorm->tyvars);
2604
return result;
2605
}
2606
2607
SubtypingResult Subtyping::isCovariantWith(
2608
SubtypingEnvironment& env,
2609
const NormalizedExternType& subExternType,
2610
const NormalizedExternType& superExternType,
2611
NotNull<Scope> scope
2612
)
2613
{
2614
for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
2615
{
2616
SubtypingResult result;
2617
2618
for (const auto& [superExternTypeTy, superNegations] : superExternType.externTypes)
2619
{
2620
result.orElse(isCovariantWith(env, subExternTypeTy, superExternTypeTy, scope));
2621
if (!result.isSubtype)
2622
continue;
2623
2624
for (TypeId negation : superNegations)
2625
{
2626
result.andAlso(SubtypingResult::negate(isCovariantWith(env, subExternTypeTy, negation, scope)));
2627
if (result.isSubtype)
2628
break;
2629
}
2630
}
2631
2632
if (!result.isSubtype)
2633
return result;
2634
}
2635
2636
return {true};
2637
}
2638
2639
SubtypingResult Subtyping::isCovariantWith(
2640
SubtypingEnvironment& env,
2641
const NormalizedExternType& subExternType,
2642
const TypeIds& superTables,
2643
NotNull<Scope> scope
2644
)
2645
{
2646
for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
2647
{
2648
SubtypingResult result;
2649
2650
for (TypeId superTableTy : superTables)
2651
result.orElse(isCovariantWith(env, subExternTypeTy, superTableTy, scope));
2652
2653
if (!result.isSubtype)
2654
return result;
2655
}
2656
2657
return {true};
2658
}
2659
2660
SubtypingResult Subtyping::isCovariantWith(
2661
SubtypingEnvironment& env,
2662
const NormalizedStringType& subString,
2663
const NormalizedStringType& superString,
2664
NotNull<Scope> scope
2665
)
2666
{
2667
bool isSubtype = Luau::isSubtype(subString, superString);
2668
return {isSubtype};
2669
}
2670
2671
SubtypingResult Subtyping::isCovariantWith(
2672
SubtypingEnvironment& env,
2673
const NormalizedStringType& subString,
2674
const TypeIds& superTables,
2675
NotNull<Scope> scope
2676
)
2677
{
2678
if (subString.isNever())
2679
return {true};
2680
2681
if (subString.isCofinite)
2682
{
2683
SubtypingResult result;
2684
for (const auto& superTable : superTables)
2685
{
2686
result.orElse(isCovariantWith(env, builtinTypes->stringType, superTable, scope));
2687
if (result.isSubtype)
2688
return result;
2689
}
2690
return result;
2691
}
2692
2693
// Finite case
2694
// S = s1 | s2 | s3 ... sn <: t1 | t2 | ... | tn
2695
// iff for some ti, S <: ti
2696
// iff for all sj, sj <: ti
2697
for (const auto& superTable : superTables)
2698
{
2699
SubtypingResult result{true};
2700
for (const auto& [_, subString] : subString.singletons)
2701
{
2702
result.andAlso(isCovariantWith(env, subString, superTable, scope));
2703
if (!result.isSubtype)
2704
break;
2705
}
2706
2707
if (!result.isSubtype)
2708
continue;
2709
else
2710
return result;
2711
}
2712
2713
return {false};
2714
}
2715
2716
SubtypingResult Subtyping::isCovariantWith(
2717
SubtypingEnvironment& env,
2718
const NormalizedFunctionType& subFunction,
2719
const NormalizedFunctionType& superFunction,
2720
NotNull<Scope> scope
2721
)
2722
{
2723
if (subFunction.isNever())
2724
return {true};
2725
else if (superFunction.isTop)
2726
return {true};
2727
else
2728
return isCovariantWith(env, subFunction.parts, superFunction.parts, scope);
2729
}
2730
2731
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes, NotNull<Scope> scope)
2732
{
2733
auto result = std::make_unique<SubtypingResult>();
2734
result->isSubtype = true;
2735
2736
for (TypeId subTy : subTypes)
2737
{
2738
auto innerResult = std::make_unique<SubtypingResult>();
2739
2740
for (TypeId superTy : superTypes)
2741
{
2742
innerResult->orElse(isCovariantWith(env, subTy, superTy, scope));
2743
2744
if (innerResult->normalizationTooComplex)
2745
return SubtypingResult{false, /* normalizationTooComplex */ true};
2746
}
2747
2748
result->andAlso(*innerResult);
2749
}
2750
2751
return *result;
2752
}
2753
2754
SubtypingResult Subtyping::isCovariantWith(
2755
SubtypingEnvironment& env,
2756
const VariadicTypePack* subVariadic,
2757
const VariadicTypePack* superVariadic,
2758
NotNull<Scope> scope
2759
)
2760
{
2761
return isCovariantWith(env, subVariadic->ty, superVariadic->ty, scope).withBothComponent(TypePath::TypeField::Variadic);
2762
}
2763
2764
bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId superTy) const
2765
{
2766
subTy = follow(subTy);
2767
superTy = follow(superTy);
2768
std::optional<SubtypingEnvironment::GenericBounds> originalSubTyBounds = std::nullopt;
2769
2770
if (const auto subBounds = env.mappedGenerics.find(subTy); subBounds && !subBounds->empty())
2771
{
2772
LUAU_ASSERT(get<GenericType>(subTy));
2773
2774
originalSubTyBounds = SubtypingEnvironment::GenericBounds{subBounds->back()};
2775
2776
auto& [lowerSubBounds, upperSubBounds] = subBounds->back();
2777
2778
if (const auto superBounds = env.mappedGenerics.find(superTy); superBounds && !superBounds->empty())
2779
{
2780
LUAU_ASSERT(get<GenericType>(superTy));
2781
2782
const auto& [lowerSuperBounds, upperSuperBounds] = superBounds->back();
2783
2784
maybeUpdateBounds(subTy, superTy, upperSubBounds, lowerSuperBounds, upperSuperBounds);
2785
}
2786
else
2787
upperSubBounds.insert(superTy);
2788
}
2789
else if (env.containsMappedType(subTy))
2790
iceReporter->ice("attempting to modify bounds of a potentially visited generic");
2791
2792
if (const auto superBounds = env.mappedGenerics.find(superTy); superBounds && !superBounds->empty())
2793
{
2794
LUAU_ASSERT(get<GenericType>(superTy));
2795
2796
auto& [lowerSuperBounds, upperSuperBounds] = superBounds->back();
2797
2798
if (originalSubTyBounds)
2799
{
2800
LUAU_ASSERT(get<GenericType>(subTy));
2801
2802
const auto& [originalLowerSubBound, originalUpperSubBound] = *originalSubTyBounds;
2803
2804
maybeUpdateBounds(superTy, subTy, lowerSuperBounds, originalUpperSubBound, originalLowerSubBound);
2805
}
2806
else
2807
lowerSuperBounds.insert(subTy);
2808
}
2809
else if (env.containsMappedType(superTy))
2810
iceReporter->ice("attempting to modify bounds of a potentially visited generic");
2811
2812
return true;
2813
}
2814
2815
SubtypingResult Subtyping::isCovariantWith(
2816
SubtypingEnvironment& env,
2817
const TypeFunctionInstanceType* subFunctionInstance,
2818
const TypeId superTy,
2819
NotNull<Scope> scope
2820
)
2821
{
2822
// Reduce the type function instance
2823
auto [ty, errors] = handleTypeFunctionReductionResult(subFunctionInstance, scope);
2824
2825
// If we return optional, that means the type function was irreducible - we can reduce that to never
2826
return isCovariantWith(env, ty, superTy, scope).withErrors(errors).withSubComponent(TypePath::Reduction{ty});
2827
}
2828
2829
SubtypingResult Subtyping::isCovariantWith(
2830
SubtypingEnvironment& env,
2831
const TypeId subTy,
2832
const TypeFunctionInstanceType* superFunctionInstance,
2833
NotNull<Scope> scope
2834
)
2835
{
2836
// Reduce the type function instance
2837
auto [ty, errors] = handleTypeFunctionReductionResult(superFunctionInstance, scope);
2838
return isCovariantWith(env, subTy, ty, scope).withErrors(errors).withSuperComponent(TypePath::Reduction{ty});
2839
}
2840
2841
template<typename T, typename Container>
2842
TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse)
2843
{
2844
if (container.empty())
2845
return orElse;
2846
else if (container.size() == 1)
2847
return *begin(container);
2848
else
2849
return arena->addType(T{std::vector<TypeId>(begin(container), end(container))});
2850
}
2851
2852
std::pair<TypeId, ErrorVec> Subtyping::handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance, NotNull<Scope> scope)
2853
{
2854
TypeFunctionContext context{arena, builtinTypes, scope, normalizer, typeFunctionRuntime, iceReporter, NotNull{&limits}};
2855
TypeId function = arena->addType(*functionInstance);
2856
FunctionGraphReductionResult result = reduceTypeFunctions(function, {}, NotNull{&context}, true);
2857
ErrorVec errors;
2858
if (result.blockedTypes.size() != 0 || result.blockedPacks.size() != 0)
2859
{
2860
errors.emplace_back(Location{}, UninhabitedTypeFunction{function});
2861
return {builtinTypes->neverType, errors};
2862
}
2863
if (result.reducedTypes.contains(function))
2864
return {function, errors};
2865
return {builtinTypes->neverType, errors};
2866
}
2867
2868
SubtypingResult Subtyping::trySemanticSubtyping(
2869
SubtypingEnvironment& env,
2870
TypeId subTy,
2871
TypeId superTy,
2872
NotNull<Scope> scope,
2873
SubtypingResult& original
2874
)
2875
{
2876
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
2877
2878
if (semantic.normalizationTooComplex)
2879
{
2880
return semantic;
2881
}
2882
else if (semantic.isSubtype)
2883
{
2884
semantic.reasoning.clear();
2885
return semantic;
2886
}
2887
2888
return original;
2889
}
2890
2891
2892
SubtypingResult Subtyping::checkGenericBounds(
2893
const SubtypingEnvironment::GenericBounds& bounds,
2894
SubtypingEnvironment& env,
2895
NotNull<Scope> scope,
2896
std::string_view genericName
2897
)
2898
{
2899
SubtypingResult result{true};
2900
2901
const auto& [lb, ub] = bounds;
2902
2903
if (FFlag::LuauSubtypingReplaceBounds)
2904
{
2905
UnionBuilder aggregateLowerBound{arena, builtinTypes};
2906
aggregateLowerBound.reserve(lb.size());
2907
for (TypeId t : lb)
2908
{
2909
if (const auto mappedBounds = env.mappedGenerics.find(t); mappedBounds && mappedBounds->empty())
2910
continue;
2911
aggregateLowerBound.add(t);
2912
}
2913
TypeId lowerBound = aggregateLowerBound.build();
2914
2915
IntersectionBuilder aggregateUpperBound{arena, builtinTypes};
2916
aggregateUpperBound.reserve(ub.size());
2917
for (TypeId t : ub)
2918
{
2919
if (const auto mappedBounds = env.mappedGenerics.find(t); mappedBounds && mappedBounds->empty())
2920
continue;
2921
aggregateUpperBound.add(t);
2922
}
2923
TypeId upperBound = aggregateUpperBound.build();
2924
2925
if (auto substLowerBound = env.applyMappedGenerics(builtinTypes, arena, lowerBound, iceReporter))
2926
lowerBound = *substLowerBound;
2927
2928
if (auto substUpperBound = env.applyMappedGenerics(builtinTypes, arena, upperBound, iceReporter))
2929
upperBound = *substUpperBound;
2930
2931
std::shared_ptr<const NormalizedType> nt = normalizer->normalize(upperBound);
2932
// we say that the result is true if normalization failed because complex types are likely to be inhabited.
2933
NormalizationResult res = nt ? normalizer->isInhabited(nt.get()) : NormalizationResult::True;
2934
2935
if (!nt || res == NormalizationResult::HitLimits)
2936
result.normalizationTooComplex = true;
2937
else if (res == NormalizationResult::False)
2938
{
2939
/* If the normalized upper bound we're mapping to a generic is
2940
* uninhabited, then we must consider the subtyping relation not to
2941
* hold.
2942
*
2943
* This happens eg in <T>() -> (T, T) <: () -> (string, number)
2944
*
2945
* T appears in covariant position and would have to be both string
2946
* and number at once.
2947
*
2948
* No actual value is both a string and a number, so the test fails.
2949
*
2950
* TODO: We'll need to add explanitory context here.
2951
*/
2952
result.isSubtype = false;
2953
}
2954
2955
SubtypingEnvironment boundsEnv;
2956
boundsEnv.parent = &env;
2957
SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope);
2958
boundsResult.reasoning.clear();
2959
2960
if (res == NormalizationResult::False)
2961
result.genericBoundsMismatches.emplace_back(genericName, bounds.lowerBound, bounds.upperBound);
2962
else if (!boundsResult.isSubtype)
2963
{
2964
// Check if the bounds are error suppressing before reporting a mismatch
2965
switch (shouldSuppressErrors(normalizer, lowerBound).orElse(shouldSuppressErrors(normalizer, upperBound)))
2966
{
2967
case ErrorSuppression::Suppress:
2968
break;
2969
case ErrorSuppression::NormalizationFailed:
2970
// intentionally fallthrough here since we couldn't prove this was error-suppressing
2971
[[fallthrough]];
2972
case ErrorSuppression::DoNotSuppress:
2973
result.genericBoundsMismatches.emplace_back(genericName, bounds.lowerBound, bounds.upperBound);
2974
break;
2975
default:
2976
LUAU_ASSERT(0);
2977
break;
2978
}
2979
}
2980
2981
result.andAlso(boundsResult);
2982
}
2983
else
2984
{
2985
2986
TypeIds lbTypes;
2987
for (TypeId t : lb)
2988
{
2989
t = follow(t);
2990
if (const auto mappedBounds = env.mappedGenerics.find(t))
2991
{
2992
if (mappedBounds->empty()) // If the generic is no longer in scope, we don't have any info about it
2993
continue;
2994
2995
auto& [lowerBound, upperBound] = mappedBounds->back();
2996
// We're populating the lower bounds, so we prioritize the upper bounds of a mapped generic
2997
if (!upperBound.empty())
2998
lbTypes.insert(upperBound.begin(), upperBound.end());
2999
else if (!lowerBound.empty())
3000
lbTypes.insert(lowerBound.begin(), lowerBound.end());
3001
else
3002
lbTypes.insert(builtinTypes->unknownType);
3003
}
3004
else
3005
lbTypes.insert(t);
3006
}
3007
3008
TypeIds ubTypes;
3009
for (TypeId t : ub)
3010
{
3011
t = follow(t);
3012
if (const auto mappedBounds = env.mappedGenerics.find(t))
3013
{
3014
if (mappedBounds->empty()) // If the generic is no longer in scope, we don't have any info about it
3015
continue;
3016
3017
auto& [lowerBound, upperBound] = mappedBounds->back();
3018
// We're populating the upper bounds, so we prioritize the lower bounds of a mapped generic
3019
if (!lowerBound.empty())
3020
ubTypes.insert(lowerBound.begin(), lowerBound.end());
3021
else if (!upperBound.empty())
3022
ubTypes.insert(upperBound.begin(), upperBound.end());
3023
else
3024
ubTypes.insert(builtinTypes->unknownType);
3025
}
3026
else
3027
ubTypes.insert(t);
3028
}
3029
TypeId lowerBound = makeAggregateType<UnionType>(lbTypes.take(), builtinTypes->neverType);
3030
TypeId upperBound = makeAggregateType<IntersectionType>(ubTypes.take(), builtinTypes->unknownType);
3031
3032
std::shared_ptr<const NormalizedType> nt = normalizer->normalize(upperBound);
3033
// we say that the result is true if normalization failed because complex types are likely to be inhabited.
3034
NormalizationResult res = nt ? normalizer->isInhabited(nt.get()) : NormalizationResult::True;
3035
3036
if (!nt || res == NormalizationResult::HitLimits)
3037
result.normalizationTooComplex = true;
3038
else if (res == NormalizationResult::False)
3039
{
3040
/* If the normalized upper bound we're mapping to a generic is
3041
* uninhabited, then we must consider the subtyping relation not to
3042
* hold.
3043
*
3044
* This happens eg in <T>() -> (T, T) <: () -> (string, number)
3045
*
3046
* T appears in covariant position and would have to be both string
3047
* and number at once.
3048
*
3049
* No actual value is both a string and a number, so the test fails.
3050
*
3051
* TODO: We'll need to add explanitory context here.
3052
*/
3053
result.isSubtype = false;
3054
}
3055
3056
SubtypingEnvironment boundsEnv;
3057
boundsEnv.parent = &env;
3058
SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope);
3059
boundsResult.reasoning.clear();
3060
3061
if (res == NormalizationResult::False)
3062
result.genericBoundsMismatches.emplace_back(genericName, bounds.lowerBound, bounds.upperBound);
3063
else if (!boundsResult.isSubtype)
3064
{
3065
// Check if the bounds are error suppressing before reporting a mismatch
3066
switch (shouldSuppressErrors(normalizer, lowerBound).orElse(shouldSuppressErrors(normalizer, upperBound)))
3067
{
3068
case ErrorSuppression::Suppress:
3069
break;
3070
case ErrorSuppression::NormalizationFailed:
3071
// intentionally fallthrough here since we couldn't prove this was error-suppressing
3072
[[fallthrough]];
3073
case ErrorSuppression::DoNotSuppress:
3074
result.genericBoundsMismatches.emplace_back(genericName, bounds.lowerBound, bounds.upperBound);
3075
break;
3076
default:
3077
LUAU_ASSERT(0);
3078
break;
3079
}
3080
}
3081
3082
result.andAlso(boundsResult);
3083
}
3084
3085
return result;
3086
}
3087
3088
void Subtyping::maybeUpdateBounds(
3089
TypeId here,
3090
TypeId there,
3091
TypeIds& boundsToUpdate,
3092
const TypeIds& firstBoundsToCheck,
3093
const TypeIds& secondBoundsToCheck
3094
)
3095
{
3096
bool boundsChanged = false;
3097
3098
if (!firstBoundsToCheck.empty())
3099
{
3100
for (const TypeId t : firstBoundsToCheck)
3101
{
3102
if (t != here) // We don't want to bound a generic by itself, ie A <: A
3103
{
3104
boundsToUpdate.insert(t);
3105
boundsChanged = true;
3106
}
3107
}
3108
}
3109
if (!boundsChanged && !secondBoundsToCheck.empty())
3110
{
3111
for (const TypeId t : secondBoundsToCheck)
3112
{
3113
if (t != here)
3114
{
3115
boundsToUpdate.insert(t);
3116
boundsChanged = true;
3117
}
3118
}
3119
}
3120
if (!boundsChanged && here != there)
3121
boundsToUpdate.insert(there);
3122
}
3123
3124
} // namespace Luau
3125
3126