Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/Analysis/src/BuiltinTypeFunctions.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/BuiltinTypeFunctions.h"
4
5
#include "Luau/Common.h"
6
#include "Luau/ConstraintSolver.h"
7
#include "Luau/Instantiation.h"
8
#include "Luau/OverloadResolution.h"
9
#include "Luau/Scope.h"
10
#include "Luau/Simplify.h"
11
#include "Luau/Subtyping.h"
12
#include "Luau/Type.h"
13
#include "Luau/TypeArena.h"
14
#include "Luau/TypeUtils.h"
15
#include "Luau/Instantiation2.h"
16
#include "Luau/Unifier2.h"
17
#include "Luau/UserDefinedTypeFunction.h"
18
#include "Luau/VisitType.h"
19
20
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
21
LUAU_DYNAMIC_FASTINTVARIABLE(LuauStepRefineRecursionLimit, 64)
22
23
LUAU_FASTFLAG(LuauOverloadGetsInstantiated)
24
LUAU_FASTFLAGVARIABLE(LuauTypeFunctionsCaptureNestedInstances)
25
LUAU_FASTFLAGVARIABLE(LuauTypeFunctionsAddFreeTypePackWithPositivePolarity)
26
LUAU_FASTFLAGVARIABLE(LuauThreadUniferStateThroughTypeFunctionReduction)
27
28
namespace Luau
29
{
30
31
namespace
32
{
33
34
template<typename F, typename... Args>
35
std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunctionApp(
36
F f,
37
TypeId instance,
38
const std::vector<TypeId>& typeParams,
39
const std::vector<TypePackId>& packParams,
40
NotNull<TypeFunctionContext> ctx,
41
Args&&... args
42
)
43
{
44
// op (a | b) (c | d) ~ (op a (c | d)) | (op b (c | d)) ~ (op a c) | (op a d) | (op b c) | (op b d)
45
Reduction reductionStatus = Reduction::MaybeOk;
46
std::vector<TypeId> blockedTypes;
47
std::vector<TypeId> results;
48
size_t cartesianProductSize = 1;
49
50
const UnionType* firstUnion = nullptr;
51
size_t unionIndex = 0;
52
53
std::vector<TypeId> arguments = typeParams;
54
for (size_t i = 0; i < arguments.size(); ++i)
55
{
56
const UnionType* ut = get<UnionType>(follow(arguments[i]));
57
if (!ut)
58
continue;
59
60
// We want to find the first union type in the set of arguments to distribute that one and only that one union.
61
// The function `f` we have is recursive, so `arguments[unionIndex]` will be updated in-place for each option in
62
// the union we've found in this context, so that index will no longer be a union type. Any other arguments at
63
// index + 1 or after will instead be distributed, if those are a union, which will be subjected to the same rules.
64
if (!firstUnion && ut)
65
{
66
firstUnion = ut;
67
unionIndex = i;
68
}
69
70
cartesianProductSize *= std::distance(begin(ut), end(ut));
71
72
// TODO: We'd like to report that the type function application is too complex here.
73
if (size_t(DFInt::LuauTypeFamilyApplicationCartesianProductLimit) <= cartesianProductSize)
74
return {{std::nullopt, Reduction::Erroneous, {}, {}}};
75
}
76
77
if (!firstUnion)
78
{
79
// If we couldn't find any union type argument, we're not distributing.
80
return std::nullopt;
81
}
82
83
for (TypeId option : firstUnion)
84
{
85
arguments[unionIndex] = option;
86
87
TypeFunctionReductionResult<TypeId> result = f(instance, arguments, packParams, ctx, args...); // NOLINT
88
blockedTypes.insert(blockedTypes.end(), result.blockedTypes.begin(), result.blockedTypes.end());
89
if (result.reductionStatus != Reduction::MaybeOk)
90
reductionStatus = result.reductionStatus;
91
92
if (reductionStatus != Reduction::MaybeOk || !result.result)
93
break;
94
else
95
results.push_back(*result.result);
96
}
97
98
if (reductionStatus != Reduction::MaybeOk || !blockedTypes.empty())
99
return {{std::nullopt, reductionStatus, std::move(blockedTypes), {}}};
100
101
if (!results.empty())
102
{
103
if (results.size() == 1)
104
return {{results[0], Reduction::MaybeOk, {}, {}}};
105
106
TypeId resultTy = ctx->arena->addType(
107
TypeFunctionInstanceType{
108
NotNull{&ctx->builtins->typeFunctions->unionFunc},
109
std::move(results),
110
{},
111
}
112
);
113
114
if (FFlag::LuauTypeFunctionsCaptureNestedInstances)
115
{
116
ctx->freshInstances.emplace_back(resultTy);
117
return {{resultTy, Reduction::MaybeOk}};
118
}
119
else
120
{
121
if (ctx->solver)
122
ctx->pushConstraint(ReduceConstraint{resultTy});
123
124
return {{resultTy, Reduction::MaybeOk, {}, {}, {}, {}, {resultTy}}};
125
}
126
}
127
128
return std::nullopt;
129
}
130
131
} // namespace
132
133
static std::optional<TypePackId> solveFunctionCall(NotNull<TypeFunctionContext> ctx, const Location& location, TypeId fnTy, TypePackId argsPack)
134
{
135
auto resolver = std::make_unique<OverloadResolver>(
136
ctx->builtins, ctx->arena, ctx->normalizer, ctx->typeFunctionRuntime, ctx->scope, ctx->ice, ctx->limits, location
137
);
138
139
DenseHashSet<TypeId> uniqueTypes{nullptr};
140
OverloadResolution resolution = resolver->resolveOverload(fnTy, argsPack, location, NotNull{&uniqueTypes}, /* useFreeTypeBounds */ false);
141
142
if (resolution.ok.empty() && resolution.potentialOverloads.empty())
143
return std::nullopt;
144
145
SelectedOverload selected = resolution.getUnambiguousOverload();
146
147
if (!selected.overload.has_value())
148
return std::nullopt;
149
150
TypePackId retPack = FFlag::LuauTypeFunctionsAddFreeTypePackWithPositivePolarity ? ctx->arena->freshTypePack(ctx->scope, Polarity::Positive)
151
: ctx->arena->freshTypePack(ctx->scope);
152
TypeId prospectiveFunction = ctx->arena->addType(FunctionType{argsPack, retPack});
153
154
// FIXME: It's too bad that we have to bust out the Unifier here. We should
155
// be able to know the set of implied constraints and generic substitutions
156
// that are implied by that overload.
157
//
158
// Given that, we should be able to compute the return pack directly.
159
160
Unifier2 unifier{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
161
162
const UnifyResult unifyResult = unifier.unify(*selected.overload, prospectiveFunction);
163
164
switch (unifyResult)
165
{
166
case Luau::UnifyResult::Ok:
167
break;
168
case Luau::UnifyResult::OccursCheckFailed:
169
return std::nullopt;
170
case Luau::UnifyResult::TooComplex:
171
return std::nullopt;
172
}
173
174
if (!unifier.genericSubstitutions.empty() || !unifier.genericPackSubstitutions.empty())
175
{
176
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice};
177
std::optional<TypePackId> subst = instantiate2(
178
ctx->arena, std::move(unifier.genericSubstitutions), std::move(unifier.genericPackSubstitutions), NotNull{&subtyping}, ctx->scope, retPack
179
);
180
if (!subst)
181
return std::nullopt;
182
else
183
retPack = *subst;
184
}
185
186
if (FFlag::LuauOverloadGetsInstantiated)
187
{
188
// After we solve for the instantiated function type of this metamethod,
189
// we may have new free types if the metamethod was generic. We capture
190
// these so that they can be generalized later and we don't end up with
191
// free types in type checking.
192
for (const auto& ty : unifier.newFreshTypes)
193
trackInteriorFreeType(ctx->scope, ty);
194
195
for (const auto& tp : unifier.newFreshTypePacks)
196
trackInteriorFreeTypePack(ctx->scope, tp);
197
}
198
199
return retPack;
200
}
201
202
TypeFunctionReductionResult<TypeId> notTypeFunction(
203
TypeId instance,
204
const std::vector<TypeId>& typeParams,
205
const std::vector<TypePackId>& packParams,
206
NotNull<TypeFunctionContext> ctx
207
)
208
{
209
if (typeParams.size() != 1 || !packParams.empty())
210
{
211
ctx->ice->ice("not type function: encountered a type function instance without the required argument structure");
212
LUAU_ASSERT(false);
213
}
214
215
TypeId ty = follow(typeParams.at(0));
216
217
if (ty == instance)
218
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
219
220
if (isPending(ty, ctx->solver))
221
return {std::nullopt, Reduction::MaybeOk, {ty}, {}};
222
223
if (auto result = tryDistributeTypeFunctionApp(notTypeFunction, instance, typeParams, packParams, ctx))
224
return *result;
225
226
// `not` operates on anything and returns a `boolean` always.
227
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
228
}
229
230
TypeFunctionReductionResult<TypeId> lenTypeFunction(
231
TypeId instance,
232
const std::vector<TypeId>& typeParams,
233
const std::vector<TypePackId>& packParams,
234
NotNull<TypeFunctionContext> ctx
235
)
236
{
237
if (typeParams.size() != 1 || !packParams.empty())
238
{
239
ctx->ice->ice("len type function: encountered a type function instance without the required argument structure");
240
LUAU_ASSERT(false);
241
}
242
243
TypeId operandTy = follow(typeParams.at(0));
244
245
if (operandTy == instance)
246
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
247
248
// check to see if the operand type is resolved enough, and wait to reduce if not
249
// the use of `typeFromNormal` later necessitates blocking on local types.
250
if (isPending(operandTy, ctx->solver))
251
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
252
253
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
254
NormalizationResult inhabited = ctx->normalizer->isInhabited(normTy.get());
255
256
// if the type failed to normalize, we can't reduce, but know nothing about inhabitance.
257
if (!normTy || inhabited == NormalizationResult::HitLimits)
258
return {std::nullopt, Reduction::MaybeOk, {}, {}};
259
260
// if the operand type is error suppressing, we can immediately reduce to `number`.
261
if (normTy->shouldSuppressErrors())
262
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
263
264
// # always returns a number, even if its operand is never.
265
// if we're checking the length of a string, that works!
266
if (inhabited == NormalizationResult::False || normTy->isSubtypeOfString())
267
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
268
269
// we use the normalized operand here in case there was an intersection or union.
270
TypeId normalizedOperand = follow(ctx->normalizer->typeFromNormal(*normTy));
271
if (normTy->hasTopTable() || get<TableType>(normalizedOperand))
272
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
273
274
if (auto result = tryDistributeTypeFunctionApp(lenTypeFunction, instance, typeParams, packParams, ctx))
275
return *result;
276
277
// findMetatableEntry demands the ability to emit errors, so we must give it
278
// the necessary state to do that, even if we intend to just eat the errors.
279
ErrorVec dummy;
280
281
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, operandTy, "__len", Location{});
282
if (!mmType)
283
{
284
// If we have a metatable type with no __len, this means we still have a table with default length function
285
if (get<MetatableType>(normalizedOperand))
286
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
287
288
return {std::nullopt, Reduction::Erroneous, {}, {}};
289
}
290
291
mmType = follow(*mmType);
292
if (isPending(*mmType, ctx->solver))
293
return {std::nullopt, Reduction::MaybeOk, {*mmType}, {}};
294
295
// We only care that we _can_ solve this function, it doesn't matter what it returns.
296
if (!solveFunctionCall(ctx, ctx->constraint ? ctx->constraint->location : Location{}, *mmType, ctx->arena->addTypePack({operandTy})))
297
return {std::nullopt, Reduction::Erroneous, {}, {}};
298
299
// `len` must return a `number`.
300
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
301
}
302
303
TypeFunctionReductionResult<TypeId> unmTypeFunction(
304
TypeId instance,
305
const std::vector<TypeId>& typeParams,
306
const std::vector<TypePackId>& packParams,
307
NotNull<TypeFunctionContext> ctx
308
)
309
{
310
if (typeParams.size() != 1 || !packParams.empty())
311
{
312
ctx->ice->ice("unm type function: encountered a type function instance without the required argument structure");
313
LUAU_ASSERT(false);
314
}
315
316
TypeId operandTy = follow(typeParams.at(0));
317
318
if (operandTy == instance)
319
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
320
321
// check to see if the operand type is resolved enough, and wait to reduce if not
322
if (isPending(operandTy, ctx->solver))
323
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
324
325
operandTy = follow(operandTy);
326
327
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
328
329
// if the operand failed to normalize, we can't reduce, but know nothing about inhabitance.
330
if (!normTy)
331
return {std::nullopt, Reduction::MaybeOk, {}, {}};
332
333
// if the operand is error suppressing, we can just go ahead and reduce.
334
if (normTy->shouldSuppressErrors())
335
return {operandTy, Reduction::MaybeOk, {}, {}};
336
337
// if we have a `never`, we can never observe that the operation didn't work.
338
if (is<NeverType>(operandTy))
339
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
340
341
// If the type is exactly `number`, we can reduce now.
342
if (normTy->isExactlyNumber())
343
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
344
345
if (auto result = tryDistributeTypeFunctionApp(unmTypeFunction, instance, typeParams, packParams, ctx))
346
return *result;
347
348
// findMetatableEntry demands the ability to emit errors, so we must give it
349
// the necessary state to do that, even if we intend to just eat the errors.
350
ErrorVec dummy;
351
352
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, operandTy, "__unm", Location{});
353
if (!mmType)
354
return {std::nullopt, Reduction::Erroneous, {}, {}};
355
356
mmType = follow(*mmType);
357
if (isPending(*mmType, ctx->solver))
358
return {std::nullopt, Reduction::MaybeOk, {*mmType}, {}};
359
360
auto result = solveFunctionCall(ctx, ctx->constraint ? ctx->constraint->location : Location{}, *mmType, ctx->arena->addTypePack({operandTy}));
361
if (!result)
362
return {std::nullopt, Reduction::Erroneous, {}, {}};
363
364
if (auto ret = first(*result))
365
return {ret, Reduction::MaybeOk, {}, {}};
366
else
367
return {std::nullopt, Reduction::Erroneous, {}, {}};
368
}
369
370
TypeFunctionContext::TypeFunctionContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint)
371
: arena(cs->arena)
372
, builtins(cs->builtinTypes)
373
, scope(scope)
374
, normalizer(cs->normalizer)
375
, typeFunctionRuntime(cs->typeFunctionRuntime)
376
, ice(NotNull{&cs->iceReporter})
377
, limits(NotNull{&cs->limits})
378
, solver(cs.get())
379
, constraint(constraint.get())
380
{
381
}
382
383
NotNull<Constraint> TypeFunctionContext::pushConstraint(ConstraintV&& c) const
384
{
385
LUAU_ASSERT(solver);
386
NotNull<Constraint> newConstraint = solver->pushConstraint(scope, constraint ? constraint->location : Location{}, std::move(c));
387
388
// Every constraint that is blocked on the current constraint must also be
389
// blocked on this new one.
390
if (constraint)
391
solver->inheritBlocks(NotNull{constraint}, newConstraint);
392
393
return newConstraint;
394
}
395
396
397
TypeFunctionReductionResult<TypeId> numericBinopTypeFunction(
398
TypeId instance,
399
const std::vector<TypeId>& typeParams,
400
const std::vector<TypePackId>& packParams,
401
NotNull<TypeFunctionContext> ctx,
402
const std::string metamethod
403
)
404
{
405
if (typeParams.size() != 2 || !packParams.empty())
406
{
407
ctx->ice->ice("encountered a type function instance without the required argument structure");
408
LUAU_ASSERT(false);
409
}
410
411
TypeId lhsTy = follow(typeParams.at(0));
412
TypeId rhsTy = follow(typeParams.at(1));
413
414
// isPending of `lhsTy` or `rhsTy` would return true, even if it cycles. We want a different answer for that.
415
if (lhsTy == instance || rhsTy == instance)
416
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
417
418
// if we have a `never`, we can never observe that the math operator is unreachable.
419
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
420
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
421
422
const Location location = ctx->constraint ? ctx->constraint->location : Location{};
423
424
// check to see if both operand types are resolved enough, and wait to reduce if not
425
if (isPending(lhsTy, ctx->solver))
426
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
427
else if (isPending(rhsTy, ctx->solver))
428
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
429
430
// TODO: Normalization needs to remove cyclic type functions from a `NormalizedType`.
431
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
432
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
433
434
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
435
if (!normLhsTy || !normRhsTy)
436
return {std::nullopt, Reduction::MaybeOk, {}, {}};
437
438
// if one of the types is error suppressing, we can reduce to `any` since we should suppress errors in the result of the usage.
439
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
440
return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}};
441
442
// if we're adding two `number` types, the result is `number`.
443
if (normLhsTy->isExactlyNumber() && normRhsTy->isExactlyNumber())
444
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
445
446
if (auto result = tryDistributeTypeFunctionApp(numericBinopTypeFunction, instance, typeParams, packParams, ctx, metamethod))
447
return *result;
448
449
// findMetatableEntry demands the ability to emit errors, so we must give it
450
// the necessary state to do that, even if we intend to just eat the errors.
451
ErrorVec dummy;
452
453
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, lhsTy, metamethod, location);
454
bool reversed = false;
455
if (!mmType)
456
{
457
mmType = findMetatableEntry(ctx->builtins, dummy, rhsTy, metamethod, location);
458
reversed = true;
459
}
460
461
if (!mmType)
462
return {std::nullopt, Reduction::Erroneous, {}, {}};
463
464
mmType = follow(*mmType);
465
if (isPending(*mmType, ctx->solver))
466
return {std::nullopt, Reduction::MaybeOk, {*mmType}, {}};
467
468
TypePackId argPack = ctx->arena->addTypePack({lhsTy, rhsTy});
469
470
if (reversed)
471
{
472
TypePack* p = getMutable<TypePack>(argPack);
473
std::swap(p->head.front(), p->head.back());
474
}
475
476
std::optional<TypePackId> retPack = solveFunctionCall(ctx, location, *mmType, argPack);
477
if (!retPack.has_value())
478
return {std::nullopt, Reduction::Erroneous, {}, {}};
479
480
TypePack extracted = extendTypePack(*ctx->arena, ctx->builtins, *retPack, 1);
481
if (extracted.head.empty())
482
return {std::nullopt, Reduction::Erroneous, {}, {}};
483
484
return {extracted.head.front(), Reduction::MaybeOk, {}, {}};
485
}
486
487
TypeFunctionReductionResult<TypeId> addTypeFunction(
488
TypeId instance,
489
const std::vector<TypeId>& typeParams,
490
const std::vector<TypePackId>& packParams,
491
NotNull<TypeFunctionContext> ctx
492
)
493
{
494
if (typeParams.size() != 2 || !packParams.empty())
495
{
496
ctx->ice->ice("add type function: encountered a type function instance without the required argument structure");
497
LUAU_ASSERT(false);
498
}
499
500
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__add");
501
}
502
503
TypeFunctionReductionResult<TypeId> subTypeFunction(
504
TypeId instance,
505
const std::vector<TypeId>& typeParams,
506
const std::vector<TypePackId>& packParams,
507
NotNull<TypeFunctionContext> ctx
508
)
509
{
510
if (typeParams.size() != 2 || !packParams.empty())
511
{
512
ctx->ice->ice("sub type function: encountered a type function instance without the required argument structure");
513
LUAU_ASSERT(false);
514
}
515
516
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__sub");
517
}
518
519
TypeFunctionReductionResult<TypeId> mulTypeFunction(
520
TypeId instance,
521
const std::vector<TypeId>& typeParams,
522
const std::vector<TypePackId>& packParams,
523
NotNull<TypeFunctionContext> ctx
524
)
525
{
526
if (typeParams.size() != 2 || !packParams.empty())
527
{
528
ctx->ice->ice("mul type function: encountered a type function instance without the required argument structure");
529
LUAU_ASSERT(false);
530
}
531
532
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__mul");
533
}
534
535
TypeFunctionReductionResult<TypeId> divTypeFunction(
536
TypeId instance,
537
const std::vector<TypeId>& typeParams,
538
const std::vector<TypePackId>& packParams,
539
NotNull<TypeFunctionContext> ctx
540
)
541
{
542
if (typeParams.size() != 2 || !packParams.empty())
543
{
544
ctx->ice->ice("div type function: encountered a type function instance without the required argument structure");
545
LUAU_ASSERT(false);
546
}
547
548
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__div");
549
}
550
551
TypeFunctionReductionResult<TypeId> idivTypeFunction(
552
TypeId instance,
553
const std::vector<TypeId>& typeParams,
554
const std::vector<TypePackId>& packParams,
555
NotNull<TypeFunctionContext> ctx
556
)
557
{
558
if (typeParams.size() != 2 || !packParams.empty())
559
{
560
ctx->ice->ice("integer div type function: encountered a type function instance without the required argument structure");
561
LUAU_ASSERT(false);
562
}
563
564
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__idiv");
565
}
566
567
TypeFunctionReductionResult<TypeId> powTypeFunction(
568
TypeId instance,
569
const std::vector<TypeId>& typeParams,
570
const std::vector<TypePackId>& packParams,
571
NotNull<TypeFunctionContext> ctx
572
)
573
{
574
if (typeParams.size() != 2 || !packParams.empty())
575
{
576
ctx->ice->ice("pow type function: encountered a type function instance without the required argument structure");
577
LUAU_ASSERT(false);
578
}
579
580
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__pow");
581
}
582
583
TypeFunctionReductionResult<TypeId> modTypeFunction(
584
TypeId instance,
585
const std::vector<TypeId>& typeParams,
586
const std::vector<TypePackId>& packParams,
587
NotNull<TypeFunctionContext> ctx
588
)
589
{
590
if (typeParams.size() != 2 || !packParams.empty())
591
{
592
ctx->ice->ice("modulo type function: encountered a type function instance without the required argument structure");
593
LUAU_ASSERT(false);
594
}
595
596
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__mod");
597
}
598
599
TypeFunctionReductionResult<TypeId> concatTypeFunction(
600
TypeId instance,
601
const std::vector<TypeId>& typeParams,
602
const std::vector<TypePackId>& packParams,
603
NotNull<TypeFunctionContext> ctx
604
)
605
{
606
if (typeParams.size() != 2 || !packParams.empty())
607
{
608
ctx->ice->ice("concat type function: encountered a type function instance without the required argument structure");
609
LUAU_ASSERT(false);
610
}
611
612
TypeId lhsTy = follow(typeParams.at(0));
613
TypeId rhsTy = follow(typeParams.at(1));
614
615
// isPending of `lhsTy` or `rhsTy` would return true, even if it cycles. We want a different answer for that.
616
if (lhsTy == instance || rhsTy == instance)
617
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
618
619
// check to see if both operand types are resolved enough, and wait to reduce if not
620
if (isPending(lhsTy, ctx->solver))
621
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
622
else if (isPending(rhsTy, ctx->solver))
623
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
624
625
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
626
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
627
628
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
629
if (!normLhsTy || !normRhsTy)
630
return {std::nullopt, Reduction::MaybeOk, {}, {}};
631
632
// if one of the types is error suppressing, we can reduce to `any` since we should suppress errors in the result of the usage.
633
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
634
return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}};
635
636
// if we have a `never`, we can never observe that the operator didn't work.
637
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
638
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
639
640
// if we're concatenating two elements that are either strings or numbers, the result is `string`.
641
if ((normLhsTy->isSubtypeOfString() || normLhsTy->isExactlyNumber()) && (normRhsTy->isSubtypeOfString() || normRhsTy->isExactlyNumber()))
642
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}};
643
644
if (auto result = tryDistributeTypeFunctionApp(concatTypeFunction, instance, typeParams, packParams, ctx))
645
return *result;
646
647
// findMetatableEntry demands the ability to emit errors, so we must give it
648
// the necessary state to do that, even if we intend to just eat the errors.
649
ErrorVec dummy;
650
651
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, lhsTy, "__concat", Location{});
652
bool reversed = false;
653
if (!mmType)
654
{
655
mmType = findMetatableEntry(ctx->builtins, dummy, rhsTy, "__concat", Location{});
656
reversed = true;
657
}
658
659
if (!mmType)
660
return {std::nullopt, Reduction::Erroneous, {}, {}};
661
662
mmType = follow(*mmType);
663
if (isPending(*mmType, ctx->solver))
664
return {std::nullopt, Reduction::MaybeOk, {*mmType}, {}};
665
666
std::vector<TypeId> inferredArgs;
667
if (!reversed)
668
inferredArgs = {lhsTy, rhsTy};
669
else
670
inferredArgs = {rhsTy, lhsTy};
671
672
if (!solveFunctionCall(ctx, ctx->constraint ? ctx->constraint->location : Location{}, *mmType, ctx->arena->addTypePack(std::move(inferredArgs))))
673
return {std::nullopt, Reduction::Erroneous, {}, {}};
674
675
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}};
676
}
677
678
namespace
679
{
680
bool isBlockedOrUnsolvedType(TypeId ty)
681
{
682
if (auto tfit = get<TypeFunctionInstanceType>(ty); tfit && tfit->state == TypeFunctionInstanceState::Unsolved)
683
return true;
684
return is<BlockedType, PendingExpansionType>(ty);
685
}
686
} // namespace
687
688
TypeFunctionReductionResult<TypeId> andTypeFunction(
689
TypeId instance,
690
const std::vector<TypeId>& typeParams,
691
const std::vector<TypePackId>& packParams,
692
NotNull<TypeFunctionContext> ctx
693
)
694
{
695
if (typeParams.size() != 2 || !packParams.empty())
696
{
697
ctx->ice->ice("and type function: encountered a type function instance without the required argument structure");
698
LUAU_ASSERT(false);
699
}
700
701
TypeId lhsTy = follow(typeParams.at(0));
702
TypeId rhsTy = follow(typeParams.at(1));
703
704
// t1 = and<lhs, t1> ~> lhs
705
if (follow(rhsTy) == instance && lhsTy != rhsTy)
706
return {lhsTy, Reduction::MaybeOk, {}, {}};
707
// t1 = and<t1, rhs> ~> rhs
708
if (follow(lhsTy) == instance && lhsTy != rhsTy)
709
return {rhsTy, Reduction::MaybeOk, {}, {}};
710
711
// check to see if both operand types are resolved enough, and wait to reduce if not
712
if (isPending(lhsTy, ctx->solver))
713
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
714
else if (isPending(rhsTy, ctx->solver))
715
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
716
717
// And evalutes to a boolean if the LHS is falsy, and the RHS type if LHS is truthy.
718
SimplifyResult filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->falsyType);
719
SimplifyResult overallResult = simplifyUnion(ctx->builtins, ctx->arena, rhsTy, filteredLhs.result);
720
std::vector<TypeId> blockedTypes{};
721
for (auto ty : filteredLhs.blockedTypes)
722
blockedTypes.push_back(ty);
723
for (auto ty : overallResult.blockedTypes)
724
blockedTypes.push_back(ty);
725
return {overallResult.result, Reduction::MaybeOk, std::move(blockedTypes), {}};
726
}
727
728
TypeFunctionReductionResult<TypeId> orTypeFunction(
729
TypeId instance,
730
const std::vector<TypeId>& typeParams,
731
const std::vector<TypePackId>& packParams,
732
NotNull<TypeFunctionContext> ctx
733
)
734
{
735
if (typeParams.size() != 2 || !packParams.empty())
736
{
737
ctx->ice->ice("or type function: encountered a type function instance without the required argument structure");
738
LUAU_ASSERT(false);
739
}
740
741
TypeId lhsTy = follow(typeParams.at(0));
742
TypeId rhsTy = follow(typeParams.at(1));
743
744
// t1 = or<lhs, t1> ~> lhs
745
if (follow(rhsTy) == instance && lhsTy != rhsTy)
746
return {lhsTy, Reduction::MaybeOk, {}, {}};
747
// t1 = or<t1, rhs> ~> rhs
748
if (follow(lhsTy) == instance && lhsTy != rhsTy)
749
return {rhsTy, Reduction::MaybeOk, {}, {}};
750
751
// check to see if both operand types are resolved enough, and wait to reduce if not
752
if (isBlockedOrUnsolvedType(lhsTy))
753
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
754
else if (isBlockedOrUnsolvedType(rhsTy))
755
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
756
757
// Or evalutes to the LHS type if the LHS is truthy, and the RHS type if LHS is falsy.
758
SimplifyResult filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->truthyType);
759
SimplifyResult overallResult = simplifyUnion(ctx->builtins, ctx->arena, rhsTy, filteredLhs.result);
760
std::vector<TypeId> blockedTypes{};
761
for (auto ty : filteredLhs.blockedTypes)
762
blockedTypes.push_back(ty);
763
for (auto ty : overallResult.blockedTypes)
764
blockedTypes.push_back(ty);
765
return {overallResult.result, Reduction::MaybeOk, std::move(blockedTypes), {}};
766
}
767
768
static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
769
TypeId instance,
770
const std::vector<TypeId>& typeParams,
771
const std::vector<TypePackId>& packParams,
772
NotNull<TypeFunctionContext> ctx,
773
const std::string metamethod
774
)
775
{
776
777
if (typeParams.size() != 2 || !packParams.empty())
778
{
779
ctx->ice->ice("encountered a type function instance without the required argument structure");
780
LUAU_ASSERT(false);
781
}
782
783
TypeId lhsTy = follow(typeParams.at(0));
784
TypeId rhsTy = follow(typeParams.at(1));
785
786
if (lhsTy == instance || rhsTy == instance)
787
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
788
789
if (isBlockedOrUnsolvedType(lhsTy))
790
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
791
else if (isBlockedOrUnsolvedType(rhsTy))
792
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
793
794
// Algebra Reduction Rules for comparison type functions
795
// Note that comparing to never tells you nothing about the other operand
796
// lt< 'a , never> -> continue
797
// lt< never, 'a> -> continue
798
// lt< 'a, t> -> 'a is t - we'll solve the constraint, return and solve lt<t, t> -> bool
799
// lt< t, 'a> -> same as above
800
bool canSubmitConstraint = ctx->solver && ctx->constraint;
801
bool lhsFree = get<FreeType>(lhsTy) != nullptr;
802
bool rhsFree = get<FreeType>(rhsTy) != nullptr;
803
if (canSubmitConstraint)
804
{
805
// Implement injective type functions for comparison type functions
806
// lt <number, t> implies t is number
807
// lt <t, number> implies t is number
808
if (lhsFree && isNumber(rhsTy))
809
emplaceType<BoundType>(asMutable(lhsTy), ctx->builtins->numberType);
810
else if (rhsFree && isNumber(lhsTy))
811
emplaceType<BoundType>(asMutable(rhsTy), ctx->builtins->numberType);
812
}
813
814
// The above might have caused the operand types to be rebound, we need to follow them again
815
lhsTy = follow(lhsTy);
816
rhsTy = follow(rhsTy);
817
818
// check to see if both operand types are resolved enough, and wait to reduce if not
819
820
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
821
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
822
NormalizationResult lhsInhabited = ctx->normalizer->isInhabited(normLhsTy.get());
823
NormalizationResult rhsInhabited = ctx->normalizer->isInhabited(normRhsTy.get());
824
825
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
826
if (!normLhsTy || !normRhsTy || lhsInhabited == NormalizationResult::HitLimits || rhsInhabited == NormalizationResult::HitLimits)
827
return {std::nullopt, Reduction::MaybeOk, {}, {}};
828
829
// if one of the types is error suppressing, we can just go ahead and reduce.
830
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
831
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
832
833
// if we have an uninhabited type (e.g. `never`), we can never observe that the comparison didn't work.
834
if (lhsInhabited == NormalizationResult::False || rhsInhabited == NormalizationResult::False)
835
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
836
837
// If both types are some strict subset of `string`, we can reduce now.
838
if (normLhsTy->isSubtypeOfString() && normRhsTy->isSubtypeOfString())
839
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
840
841
// If both types are exactly `number`, we can reduce now.
842
if (normLhsTy->isExactlyNumber() && normRhsTy->isExactlyNumber())
843
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
844
845
if (auto result = tryDistributeTypeFunctionApp(comparisonTypeFunction, instance, typeParams, packParams, ctx, metamethod))
846
return *result;
847
848
// findMetatableEntry demands the ability to emit errors, so we must give it
849
// the necessary state to do that, even if we intend to just eat the errors.
850
ErrorVec dummy;
851
852
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, lhsTy, metamethod, Location{});
853
if (!mmType)
854
mmType = findMetatableEntry(ctx->builtins, dummy, rhsTy, metamethod, Location{});
855
856
if (!mmType)
857
return {std::nullopt, Reduction::Erroneous, {}, {}};
858
859
mmType = follow(*mmType);
860
if (isPending(*mmType, ctx->solver))
861
return {std::nullopt, Reduction::MaybeOk, {*mmType}, {}};
862
863
// We only care that we _can_ solve this function, it doesn't matter what it returns.
864
if (!solveFunctionCall(ctx, ctx->constraint ? ctx->constraint->location : Location{}, *mmType, ctx->arena->addTypePack({lhsTy, rhsTy})))
865
return {std::nullopt, Reduction::Erroneous, {}, {}};
866
867
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
868
}
869
870
TypeFunctionReductionResult<TypeId> ltTypeFunction(
871
TypeId instance,
872
const std::vector<TypeId>& typeParams,
873
const std::vector<TypePackId>& packParams,
874
NotNull<TypeFunctionContext> ctx
875
)
876
{
877
if (typeParams.size() != 2 || !packParams.empty())
878
{
879
ctx->ice->ice("lt type function: encountered a type function instance without the required argument structure");
880
LUAU_ASSERT(false);
881
}
882
883
return comparisonTypeFunction(instance, typeParams, packParams, ctx, "__lt");
884
}
885
886
TypeFunctionReductionResult<TypeId> leTypeFunction(
887
TypeId instance,
888
const std::vector<TypeId>& typeParams,
889
const std::vector<TypePackId>& packParams,
890
NotNull<TypeFunctionContext> ctx
891
)
892
{
893
if (typeParams.size() != 2 || !packParams.empty())
894
{
895
ctx->ice->ice("le type function: encountered a type function instance without the required argument structure");
896
LUAU_ASSERT(false);
897
}
898
899
return comparisonTypeFunction(instance, typeParams, packParams, ctx, "__le");
900
}
901
902
TypeFunctionReductionResult<TypeId> eqTypeFunction(
903
TypeId instance,
904
const std::vector<TypeId>& typeParams,
905
const std::vector<TypePackId>& packParams,
906
NotNull<TypeFunctionContext> ctx
907
)
908
{
909
if (typeParams.size() != 2 || !packParams.empty())
910
{
911
ctx->ice->ice("eq type function: encountered a type function instance without the required argument structure");
912
LUAU_ASSERT(false);
913
}
914
915
TypeId lhsTy = follow(typeParams.at(0));
916
TypeId rhsTy = follow(typeParams.at(1));
917
918
// check to see if both operand types are resolved enough, and wait to reduce if not
919
if (isPending(lhsTy, ctx->solver))
920
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
921
else if (isPending(rhsTy, ctx->solver))
922
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
923
924
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
925
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
926
NormalizationResult lhsInhabited = ctx->normalizer->isInhabited(normLhsTy.get());
927
NormalizationResult rhsInhabited = ctx->normalizer->isInhabited(normRhsTy.get());
928
929
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
930
if (!normLhsTy || !normRhsTy || lhsInhabited == NormalizationResult::HitLimits || rhsInhabited == NormalizationResult::HitLimits)
931
return {std::nullopt, Reduction::MaybeOk, {}, {}};
932
933
// if one of the types is error suppressing, we can just go ahead and reduce.
934
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
935
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
936
937
// if we have a `never`, we can never observe that the comparison didn't work.
938
if (lhsInhabited == NormalizationResult::False || rhsInhabited == NormalizationResult::False)
939
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
940
941
// findMetatableEntry demands the ability to emit errors, so we must give it
942
// the necessary state to do that, even if we intend to just eat the errors.
943
ErrorVec dummy;
944
945
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, lhsTy, "__eq", Location{});
946
if (!mmType)
947
mmType = findMetatableEntry(ctx->builtins, dummy, rhsTy, "__eq", Location{});
948
949
// if neither type has a metatable entry for `__eq`, then we'll check for inhabitance of the intersection!
950
NormalizationResult intersectInhabited = ctx->normalizer->isIntersectionInhabited(lhsTy, rhsTy);
951
if (!mmType)
952
{
953
if (intersectInhabited == NormalizationResult::True)
954
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}}; // if it's inhabited, everything is okay!
955
956
// we might be in a case where we still want to accept the comparison...
957
if (intersectInhabited == NormalizationResult::False)
958
{
959
// if they're both subtypes of `string` but have no common intersection, the comparison is allowed but always `false`.
960
if (normLhsTy->isSubtypeOfString() && normRhsTy->isSubtypeOfString())
961
return {ctx->builtins->falseType, Reduction::MaybeOk, {}, {}};
962
963
// if they're both subtypes of `boolean` but have no common intersection, the comparison is allowed but always `false`.
964
if (normLhsTy->isSubtypeOfBooleans() && normRhsTy->isSubtypeOfBooleans())
965
return {ctx->builtins->falseType, Reduction::MaybeOk, {}, {}};
966
}
967
968
return {std::nullopt, Reduction::Erroneous, {}, {}}; // if it's not, then this type function is irreducible!
969
}
970
971
mmType = follow(*mmType);
972
if (isPending(*mmType, ctx->solver))
973
return {std::nullopt, Reduction::MaybeOk, {*mmType}, {}};
974
975
if (!solveFunctionCall(ctx, ctx->constraint ? ctx->constraint->location : Location{}, *mmType, ctx->arena->addTypePack({lhsTy, rhsTy})))
976
return {std::nullopt, Reduction::Erroneous, {}, {}};
977
978
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
979
}
980
981
// Collect types that prevent us from reducing a particular refinement.
982
struct FindRefinementBlockers : TypeOnceVisitor
983
{
984
DenseHashSet<TypeId> found{nullptr};
985
986
FindRefinementBlockers()
987
: TypeOnceVisitor("FindRefinementBlockers", /* skipBoundTypes */ true)
988
{
989
}
990
991
bool visit(TypeId ty, const BlockedType&) override
992
{
993
found.insert(ty);
994
return false;
995
}
996
997
bool visit(TypeId ty, const PendingExpansionType&) override
998
{
999
found.insert(ty);
1000
return false;
1001
}
1002
1003
bool visit(TypeId ty, const ExternType&) override
1004
{
1005
return false;
1006
}
1007
};
1008
1009
struct ContainsRefinableType : TypeOnceVisitor
1010
{
1011
bool found = false;
1012
ContainsRefinableType()
1013
: TypeOnceVisitor("ContainsRefinableType", /* skipBoundTypes */ true)
1014
{
1015
}
1016
1017
bool visit(TypeId ty) override
1018
{
1019
// Default case: if we find *some* type that's worth refining against,
1020
// then we can claim that this type contains a refineable type.
1021
found = true;
1022
return false;
1023
}
1024
1025
bool visit(TypeId Ty, const NoRefineType&) override
1026
{
1027
// No refine types aren't interesting
1028
return false;
1029
}
1030
1031
bool visit(TypeId ty, const TableType&) override
1032
{
1033
return !found;
1034
}
1035
bool visit(TypeId ty, const MetatableType&) override
1036
{
1037
return !found;
1038
}
1039
bool visit(TypeId ty, const FunctionType&) override
1040
{
1041
return !found;
1042
}
1043
bool visit(TypeId ty, const UnionType&) override
1044
{
1045
return !found;
1046
}
1047
bool visit(TypeId ty, const IntersectionType&) override
1048
{
1049
return !found;
1050
}
1051
bool visit(TypeId ty, const NegationType&) override
1052
{
1053
return !found;
1054
}
1055
};
1056
1057
namespace
1058
{
1059
1060
bool isTruthyOrFalsyType(TypeId ty)
1061
{
1062
ty = follow(ty);
1063
return isApproximatelyTruthyType(ty) || isApproximatelyFalsyType(ty);
1064
}
1065
1066
struct RefineTypeScrubber : public Substitution
1067
{
1068
NotNull<TypeFunctionContext> ctx;
1069
TypeId needle;
1070
1071
explicit RefineTypeScrubber(NotNull<TypeFunctionContext> ctx, TypeId needle)
1072
: Substitution(ctx->arena)
1073
, ctx{ctx}
1074
, needle{needle}
1075
{
1076
}
1077
1078
bool isDirty(TypePackId tp) override
1079
{
1080
return false;
1081
}
1082
1083
bool ignoreChildren(TypePackId tp) override
1084
{
1085
return false;
1086
}
1087
1088
TypePackId clean(TypePackId tp) override
1089
{
1090
return tp;
1091
}
1092
1093
bool isDirty(TypeId ty) override
1094
{
1095
if (auto ut = get<UnionType>(ty))
1096
{
1097
for (auto option : ut)
1098
{
1099
if (option == needle)
1100
return true;
1101
}
1102
}
1103
else if (auto it = get<IntersectionType>(ty))
1104
{
1105
for (auto part : it)
1106
{
1107
if (part == needle)
1108
return true;
1109
}
1110
}
1111
return ty == needle;
1112
}
1113
1114
bool ignoreChildren(TypeId ty) override
1115
{
1116
return !is<UnionType, IntersectionType>(ty);
1117
}
1118
1119
TypeId clean(TypeId ty) override
1120
{
1121
// NOTE: this feels pretty similar to other places where we try to
1122
// filter over a set type, may be worth combining those in the future.
1123
if (auto ut = get<UnionType>(ty))
1124
{
1125
TypeIds newOptions;
1126
for (auto option : ut)
1127
{
1128
if (option != needle && !is<NeverType>(option))
1129
newOptions.insert(option);
1130
}
1131
if (newOptions.empty())
1132
return ctx->builtins->neverType;
1133
else if (newOptions.size() == 1)
1134
return *newOptions.begin();
1135
else
1136
return ctx->arena->addType(UnionType{newOptions.take()});
1137
}
1138
else if (auto it = get<IntersectionType>(ty))
1139
{
1140
TypeIds newParts;
1141
for (auto part : it)
1142
{
1143
if (part != needle && !is<UnknownType>(part))
1144
newParts.insert(part);
1145
}
1146
if (newParts.empty())
1147
return ctx->builtins->unknownType;
1148
else if (newParts.size() == 1)
1149
return *newParts.begin();
1150
else
1151
return ctx->arena->addType(IntersectionType{newParts.take()});
1152
}
1153
else if (ty == needle)
1154
return ctx->builtins->unknownType;
1155
else
1156
return ty;
1157
}
1158
};
1159
1160
bool occurs(TypeId haystack, TypeId needle, DenseHashSet<TypeId>& seen)
1161
{
1162
if (needle == haystack)
1163
return true;
1164
1165
if (seen.contains(haystack))
1166
return false;
1167
1168
seen.insert(haystack);
1169
1170
if (auto ut = get<UnionType>(haystack))
1171
{
1172
for (auto option : ut)
1173
if (occurs(option, needle, seen))
1174
return true;
1175
}
1176
1177
if (auto it = get<UnionType>(haystack))
1178
{
1179
for (auto part : it)
1180
if (occurs(part, needle, seen))
1181
return true;
1182
}
1183
1184
return false;
1185
}
1186
1187
bool occurs(TypeId haystack, TypeId needle)
1188
{
1189
DenseHashSet<TypeId> seen{nullptr};
1190
return occurs(haystack, needle, seen);
1191
}
1192
1193
} // namespace
1194
1195
TypeFunctionReductionResult<TypeId> refineTypeFunction(
1196
TypeId instance,
1197
const std::vector<TypeId>& typeParams,
1198
const std::vector<TypePackId>& packParams,
1199
NotNull<TypeFunctionContext> ctx
1200
)
1201
{
1202
if (typeParams.size() < 2 || !packParams.empty())
1203
{
1204
ctx->ice->ice("refine type function: encountered a type function instance without the required argument structure");
1205
LUAU_ASSERT(false);
1206
}
1207
1208
TypeId targetTy = follow(typeParams.at(0));
1209
1210
// If we end up minting a refine type like:
1211
//
1212
// t1 where t1 = refine<T | t1, Y>
1213
//
1214
// This can create a degenerate set type such as:
1215
//
1216
// t1 where t1 = (T | t1) & Y
1217
//
1218
// Instead, we can clip the recursive part:
1219
//
1220
// t1 where t1 = refine<T | t1, Y> => refine<T, Y>
1221
if (occurs(targetTy, instance))
1222
{
1223
RefineTypeScrubber rts{ctx, instance};
1224
if (auto result = rts.substitute(targetTy))
1225
targetTy = *result;
1226
}
1227
1228
std::vector<TypeId> discriminantTypes;
1229
for (size_t i = 1; i < typeParams.size(); i++)
1230
{
1231
auto discriminant = follow(typeParams[i]);
1232
1233
// Filter out any top level types that are meaningless to refine
1234
// against.
1235
if (is<UnknownType, NoRefineType>(discriminant))
1236
continue;
1237
1238
// If the discriminant type is only:
1239
// - The `*no-refine*` type (covered above) or;
1240
// - tables, metatables, unions, intersections, functions, or
1241
// negations containing `*no-refine*` (covered below).
1242
// There's no point in refining against it.
1243
ContainsRefinableType crt;
1244
crt.traverse(discriminant);
1245
1246
if (crt.found)
1247
discriminantTypes.push_back(discriminant);
1248
}
1249
1250
// if we don't have any real refinements, i.e. they're all `*no-refine*`, then we can reduce immediately.
1251
if (discriminantTypes.empty())
1252
return {targetTy, {}};
1253
1254
const bool targetIsPending = isBlockedOrUnsolvedType(targetTy);
1255
1256
// check to see if both operand types are resolved enough, and wait to reduce if not
1257
if (targetIsPending)
1258
return {std::nullopt, Reduction::MaybeOk, {targetTy}, {}};
1259
else
1260
{
1261
for (auto t : discriminantTypes)
1262
{
1263
if (isPending(t, ctx->solver))
1264
return {std::nullopt, Reduction::MaybeOk, {t}, {}};
1265
}
1266
}
1267
1268
// If we have a blocked type in the target, we *could* potentially
1269
// refine it, but more likely we end up with some type explosion in
1270
// normalization.
1271
FindRefinementBlockers frb;
1272
frb.traverse(targetTy);
1273
if (!frb.found.empty())
1274
return {std::nullopt, Reduction::MaybeOk, {frb.found.begin(), frb.found.end()}, {}};
1275
1276
int stepRefineCount = 0;
1277
1278
// Refine a target type and a discriminant one at a time.
1279
// Returns result : TypeId, toBlockOn : vector<TypeId>
1280
auto stepRefine = [&stepRefineCount, &ctx](TypeId target, TypeId discriminant) -> std::pair<TypeId, std::vector<TypeId>>
1281
{
1282
RecursionLimiter rl{"BuiltInTypeFunctions::stepRefine", &stepRefineCount, DFInt::LuauStepRefineRecursionLimit};
1283
1284
std::vector<TypeId> toBlock;
1285
// we need a more complex check for blocking on the discriminant in particular
1286
FindRefinementBlockers frb;
1287
frb.traverse(discriminant);
1288
1289
if (!frb.found.empty())
1290
return {nullptr, {frb.found.begin(), frb.found.end()}};
1291
1292
if (auto ty = intersectWithSimpleDiscriminant(ctx->builtins, ctx->arena, target, discriminant))
1293
return {*ty, {}};
1294
1295
// NOTE: This block causes us to refine too early in some cases.
1296
if (auto negation = get<NegationType>(discriminant))
1297
{
1298
if (auto primitive = get<PrimitiveType>(follow(negation->ty)); primitive && primitive->type == PrimitiveType::NilType)
1299
{
1300
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
1301
return {result.result, {}};
1302
}
1303
}
1304
1305
// If the target type is a table, then simplification already implements the logic to deal with refinements properly since the
1306
// type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type.
1307
// We also fire for simple discriminants such as false? and ~(false?): the falsy and truthy types respectively.
1308
if (is<TableType>(target) || isTruthyOrFalsyType(discriminant))
1309
{
1310
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
1311
// Simplification considers free and generic types to be
1312
// 'blocking', but that's not suitable for refine<>.
1313
//
1314
// If we are only blocked on those types, we consider
1315
// the simplification a success and reduce.
1316
if (std::all_of(
1317
begin(result.blockedTypes),
1318
end(result.blockedTypes),
1319
[](TypeId v)
1320
{
1321
return is<FreeType, GenericType>(follow(v));
1322
}
1323
))
1324
{
1325
return {result.result, {}};
1326
}
1327
else
1328
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
1329
1330
return {result.result, {}};
1331
}
1332
1333
1334
// In the general case, we'll still use normalization though.
1335
TypeId intersection = ctx->arena->addType(IntersectionType{{target, discriminant}});
1336
std::shared_ptr<const NormalizedType> normIntersection = ctx->normalizer->normalize(intersection);
1337
std::shared_ptr<const NormalizedType> normType = ctx->normalizer->normalize(target);
1338
1339
// if the intersection failed to normalize, we can't reduce, but know nothing about inhabitance.
1340
if (!normIntersection || !normType)
1341
return {nullptr, {}};
1342
1343
TypeId resultTy = ctx->normalizer->typeFromNormal(*normIntersection);
1344
// include the error type if the target type is error-suppressing and the intersection we computed is not
1345
if (normType->shouldSuppressErrors() && !normIntersection->shouldSuppressErrors())
1346
resultTy = addUnion(ctx->arena, ctx->builtins, {resultTy, ctx->builtins->errorType});
1347
1348
return {resultTy, {}};
1349
};
1350
1351
// refine target with each discriminant type in sequence (reverse of insertion order)
1352
// If we cannot proceed, block. If all discriminant types refine successfully, return
1353
// the result
1354
TypeId target = targetTy;
1355
while (!discriminantTypes.empty())
1356
{
1357
TypeId discriminant = discriminantTypes.back();
1358
1359
discriminant = follow(discriminant);
1360
1361
// first, we'll see if simplifying the discriminant alone will solve our problem...
1362
if (auto ut = get<UnionType>(discriminant))
1363
{
1364
TypeId workingType = ctx->builtins->neverType;
1365
1366
for (auto optionAsDiscriminant : ut->options)
1367
{
1368
SimplifyResult simplified = simplifyUnion(ctx->builtins, ctx->arena, workingType, optionAsDiscriminant);
1369
1370
if (!simplified.blockedTypes.empty())
1371
return {std::nullopt, Reduction::MaybeOk, {simplified.blockedTypes.begin(), simplified.blockedTypes.end()}, {}};
1372
1373
workingType = simplified.result;
1374
}
1375
1376
discriminant = workingType;
1377
}
1378
1379
// if not, we try distributivity: a & (b | c) <=> (a & b) | (a & c)
1380
if (auto ut = get<UnionType>(discriminant))
1381
{
1382
TypeId finalRefined = ctx->builtins->neverType;
1383
1384
for (auto optionAsDiscriminant : ut->options)
1385
{
1386
auto [refined, blocked] = stepRefine(target, follow(optionAsDiscriminant));
1387
1388
if (blocked.empty() && refined == nullptr)
1389
return {std::nullopt, Reduction::MaybeOk, {}, {}};
1390
1391
if (!blocked.empty())
1392
return {std::nullopt, Reduction::MaybeOk, blocked, {}};
1393
1394
SimplifyResult simplified = simplifyUnion(ctx->builtins, ctx->arena, finalRefined, refined);
1395
1396
if (!simplified.blockedTypes.empty())
1397
return {std::nullopt, Reduction::MaybeOk, {simplified.blockedTypes.begin(), simplified.blockedTypes.end()}, {}};
1398
1399
finalRefined = simplified.result;
1400
}
1401
1402
target = finalRefined;
1403
discriminantTypes.pop_back();
1404
1405
continue;
1406
}
1407
1408
auto [refined, blocked] = stepRefine(target, discriminant);
1409
1410
if (blocked.empty() && refined == nullptr)
1411
return {std::nullopt, Reduction::MaybeOk, {}, {}};
1412
1413
if (!blocked.empty())
1414
return {std::nullopt, Reduction::MaybeOk, blocked, {}};
1415
1416
target = refined;
1417
discriminantTypes.pop_back();
1418
}
1419
return {target, Reduction::MaybeOk, {}, {}};
1420
}
1421
1422
TypeFunctionReductionResult<TypeId> singletonTypeFunction(
1423
TypeId instance,
1424
const std::vector<TypeId>& typeParams,
1425
const std::vector<TypePackId>& packParams,
1426
NotNull<TypeFunctionContext> ctx
1427
)
1428
{
1429
if (typeParams.size() != 1 || !packParams.empty())
1430
{
1431
ctx->ice->ice("singleton type function: encountered a type function instance without the required argument structure");
1432
LUAU_ASSERT(false);
1433
}
1434
1435
TypeId type = follow(typeParams.at(0));
1436
1437
// check to see if both operand types are resolved enough, and wait to reduce if not
1438
if (isPending(type, ctx->solver))
1439
return {std::nullopt, Reduction::MaybeOk, {type}, {}};
1440
1441
TypeId followed = type;
1442
// we want to follow through a negation here as well.
1443
if (auto negation = get<NegationType>(followed))
1444
followed = follow(negation->ty);
1445
1446
// if we have a singleton type or `nil`, which is its own singleton type...
1447
if (get<SingletonType>(followed) || isNil(followed))
1448
return {type, Reduction::MaybeOk, {}, {}};
1449
1450
// otherwise, we'll return the top type, `unknown`.
1451
return {ctx->builtins->unknownType, Reduction::MaybeOk, {}, {}};
1452
}
1453
1454
struct CollectUnionTypeOptions : TypeOnceVisitor
1455
{
1456
NotNull<TypeFunctionContext> ctx;
1457
DenseHashSet<TypeId> options{nullptr};
1458
DenseHashSet<TypeId> blockingTypes{nullptr};
1459
1460
explicit CollectUnionTypeOptions(NotNull<TypeFunctionContext> ctx)
1461
: TypeOnceVisitor("CollectUnionTypeOptions", /* skipBoundTypes */ true)
1462
, ctx(ctx)
1463
{
1464
}
1465
1466
bool visit(TypeId ty) override
1467
{
1468
options.insert(ty);
1469
if (isPending(ty, ctx->solver))
1470
blockingTypes.insert(ty);
1471
return false;
1472
}
1473
1474
bool visit(TypePackId tp) override
1475
{
1476
return false;
1477
}
1478
1479
bool visit(TypeId ty, const UnionType& ut) override
1480
{
1481
// If we have something like:
1482
//
1483
// union<A | B, C | D>
1484
//
1485
// We probably just want to consider this to be the same as
1486
//
1487
// union<A, B, C, D>
1488
return true;
1489
}
1490
1491
bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override
1492
{
1493
if (tfit.function->name != ctx->builtins->typeFunctions->unionFunc.name)
1494
{
1495
options.insert(ty);
1496
blockingTypes.insert(ty);
1497
return false;
1498
}
1499
1500
return true;
1501
}
1502
};
1503
1504
TypeFunctionReductionResult<TypeId> unionTypeFunction(
1505
TypeId instance,
1506
const std::vector<TypeId>& typeParams,
1507
const std::vector<TypePackId>& packParams,
1508
NotNull<TypeFunctionContext> ctx
1509
)
1510
{
1511
if (!packParams.empty())
1512
{
1513
ctx->ice->ice("union type function: encountered a type function instance without the required argument structure");
1514
LUAU_ASSERT(false);
1515
}
1516
1517
// if we only have one parameter, there's nothing to do.
1518
if (typeParams.size() == 1)
1519
return {follow(typeParams[0]), Reduction::MaybeOk, {}, {}};
1520
1521
1522
CollectUnionTypeOptions collector{ctx};
1523
collector.traverse(instance);
1524
1525
if (!collector.blockingTypes.empty())
1526
{
1527
std::vector<TypeId> blockingTypes{collector.blockingTypes.begin(), collector.blockingTypes.end()};
1528
return {std::nullopt, Reduction::MaybeOk, std::move(blockingTypes), {}};
1529
}
1530
1531
TypeId resultTy = ctx->builtins->neverType;
1532
for (auto ty : collector.options)
1533
{
1534
SimplifyResult result = simplifyUnion(ctx->builtins, ctx->arena, resultTy, ty);
1535
// This condition might fire if one of the arguments to this type
1536
// function is a free type somewhere deep in a nested union or
1537
// intersection type, even though we ran a pass above to capture
1538
// some blocked types.
1539
if (!result.blockedTypes.empty())
1540
return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
1541
1542
resultTy = result.result;
1543
}
1544
1545
return {resultTy, Reduction::MaybeOk, {}, {}};
1546
}
1547
1548
1549
TypeFunctionReductionResult<TypeId> intersectTypeFunction(
1550
TypeId instance,
1551
const std::vector<TypeId>& typeParams,
1552
const std::vector<TypePackId>& packParams,
1553
NotNull<TypeFunctionContext> ctx
1554
)
1555
{
1556
if (!packParams.empty())
1557
{
1558
ctx->ice->ice("intersect type function: encountered a type function instance without the required argument structure");
1559
LUAU_ASSERT(false);
1560
}
1561
1562
// if we only have one parameter, there's nothing to do.
1563
if (typeParams.size() == 1)
1564
return {follow(typeParams[0]), Reduction::MaybeOk, {}, {}};
1565
1566
// we need to follow all of the type parameters.
1567
std::vector<TypeId> types;
1568
types.reserve(typeParams.size());
1569
for (auto ty : typeParams)
1570
types.emplace_back(follow(ty));
1571
1572
// if we only have two parameters and one is `*no-refine*`, we're all done.
1573
if (types.size() == 2 && get<NoRefineType>(types[1]))
1574
return {types[0], Reduction::MaybeOk, {}, {}};
1575
else if (types.size() == 2 && get<NoRefineType>(types[0]))
1576
return {types[1], Reduction::MaybeOk, {}, {}};
1577
1578
// check to see if the operand types are resolved enough, and wait to reduce if not
1579
// if any of them are `never`, the intersection will always be `never`, so we can reduce directly.
1580
for (auto ty : types)
1581
{
1582
if (isPending(ty, ctx->solver))
1583
return {std::nullopt, Reduction::MaybeOk, {ty}, {}};
1584
else if (get<NeverType>(ty))
1585
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
1586
}
1587
1588
// fold over the types with `simplifyIntersection`
1589
TypeId resultTy = ctx->builtins->unknownType;
1590
// collect types which caused intersection to return never
1591
DenseHashSet<TypeId> unintersectableTypes{nullptr};
1592
for (auto ty : types)
1593
{
1594
// skip any `*no-refine*` types.
1595
if (get<NoRefineType>(ty))
1596
continue;
1597
1598
if (auto simpleResult = intersectWithSimpleDiscriminant(ctx->builtins, ctx->arena, resultTy, ty))
1599
{
1600
if (get<NeverType>(*simpleResult))
1601
unintersectableTypes.insert(follow(ty));
1602
else
1603
resultTy = *simpleResult;
1604
continue;
1605
}
1606
1607
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty);
1608
1609
// If simplifying the intersection returned never, note the type we tried to intersect it with, and continue trying to intersect with the
1610
// rest
1611
if (get<NeverType>(result.result))
1612
{
1613
unintersectableTypes.insert(follow(ty));
1614
continue;
1615
}
1616
for (TypeId blockedType : result.blockedTypes)
1617
{
1618
if (!get<GenericType>(blockedType))
1619
return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
1620
}
1621
1622
resultTy = result.result;
1623
}
1624
1625
if (!unintersectableTypes.empty())
1626
{
1627
unintersectableTypes.insert(resultTy);
1628
if (unintersectableTypes.size() > 1)
1629
{
1630
TypeId intersection =
1631
ctx->arena->addType(IntersectionType{std::vector<TypeId>(unintersectableTypes.begin(), unintersectableTypes.end())});
1632
return {intersection, Reduction::MaybeOk, {}, {}};
1633
}
1634
else
1635
{
1636
return {*unintersectableTypes.begin(), Reduction::MaybeOk, {}, {}};
1637
}
1638
}
1639
// if the intersection simplifies to `never`, this gives us bad autocomplete.
1640
// we'll just produce the intersection plainly instead, but this might be revisitable
1641
// if we ever give `never` some kind of "explanation" trail.
1642
if (get<NeverType>(resultTy))
1643
{
1644
TypeId intersection = ctx->arena->addType(IntersectionType{typeParams});
1645
return {intersection, Reduction::MaybeOk, {}, {}};
1646
}
1647
1648
return {resultTy, Reduction::MaybeOk, {}, {}};
1649
}
1650
1651
namespace
1652
{
1653
1654
/**
1655
* Computes the keys of `ty` into `result`
1656
* `isRaw` parameter indicates whether or not we should follow __index metamethods
1657
* returns `false` if `result` should be ignored because the answer is "all strings"
1658
*/
1659
bool computeKeysOf(TypeId ty, Set<std::optional<std::string>>& result, DenseHashSet<TypeId>& seen, bool isRaw, NotNull<TypeFunctionContext> ctx)
1660
{
1661
1662
// if the type is the top table type, the answer is just "all strings"
1663
if (get<PrimitiveType>(ty))
1664
return false;
1665
1666
// if we've already seen this type, we can do nothing
1667
if (seen.contains(ty))
1668
return true;
1669
seen.insert(ty);
1670
1671
// if we have a particular table type, we can insert the keys
1672
if (auto tableTy = get<TableType>(ty))
1673
{
1674
if (tableTy->indexer)
1675
{
1676
// if we have a string indexer, the answer is, again, "all strings"
1677
if (isString(tableTy->indexer->indexType))
1678
return false;
1679
}
1680
1681
for (const auto& [key, _] : tableTy->props)
1682
result.insert(key);
1683
return true;
1684
}
1685
1686
// otherwise, we have a metatable to deal with
1687
if (auto metatableTy = get<MetatableType>(ty))
1688
{
1689
bool res = true;
1690
1691
if (!isRaw)
1692
{
1693
// findMetatableEntry demands the ability to emit errors, so we must give it
1694
// the necessary state to do that, even if we intend to just eat the errors.
1695
ErrorVec dummy;
1696
1697
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, ty, "__index", Location{});
1698
if (mmType)
1699
res = res && computeKeysOf(*mmType, result, seen, isRaw, ctx);
1700
}
1701
1702
res = res && computeKeysOf(metatableTy->table, result, seen, isRaw, ctx);
1703
1704
return res;
1705
}
1706
1707
if (auto classTy = get<ExternType>(ty))
1708
{
1709
for (const auto& [key, _] : classTy->props)
1710
result.insert(key);
1711
1712
bool res = true;
1713
if (classTy->metatable && !isRaw)
1714
{
1715
// findMetatableEntry demands the ability to emit errors, so we must give it
1716
// the necessary state to do that, even if we intend to just eat the errors.
1717
ErrorVec dummy;
1718
1719
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, ty, "__index", Location{});
1720
if (mmType)
1721
res = res && computeKeysOf(*mmType, result, seen, isRaw, ctx);
1722
}
1723
1724
if (classTy->parent)
1725
res = res && computeKeysOf(follow(*classTy->parent), result, seen, isRaw, ctx);
1726
1727
return res;
1728
}
1729
1730
// this should not be reachable since the type should be a valid tables or extern types part from normalization.
1731
LUAU_ASSERT(false);
1732
return false;
1733
}
1734
1735
} // namespace
1736
1737
TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
1738
const std::vector<TypeId>& typeParams,
1739
const std::vector<TypePackId>& packParams,
1740
NotNull<TypeFunctionContext> ctx,
1741
bool isRaw
1742
)
1743
{
1744
if (typeParams.size() != 1 || !packParams.empty())
1745
{
1746
ctx->ice->ice("keyof type function: encountered a type function instance without the required argument structure");
1747
LUAU_ASSERT(false);
1748
}
1749
1750
TypeId operandTy = follow(typeParams.at(0));
1751
1752
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
1753
1754
// if the operand failed to normalize, we can't reduce, but know nothing about inhabitance.
1755
if (!normTy)
1756
return {std::nullopt, Reduction::MaybeOk, {}, {}};
1757
1758
// if we don't have either just tables or just extern types, we've got nothing to get keys of (at least until a future version perhaps adds extern
1759
// types as well)
1760
if (normTy->hasTables() == normTy->hasExternTypes())
1761
return {std::nullopt, Reduction::Erroneous, {}, {}};
1762
1763
// this is sort of atrocious, but we're trying to reject any type that has not normalized to a table or a union of tables.
1764
if (normTy->hasTops() || normTy->hasBooleans() || normTy->hasErrors() || normTy->hasNils() || normTy->hasNumbers() || normTy->hasStrings() ||
1765
normTy->hasThreads() || normTy->hasBuffers() || normTy->hasFunctions() || normTy->hasTyvars())
1766
return {std::nullopt, Reduction::Erroneous, {}, {}};
1767
1768
// We're going to collect the keys in here, and we use optional strings
1769
// so that we can differentiate between the empty string and _no_ string.
1770
Set<std::optional<std::string>> keys{std::nullopt};
1771
1772
// computing the keys for extern types
1773
if (normTy->hasExternTypes())
1774
{
1775
LUAU_ASSERT(!normTy->hasTables());
1776
1777
// seen set for key computation for extern types
1778
DenseHashSet<TypeId> seen{{}};
1779
1780
auto externTypeIter = normTy->externTypes.ordering.begin();
1781
auto externTypeIterEnd = normTy->externTypes.ordering.end();
1782
LUAU_ASSERT(externTypeIter != externTypeIterEnd); // should be guaranteed by the `hasExternTypes` check earlier
1783
1784
// collect all the properties from the first class type
1785
if (!computeKeysOf(*externTypeIter, keys, seen, isRaw, ctx))
1786
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have a top type!
1787
1788
// we need to look at each class to remove any keys that are not common amongst them all
1789
while (++externTypeIter != externTypeIterEnd)
1790
{
1791
seen.clear(); // we'll reuse the same seen set
1792
1793
Set<std::optional<std::string>> localKeys{std::nullopt};
1794
1795
// we can skip to the next class if this one is a top type
1796
if (!computeKeysOf(*externTypeIter, localKeys, seen, isRaw, ctx))
1797
continue;
1798
1799
for (auto& key : keys)
1800
{
1801
// remove any keys that are not present in each class
1802
if (!localKeys.contains(key))
1803
keys.erase(key);
1804
}
1805
}
1806
}
1807
1808
// computing the keys for tables
1809
if (normTy->hasTables())
1810
{
1811
LUAU_ASSERT(!normTy->hasExternTypes());
1812
1813
// seen set for key computation for tables
1814
DenseHashSet<TypeId> seen{{}};
1815
1816
auto tablesIter = normTy->tables.begin();
1817
LUAU_ASSERT(tablesIter != normTy->tables.end()); // should be guaranteed by the `hasTables` check earlier
1818
1819
// collect all the properties from the first table type
1820
if (!computeKeysOf(*tablesIter, keys, seen, isRaw, ctx))
1821
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have the top table type!
1822
1823
// we need to look at each tables to remove any keys that are not common amongst them all
1824
while (++tablesIter != normTy->tables.end())
1825
{
1826
seen.clear(); // we'll reuse the same seen set
1827
1828
Set<std::optional<std::string>> localKeys{std::nullopt};
1829
1830
// we can skip to the next table if this one is the top table type
1831
if (!computeKeysOf(*tablesIter, localKeys, seen, isRaw, ctx))
1832
continue;
1833
1834
for (auto& key : keys)
1835
{
1836
// remove any keys that are not present in each table
1837
if (!localKeys.contains(key))
1838
keys.erase(key);
1839
}
1840
}
1841
}
1842
1843
// if the set of keys is empty, `keyof<T>` is `never`
1844
if (keys.empty())
1845
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
1846
1847
// everything is validated, we need only construct our big union of singletons now!
1848
std::vector<TypeId> singletons;
1849
singletons.reserve(keys.size());
1850
1851
for (const auto& key : keys)
1852
{
1853
if (key)
1854
singletons.push_back(ctx->arena->addType(SingletonType{StringSingleton{*key}}));
1855
}
1856
1857
// If there's only one entry, we don't need a UnionType.
1858
// We can take straight take it from the first entry
1859
// because it was added into the type arena already.
1860
if (singletons.size() == 1)
1861
return {singletons.front(), Reduction::MaybeOk, {}, {}};
1862
1863
return {ctx->arena->addType(UnionType{std::move(singletons)}), Reduction::MaybeOk, {}, {}};
1864
}
1865
1866
TypeFunctionReductionResult<TypeId> keyofTypeFunction(
1867
TypeId instance,
1868
const std::vector<TypeId>& typeParams,
1869
const std::vector<TypePackId>& packParams,
1870
NotNull<TypeFunctionContext> ctx
1871
)
1872
{
1873
if (typeParams.size() != 1 || !packParams.empty())
1874
{
1875
ctx->ice->ice("keyof type function: encountered a type function instance without the required argument structure");
1876
LUAU_ASSERT(false);
1877
}
1878
1879
return keyofFunctionImpl(typeParams, packParams, ctx, /* isRaw */ false);
1880
}
1881
1882
TypeFunctionReductionResult<TypeId> rawkeyofTypeFunction(
1883
TypeId instance,
1884
const std::vector<TypeId>& typeParams,
1885
const std::vector<TypePackId>& packParams,
1886
NotNull<TypeFunctionContext> ctx
1887
)
1888
{
1889
if (typeParams.size() != 1 || !packParams.empty())
1890
{
1891
ctx->ice->ice("rawkeyof type function: encountered a type function instance without the required argument structure");
1892
LUAU_ASSERT(false);
1893
}
1894
1895
return keyofFunctionImpl(typeParams, packParams, ctx, /* isRaw */ true);
1896
}
1897
1898
/* Searches through table's or class's props/indexer to find the property of `ty`
1899
If found, appends that property to `result` and returns true
1900
Else, returns false */
1901
bool searchPropsAndIndexer(
1902
TypeId ty,
1903
TableType::Props tblProps,
1904
std::optional<TableIndexer> tblIndexer,
1905
DenseHashSet<TypeId>& result,
1906
NotNull<TypeFunctionContext> ctx
1907
)
1908
{
1909
ty = follow(ty);
1910
1911
// index into tbl's properties
1912
if (auto stringSingleton = get<StringSingleton>(get<SingletonType>(ty)))
1913
{
1914
if (tblProps.find(stringSingleton->value) != tblProps.end())
1915
{
1916
1917
Property& prop = tblProps.at(stringSingleton->value);
1918
1919
TypeId propTy;
1920
if (prop.readTy)
1921
propTy = follow(*prop.readTy);
1922
else if (prop.writeTy)
1923
propTy = follow(*prop.writeTy);
1924
else // found the property, but there was no type associated with it
1925
return false;
1926
1927
// property is a union type -> we need to extend our reduction type
1928
if (auto propUnionTy = get<UnionType>(propTy))
1929
{
1930
for (TypeId option : propUnionTy->options)
1931
{
1932
result.insert(follow(option));
1933
}
1934
}
1935
else // property is a singular type or intersection type -> we can simply append
1936
result.insert(propTy);
1937
1938
return true;
1939
}
1940
}
1941
1942
// index into tbl's indexer
1943
if (tblIndexer)
1944
{
1945
TypeId indexType = follow(tblIndexer->indexType);
1946
1947
if (auto tfit = get<TypeFunctionInstanceType>(indexType))
1948
{
1949
// if we have an index function here, it means we're in a cycle, so let's see if it's well-founded if we tie the knot
1950
if (tfit->function.get() == &ctx->builtins->typeFunctions->indexFunc)
1951
indexType = follow(tblIndexer->indexResultType);
1952
}
1953
1954
if (FFlag::LuauThreadUniferStateThroughTypeFunctionReduction)
1955
{
1956
if (isSubtype(ty, indexType, ctx->arena, ctx->builtins, ctx->scope, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice))
1957
{
1958
TypeId idxResultTy = follow(tblIndexer->indexResultType);
1959
1960
// indexResultType is a union type -> we need to extend our reduction type
1961
if (auto idxResUnionTy = get<UnionType>(idxResultTy))
1962
{
1963
for (TypeId option : idxResUnionTy->options)
1964
{
1965
result.insert(follow(option));
1966
}
1967
}
1968
else // indexResultType is a singular type or intersection type -> we can simply append
1969
result.insert(idxResultTy);
1970
1971
return true;
1972
}
1973
}
1974
else
1975
{
1976
if (isSubtype_DEPRECATED(ty, indexType, ctx->scope, ctx->builtins, *ctx->ice, SolverMode::New))
1977
{
1978
TypeId idxResultTy = follow(tblIndexer->indexResultType);
1979
1980
// indexResultType is a union type -> we need to extend our reduction type
1981
if (auto idxResUnionTy = get<UnionType>(idxResultTy))
1982
{
1983
for (TypeId option : idxResUnionTy->options)
1984
{
1985
result.insert(follow(option));
1986
}
1987
}
1988
else // indexResultType is a singular type or intersection type -> we can simply append
1989
result.insert(idxResultTy);
1990
1991
return true;
1992
}
1993
}
1994
}
1995
1996
return false;
1997
}
1998
1999
bool tblIndexInto(
2000
TypeId indexer,
2001
TypeId indexee,
2002
DenseHashSet<TypeId>& result,
2003
DenseHashSet<TypeId>& seenSet,
2004
NotNull<TypeFunctionContext> ctx,
2005
bool isRaw
2006
)
2007
{
2008
indexer = follow(indexer);
2009
indexee = follow(indexee);
2010
2011
if (seenSet.contains(indexee))
2012
return false;
2013
seenSet.insert(indexee);
2014
2015
if (auto unionTy = get<UnionType>(indexee))
2016
{
2017
bool res = true;
2018
for (auto component : unionTy)
2019
{
2020
// if the component is in the seen set and isn't the indexee itself,
2021
// we can skip it cause it means we encountered it in an earlier component in the union.
2022
if (seenSet.contains(component) && component != indexee)
2023
continue;
2024
2025
res = res && tblIndexInto(indexer, component, result, seenSet, ctx, isRaw);
2026
}
2027
return res;
2028
}
2029
2030
if (get<FunctionType>(indexee))
2031
{
2032
TypePackId argPack = ctx->arena->addTypePack({indexer});
2033
2034
std::optional<TypePackId> retPack = solveFunctionCall(ctx, ctx->scope->location, indexee, argPack);
2035
2036
if (!retPack.has_value())
2037
return false;
2038
2039
TypePack extracted = extendTypePack(*ctx->arena, ctx->builtins, *retPack, 1);
2040
if (extracted.head.empty())
2041
return false;
2042
2043
result.insert(follow(extracted.head.front()));
2044
return true;
2045
}
2046
2047
// we have a table type to try indexing
2048
if (auto tableTy = get<TableType>(indexee))
2049
{
2050
return searchPropsAndIndexer(indexer, tableTy->props, tableTy->indexer, result, ctx);
2051
}
2052
2053
// we have a metatable type to try indexing
2054
if (auto metatableTy = get<MetatableType>(indexee))
2055
{
2056
if (auto tableTy = get<TableType>(follow(metatableTy->table)))
2057
{
2058
2059
// try finding all properties within the current scope of the table
2060
if (searchPropsAndIndexer(indexer, tableTy->props, tableTy->indexer, result, ctx))
2061
return true;
2062
}
2063
2064
// if the code reached here, it means we weren't able to find all properties -> look into __index metamethod
2065
if (!isRaw)
2066
{
2067
// findMetatableEntry demands the ability to emit errors, so we must give it
2068
// the necessary state to do that, even if we intend to just eat the errors.
2069
ErrorVec dummy;
2070
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, indexee, "__index", Location{});
2071
if (mmType)
2072
return tblIndexInto(indexer, *mmType, result, seenSet, ctx, isRaw);
2073
}
2074
}
2075
2076
return false;
2077
}
2078
2079
bool tblIndexInto(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result, NotNull<TypeFunctionContext> ctx, bool isRaw)
2080
{
2081
DenseHashSet<TypeId> seenSet{{}};
2082
return tblIndexInto(indexer, indexee, result, seenSet, ctx, isRaw);
2083
}
2084
2085
/* Vocabulary note: indexee refers to the type that contains the properties,
2086
indexer refers to the type that is used to access indexee
2087
Example: index<Person, "name"> => `Person` is the indexee and `"name"` is the indexer */
2088
TypeFunctionReductionResult<TypeId> indexFunctionImpl(
2089
const std::vector<TypeId>& typeParams,
2090
const std::vector<TypePackId>& packParams,
2091
NotNull<TypeFunctionContext> ctx,
2092
bool isRaw
2093
)
2094
{
2095
TypeId indexeeTy = follow(typeParams.at(0));
2096
2097
if (isPending(indexeeTy, ctx->solver))
2098
return {std::nullopt, Reduction::MaybeOk, {indexeeTy}, {}};
2099
2100
std::shared_ptr<const NormalizedType> indexeeNormTy = ctx->normalizer->normalize(indexeeTy);
2101
2102
// if the indexee failed to normalize, we can't reduce, but know nothing about inhabitance.
2103
if (!indexeeNormTy)
2104
return {std::nullopt, Reduction::MaybeOk, {}, {}};
2105
2106
// if the indexee is `any`, then indexing also gives us `any`.
2107
if (indexeeNormTy->shouldSuppressErrors())
2108
return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}};
2109
2110
// if we don't have either just tables or just extern types, we've got nothing to index into
2111
if (indexeeNormTy->hasTables() == indexeeNormTy->hasExternTypes())
2112
return {std::nullopt, Reduction::Erroneous, {}, {}};
2113
2114
// we're trying to reject any type that has not normalized to a table or extern type or a union of tables or extern types.
2115
if (indexeeNormTy->hasTops() || indexeeNormTy->hasBooleans() || indexeeNormTy->hasErrors() || indexeeNormTy->hasNils() ||
2116
indexeeNormTy->hasNumbers() || indexeeNormTy->hasStrings() || indexeeNormTy->hasThreads() || indexeeNormTy->hasBuffers() ||
2117
indexeeNormTy->hasFunctions() || indexeeNormTy->hasTyvars())
2118
return {std::nullopt, Reduction::Erroneous, {}, {}};
2119
2120
TypeId indexerTy = follow(typeParams.at(1));
2121
2122
if (isPending(indexerTy, ctx->solver))
2123
return {std::nullopt, Reduction::MaybeOk, {indexerTy}, {}};
2124
2125
std::shared_ptr<const NormalizedType> indexerNormTy = ctx->normalizer->normalize(indexerTy);
2126
2127
// if the indexer failed to normalize, we can't reduce, but know nothing about inhabitance.
2128
if (!indexerNormTy)
2129
return {std::nullopt, Reduction::MaybeOk, {}, {}};
2130
2131
// we're trying to reject any type that is not a string singleton or primitive (string, number, boolean, thread, nil, function, table, or buffer)
2132
if (indexerNormTy->hasTops() || indexerNormTy->hasErrors())
2133
return {std::nullopt, Reduction::Erroneous, {}, {}};
2134
2135
// indexer can be a union —> break them down into a vector
2136
const std::vector<TypeId>* typesToFind = nullptr;
2137
const std::vector<TypeId> singleType{indexerTy};
2138
if (auto unionTy = get<UnionType>(indexerTy))
2139
typesToFind = &unionTy->options;
2140
else
2141
typesToFind = &singleType;
2142
2143
DenseHashSet<TypeId> properties{{}}; // vector of types that will be returned
2144
2145
if (indexeeNormTy->hasExternTypes())
2146
{
2147
LUAU_ASSERT(!indexeeNormTy->hasTables());
2148
2149
if (isRaw) // rawget should never reduce for extern types (to match the behavior of the rawget global function)
2150
return {std::nullopt, Reduction::Erroneous, {}, {}};
2151
2152
// at least one class is guaranteed to be in the iterator by .hasExternTypes()
2153
for (auto externTypeIter = indexeeNormTy->externTypes.ordering.begin(); externTypeIter != indexeeNormTy->externTypes.ordering.end();
2154
++externTypeIter)
2155
{
2156
auto externTy = get<ExternType>(*externTypeIter);
2157
if (!externTy)
2158
{
2159
LUAU_ASSERT(false); // this should not be possible according to normalization's spec
2160
return {std::nullopt, Reduction::Erroneous, {}, {}};
2161
}
2162
2163
for (TypeId ty : *typesToFind)
2164
{
2165
// Search for all instances of indexer in class->props and class->indexer
2166
if (searchPropsAndIndexer(ty, externTy->props, externTy->indexer, properties, ctx))
2167
continue; // Indexer was found in this class, so we can move on to the next
2168
2169
auto parent = externTy->parent;
2170
bool foundInParent = false;
2171
while (parent && !foundInParent)
2172
{
2173
auto parentExternType = get<ExternType>(follow(*parent));
2174
foundInParent = searchPropsAndIndexer(ty, parentExternType->props, parentExternType->indexer, properties, ctx);
2175
parent = parentExternType->parent;
2176
}
2177
2178
// we move on to the next type if any of the parents we went through had the property.
2179
if (foundInParent)
2180
continue;
2181
2182
// If code reaches here,that means the property not found -> check in the metatable's __index
2183
2184
// findMetatableEntry demands the ability to emit errors, so we must give it
2185
// the necessary state to do that, even if we intend to just eat the errors.
2186
ErrorVec dummy;
2187
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, *externTypeIter, "__index", Location{});
2188
if (!mmType) // if a metatable does not exist, there is no where else to look
2189
return {std::nullopt, Reduction::Erroneous, {}, {}};
2190
2191
if (!tblIndexInto(ty, *mmType, properties, ctx, isRaw)) // if indexer is not in the metatable, we fail to reduce
2192
return {std::nullopt, Reduction::Erroneous, {}, {}};
2193
}
2194
}
2195
}
2196
2197
if (indexeeNormTy->hasTables())
2198
{
2199
LUAU_ASSERT(!indexeeNormTy->hasExternTypes());
2200
2201
// at least one table is guaranteed to be in the iterator by .hasTables()
2202
for (auto tablesIter = indexeeNormTy->tables.begin(); tablesIter != indexeeNormTy->tables.end(); ++tablesIter)
2203
{
2204
for (TypeId ty : *typesToFind)
2205
if (!tblIndexInto(ty, *tablesIter, properties, ctx, isRaw))
2206
{
2207
if (isRaw)
2208
properties.insert(ctx->builtins->nilType);
2209
else
2210
return {std::nullopt, Reduction::Erroneous, {}, {}};
2211
}
2212
}
2213
}
2214
2215
// If the type being reduced to is a single type, no need to union
2216
if (properties.size() == 1)
2217
return {*properties.begin(), Reduction::MaybeOk, {}, {}};
2218
2219
return {ctx->arena->addType(UnionType{std::vector<TypeId>(properties.begin(), properties.end())}), Reduction::MaybeOk, {}, {}};
2220
}
2221
2222
TypeFunctionReductionResult<TypeId> indexTypeFunction(
2223
TypeId instance,
2224
const std::vector<TypeId>& typeParams,
2225
const std::vector<TypePackId>& packParams,
2226
NotNull<TypeFunctionContext> ctx
2227
)
2228
{
2229
if (typeParams.size() != 2 || !packParams.empty())
2230
{
2231
ctx->ice->ice("index type function: encountered a type function instance without the required argument structure");
2232
LUAU_ASSERT(false);
2233
}
2234
2235
return indexFunctionImpl(typeParams, packParams, ctx, /* isRaw */ false);
2236
}
2237
2238
TypeFunctionReductionResult<TypeId> rawgetTypeFunction(
2239
TypeId instance,
2240
const std::vector<TypeId>& typeParams,
2241
const std::vector<TypePackId>& packParams,
2242
NotNull<TypeFunctionContext> ctx
2243
)
2244
{
2245
if (typeParams.size() != 2 || !packParams.empty())
2246
{
2247
ctx->ice->ice("rawget type function: encountered a type function instance without the required argument structure");
2248
LUAU_ASSERT(false);
2249
}
2250
2251
return indexFunctionImpl(typeParams, packParams, ctx, /* isRaw */ true);
2252
}
2253
2254
TypeFunctionReductionResult<TypeId> setmetatableTypeFunction(
2255
TypeId instance,
2256
const std::vector<TypeId>& typeParams,
2257
const std::vector<TypePackId>& packParams,
2258
NotNull<TypeFunctionContext> ctx
2259
)
2260
{
2261
if (typeParams.size() != 2 || !packParams.empty())
2262
{
2263
ctx->ice->ice("setmetatable type function: encountered a type function instance without the required argument structure");
2264
LUAU_ASSERT(false);
2265
}
2266
2267
const Location location = ctx->constraint ? ctx->constraint->location : Location{};
2268
2269
TypeId targetTy = follow(typeParams.at(0));
2270
TypeId metatableTy = follow(typeParams.at(1));
2271
2272
if (isPending(targetTy, ctx->solver))
2273
return {std::nullopt, Reduction::MaybeOk, {targetTy}, {}};
2274
2275
std::shared_ptr<const NormalizedType> targetNorm = ctx->normalizer->normalize(targetTy);
2276
2277
// if the operand failed to normalize, we can't reduce, but know nothing about inhabitance.
2278
if (!targetNorm)
2279
return {std::nullopt, Reduction::MaybeOk, {}, {}};
2280
2281
// cannot setmetatable on something without table parts.
2282
if (!targetNorm->hasTables())
2283
return {std::nullopt, Reduction::Erroneous, {}, {}};
2284
2285
// we're trying to reject any type that has not normalized to a table or a union/intersection of tables.
2286
if (targetNorm->hasTops() || targetNorm->hasBooleans() || targetNorm->hasErrors() || targetNorm->hasNils() || targetNorm->hasNumbers() ||
2287
targetNorm->hasStrings() || targetNorm->hasThreads() || targetNorm->hasBuffers() || targetNorm->hasFunctions() || targetNorm->hasTyvars() ||
2288
targetNorm->hasExternTypes())
2289
return {std::nullopt, Reduction::Erroneous, {}, {}};
2290
2291
if (isPending(metatableTy, ctx->solver))
2292
return {std::nullopt, Reduction::MaybeOk, {metatableTy}, {}};
2293
2294
// if the supposed metatable is not a table, we will fail to reduce.
2295
if (!get<TableType>(metatableTy) && !get<MetatableType>(metatableTy))
2296
return {std::nullopt, Reduction::Erroneous, {}, {}};
2297
2298
if (targetNorm->tables.size() == 1)
2299
{
2300
TypeId table = *targetNorm->tables.begin();
2301
2302
// findMetatableEntry demands the ability to emit errors, so we must give it
2303
// the necessary state to do that, even if we intend to just eat the errors.
2304
ErrorVec dummy;
2305
2306
std::optional<TypeId> metatableMetamethod = findMetatableEntry(ctx->builtins, dummy, table, "__metatable", location);
2307
2308
// if the `__metatable` metamethod is present, then the table is locked and we cannot `setmetatable` on it.
2309
if (metatableMetamethod)
2310
return {std::nullopt, Reduction::Erroneous, {}, {}};
2311
2312
TypeId withMetatable = ctx->arena->addType(MetatableType{table, metatableTy});
2313
2314
return {withMetatable, Reduction::MaybeOk, {}, {}};
2315
}
2316
2317
TypeId result = ctx->builtins->neverType;
2318
2319
for (auto componentTy : targetNorm->tables)
2320
{
2321
// findMetatableEntry demands the ability to emit errors, so we must give it
2322
// the necessary state to do that, even if we intend to just eat the errors.
2323
ErrorVec dummy;
2324
2325
std::optional<TypeId> metatableMetamethod = findMetatableEntry(ctx->builtins, dummy, componentTy, "__metatable", location);
2326
2327
// if the `__metatable` metamethod is present, then the table is locked and we cannot `setmetatable` on it.
2328
if (metatableMetamethod)
2329
return {std::nullopt, Reduction::Erroneous, {}, {}};
2330
2331
TypeId withMetatable = ctx->arena->addType(MetatableType{componentTy, metatableTy});
2332
SimplifyResult simplified = simplifyUnion(ctx->builtins, ctx->arena, result, withMetatable);
2333
2334
if (!simplified.blockedTypes.empty())
2335
{
2336
std::vector<TypeId> blockedTypes{};
2337
blockedTypes.reserve(simplified.blockedTypes.size());
2338
for (auto ty : simplified.blockedTypes)
2339
blockedTypes.push_back(ty);
2340
return {std::nullopt, Reduction::MaybeOk, std::move(blockedTypes), {}};
2341
}
2342
2343
result = simplified.result;
2344
}
2345
2346
return {result, Reduction::MaybeOk, {}, {}};
2347
}
2348
2349
static TypeFunctionReductionResult<TypeId> getmetatableHelper(TypeId targetTy, const Location& location, NotNull<TypeFunctionContext> ctx)
2350
{
2351
targetTy = follow(targetTy);
2352
2353
std::optional<TypeId> result = std::nullopt;
2354
bool erroneous = true;
2355
2356
if (get<TableType>(targetTy))
2357
erroneous = false;
2358
2359
if (auto mt = get<MetatableType>(targetTy))
2360
{
2361
result = mt->metatable;
2362
erroneous = false;
2363
}
2364
2365
if (auto clazz = get<ExternType>(targetTy))
2366
{
2367
result = clazz->metatable;
2368
erroneous = false;
2369
}
2370
2371
if (auto primitive = get<PrimitiveType>(targetTy))
2372
{
2373
if (primitive->type == PrimitiveType::Table)
2374
{
2375
// If we have `table` then we could have something with a
2376
// metatable, so claim the result is `table?`.
2377
result = ctx->arena->addType(UnionType{{ctx->builtins->tableType, ctx->builtins->nilType}});
2378
}
2379
else
2380
{
2381
result = primitive->metatable;
2382
}
2383
erroneous = false;
2384
}
2385
2386
if (auto singleton = get<SingletonType>(targetTy))
2387
{
2388
if (get<StringSingleton>(singleton))
2389
{
2390
auto primitiveString = get<PrimitiveType>(ctx->builtins->stringType);
2391
result = primitiveString->metatable;
2392
}
2393
erroneous = false;
2394
}
2395
2396
if (get<AnyType>(targetTy))
2397
{
2398
// getmetatable<any> ~ any
2399
result = targetTy;
2400
erroneous = false;
2401
}
2402
2403
if (get<ErrorType>(targetTy))
2404
{
2405
// getmetatable<error> ~ error
2406
result = targetTy;
2407
erroneous = false;
2408
}
2409
2410
if (erroneous)
2411
return {std::nullopt, Reduction::Erroneous, {}, {}};
2412
2413
// findMetatableEntry demands the ability to emit errors, so we must give it
2414
// the necessary state to do that, even if we intend to just eat the errors.
2415
ErrorVec dummy;
2416
2417
std::optional<TypeId> metatableMetamethod = findMetatableEntry(ctx->builtins, dummy, targetTy, "__metatable", location);
2418
2419
if (metatableMetamethod)
2420
return {metatableMetamethod, Reduction::MaybeOk, {}, {}};
2421
2422
if (result)
2423
return {result, Reduction::MaybeOk, {}, {}};
2424
2425
return {ctx->builtins->nilType, Reduction::MaybeOk, {}, {}};
2426
}
2427
2428
TypeFunctionReductionResult<TypeId> getmetatableTypeFunction(
2429
TypeId instance,
2430
const std::vector<TypeId>& typeParams,
2431
const std::vector<TypePackId>& packParams,
2432
NotNull<TypeFunctionContext> ctx
2433
)
2434
{
2435
if (typeParams.size() != 1 || !packParams.empty())
2436
{
2437
ctx->ice->ice("getmetatable type function: encountered a type function instance without the required argument structure");
2438
LUAU_ASSERT(false);
2439
}
2440
2441
const Location location = ctx->constraint ? ctx->constraint->location : Location{};
2442
2443
TypeId targetTy = follow(typeParams.at(0));
2444
2445
if (isPending(targetTy, ctx->solver))
2446
return {std::nullopt, Reduction::MaybeOk, {targetTy}, {}};
2447
2448
if (auto ut = get<UnionType>(targetTy))
2449
{
2450
std::vector<TypeId> options{};
2451
options.reserve(ut->options.size());
2452
2453
for (auto option : ut->options)
2454
{
2455
TypeFunctionReductionResult<TypeId> result = getmetatableHelper(option, location, ctx);
2456
2457
if (!result.result)
2458
return result;
2459
2460
options.push_back(*result.result);
2461
}
2462
2463
return {ctx->arena->addType(UnionType{std::move(options)}), Reduction::MaybeOk, {}, {}};
2464
}
2465
2466
if (auto it = get<IntersectionType>(targetTy))
2467
{
2468
std::vector<TypeId> parts{};
2469
parts.reserve(it->parts.size());
2470
2471
bool erroredWithUnknown = false;
2472
2473
for (auto part : it->parts)
2474
{
2475
TypeFunctionReductionResult<TypeId> result = getmetatableHelper(part, location, ctx);
2476
2477
if (!result.result)
2478
{
2479
// Don't immediately error if part is unknown
2480
if (get<UnknownType>(follow(part)))
2481
{
2482
erroredWithUnknown = true;
2483
continue;
2484
}
2485
else
2486
return result;
2487
}
2488
2489
parts.push_back(*result.result);
2490
}
2491
2492
// If all parts are unknown, return erroneous reduction
2493
if (erroredWithUnknown && parts.empty())
2494
return {std::nullopt, Reduction::Erroneous, {}, {}};
2495
2496
if (parts.size() == 1)
2497
return {parts.front(), Reduction::MaybeOk, {}, {}};
2498
2499
return {ctx->arena->addType(IntersectionType{std::move(parts)}), Reduction::MaybeOk, {}, {}};
2500
}
2501
2502
return getmetatableHelper(targetTy, location, ctx);
2503
}
2504
2505
TypeFunctionReductionResult<TypeId> weakoptionalTypeFunc(
2506
TypeId instance,
2507
const std::vector<TypeId>& typeParams,
2508
const std::vector<TypePackId>& packParams,
2509
NotNull<TypeFunctionContext> ctx
2510
)
2511
{
2512
if (typeParams.size() != 1 || !packParams.empty())
2513
{
2514
ctx->ice->ice("weakoptional type function: encountered a type function instance without the required argument structure");
2515
LUAU_ASSERT(false);
2516
}
2517
2518
TypeId targetTy = follow(typeParams.at(0));
2519
2520
if (isPending(targetTy, ctx->solver))
2521
return {std::nullopt, Reduction::MaybeOk, {targetTy}, {}};
2522
2523
if (is<NeverType>(instance))
2524
return {ctx->builtins->nilType, Reduction::MaybeOk, {}, {}};
2525
2526
std::shared_ptr<const NormalizedType> targetNorm = ctx->normalizer->normalize(targetTy);
2527
2528
if (!targetNorm)
2529
return {std::nullopt, Reduction::MaybeOk, {}, {}};
2530
2531
auto result = ctx->normalizer->isInhabited(targetNorm.get());
2532
if (result == NormalizationResult::False)
2533
return {ctx->builtins->nilType, Reduction::MaybeOk, {}, {}};
2534
2535
return {targetTy, Reduction::MaybeOk, {}, {}};
2536
}
2537
2538
BuiltinTypeFunctions::BuiltinTypeFunctions()
2539
: userFunc{"user", userDefinedTypeFunction}
2540
, notFunc{"not", notTypeFunction}
2541
, lenFunc{"len", lenTypeFunction}
2542
, unmFunc{"unm", unmTypeFunction}
2543
, addFunc{"add", addTypeFunction}
2544
, subFunc{"sub", subTypeFunction}
2545
, mulFunc{"mul", mulTypeFunction}
2546
, divFunc{"div", divTypeFunction}
2547
, idivFunc{"idiv", idivTypeFunction}
2548
, powFunc{"pow", powTypeFunction}
2549
, modFunc{"mod", modTypeFunction}
2550
, concatFunc{"concat", concatTypeFunction}
2551
, andFunc{"and", andTypeFunction, /*canReduceGenerics*/ true}
2552
, orFunc{"or", orTypeFunction, /*canReduceGenerics*/ true}
2553
, ltFunc{"lt", ltTypeFunction}
2554
, leFunc{"le", leTypeFunction}
2555
, refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ true}
2556
, singletonFunc{"singleton", singletonTypeFunction}
2557
, unionFunc{"union", unionTypeFunction}
2558
, intersectFunc{"intersect", intersectTypeFunction}
2559
, keyofFunc{"keyof", keyofTypeFunction}
2560
, rawkeyofFunc{"rawkeyof", rawkeyofTypeFunction}
2561
, indexFunc{"index", indexTypeFunction}
2562
, rawgetFunc{"rawget", rawgetTypeFunction}
2563
, setmetatableFunc{"setmetatable", setmetatableTypeFunction}
2564
, getmetatableFunc{"getmetatable", getmetatableTypeFunction}
2565
, weakoptionalFunc{"weakoptional", weakoptionalTypeFunc}
2566
{
2567
}
2568
2569
void BuiltinTypeFunctions::addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const
2570
{
2571
// make a type function for a one-argument type function
2572
auto mkUnaryTypeFunction = [&](const TypeFunction* tf)
2573
{
2574
TypeId t = arena->addType(GenericType{"T", Polarity::Negative});
2575
GenericTypeDefinition genericT{t};
2576
2577
return TypeFun{{genericT}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t}, {}})};
2578
};
2579
2580
// make a type function for a two-argument type function with a default argument for the second type being the first
2581
auto mkBinaryTypeFunctionWithDefault = [&](const TypeFunction* tf)
2582
{
2583
TypeId t = arena->addType(GenericType{"T", Polarity::Negative});
2584
TypeId u = arena->addType(GenericType{"U", Polarity::Negative});
2585
GenericTypeDefinition genericT{t};
2586
GenericTypeDefinition genericU{u, {t}};
2587
2588
return TypeFun{{genericT, genericU}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t, u}, {}})};
2589
};
2590
2591
// make a two-argument type function without the default arguments
2592
auto mkBinaryTypeFunction = [&](const TypeFunction* tf)
2593
{
2594
TypeId t = arena->addType(GenericType{"T", Polarity::Negative});
2595
TypeId u = arena->addType(GenericType{"U", Polarity::Negative});
2596
GenericTypeDefinition genericT{t};
2597
GenericTypeDefinition genericU{u};
2598
2599
return TypeFun{{genericT, genericU}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t, u}, {}})};
2600
};
2601
2602
scope->exportedTypeBindings[lenFunc.name] = mkUnaryTypeFunction(&lenFunc);
2603
scope->exportedTypeBindings[unmFunc.name] = mkUnaryTypeFunction(&unmFunc);
2604
2605
scope->exportedTypeBindings[addFunc.name] = mkBinaryTypeFunctionWithDefault(&addFunc);
2606
scope->exportedTypeBindings[subFunc.name] = mkBinaryTypeFunctionWithDefault(&subFunc);
2607
scope->exportedTypeBindings[mulFunc.name] = mkBinaryTypeFunctionWithDefault(&mulFunc);
2608
scope->exportedTypeBindings[divFunc.name] = mkBinaryTypeFunctionWithDefault(&divFunc);
2609
scope->exportedTypeBindings[idivFunc.name] = mkBinaryTypeFunctionWithDefault(&idivFunc);
2610
scope->exportedTypeBindings[powFunc.name] = mkBinaryTypeFunctionWithDefault(&powFunc);
2611
scope->exportedTypeBindings[modFunc.name] = mkBinaryTypeFunctionWithDefault(&modFunc);
2612
scope->exportedTypeBindings[concatFunc.name] = mkBinaryTypeFunctionWithDefault(&concatFunc);
2613
2614
scope->exportedTypeBindings[ltFunc.name] = mkBinaryTypeFunctionWithDefault(&ltFunc);
2615
scope->exportedTypeBindings[leFunc.name] = mkBinaryTypeFunctionWithDefault(&leFunc);
2616
scope->exportedTypeBindings[keyofFunc.name] = mkUnaryTypeFunction(&keyofFunc);
2617
scope->exportedTypeBindings[rawkeyofFunc.name] = mkUnaryTypeFunction(&rawkeyofFunc);
2618
2619
scope->exportedTypeBindings[indexFunc.name] = mkBinaryTypeFunction(&indexFunc);
2620
scope->exportedTypeBindings[rawgetFunc.name] = mkBinaryTypeFunction(&rawgetFunc);
2621
2622
scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunction(&setmetatableFunc);
2623
scope->exportedTypeBindings[getmetatableFunc.name] = mkUnaryTypeFunction(&getmetatableFunc);
2624
}
2625
2626
} // namespace Luau
2627
2628