mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
Sync to upstream/release/612 (#1162)
New solver * Fix bugs where bidirectional type inference would fail to take effect at the proper stage. * Improve inference of mutually recursive functions * Fix crashes --------- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
parent
67ce75e870
commit
d6c2472f0c
@ -102,6 +102,7 @@ struct FunctionCheckConstraint
|
||||
TypePackId argsPack;
|
||||
|
||||
class AstExprCall* callSite = nullptr;
|
||||
NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes;
|
||||
};
|
||||
|
||||
// prim FreeType ExpectedType PrimitiveType
|
||||
|
@ -138,6 +138,53 @@ void forEachConstraint(const Checkpoint& start, const Checkpoint& end, const Con
|
||||
f(cg->constraints[i]);
|
||||
}
|
||||
|
||||
struct HasFreeType : TypeOnceVisitor
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
HasFreeType()
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
if (result || ty->persistent)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp) override
|
||||
{
|
||||
if (result)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const ClassType&) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const FreeType&) override
|
||||
{
|
||||
result = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypePackId ty, const FreeTypePack&) override
|
||||
{
|
||||
result = true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
bool hasFreeType(TypeId ty)
|
||||
{
|
||||
HasFreeType hft{};
|
||||
hft.traverse(ty);
|
||||
return hft.result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ConstraintGenerator::ConstraintGenerator(ModulePtr module, NotNull<Normalizer> normalizer, NotNull<ModuleResolver> moduleResolver,
|
||||
@ -229,6 +276,8 @@ std::optional<TypeId> ConstraintGenerator::lookup(const ScopePtr& scope, DefId d
|
||||
{
|
||||
if (auto found = scope->lookup(def))
|
||||
return *found;
|
||||
else if (phi->operands.size() == 1)
|
||||
return lookup(scope, phi->operands[0], prototype);
|
||||
else if (!prototype)
|
||||
return std::nullopt;
|
||||
|
||||
@ -837,6 +886,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
|
||||
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
|
||||
sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->func->location};
|
||||
|
||||
bool sigFullyDefined = !hasFreeType(sig.signature);
|
||||
if (sigFullyDefined)
|
||||
asMutable(functionType)->ty.emplace<BoundType>(sig.signature);
|
||||
|
||||
DefId def = dfg->getDef(function->name);
|
||||
scope->lvalueTypes[def] = functionType;
|
||||
scope->rvalueRefinements[def] = functionType;
|
||||
@ -847,12 +900,16 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
|
||||
checkFunctionBody(sig.bodyScope, function->func);
|
||||
Checkpoint end = checkpoint(this);
|
||||
|
||||
if (!sigFullyDefined)
|
||||
{
|
||||
NotNull<Scope> constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()};
|
||||
std::unique_ptr<Constraint> c =
|
||||
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature});
|
||||
|
||||
Constraint* previous = nullptr;
|
||||
forEachConstraint(start, end, this, [&c, &previous](const ConstraintPtr& constraint) {
|
||||
forEachConstraint(start, end, this,
|
||||
[&c, &previous](const ConstraintPtr& constraint)
|
||||
{
|
||||
c->dependencies.push_back(NotNull{constraint.get()});
|
||||
|
||||
if (auto psc = get<PackSubtypeConstraint>(*constraint); psc && psc->returns)
|
||||
@ -866,6 +923,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
|
||||
|
||||
addConstraint(scope, std::move(c));
|
||||
module->astTypes[function->func] = functionType;
|
||||
}
|
||||
else
|
||||
module->astTypes[function->func] = sig.signature;
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
@ -879,12 +939,19 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
|
||||
|
||||
Checkpoint start = checkpoint(this);
|
||||
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
|
||||
bool sigFullyDefined = !hasFreeType(sig.signature);
|
||||
|
||||
if (sigFullyDefined)
|
||||
asMutable(generalizedType)->ty.emplace<BoundType>(sig.signature);
|
||||
|
||||
DenseHashSet<Constraint*> excludeList{nullptr};
|
||||
|
||||
DefId def = dfg->getDef(function->name);
|
||||
std::optional<TypeId> existingFunctionTy = lookup(scope, def);
|
||||
|
||||
if (sigFullyDefined && existingFunctionTy && get<BlockedType>(*existingFunctionTy))
|
||||
asMutable(*existingFunctionTy)->ty.emplace<BoundType>(sig.signature);
|
||||
|
||||
if (AstExprLocal* localName = function->name->as<AstExprLocal>())
|
||||
{
|
||||
if (existingFunctionTy)
|
||||
@ -906,6 +973,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
|
||||
if (!existingFunctionTy)
|
||||
ice->ice("prepopulateGlobalScope did not populate a global name", globalName->location);
|
||||
|
||||
if (!sigFullyDefined)
|
||||
generalizedType = *existingFunctionTy;
|
||||
|
||||
sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location};
|
||||
@ -943,12 +1011,16 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
|
||||
checkFunctionBody(sig.bodyScope, function->func);
|
||||
Checkpoint end = checkpoint(this);
|
||||
|
||||
if (!sigFullyDefined)
|
||||
{
|
||||
NotNull<Scope> constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()};
|
||||
std::unique_ptr<Constraint> c =
|
||||
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{generalizedType, sig.signature});
|
||||
|
||||
Constraint* previous = nullptr;
|
||||
forEachConstraint(start, end, this, [&c, &excludeList, &previous](const ConstraintPtr& constraint) {
|
||||
forEachConstraint(start, end, this,
|
||||
[&c, &excludeList, &previous](const ConstraintPtr& constraint)
|
||||
{
|
||||
if (!excludeList.contains(constraint.get()))
|
||||
c->dependencies.push_back(NotNull{constraint.get()});
|
||||
|
||||
@ -962,6 +1034,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
|
||||
});
|
||||
|
||||
addConstraint(scope, std::move(c));
|
||||
}
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
@ -1626,24 +1699,6 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
||||
TypePackId argPack = addTypePack(std::move(args), argTail);
|
||||
FunctionType ftv(TypeLevel{}, scope.get(), argPack, rets, std::nullopt, call->self);
|
||||
|
||||
NotNull<Constraint> fcc = addConstraint(scope, call->func->location,
|
||||
FunctionCallConstraint{
|
||||
fnType,
|
||||
argPack,
|
||||
rets,
|
||||
call,
|
||||
std::move(discriminantTypes),
|
||||
&module->astOverloadResolvedTypes,
|
||||
});
|
||||
|
||||
NotNull<Constraint> foo = addConstraint(scope, call->func->location,
|
||||
FunctionCheckConstraint{
|
||||
fnType,
|
||||
argPack,
|
||||
call
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
* To make bidirectional type checking work, we need to solve these constraints in a particular order:
|
||||
*
|
||||
@ -1653,14 +1708,35 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
||||
* 4. Solve the call
|
||||
*/
|
||||
|
||||
forEachConstraint(funcBeginCheckpoint, funcEndCheckpoint, this, [foo](const ConstraintPtr& constraint) {
|
||||
foo->dependencies.emplace_back(constraint.get());
|
||||
NotNull<Constraint> checkConstraint = addConstraint(scope, call->func->location,
|
||||
FunctionCheckConstraint{
|
||||
fnType,
|
||||
argPack,
|
||||
call,
|
||||
NotNull{&module->astExpectedTypes}
|
||||
}
|
||||
);
|
||||
|
||||
forEachConstraint(funcBeginCheckpoint, funcEndCheckpoint, this, [checkConstraint](const ConstraintPtr& constraint) {
|
||||
checkConstraint->dependencies.emplace_back(constraint.get());
|
||||
});
|
||||
|
||||
forEachConstraint(argBeginCheckpoint, argEndCheckpoint, this, [foo, fcc](const ConstraintPtr& constraint) {
|
||||
constraint->dependencies.emplace_back(foo);
|
||||
NotNull<Constraint> callConstraint = addConstraint(scope, call->func->location,
|
||||
FunctionCallConstraint{
|
||||
fnType,
|
||||
argPack,
|
||||
rets,
|
||||
call,
|
||||
std::move(discriminantTypes),
|
||||
&module->astOverloadResolvedTypes,
|
||||
});
|
||||
|
||||
fcc->dependencies.emplace_back(constraint.get());
|
||||
callConstraint->dependencies.push_back(checkConstraint);
|
||||
|
||||
forEachConstraint(argBeginCheckpoint, argEndCheckpoint, this, [checkConstraint, callConstraint](const ConstraintPtr& constraint) {
|
||||
constraint->dependencies.emplace_back(checkConstraint);
|
||||
|
||||
callConstraint->dependencies.emplace_back(constraint.get());
|
||||
});
|
||||
|
||||
return InferencePack{rets, {refinementArena.variadic(returnRefinements)}};
|
||||
@ -1884,7 +1960,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
|
||||
checkFunctionBody(sig.bodyScope, func);
|
||||
Checkpoint endCheckpoint = checkpoint(this);
|
||||
|
||||
if (generalize)
|
||||
if (generalize && hasFreeType(sig.signature))
|
||||
{
|
||||
TypeId generalizedTy = arena->addType(BlockedType{});
|
||||
NotNull<Constraint> gc = addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature});
|
||||
|
@ -1139,7 +1139,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
||||
return block(fn, constraint);
|
||||
|
||||
if (isBlocked(argsPack))
|
||||
return block(argsPack, constraint);
|
||||
return true;
|
||||
|
||||
// We know the type of the function and the arguments it expects to receive.
|
||||
// We also know the TypeIds of the actual arguments that will be passed.
|
||||
@ -1152,8 +1152,10 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
||||
// types.
|
||||
|
||||
// FIXME: Bidirectional type checking of overloaded functions is not yet supported.
|
||||
if (auto ftv = get<FunctionType>(fn))
|
||||
{
|
||||
const FunctionType* ftv = get<FunctionType>(fn);
|
||||
if (!ftv)
|
||||
return true;
|
||||
|
||||
const std::vector<TypeId> expectedArgs = flatten(ftv->argTypes).first;
|
||||
const std::vector<TypeId> argPackHead = flatten(argsPack).first;
|
||||
|
||||
@ -1161,10 +1163,13 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
||||
{
|
||||
const TypeId expectedArgTy = follow(expectedArgs[i]);
|
||||
const TypeId actualArgTy = follow(argPackHead[i]);
|
||||
const AstExpr* expr = c.callSite->args.data[i];
|
||||
|
||||
(*c.astExpectedTypes)[expr] = expectedArgTy;
|
||||
|
||||
const FunctionType* expectedLambdaTy = get<FunctionType>(expectedArgTy);
|
||||
const FunctionType* lambdaTy = get<FunctionType>(actualArgTy);
|
||||
const AstExprFunction* lambdaExpr = c.callSite->args.data[i]->as<AstExprFunction>();
|
||||
const AstExprFunction* lambdaExpr = expr->as<AstExprFunction>();
|
||||
|
||||
if (expectedLambdaTy && lambdaTy && lambdaExpr)
|
||||
{
|
||||
@ -1179,13 +1184,12 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (expr->is<AstExprConstantBool>() || expr->is<AstExprConstantString>() || expr->is<AstExprConstantNumber>() || expr->is<AstExprConstantNil>() || expr->is<AstExprTable>())
|
||||
{
|
||||
Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}};
|
||||
u2.unify(actualArgTy, expectedArgTy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -593,6 +593,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
return SubtypingResult{false}.withSubComponent(TypePath::PackField::Tail);
|
||||
}
|
||||
}
|
||||
else if (get<ErrorTypePack>(*subTail))
|
||||
return SubtypingResult{true}.withSubComponent(TypePath::PackField::Tail);
|
||||
else
|
||||
unexpected(*subTail);
|
||||
}
|
||||
@ -643,6 +645,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
return SubtypingResult{false}.withSuperComponent(TypePath::PackField::Tail);
|
||||
}
|
||||
}
|
||||
else if (get<ErrorTypePack>(*superTail))
|
||||
return SubtypingResult{true}.withSuperComponent(TypePath::PackField::Tail);
|
||||
else
|
||||
unexpected(*superTail);
|
||||
}
|
||||
|
@ -495,11 +495,12 @@ struct TypeChecker2
|
||||
return checkForFamilyInhabitance(follow(*ty), annotation->location);
|
||||
}
|
||||
|
||||
TypePackId lookupPackAnnotation(AstTypePack* annotation)
|
||||
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation)
|
||||
{
|
||||
TypePackId* tp = module->astResolvedTypePacks.find(annotation);
|
||||
LUAU_ASSERT(tp);
|
||||
return follow(*tp);
|
||||
if (tp != nullptr)
|
||||
return {follow(*tp)};
|
||||
return {};
|
||||
}
|
||||
|
||||
TypeId lookupExpectedType(AstExpr* expr)
|
||||
@ -2187,9 +2188,11 @@ struct TypeChecker2
|
||||
}
|
||||
else if (p.typePack)
|
||||
{
|
||||
TypePackId tp = lookupPackAnnotation(p.typePack);
|
||||
std::optional<TypePackId> tp = lookupPackAnnotation(p.typePack);
|
||||
if (!tp.has_value())
|
||||
continue;
|
||||
|
||||
if (typesProvided < typesRequired && size(tp) == 1 && finite(tp) && first(tp))
|
||||
if (typesProvided < typesRequired && size(*tp) == 1 && finite(*tp) && first(*tp))
|
||||
{
|
||||
typesProvided += 1;
|
||||
}
|
||||
@ -2607,7 +2610,12 @@ struct TypeChecker2
|
||||
// shape. We instead want to report the unknown property error of
|
||||
// the `else` branch.
|
||||
else if (context == ValueContext::LValue && !get<ClassType>(tableTy))
|
||||
{
|
||||
if (get<PrimitiveType>(tableTy) || get<FunctionType>(tableTy))
|
||||
reportError(NotATable{tableTy}, location);
|
||||
else
|
||||
reportError(CannotExtendTable{tableTy, CannotExtendTable::Property, prop}, location);
|
||||
}
|
||||
else
|
||||
reportError(UnknownProperty{tableTy, prop}, location);
|
||||
}
|
||||
|
@ -92,10 +92,11 @@ private:
|
||||
size_t tail_size = queue_size - head_size; // how many elements are in the tail portion (i.e. any portion that wrapped to the front)
|
||||
|
||||
// move the head into the new buffer
|
||||
if (head_size != 0)
|
||||
std::uninitialized_move(buffer + head, buffer + head + head_size, new_buffer);
|
||||
|
||||
// move the tail into the new buffer immediately after
|
||||
if (head_size < queue_size)
|
||||
if (tail_size != 0)
|
||||
std::uninitialized_move(buffer, buffer + tail_size, new_buffer + head_size);
|
||||
|
||||
// destroy the old elements
|
||||
@ -128,8 +129,16 @@ public:
|
||||
, head(other.head)
|
||||
, queue_size(other.queue_size)
|
||||
{
|
||||
// copy the contents of the other buffer to this one
|
||||
std::uninitialized_copy(other.buffer, other.buffer + other.buffer_capacity, buffer);
|
||||
// copy the initialized contents of the other buffer to this one
|
||||
size_t head_size = std::min(other.queue_size,
|
||||
other.buffer_capacity - other.head); // how many elements are in the head portion (i.e. from the head to the end of the buffer)
|
||||
size_t tail_size = other.queue_size - head_size; // how many elements are in the tail portion (i.e. any portion that wrapped to the front)
|
||||
|
||||
if (head_size != 0)
|
||||
std::uninitialized_copy(other.buffer + other.head, other.buffer + other.head + head_size, buffer + head);
|
||||
|
||||
if (tail_size != 0)
|
||||
std::uninitialized_copy(other.buffer, other.buffer + tail_size, buffer);
|
||||
}
|
||||
|
||||
VecDeque(const VecDeque& other, const Allocator& alloc)
|
||||
@ -139,8 +148,16 @@ public:
|
||||
, head(other.head)
|
||||
, queue_size(other.queue_size)
|
||||
{
|
||||
// copy the contents of the other buffer to this one
|
||||
std::uninitialized_copy(other.buffer, other.buffer + other.buffer_capacity, buffer);
|
||||
// copy the initialized contents of the other buffer to this one
|
||||
size_t head_size = std::min(other.queue_size,
|
||||
other.buffer_capacity - other.head); // how many elements are in the head portion (i.e. from the head to the end of the buffer)
|
||||
size_t tail_size = other.queue_size - head_size; // how many elements are in the tail portion (i.e. any portion that wrapped to the front)
|
||||
|
||||
if (head_size != 0)
|
||||
std::uninitialized_copy(other.buffer + other.head, other.buffer + other.head + head_size, buffer + head);
|
||||
|
||||
if (tail_size != 0)
|
||||
std::uninitialized_copy(other.buffer, other.buffer + tail_size, buffer);
|
||||
}
|
||||
|
||||
VecDeque(VecDeque&& other) noexcept
|
||||
@ -195,13 +212,18 @@ public:
|
||||
buffer_capacity = other.buffer_capacity;
|
||||
}
|
||||
|
||||
size_t head_size = other.capacity() - other.head; // how many elements are in the head portion (i.e. from the head to the end of the buffer)
|
||||
size_t tail_size = other.size() - head_size; // how many elements are in the tail portion (i.e. any portion that wrapped to the front)
|
||||
size_t head_size = std::min(other.queue_size,
|
||||
other.buffer_capacity - other.head); // how many elements are in the head portion (i.e. from the head to the end of the buffer)
|
||||
size_t tail_size = other.queue_size - head_size; // how many elements are in the tail portion (i.e. any portion that wrapped to the front)
|
||||
|
||||
// copy the contents of the other buffer's head into place
|
||||
std::uninitialized_copy(other.buffer + other.head, other.buffer + head + head_size, buffer);
|
||||
// Assignment doesn't try to match the capacity of 'other' and thus makes the buffer contiguous
|
||||
head = 0;
|
||||
queue_size = other.queue_size;
|
||||
|
||||
// copy the contents of the other buffer's tail into place immediately after
|
||||
if (head_size != 0)
|
||||
std::uninitialized_copy(other.buffer + other.head, other.buffer + other.head + head_size, buffer);
|
||||
|
||||
if (tail_size != 0)
|
||||
std::uninitialized_copy(other.buffer, other.buffer + tail_size, buffer + head_size);
|
||||
|
||||
return *this;
|
||||
@ -325,17 +347,16 @@ public:
|
||||
T* new_buffer = this->allocate(new_capacity);
|
||||
|
||||
// move the head into the new buffer
|
||||
if (head_size != 0)
|
||||
std::uninitialized_move(buffer + head, buffer + head + head_size, new_buffer);
|
||||
|
||||
// move the tail into the new buffer immediately after, if we have one
|
||||
if (head_size < queue_size)
|
||||
std::uninitialized_move(buffer, buffer + tail_size, new_buffer + head_size);
|
||||
|
||||
// move the tail into the new buffer immediately after
|
||||
if (tail_size != 0)
|
||||
std::uninitialized_move(buffer, buffer + tail_size, new_buffer + head_size);
|
||||
|
||||
// destroy all the existing elements before freeing the old buffer
|
||||
destroyElements();
|
||||
|
||||
// deallocate the old buffer
|
||||
this->deallocate(buffer, old_capacity);
|
||||
|
||||
@ -350,7 +371,8 @@ public:
|
||||
return buffer_capacity;
|
||||
}
|
||||
|
||||
void shrink_to_fit() {
|
||||
void shrink_to_fit()
|
||||
{
|
||||
size_t old_capacity = capacity();
|
||||
size_t new_capacity = queue_size;
|
||||
|
||||
@ -365,10 +387,11 @@ public:
|
||||
T* new_buffer = this->allocate(new_capacity);
|
||||
|
||||
// move the head into the new buffer
|
||||
if (head_size != 0)
|
||||
std::uninitialized_move(buffer + head, buffer + head + head_size, new_buffer);
|
||||
|
||||
// move the tail into the new buffer immediately after, if we have one
|
||||
if (head_size < queue_size)
|
||||
if (tail_size != 0)
|
||||
std::uninitialized_move(buffer, buffer + tail_size, new_buffer + head_size);
|
||||
|
||||
// destroy all the existing elements before freeing the old buffer
|
||||
|
@ -2707,16 +2707,26 @@ local t: Test = { @1 }
|
||||
CHECK(ac.entryMap.count("first"));
|
||||
CHECK(ac.entryMap.count("second"));
|
||||
CHECK_EQ(ac.context, AutocompleteContext::Property);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "suggest_table_keys_no_initial_character_2")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauAutocompleteTableKeysNoInitialCharacter, true};
|
||||
|
||||
check(R"(
|
||||
type Test = { first: number, second: number }
|
||||
local t: Test = { first = 1, @1 }
|
||||
)");
|
||||
|
||||
ac = autocomplete('1');
|
||||
auto ac = autocomplete('1');
|
||||
CHECK_EQ(ac.entryMap.count("first"), 0);
|
||||
CHECK(ac.entryMap.count("second"));
|
||||
CHECK_EQ(ac.context, AutocompleteContext::Property);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "suggest_table_keys_no_initial_character_3")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauAutocompleteTableKeysNoInitialCharacter, true};
|
||||
|
||||
check(R"(
|
||||
type Properties = { TextScaled: boolean, Text: string }
|
||||
@ -2725,7 +2735,8 @@ local function create(props: Properties) end
|
||||
create({ @1 })
|
||||
)");
|
||||
|
||||
ac = autocomplete('1');
|
||||
auto ac = autocomplete('1');
|
||||
CHECK(ac.entryMap.size() > 0);
|
||||
CHECK(ac.entryMap.count("TextScaled"));
|
||||
CHECK(ac.entryMap.count("Text"));
|
||||
CHECK_EQ(ac.context, AutocompleteContext::Property);
|
||||
|
@ -582,4 +582,28 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_rfc_example")
|
||||
CHECK_EQ("\"cactus\"", toString(tm->givenType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_oss_crash_gh1161")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local EnumVariants = {
|
||||
["a"] = 1, ["b"] = 2, ["c"] = 3
|
||||
}
|
||||
|
||||
type EnumKey = keyof<typeof(EnumVariants)>
|
||||
|
||||
function fnA<T>(i: T): keyof<T> end
|
||||
|
||||
function fnB(i: EnumKey) end
|
||||
|
||||
local result = fnA(EnumVariants)
|
||||
fnB(result)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(get<FunctionExitsWithoutReturning>(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -1075,4 +1075,11 @@ TEST_CASE_FIXTURE(Fixture, "typeof_is_not_a_valid_alias_name")
|
||||
CHECK("Type aliases cannot be named typeof" == toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzzer_bug_doesnt_crash")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type t0 = (t0<t0...>)
|
||||
)");
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
TEST_SUITE_END();
|
||||
|
@ -50,14 +50,31 @@ TEST_CASE_FIXTURE(Fixture, "tc_function")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "check_function_bodies")
|
||||
{
|
||||
CheckResult result = check("function myFunction() local a = 0 a = true end");
|
||||
CheckResult result = check(R"(
|
||||
function myFunction(): number
|
||||
local a = 0
|
||||
a = true
|
||||
return a
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(result.errors[0], (TypeError{Location{Position{0, 44}, Position{0, 48}}, TypeMismatch{
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
const TypePackMismatch* tm = get<TypePackMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK(toString(tm->wantedTp) == "number");
|
||||
CHECK(toString(tm->givenTp) == "boolean");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(result.errors[0], (TypeError{Location{Position{3, 16}, Position{3, 20}}, TypeMismatch{
|
||||
builtinTypes->numberType,
|
||||
builtinTypes->booleanType,
|
||||
}}));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "cannot_hoist_interior_defns_into_signature")
|
||||
{
|
||||
@ -2227,4 +2244,69 @@ a = function(a, b) return a + b end
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "simple_unannotated_mutual_recursion")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function even(n)
|
||||
if n == 0 then
|
||||
return true
|
||||
else
|
||||
return odd(n - 1)
|
||||
end
|
||||
end
|
||||
|
||||
function odd(n)
|
||||
if n == 0 then
|
||||
return false
|
||||
elseif n == 1 then
|
||||
return true
|
||||
else
|
||||
return even(n - 1)
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||
CHECK(toString(result.errors[0]) == "Type family instance sub<unknown, number> is uninhabited");
|
||||
CHECK(toString(result.errors[1]) == "Type family instance sub<unknown, number> is uninhabited");
|
||||
CHECK(toString(result.errors[2]) == "Type family instance sub<unknown, number> is uninhabited");
|
||||
CHECK(toString(result.errors[3]) == "Type family instance sub<unknown, number> is uninhabited");
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(toString(result.errors[0]) == "Unknown type used in - operation; consider adding a type annotation to 'n'");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "simple_lightly_annotated_mutual_recursion")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function even(n: number)
|
||||
if n == 0 then
|
||||
return true
|
||||
else
|
||||
return odd(n - 1)
|
||||
end
|
||||
end
|
||||
|
||||
function odd(n: number)
|
||||
if n == 0 then
|
||||
return false
|
||||
elseif n == 1 then
|
||||
return true
|
||||
else
|
||||
return even(n - 1)
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("(number) -> boolean", toString(requireType("even")));
|
||||
CHECK_EQ("(number) -> boolean", toString(requireType("odd")));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -144,12 +144,12 @@ TEST_CASE_FIXTURE(Fixture, "check_recursive_generic_function")
|
||||
TEST_CASE_FIXTURE(Fixture, "check_mutual_generic_functions")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local id2
|
||||
local function id1<a>(x:a):a
|
||||
function id1<a>(x:a):a
|
||||
local y: string = id2("hi")
|
||||
local z: number = id2(37)
|
||||
return x
|
||||
end
|
||||
|
||||
function id2<a>(x:a):a
|
||||
local y: string = id1("hi")
|
||||
local z: number = id1(37)
|
||||
@ -159,6 +159,68 @@ TEST_CASE_FIXTURE(Fixture, "check_mutual_generic_functions")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "check_mutual_generic_functions_unannotated")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function id1(x)
|
||||
local y: string = id2("hi")
|
||||
local z: number = id2(37)
|
||||
return x
|
||||
end
|
||||
|
||||
function id2(x)
|
||||
local y: string = id1("hi")
|
||||
local z: number = id1(37)
|
||||
return x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "check_mutual_generic_functions_errors")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function id1(x)
|
||||
local y: string = id2(37) -- odd
|
||||
local z: number = id2("hi") -- even
|
||||
return x
|
||||
end
|
||||
|
||||
function id2(x)
|
||||
local y: string = id1(37) -- odd
|
||||
local z: number = id1("hi") -- even
|
||||
return x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||
|
||||
// odd errors
|
||||
for (int i = 0; i < 4; i += 2)
|
||||
{
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[i]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ("string", toString(tm->wantedType));
|
||||
CHECK_EQ("number", toString(tm->givenType));
|
||||
}
|
||||
|
||||
// even errors
|
||||
for (int i = 1; i < 4; i += 2)
|
||||
{
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[i]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ("number", toString(tm->wantedType));
|
||||
CHECK_EQ("string", toString(tm->givenType));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_functions_in_types")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
@ -947,7 +1009,7 @@ TEST_CASE_FIXTURE(Fixture, "instantiate_cyclic_generic_function")
|
||||
|
||||
TypeId arg = follow(*optionArg);
|
||||
const TableType* argTable = get<TableType>(arg);
|
||||
REQUIRE(argTable != nullptr);
|
||||
REQUIRE_MESSAGE(argTable != nullptr, "Expected table but got " << toString(arg));
|
||||
|
||||
std::optional<Property> methodProp = get(argTable->props, "method");
|
||||
REQUIRE(bool(methodProp));
|
||||
|
@ -287,23 +287,28 @@ TEST_CASE("shrink_to_fit_works")
|
||||
CHECK_EQ(queue[j], j);
|
||||
}
|
||||
|
||||
// To avoid hitting SSO issues, we need sufficiently long strings.
|
||||
// This list of test strings consists of quotes from Ursula K. Le Guin.
|
||||
const static std::string testStrings[] = {
|
||||
"Love doesn't just sit there, like a stone, it has to be made, like bread; remade all the time, made new.",
|
||||
const static std::string testStringSet[2][10] = {
|
||||
|
||||
// To hit potential SSO issues showing memory management issues, we need small strings
|
||||
{"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"},
|
||||
|
||||
// This list of non-SSO test strings consists of quotes from Ursula K. Le Guin.
|
||||
{"Love doesn't just sit there, like a stone, it has to be made, like bread; remade all the time, made new.",
|
||||
"People who deny the existence of dragons are often eaten by dragons. From within.",
|
||||
"It is good to have an end to journey toward; but it is the journey that matters, in the end.",
|
||||
"We're each of us alone, to be sure. What can you do but hold your hand out in the dark?",
|
||||
"When you light a candle, you also cast a shadow.",
|
||||
"We're each of us alone, to be sure. What can you do but hold your hand out in the dark?", "When you light a candle, you also cast a shadow.",
|
||||
"You cannot buy the revolution. You cannot make the revolution. You can only be the revolution. It is in your spirit, or it is nowhere.",
|
||||
"To learn which questions are unanswerable, and not to answer them: this skill is most needful in times of stress and darkness.",
|
||||
"What sane person could live in this world and not be crazy?",
|
||||
"The only thing that makes life possible is permanent, intolerable uncertainty: not knowing what comes next.",
|
||||
"My imagination makes me human and makes me a fool; it gives me all the world and exiles me from it."
|
||||
};
|
||||
"My imagination makes me human and makes me a fool; it gives me all the world and exiles me from it."}};
|
||||
|
||||
TEST_CASE("string_queue_test_no_initial_capacity")
|
||||
{
|
||||
for (size_t stringSet = 0; stringSet < 2; stringSet++)
|
||||
{
|
||||
auto testStrings = testStringSet[stringSet];
|
||||
|
||||
// initial capacity is not set, so this should grow to be 11
|
||||
Luau::VecDeque<std::string> queue;
|
||||
|
||||
@ -327,9 +332,14 @@ TEST_CASE("string_queue_test_no_initial_capacity")
|
||||
queue.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("string_queue_test")
|
||||
{
|
||||
for (size_t stringSet = 0; stringSet < 2; stringSet++)
|
||||
{
|
||||
auto testStrings = testStringSet[stringSet];
|
||||
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<std::string> queue;
|
||||
queue.reserve(5);
|
||||
@ -354,9 +364,14 @@ TEST_CASE("string_queue_test")
|
||||
queue.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("string_queue_test_initializer_list")
|
||||
{
|
||||
for (size_t stringSet = 0; stringSet < 2; stringSet++)
|
||||
{
|
||||
auto testStrings = testStringSet[stringSet];
|
||||
|
||||
Luau::VecDeque<std::string> queue{
|
||||
testStrings[0],
|
||||
testStrings[1],
|
||||
@ -385,9 +400,14 @@ TEST_CASE("string_queue_test_initializer_list")
|
||||
queue.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("reverse_string_queue_test")
|
||||
{
|
||||
for (size_t stringSet = 0; stringSet < 2; stringSet++)
|
||||
{
|
||||
auto testStrings = testStringSet[stringSet];
|
||||
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<std::string> queue;
|
||||
queue.reserve(5);
|
||||
@ -412,9 +432,14 @@ TEST_CASE("reverse_string_queue_test")
|
||||
queue.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("random_access_string_queue_test")
|
||||
{
|
||||
for (size_t stringSet = 0; stringSet < 2; stringSet++)
|
||||
{
|
||||
auto testStrings = testStringSet[stringSet];
|
||||
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<std::string> queue;
|
||||
queue.reserve(5);
|
||||
@ -434,9 +459,14 @@ TEST_CASE("random_access_string_queue_test")
|
||||
CHECK_EQ(queue[j], testStrings[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("clear_works_on_string_queue")
|
||||
{
|
||||
for (size_t stringSet = 0; stringSet < 2; stringSet++)
|
||||
{
|
||||
auto testStrings = testStringSet[stringSet];
|
||||
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<std::string> queue;
|
||||
queue.reserve(5);
|
||||
@ -457,9 +487,14 @@ TEST_CASE("clear_works_on_string_queue")
|
||||
CHECK(queue.empty());
|
||||
CHECK(queue.size() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("pop_front_string_at_end")
|
||||
{
|
||||
for (size_t stringSet = 0; stringSet < 2; stringSet++)
|
||||
{
|
||||
auto testStrings = testStringSet[stringSet];
|
||||
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<std::string> queue;
|
||||
queue.reserve(5);
|
||||
@ -485,9 +520,14 @@ TEST_CASE("pop_front_string_at_end")
|
||||
queue.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("pop_back_string_at_front")
|
||||
{
|
||||
for (size_t stringSet = 0; stringSet < 2; stringSet++)
|
||||
{
|
||||
auto testStrings = testStringSet[stringSet];
|
||||
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<std::string> queue;
|
||||
queue.reserve(5);
|
||||
@ -513,9 +553,14 @@ TEST_CASE("pop_back_string_at_front")
|
||||
queue.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("string_queue_is_contiguous")
|
||||
{
|
||||
for (size_t stringSet = 0; stringSet < 2; stringSet++)
|
||||
{
|
||||
auto testStrings = testStringSet[stringSet];
|
||||
|
||||
// initial capacity is not set, so this should grow to be 11
|
||||
Luau::VecDeque<std::string> queue{};
|
||||
|
||||
@ -530,10 +575,68 @@ TEST_CASE("string_queue_is_contiguous")
|
||||
|
||||
CHECK_EQ(queue.capacity(), 11);
|
||||
CHECK(queue.is_contiguous());
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue[j], testStrings[j]);
|
||||
|
||||
// Check copy construction
|
||||
Luau::VecDeque<std::string> queue2 = queue;
|
||||
|
||||
REQUIRE(!queue2.empty());
|
||||
REQUIRE(queue2.size() == 10);
|
||||
|
||||
CHECK_EQ(queue2.capacity(), 11);
|
||||
CHECK(queue2.is_contiguous());
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue2[j], testStrings[j]);
|
||||
|
||||
// Check copy assignment
|
||||
Luau::VecDeque<std::string> queue3;
|
||||
queue3 = queue;
|
||||
|
||||
REQUIRE(!queue3.empty());
|
||||
REQUIRE(queue3.size() == 10);
|
||||
|
||||
CHECK_EQ(queue3.capacity(), 11);
|
||||
CHECK(queue3.is_contiguous());
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue3[j], testStrings[j]);
|
||||
|
||||
// Check move construction
|
||||
Luau::VecDeque<std::string> queue4 = std::move(queue3);
|
||||
|
||||
REQUIRE(!queue4.empty());
|
||||
REQUIRE(queue4.size() == 10);
|
||||
|
||||
CHECK_EQ(queue4.capacity(), 11);
|
||||
CHECK(queue4.is_contiguous());
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue4[j], testStrings[j]);
|
||||
|
||||
// Check move assignment
|
||||
Luau::VecDeque<std::string> queue5;
|
||||
queue5 = std::move(queue2);
|
||||
|
||||
REQUIRE(!queue5.empty());
|
||||
REQUIRE(queue5.size() == 10);
|
||||
|
||||
CHECK_EQ(queue5.capacity(), 11);
|
||||
CHECK(queue5.is_contiguous());
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue5[j], testStrings[j]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("string_queue_is_not_contiguous")
|
||||
{
|
||||
for (size_t stringSet = 0; stringSet < 2; stringSet++)
|
||||
{
|
||||
auto testStrings = testStringSet[stringSet];
|
||||
|
||||
// initial capacity is not set, so this should grow to be 11
|
||||
Luau::VecDeque<std::string> queue{};
|
||||
|
||||
@ -555,10 +658,80 @@ TEST_CASE("string_queue_is_not_contiguous")
|
||||
// checking that it is indeed sequential integers from 0 to 9
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue[j], testStrings[j]);
|
||||
|
||||
// Check copy construction
|
||||
Luau::VecDeque<std::string> queue2 = queue;
|
||||
|
||||
REQUIRE(!queue2.empty());
|
||||
REQUIRE(queue2.size() == 10);
|
||||
|
||||
CHECK_EQ(queue2.capacity(), 11);
|
||||
CHECK(!queue2.is_contiguous());
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue2[j], testStrings[j]);
|
||||
|
||||
// Check copy assignment
|
||||
Luau::VecDeque<std::string> queue3;
|
||||
queue3 = queue;
|
||||
|
||||
REQUIRE(!queue3.empty());
|
||||
REQUIRE(queue3.size() == 10);
|
||||
|
||||
CHECK_EQ(queue3.capacity(), 11);
|
||||
CHECK(queue3.is_contiguous());
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue3[j], testStrings[j]);
|
||||
|
||||
// Check move construction
|
||||
Luau::VecDeque<std::string> queue4 = std::move(queue);
|
||||
|
||||
REQUIRE(!queue4.empty());
|
||||
REQUIRE(queue4.size() == 10);
|
||||
|
||||
CHECK_EQ(queue4.capacity(), 11);
|
||||
CHECK(!queue4.is_contiguous());
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue4[j], testStrings[j]);
|
||||
|
||||
// Check move assignment
|
||||
Luau::VecDeque<std::string> queue5;
|
||||
queue5 = std::move(queue2);
|
||||
|
||||
REQUIRE(!queue5.empty());
|
||||
REQUIRE(queue5.size() == 10);
|
||||
|
||||
CHECK_EQ(queue5.capacity(), 11);
|
||||
CHECK(!queue5.is_contiguous());
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue5[j], testStrings[j]);
|
||||
|
||||
// Check that grow from discontiguous is handled well
|
||||
queue4.push_back("zero");
|
||||
queue4.push_back("?");
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue4[j], testStrings[j]);
|
||||
CHECK_EQ(queue4[10], "zero");
|
||||
CHECK_EQ(queue4[11], "?");
|
||||
|
||||
// Check that reserve from discontiguous is handled well
|
||||
queue5.reserve(20);
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue5[j], testStrings[j]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("shrink_to_fit_works_with_strings")
|
||||
{
|
||||
for (size_t stringSet = 0; stringSet < 2; stringSet++)
|
||||
{
|
||||
auto testStrings = testStringSet[stringSet];
|
||||
|
||||
// initial capacity is not set, so this should grow to be 11
|
||||
Luau::VecDeque<std::string> queue{};
|
||||
|
||||
@ -593,6 +766,7 @@ TEST_CASE("shrink_to_fit_works_with_strings")
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue[j], testStrings[j]);
|
||||
}
|
||||
}
|
||||
|
||||
struct TestStruct
|
||||
{
|
||||
@ -610,6 +784,11 @@ TEST_CASE("push_front_elements_are_destroyed_correctly")
|
||||
queue.push_front(t);
|
||||
REQUIRE(t.use_count() == 3); // Num of references to the TestStruct instance is now 3
|
||||
// <-- call destructor here
|
||||
|
||||
// Extra check for correct copies
|
||||
Luau::VecDeque<std::shared_ptr<TestStruct>> queue2 = queue;
|
||||
Luau::VecDeque<std::shared_ptr<TestStruct>> queue3;
|
||||
queue3 = queue;
|
||||
}
|
||||
|
||||
// At this point the destructor should be called and we should be back down to one instance of TestStruct
|
||||
|
@ -2,24 +2,12 @@ AnnotationTests.typeof_expr
|
||||
AstQuery.last_argument_function_call_type
|
||||
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
|
||||
AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg
|
||||
AutocompleteTest.autocomplete_interpolated_string_as_singleton
|
||||
AutocompleteTest.autocomplete_response_perf1
|
||||
AutocompleteTest.autocomplete_string_singleton_equality
|
||||
AutocompleteTest.autocomplete_string_singleton_escape
|
||||
AutocompleteTest.autocomplete_string_singletons
|
||||
AutocompleteTest.do_wrong_compatible_nonself_calls
|
||||
AutocompleteTest.suggest_external_module_type
|
||||
AutocompleteTest.type_correct_expected_argument_type_pack_suggestion
|
||||
AutocompleteTest.type_correct_expected_argument_type_suggestion
|
||||
AutocompleteTest.type_correct_expected_argument_type_suggestion_optional
|
||||
AutocompleteTest.type_correct_expected_argument_type_suggestion_self
|
||||
AutocompleteTest.type_correct_expected_return_type_pack_suggestion
|
||||
AutocompleteTest.type_correct_expected_return_type_suggestion
|
||||
AutocompleteTest.type_correct_function_no_parenthesis
|
||||
AutocompleteTest.type_correct_function_return_types
|
||||
AutocompleteTest.type_correct_keywords
|
||||
AutocompleteTest.type_correct_suggestion_for_overloads
|
||||
AutocompleteTest.type_correct_suggestion_in_argument
|
||||
BuiltinTests.aliased_string_format
|
||||
BuiltinTests.assert_removes_falsy_types
|
||||
BuiltinTests.assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type
|
||||
@ -110,6 +98,8 @@ GenericsTests.bound_tables_do_not_clone_original_fields
|
||||
GenericsTests.check_generic_function
|
||||
GenericsTests.check_generic_local_function
|
||||
GenericsTests.check_mutual_generic_functions
|
||||
GenericsTests.check_mutual_generic_functions_unannotated
|
||||
GenericsTests.check_mutual_generic_functions_errors
|
||||
GenericsTests.check_nested_generic_function
|
||||
GenericsTests.check_recursive_generic_function
|
||||
GenericsTests.correctly_instantiate_polymorphic_member_functions
|
||||
@ -139,7 +129,6 @@ GenericsTests.infer_generic_local_function
|
||||
GenericsTests.infer_generic_property
|
||||
GenericsTests.infer_nested_generic_function
|
||||
GenericsTests.inferred_local_vars_can_be_polytypes
|
||||
GenericsTests.instantiate_cyclic_generic_function
|
||||
GenericsTests.instantiated_function_argument_names
|
||||
GenericsTests.local_vars_can_be_polytypes
|
||||
GenericsTests.no_stack_overflow_from_quantifying
|
||||
@ -347,7 +336,6 @@ TableTests.table_unifies_into_map
|
||||
TableTests.top_table_type
|
||||
TableTests.type_mismatch_on_massive_table_is_cut_short
|
||||
TableTests.unification_of_unions_in_a_self_referential_type
|
||||
TableTests.unifying_tables_shouldnt_uaf1
|
||||
TableTests.used_colon_instead_of_dot
|
||||
TableTests.used_dot_instead_of_colon
|
||||
TableTests.when_augmenting_an_unsealed_table_with_an_indexer_apply_the_correct_scope_to_the_indexer_type
|
||||
@ -411,7 +399,6 @@ TypeInfer.no_stack_overflow_from_isoptional
|
||||
TypeInfer.promote_tail_type_packs
|
||||
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter
|
||||
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2
|
||||
TypeInfer.statements_are_topologically_sorted
|
||||
TypeInfer.stringify_nested_unions_with_optionals
|
||||
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
||||
TypeInfer.type_infer_recursion_limit_no_ice
|
||||
@ -445,7 +432,6 @@ TypeInferClasses.unions_of_intersections_of_classes
|
||||
TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class
|
||||
TypeInferFunctions.another_other_higher_order_function
|
||||
TypeInferFunctions.calling_function_with_anytypepack_doesnt_leak_free_types
|
||||
TypeInferFunctions.check_function_bodies
|
||||
TypeInferFunctions.complicated_return_types_require_an_explicit_annotation
|
||||
TypeInferFunctions.concrete_functions_are_not_supertypes_of_function
|
||||
TypeInferFunctions.dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization
|
||||
@ -476,15 +462,12 @@ TypeInferFunctions.infer_generic_function_function_argument_overloaded
|
||||
TypeInferFunctions.infer_generic_lib_function_function_argument
|
||||
TypeInferFunctions.infer_return_type_from_selected_overload
|
||||
TypeInferFunctions.infer_return_value_type
|
||||
TypeInferFunctions.infer_that_function_does_not_return_a_table
|
||||
TypeInferFunctions.inferred_higher_order_functions_are_quantified_at_the_right_time3
|
||||
TypeInferFunctions.instantiated_type_packs_must_have_a_non_null_scope
|
||||
TypeInferFunctions.list_all_overloads_if_no_overload_takes_given_argument_count
|
||||
TypeInferFunctions.list_only_alternative_overloads_that_match_argument_count
|
||||
TypeInferFunctions.luau_subtyping_is_np_hard
|
||||
TypeInferFunctions.mutual_recursion
|
||||
TypeInferFunctions.no_lossy_function_type
|
||||
TypeInferFunctions.num_is_solved_after_num_or_str
|
||||
TypeInferFunctions.occurs_check_failure_in_function_return_type
|
||||
TypeInferFunctions.other_things_are_not_related_to_function
|
||||
TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible
|
||||
@ -500,10 +483,8 @@ TypeInferFunctions.too_many_arguments
|
||||
TypeInferFunctions.too_many_arguments_error_location
|
||||
TypeInferFunctions.too_many_return_values_in_parentheses
|
||||
TypeInferFunctions.too_many_return_values_no_function
|
||||
TypeInferFunctions.toposort_doesnt_break_mutual_recursion
|
||||
TypeInferLoops.cli_68448_iterators_need_not_accept_nil
|
||||
TypeInferLoops.dcr_iteration_explore_raycast_minimization
|
||||
TypeInferLoops.dcr_iteration_fragmented_keys
|
||||
TypeInferLoops.dcr_iteration_on_never_gives_never
|
||||
TypeInferLoops.dcr_xpath_candidates
|
||||
TypeInferLoops.for_in_loop
|
||||
@ -516,6 +497,7 @@ TypeInferLoops.for_in_loop_with_incompatible_args_to_iterator
|
||||
TypeInferLoops.for_in_loop_with_next
|
||||
TypeInferLoops.for_in_with_an_iterator_of_type_any
|
||||
TypeInferLoops.for_in_with_generic_next
|
||||
TypeInferLoops.for_in_with_just_one_iterator_is_ok
|
||||
TypeInferLoops.for_loop
|
||||
TypeInferLoops.ipairs_produces_integral_indices
|
||||
TypeInferLoops.iterate_over_free_table
|
||||
@ -545,7 +527,6 @@ TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2
|
||||
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon
|
||||
TypeInferOOP.inferred_methods_of_free_tables_have_the_same_level_as_the_enclosing_table
|
||||
TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
|
||||
TypeInferOOP.method_depends_on_table
|
||||
TypeInferOOP.methods_are_topologically_sorted
|
||||
TypeInferOOP.object_constructor_can_refer_to_method_of_self
|
||||
TypeInferOOP.promise_type_error_too_complex
|
||||
|
Loading…
Reference in New Issue
Block a user