From d6c2472f0c5c946a12ad54bb234747f62e15458f Mon Sep 17 00:00:00 2001 From: Andy Friesen Date: Fri, 9 Feb 2024 09:51:12 -0800 Subject: [PATCH] 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 Co-authored-by: Andy Friesen Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/Constraint.h | 1 + Analysis/src/ConstraintGenerator.cpp | 186 ++++++--- Analysis/src/ConstraintSolver.cpp | 54 +-- Analysis/src/Subtyping.cpp | 4 + Analysis/src/TypeChecker2.cpp | 20 +- Common/include/Luau/VecDeque.h | 65 ++- tests/Autocomplete.test.cpp | 15 +- tests/TypeFamily.test.cpp | 24 ++ tests/TypeInfer.aliases.test.cpp | 7 + tests/TypeInfer.functions.test.cpp | 92 ++++- tests/TypeInfer.generics.test.cpp | 68 +++- tests/VecDeque.test.cpp | 569 ++++++++++++++++++--------- tools/faillist.txt | 25 +- 13 files changed, 796 insertions(+), 334 deletions(-) diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index 692e4a14c..08302c525 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -102,6 +102,7 @@ struct FunctionCheckConstraint TypePackId argsPack; class AstExprCall* callSite = nullptr; + NotNull> astExpectedTypes; }; // prim FreeType ExpectedType PrimitiveType diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index b6c490929..f3fb163e5 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -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, NotNull moduleResolver, @@ -229,6 +276,8 @@ std::optional 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(sig.signature); + DefId def = dfg->getDef(function->name); scope->lvalueTypes[def] = functionType; scope->rvalueRefinements[def] = functionType; @@ -847,25 +900,32 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti checkFunctionBody(sig.bodyScope, function->func); Checkpoint end = checkpoint(this); - NotNull constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()}; - std::unique_ptr c = - std::make_unique(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature}); + if (!sigFullyDefined) + { + NotNull constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()}; + std::unique_ptr c = + std::make_unique(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature}); - Constraint* previous = nullptr; - forEachConstraint(start, end, this, [&c, &previous](const ConstraintPtr& constraint) { - c->dependencies.push_back(NotNull{constraint.get()}); + Constraint* previous = nullptr; + forEachConstraint(start, end, this, + [&c, &previous](const ConstraintPtr& constraint) + { + c->dependencies.push_back(NotNull{constraint.get()}); - if (auto psc = get(*constraint); psc && psc->returns) - { - if (previous) - constraint->dependencies.push_back(NotNull{previous}); + if (auto psc = get(*constraint); psc && psc->returns) + { + if (previous) + constraint->dependencies.push_back(NotNull{previous}); - previous = constraint.get(); - } - }); + previous = constraint.get(); + } + }); - addConstraint(scope, std::move(c)); - module->astTypes[function->func] = functionType; + 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(sig.signature); DenseHashSet excludeList{nullptr}; DefId def = dfg->getDef(function->name); std::optional existingFunctionTy = lookup(scope, def); + if (sigFullyDefined && existingFunctionTy && get(*existingFunctionTy)) + asMutable(*existingFunctionTy)->ty.emplace(sig.signature); + if (AstExprLocal* localName = function->name->as()) { if (existingFunctionTy) @@ -906,7 +973,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f if (!existingFunctionTy) ice->ice("prepopulateGlobalScope did not populate a global name", globalName->location); - generalizedType = *existingFunctionTy; + if (!sigFullyDefined) + generalizedType = *existingFunctionTy; sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location}; sig.bodyScope->lvalueTypes[def] = sig.signature; @@ -943,25 +1011,30 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f checkFunctionBody(sig.bodyScope, function->func); Checkpoint end = checkpoint(this); - NotNull constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()}; - std::unique_ptr c = - std::make_unique(constraintScope, function->name->location, GeneralizationConstraint{generalizedType, sig.signature}); + if (!sigFullyDefined) + { + NotNull constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()}; + std::unique_ptr c = + std::make_unique(constraintScope, function->name->location, GeneralizationConstraint{generalizedType, sig.signature}); - Constraint* previous = nullptr; - forEachConstraint(start, end, this, [&c, &excludeList, &previous](const ConstraintPtr& constraint) { - if (!excludeList.contains(constraint.get())) - c->dependencies.push_back(NotNull{constraint.get()}); + Constraint* previous = nullptr; + forEachConstraint(start, end, this, + [&c, &excludeList, &previous](const ConstraintPtr& constraint) + { + if (!excludeList.contains(constraint.get())) + c->dependencies.push_back(NotNull{constraint.get()}); - if (auto psc = get(*constraint); psc && psc->returns) - { - if (previous) - constraint->dependencies.push_back(NotNull{previous}); + if (auto psc = get(*constraint); psc && psc->returns) + { + if (previous) + constraint->dependencies.push_back(NotNull{previous}); - previous = constraint.get(); - } - }); + previous = constraint.get(); + } + }); - addConstraint(scope, std::move(c)); + 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 fcc = addConstraint(scope, call->func->location, - FunctionCallConstraint{ - fnType, - argPack, - rets, - call, - std::move(discriminantTypes), - &module->astOverloadResolvedTypes, - }); - - NotNull 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 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 callConstraint = addConstraint(scope, call->func->location, + FunctionCallConstraint{ + fnType, + argPack, + rets, + call, + std::move(discriminantTypes), + &module->astOverloadResolvedTypes, + }); + + callConstraint->dependencies.push_back(checkConstraint); + + forEachConstraint(argBeginCheckpoint, argEndCheckpoint, this, [checkConstraint, callConstraint](const ConstraintPtr& constraint) { + constraint->dependencies.emplace_back(checkConstraint); - fcc->dependencies.emplace_back(constraint.get()); + 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 gc = addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature}); diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 922dc79dd..304f710f7 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -1139,7 +1139,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull(fn)) + const FunctionType* ftv = get(fn); + if (!ftv) + return true; + + const std::vector expectedArgs = flatten(ftv->argTypes).first; + const std::vector argPackHead = flatten(argsPack).first; + + for (size_t i = 0; i < c.callSite->args.size && i < expectedArgs.size() && i < argPackHead.size(); ++i) { - const std::vector expectedArgs = flatten(ftv->argTypes).first; - const std::vector argPackHead = flatten(argsPack).first; + const TypeId expectedArgTy = follow(expectedArgs[i]); + const TypeId actualArgTy = follow(argPackHead[i]); + const AstExpr* expr = c.callSite->args.data[i]; - for (size_t i = 0; i < c.callSite->args.size && i < expectedArgs.size() && i < argPackHead.size(); ++i) - { - const TypeId expectedArgTy = follow(expectedArgs[i]); - const TypeId actualArgTy = follow(argPackHead[i]); + (*c.astExpectedTypes)[expr] = expectedArgTy; - const FunctionType* expectedLambdaTy = get(expectedArgTy); - const FunctionType* lambdaTy = get(actualArgTy); - const AstExprFunction* lambdaExpr = c.callSite->args.data[i]->as(); + const FunctionType* expectedLambdaTy = get(expectedArgTy); + const FunctionType* lambdaTy = get(actualArgTy); + const AstExprFunction* lambdaExpr = expr->as(); - if (expectedLambdaTy && lambdaTy && lambdaExpr) - { - const std::vector expectedLambdaArgTys = flatten(expectedLambdaTy->argTypes).first; - const std::vector lambdaArgTys = flatten(lambdaTy->argTypes).first; + if (expectedLambdaTy && lambdaTy && lambdaExpr) + { + const std::vector expectedLambdaArgTys = flatten(expectedLambdaTy->argTypes).first; + const std::vector lambdaArgTys = flatten(lambdaTy->argTypes).first; - for (size_t j = 0; j < expectedLambdaArgTys.size() && j < lambdaArgTys.size() && j < lambdaExpr->args.size; ++j) + for (size_t j = 0; j < expectedLambdaArgTys.size() && j < lambdaArgTys.size() && j < lambdaExpr->args.size; ++j) + { + if (!lambdaExpr->args.data[j]->annotation && get(follow(lambdaArgTys[j]))) { - if (!lambdaExpr->args.data[j]->annotation && get(follow(lambdaArgTys[j]))) - { - asMutable(lambdaArgTys[j])->ty.emplace(expectedLambdaArgTys[j]); - } + asMutable(lambdaArgTys[j])->ty.emplace(expectedLambdaArgTys[j]); } } - else - { - Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}}; - u2.unify(actualArgTy, expectedArgTy); - } + } + else if (expr->is() || expr->is() || expr->is() || expr->is() || expr->is()) + { + Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}}; + u2.unify(actualArgTy, expectedArgTy); } } diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index 0d621600e..8d3991237 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -593,6 +593,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId return SubtypingResult{false}.withSubComponent(TypePath::PackField::Tail); } } + else if (get(*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(*superTail)) + return SubtypingResult{true}.withSuperComponent(TypePath::PackField::Tail); else unexpected(*superTail); } diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 0badb2c57..5411bf248 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -495,11 +495,12 @@ struct TypeChecker2 return checkForFamilyInhabitance(follow(*ty), annotation->location); } - TypePackId lookupPackAnnotation(AstTypePack* annotation) + std::optional 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 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(tableTy)) - reportError(CannotExtendTable{tableTy, CannotExtendTable::Property, prop}, location); + { + if (get(tableTy) || get(tableTy)) + reportError(NotATable{tableTy}, location); + else + reportError(CannotExtendTable{tableTy, CannotExtendTable::Property, prop}, location); + } else reportError(UnknownProperty{tableTy, prop}, location); } diff --git a/Common/include/Luau/VecDeque.h b/Common/include/Luau/VecDeque.h index 24d3d165f..dd973c1de 100644 --- a/Common/include/Luau/VecDeque.h +++ b/Common/include/Luau/VecDeque.h @@ -92,10 +92,11 @@ class VecDeque : Allocator 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 - std::uninitialized_move(buffer + head, buffer + head + head_size, 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 @@ class VecDeque : Allocator , 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 @@ class VecDeque : Allocator , 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,14 +212,19 @@ class VecDeque : Allocator 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) + + // 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 head into place - std::uninitialized_copy(other.buffer + other.head, other.buffer + head + head_size, buffer); + if (head_size != 0) + std::uninitialized_copy(other.buffer + other.head, other.buffer + other.head + head_size, buffer); - // copy the contents of the other buffer's tail into place immediately after - std::uninitialized_copy(other.buffer, other.buffer + tail_size, buffer + head_size); + if (tail_size != 0) + std::uninitialized_copy(other.buffer, other.buffer + tail_size, buffer + head_size); return *this; } @@ -325,17 +347,16 @@ class VecDeque : Allocator T* new_buffer = this->allocate(new_capacity); // move the head into the new buffer - 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); + if (head_size != 0) + std::uninitialized_move(buffer + head, buffer + head + head_size, new_buffer); // move the tail into the new buffer immediately after - std::uninitialized_move(buffer, buffer + tail_size, new_buffer + head_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 destroyElements(); + // deallocate the old buffer this->deallocate(buffer, old_capacity); @@ -350,7 +371,8 @@ class VecDeque : Allocator 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 @@ class VecDeque : Allocator T* new_buffer = this->allocate(new_capacity); // move the head into the new buffer - std::uninitialized_move(buffer + head, buffer + head + head_size, 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 diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 5b031e795..560f011f4 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -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); diff --git a/tests/TypeFamily.test.cpp b/tests/TypeFamily.test.cpp index a99991236..abcd699ac 100644 --- a/tests/TypeFamily.test.cpp +++ b/tests/TypeFamily.test.cpp @@ -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 + + function fnA(i: T): keyof end + + function fnB(i: EnumKey) end + + local result = fnA(EnumVariants) + fnB(result) + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index 93b8bbabe..a9f358eb4 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -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) +)"); + LUAU_REQUIRE_ERRORS(result); +} TEST_SUITE_END(); diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 9a563046d..1cb97ea24 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -50,13 +50,30 @@ 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{ - builtinTypes->numberType, - builtinTypes->booleanType, - }})); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + const TypePackMismatch* tm = get(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 is uninhabited"); + CHECK(toString(result.errors[1]) == "Type family instance sub is uninhabited"); + CHECK(toString(result.errors[2]) == "Type family instance sub is uninhabited"); + CHECK(toString(result.errors[3]) == "Type family instance sub 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(); diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index e145f30a0..8b647f99e 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -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(x:a):a + function id1(x:a):a local y: string = id2("hi") local z: number = id2(37) return x end + function id2(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(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(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(arg); - REQUIRE(argTable != nullptr); + REQUIRE_MESSAGE(argTable != nullptr, "Expected table but got " << toString(arg)); std::optional methodProp = get(argTable->props, "method"); REQUIRE(bool(methodProp)); diff --git a/tests/VecDeque.test.cpp b/tests/VecDeque.test.cpp index bc8ef2723..d5f32c3f1 100644 --- a/tests/VecDeque.test.cpp +++ b/tests/VecDeque.test.cpp @@ -287,311 +287,485 @@ 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.", - "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.", - "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." -}; +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.", + "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."}}; TEST_CASE("string_queue_test_no_initial_capacity") { - // initial capacity is not set, so this should grow to be 11 - Luau::VecDeque queue; + for (size_t stringSet = 0; stringSet < 2; stringSet++) + { + auto testStrings = testStringSet[stringSet]; - REQUIRE(queue.empty()); + // initial capacity is not set, so this should grow to be 11 + Luau::VecDeque queue; - for (int i = 0; i < 10; i++) - queue.push_back(testStrings[i]); - // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + REQUIRE(queue.empty()); - REQUIRE(!queue.empty()); - REQUIRE(queue.size() == 10); + for (int i = 0; i < 10; i++) + queue.push_back(testStrings[i]); + // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 - CHECK_EQ(queue.capacity(), 11); + REQUIRE(!queue.empty()); + REQUIRE(queue.size() == 10); - for (int j = 0; j < 10; j++) - { - CHECK_EQ(queue.front(), testStrings[j]); - CHECK_EQ(queue.back(), testStrings[9]); + CHECK_EQ(queue.capacity(), 11); - REQUIRE(!queue.empty()); - queue.pop_front(); + for (int j = 0; j < 10; j++) + { + CHECK_EQ(queue.front(), testStrings[j]); + CHECK_EQ(queue.back(), testStrings[9]); + + REQUIRE(!queue.empty()); + queue.pop_front(); + } } } TEST_CASE("string_queue_test") { - // initial capacity set to 5 so that a grow is necessary - Luau::VecDeque queue; - queue.reserve(5); + for (size_t stringSet = 0; stringSet < 2; stringSet++) + { + auto testStrings = testStringSet[stringSet]; - REQUIRE(queue.empty()); + // initial capacity set to 5 so that a grow is necessary + Luau::VecDeque queue; + queue.reserve(5); - for (int i = 0; i < 10; i++) - queue.push_back(testStrings[i]); - // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + REQUIRE(queue.empty()); - REQUIRE(!queue.empty()); - REQUIRE(queue.size() == 10); + for (int i = 0; i < 10; i++) + queue.push_back(testStrings[i]); + // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 - CHECK_EQ(queue.capacity(), 13); + REQUIRE(!queue.empty()); + REQUIRE(queue.size() == 10); - for (int j = 0; j < 10; j++) - { - CHECK_EQ(queue.front(), testStrings[j]); - CHECK_EQ(queue.back(), testStrings[9]); + CHECK_EQ(queue.capacity(), 13); - REQUIRE(!queue.empty()); - queue.pop_front(); + for (int j = 0; j < 10; j++) + { + CHECK_EQ(queue.front(), testStrings[j]); + CHECK_EQ(queue.back(), testStrings[9]); + + REQUIRE(!queue.empty()); + queue.pop_front(); + } } } TEST_CASE("string_queue_test_initializer_list") { - Luau::VecDeque queue{ - testStrings[0], - testStrings[1], - testStrings[2], - testStrings[3], - testStrings[4], - testStrings[5], - testStrings[6], - testStrings[7], - testStrings[8], - testStrings[9], - }; - // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + for (size_t stringSet = 0; stringSet < 2; stringSet++) + { + auto testStrings = testStringSet[stringSet]; + + Luau::VecDeque queue{ + testStrings[0], + testStrings[1], + testStrings[2], + testStrings[3], + testStrings[4], + testStrings[5], + testStrings[6], + testStrings[7], + testStrings[8], + testStrings[9], + }; + // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 - REQUIRE(!queue.empty()); - REQUIRE(queue.size() == 10); + REQUIRE(!queue.empty()); + REQUIRE(queue.size() == 10); - CHECK_EQ(queue.capacity(), 10); + CHECK_EQ(queue.capacity(), 10); - for (int j = 0; j < 10; j++) - { - CHECK_EQ(queue.front(), testStrings[j]); - CHECK_EQ(queue.back(), testStrings[9]); + for (int j = 0; j < 10; j++) + { + CHECK_EQ(queue.front(), testStrings[j]); + CHECK_EQ(queue.back(), testStrings[9]); - REQUIRE(!queue.empty()); - queue.pop_front(); + REQUIRE(!queue.empty()); + queue.pop_front(); + } } } TEST_CASE("reverse_string_queue_test") { - // initial capacity set to 5 so that a grow is necessary - Luau::VecDeque queue; - queue.reserve(5); + for (size_t stringSet = 0; stringSet < 2; stringSet++) + { + auto testStrings = testStringSet[stringSet]; - REQUIRE(queue.empty()); + // initial capacity set to 5 so that a grow is necessary + Luau::VecDeque queue; + queue.reserve(5); - for (int i = 0; i < 10; i++) - queue.push_front(testStrings[i]); - // q: 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 + REQUIRE(queue.empty()); - REQUIRE(!queue.empty()); - REQUIRE(queue.size() == 10); + for (int i = 0; i < 10; i++) + queue.push_front(testStrings[i]); + // q: 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 - CHECK_EQ(queue.capacity(), 13); + REQUIRE(!queue.empty()); + REQUIRE(queue.size() == 10); - for (int j = 0; j < 10; j++) - { - CHECK_EQ(queue.front(), testStrings[9]); - CHECK_EQ(queue.back(), testStrings[j]); + CHECK_EQ(queue.capacity(), 13); - REQUIRE(!queue.empty()); - queue.pop_back(); + for (int j = 0; j < 10; j++) + { + CHECK_EQ(queue.front(), testStrings[9]); + CHECK_EQ(queue.back(), testStrings[j]); + + REQUIRE(!queue.empty()); + queue.pop_back(); + } } } TEST_CASE("random_access_string_queue_test") { - // initial capacity set to 5 so that a grow is necessary - Luau::VecDeque queue; - queue.reserve(5); + for (size_t stringSet = 0; stringSet < 2; stringSet++) + { + auto testStrings = testStringSet[stringSet]; - REQUIRE(queue.empty()); + // initial capacity set to 5 so that a grow is necessary + Luau::VecDeque queue; + queue.reserve(5); - for (int i = 0; i < 10; i++) - queue.push_back(testStrings[i]); - // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + REQUIRE(queue.empty()); - REQUIRE(!queue.empty()); - REQUIRE(queue.size() == 10); + for (int i = 0; i < 10; i++) + queue.push_back(testStrings[i]); + // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 - for (int j = 0; j < 10; j++) - { - CHECK_EQ(queue.at(j), testStrings[j]); - CHECK_EQ(queue[j], testStrings[j]); + REQUIRE(!queue.empty()); + REQUIRE(queue.size() == 10); + + for (int j = 0; j < 10; j++) + { + CHECK_EQ(queue.at(j), testStrings[j]); + CHECK_EQ(queue[j], testStrings[j]); + } } } TEST_CASE("clear_works_on_string_queue") { - // initial capacity set to 5 so that a grow is necessary - Luau::VecDeque queue; - queue.reserve(5); + for (size_t stringSet = 0; stringSet < 2; stringSet++) + { + auto testStrings = testStringSet[stringSet]; - REQUIRE(queue.empty()); + // initial capacity set to 5 so that a grow is necessary + Luau::VecDeque queue; + queue.reserve(5); - for (int i = 0; i < 10; i++) - queue.push_back(testStrings[i]); - // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + REQUIRE(queue.empty()); - REQUIRE(!queue.empty()); - REQUIRE(queue.size() == 10); + for (int i = 0; i < 10; i++) + queue.push_back(testStrings[i]); + // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 - for (int j = 0; j < 10; j++) - CHECK_EQ(queue[j], testStrings[j]); + REQUIRE(!queue.empty()); + REQUIRE(queue.size() == 10); - queue.clear(); - CHECK(queue.empty()); - CHECK(queue.size() == 0); + for (int j = 0; j < 10; j++) + CHECK_EQ(queue[j], testStrings[j]); + + queue.clear(); + CHECK(queue.empty()); + CHECK(queue.size() == 0); + } } TEST_CASE("pop_front_string_at_end") { - // initial capacity set to 5 so that a grow is necessary - Luau::VecDeque queue; - queue.reserve(5); - - REQUIRE(queue.empty()); + for (size_t stringSet = 0; stringSet < 2; stringSet++) + { + auto testStrings = testStringSet[stringSet]; - // setting up the internal buffer to be: 1234567890 by the end (i.e. 0 at the end of the buffer) - queue.push_front(testStrings[0]); + // initial capacity set to 5 so that a grow is necessary + Luau::VecDeque queue; + queue.reserve(5); - for (int i = 1; i < 10; i++) - queue.push_back(testStrings[i]); - // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + REQUIRE(queue.empty()); - REQUIRE(!queue.empty()); - REQUIRE(queue.size() == 10); + // setting up the internal buffer to be: 1234567890 by the end (i.e. 0 at the end of the buffer) + queue.push_front(testStrings[0]); - for (int j = 0; j < 10; j++) - { - CHECK_EQ(queue.front(), testStrings[j]); - CHECK_EQ(queue.back(), testStrings[9]); + for (int i = 1; i < 10; i++) + queue.push_back(testStrings[i]); + // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 REQUIRE(!queue.empty()); - queue.pop_front(); + REQUIRE(queue.size() == 10); + + for (int j = 0; j < 10; j++) + { + CHECK_EQ(queue.front(), testStrings[j]); + CHECK_EQ(queue.back(), testStrings[9]); + + REQUIRE(!queue.empty()); + queue.pop_front(); + } } } TEST_CASE("pop_back_string_at_front") { - // initial capacity set to 5 so that a grow is necessary - Luau::VecDeque queue; - queue.reserve(5); - - REQUIRE(queue.empty()); + for (size_t stringSet = 0; stringSet < 2; stringSet++) + { + auto testStrings = testStringSet[stringSet]; - // setting up the internal buffer to be: 9012345678 by the end (i.e. 9 at the front of the buffer) - queue.push_back(testStrings[0]); + // initial capacity set to 5 so that a grow is necessary + Luau::VecDeque queue; + queue.reserve(5); - for (int i = 1; i < 10; i++) - queue.push_front(testStrings[i]); - // q: 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 + REQUIRE(queue.empty()); - REQUIRE(!queue.empty()); - REQUIRE(queue.size() == 10); + // setting up the internal buffer to be: 9012345678 by the end (i.e. 9 at the front of the buffer) + queue.push_back(testStrings[0]); - for (int j = 0; j < 10; j++) - { - CHECK_EQ(queue.front(), testStrings[9]); - CHECK_EQ(queue.back(), testStrings[j]); + for (int i = 1; i < 10; i++) + queue.push_front(testStrings[i]); + // q: 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 REQUIRE(!queue.empty()); - queue.pop_back(); + REQUIRE(queue.size() == 10); + + for (int j = 0; j < 10; j++) + { + CHECK_EQ(queue.front(), testStrings[9]); + CHECK_EQ(queue.back(), testStrings[j]); + + REQUIRE(!queue.empty()); + queue.pop_back(); + } } } TEST_CASE("string_queue_is_contiguous") { - // initial capacity is not set, so this should grow to be 11 - Luau::VecDeque queue{}; + for (size_t stringSet = 0; stringSet < 2; stringSet++) + { + auto testStrings = testStringSet[stringSet]; - REQUIRE(queue.empty()); + // initial capacity is not set, so this should grow to be 11 + Luau::VecDeque queue{}; - for (int i = 0; i < 10; i++) - queue.push_back(testStrings[i]); - // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + REQUIRE(queue.empty()); - REQUIRE(!queue.empty()); - REQUIRE(queue.size() == 10); + for (int i = 0; i < 10; i++) + queue.push_back(testStrings[i]); + // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 - CHECK_EQ(queue.capacity(), 11); - CHECK(queue.is_contiguous()); + REQUIRE(!queue.empty()); + REQUIRE(queue.size() == 10); + + 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 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 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 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 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") { - // initial capacity is not set, so this should grow to be 11 - Luau::VecDeque queue{}; + for (size_t stringSet = 0; stringSet < 2; stringSet++) + { + auto testStrings = testStringSet[stringSet]; - REQUIRE(queue.empty()); + // initial capacity is not set, so this should grow to be 11 + Luau::VecDeque queue{}; - for (int i = 5; i < 10; i++) - queue.push_back(testStrings[i]); - for (int i = 4; i >= 0; i--) - queue.push_front(testStrings[i]); - // buffer: 56789......01234 - // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + REQUIRE(queue.empty()); - REQUIRE(!queue.empty()); - REQUIRE(queue.size() == 10); + for (int i = 5; i < 10; i++) + queue.push_back(testStrings[i]); + for (int i = 4; i >= 0; i--) + queue.push_front(testStrings[i]); + // buffer: 56789......01234 + // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 - CHECK_EQ(queue.capacity(), 11); - CHECK(!queue.is_contiguous()); + REQUIRE(!queue.empty()); + REQUIRE(queue.size() == 10); - // 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_EQ(queue.capacity(), 11); + CHECK(!queue.is_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 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 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 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 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") { - // initial capacity is not set, so this should grow to be 11 - Luau::VecDeque queue{}; + for (size_t stringSet = 0; stringSet < 2; stringSet++) + { + auto testStrings = testStringSet[stringSet]; - REQUIRE(queue.empty()); + // initial capacity is not set, so this should grow to be 11 + Luau::VecDeque queue{}; - for (int i = 5; i < 10; i++) - queue.push_back(testStrings[i]); - for (int i = 4; i >= 0; i--) - queue.push_front(testStrings[i]); - // buffer: 56789......01234 - // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + REQUIRE(queue.empty()); - REQUIRE(!queue.empty()); - REQUIRE(queue.size() == 10); + for (int i = 5; i < 10; i++) + queue.push_back(testStrings[i]); + for (int i = 4; i >= 0; i--) + queue.push_front(testStrings[i]); + // buffer: 56789......01234 + // q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 - REQUIRE_EQ(queue.capacity(), 11); - CHECK(!queue.is_contiguous()); + REQUIRE(!queue.empty()); + REQUIRE(queue.size() == 10); - // checking that it is indeed sequential integers from 0 to 9 - for (int j = 0; j < 10; j++) - CHECK_EQ(queue[j], testStrings[j]); + REQUIRE_EQ(queue.capacity(), 11); + CHECK(!queue.is_contiguous()); - queue.shrink_to_fit(); - // shrink to fit always makes a contiguous buffer - CHECK(queue.is_contiguous()); - // the capacity should be exactly the size now - CHECK_EQ(queue.capacity(), queue.size()); + // checking that it is indeed sequential integers from 0 to 9 + for (int j = 0; j < 10; j++) + CHECK_EQ(queue[j], testStrings[j]); - REQUIRE(!queue.empty()); + queue.shrink_to_fit(); + // shrink to fit always makes a contiguous buffer + CHECK(queue.is_contiguous()); + // the capacity should be exactly the size now + CHECK_EQ(queue.capacity(), queue.size()); - // checking that it is still sequential integers from 0 to 9 - for (int j = 0; j < 10; j++) - CHECK_EQ(queue[j], testStrings[j]); + REQUIRE(!queue.empty()); + + // checking that it is still sequential integers from 0 to 9 + 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> queue2 = queue; + Luau::VecDeque> queue3; + queue3 = queue; } // At this point the destructor should be called and we should be back down to one instance of TestStruct diff --git a/tools/faillist.txt b/tools/faillist.txt index 82dae05f7..8299cb75a 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -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