diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index 1499802d..74187556 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -51,6 +51,10 @@ struct GeneralizationConstraint std::vector interiorTypes; bool hasDeprecatedAttribute = false; + + /// If true, never introduce generics. Always replace free types by their + /// bounds or unknown. Presently used only to generalize the whole module. + bool noGenerics = false; }; // variables ~ iterate iterator diff --git a/Analysis/include/Luau/TypeChecker2.h b/Analysis/include/Luau/TypeChecker2.h index 838688ee..ebd2f4bc 100644 --- a/Analysis/include/Luau/TypeChecker2.h +++ b/Analysis/include/Luau/TypeChecker2.h @@ -13,8 +13,6 @@ #include "Luau/TypeOrPack.h" #include "Luau/TypeUtils.h" -LUAU_FASTFLAG(LuauImproveTypePathsInErrors) - namespace Luau { @@ -40,29 +38,18 @@ struct Reasonings std::string toString() { - if (FFlag::LuauImproveTypePathsInErrors && reasons.empty()) + if (reasons.empty()) return ""; // DenseHashSet ordering is entirely undefined, so we want to // sort the reasons here to achieve a stable error // stringification. std::sort(reasons.begin(), reasons.end()); - std::string allReasons = FFlag::LuauImproveTypePathsInErrors ? "\nthis is because " : ""; - bool first = true; + std::string allReasons = "\nthis is because "; for (const std::string& reason : reasons) { - if (FFlag::LuauImproveTypePathsInErrors) - { - if (reasons.size() > 1) - allReasons += "\n\t * "; - } - else - { - if (first) - first = false; - else - allReasons += "\n\t"; - } + if (reasons.size() > 1) + allReasons += "\n\t * "; allReasons += reason; } diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index f328f3b3..19595cce 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -33,7 +33,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck) -LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked) +LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2) LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition) namespace Luau @@ -1616,11 +1616,11 @@ bool MagicFreeze::infer(const MagicFunctionCallContext& context) std::optional resultDef = dfg->getDefOptional(targetExpr); std::optional resultTy = resultDef ? scope->lookup(*resultDef) : std::nullopt; - if (FFlag::LuauMagicFreezeCheckBlocked) + if (FFlag::LuauMagicFreezeCheckBlocked2) { - if (resultTy && !get(resultTy)) + if (resultTy && !get(follow(resultTy))) { - // If there's an existing result type but it's _not_ blocked, then + // If there's an existing result type, but it's _not_ blocked, then // we aren't type stating this builtin and should fall back to // regular inference. return false; @@ -1629,6 +1629,8 @@ bool MagicFreeze::infer(const MagicFunctionCallContext& context) std::optional frozenType = freezeTable(inputType, context); + // At this point: we know for sure that if `resultTy` exists, it is a + // blocked type, and can safely emplace it. if (!frozenType) { if (resultTy) diff --git a/Analysis/src/Clone.cpp b/Analysis/src/Clone.cpp index 00618100..1257a4b2 100644 --- a/Analysis/src/Clone.cpp +++ b/Analysis/src/Clone.cpp @@ -14,7 +14,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000) LUAU_FASTFLAGVARIABLE(LuauClonedTableAndFunctionTypesMustHaveScopes) LUAU_FASTFLAGVARIABLE(LuauDoNotClonePersistentBindings) -LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning) namespace Luau { @@ -549,11 +548,6 @@ public: void cloneChildren(LazyType* t) override { // Do not clone lazy types - if (!FFlag::LuauIncrementalAutocompleteDemandBasedCloning) - { - if (auto unwrapped = t->unwrapped.load()) - t->unwrapped.store(shallowClone(unwrapped)); - } } }; diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 22ec7280..c3ccf60d 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -42,7 +42,6 @@ LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions) LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations) -LUAU_FASTFLAG(LuauDeprecatedAttribute) LUAU_FASTFLAGVARIABLE(LuauCacheInferencePerAstExpr) LUAU_FASTFLAGVARIABLE(LuauAlwaysResolveAstTypes) LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType) @@ -52,6 +51,7 @@ LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAGVARIABLE(LuauNoTypeFunctionsNamedTypeOf) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) +LUAU_FASTFLAGVARIABLE(LuauAvoidDoubleNegation) namespace Luau { @@ -288,7 +288,9 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) GeneralizationConstraint{ result, moduleFnTy, - std::vector{}, + /*interiorTypes*/ std::vector{}, + /*hasDeprecatedAttribute*/ false, + /*noGenerics*/ true } ); @@ -573,7 +575,17 @@ void ConstraintGenerator::computeRefinement( // if we have a negative sense, then we need to negate the discriminant if (!sense) - discriminantTy = arena->addType(NegationType{discriminantTy}); + { + if (FFlag::LuauAvoidDoubleNegation) + { + if (auto nt = get(follow(discriminantTy))) + discriminantTy = nt->ty; + else + discriminantTy = arena->addType(NegationType{discriminantTy}); + } + else + discriminantTy = arena->addType(NegationType{discriminantTy}); + } if (eq) discriminantTy = createTypeFunctionInstance(builtinTypeFunctions().singletonFunc, {discriminantTy}, {}, scope, location); @@ -1377,7 +1389,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatRepeat* rep static void propagateDeprecatedAttributeToConstraint(ConstraintV& c, const AstExprFunction* func) { - LUAU_ASSERT(FFlag::LuauDeprecatedAttribute); if (GeneralizationConstraint* genConstraint = c.get_if()) { genConstraint->hasDeprecatedAttribute = func->hasAttribute(AstAttr::Type::Deprecated); @@ -1386,7 +1397,6 @@ static void propagateDeprecatedAttributeToConstraint(ConstraintV& c, const AstEx static void propagateDeprecatedAttributeToType(TypeId signature, const AstExprFunction* func) { - LUAU_ASSERT(FFlag::LuauDeprecatedAttribute); FunctionType* fty = getMutable(signature); LUAU_ASSERT(fty); fty->isDeprecatedFunction = func->hasAttribute(AstAttr::Type::Deprecated); @@ -1429,8 +1439,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti std::unique_ptr c = std::make_unique(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature}); - if (FFlag::LuauDeprecatedAttribute) - propagateDeprecatedAttributeToConstraint(c->c, function->func); + propagateDeprecatedAttributeToConstraint(c->c, function->func); Constraint* previous = nullptr; forEachConstraint( @@ -1457,8 +1466,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti else { module->astTypes[function->func] = sig.signature; - if (FFlag::LuauDeprecatedAttribute) - propagateDeprecatedAttributeToType(sig.signature, function->func); + propagateDeprecatedAttributeToType(sig.signature, function->func); } return ControlFlow::None; @@ -1502,8 +1510,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f if (sigFullyDefined) { emplaceType(asMutable(generalizedType), sig.signature); - if (FFlag::LuauDeprecatedAttribute) - propagateDeprecatedAttributeToType(sig.signature, function->func); + propagateDeprecatedAttributeToType(sig.signature, function->func); } else { @@ -1512,8 +1519,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f NotNull c = addConstraint(constraintScope, function->name->location, GeneralizationConstraint{generalizedType, sig.signature}); getMutable(generalizedType)->setOwner(c); - if (FFlag::LuauDeprecatedAttribute) - propagateDeprecatedAttributeToConstraint(c->c, function->func); + propagateDeprecatedAttributeToConstraint(c->c, function->func); Constraint* previous = nullptr; forEachConstraint( @@ -2054,8 +2060,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc FunctionType* ftv = getMutable(fnType); ftv->isCheckedFunction = global->isCheckedFunction(); - if (FFlag::LuauDeprecatedAttribute) - ftv->isDeprecatedFunction = global->hasAttribute(AstAttr::Type::Deprecated); + ftv->isDeprecatedFunction = global->hasAttribute(AstAttr::Type::Deprecated); ftv->argNames.reserve(global->paramNames.size); for (const auto& el : global->paramNames) @@ -3710,8 +3715,7 @@ TypeId ConstraintGenerator::resolveFunctionType( // how to quantify/instantiate it. FunctionType ftv{TypeLevel{}, {}, {}, argTypes, returnTypes}; ftv.isCheckedFunction = fn->isCheckedFunction(); - if (FFlag::LuauDeprecatedAttribute) - ftv.isDeprecatedFunction = fn->hasAttribute(AstAttr::Type::Deprecated); + ftv.isDeprecatedFunction = fn->hasAttribute(AstAttr::Type::Deprecated); // This replicates the behavior of the appropriate FunctionType // constructors. diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index e518a56a..63e2178d 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -36,10 +36,10 @@ LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock) LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauDeprecatedAttribute) LUAU_FASTFLAG(LuauNonReentrantGeneralization2) -LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes) LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2) LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall) LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions) +LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion) namespace Luau { @@ -870,13 +870,10 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull(follow(generalizedType))) { - if (FunctionType* fty = getMutable(follow(generalizedType))) - { - if (c.hasDeprecatedAttribute) - fty->isDeprecatedFunction = true; - } + if (c.hasDeprecatedAttribute) + fty->isDeprecatedFunction = true; } } else @@ -933,6 +930,23 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull(c.sourceType)) + { + for (TypeId gen : ft->generics) + asMutable(gen)->ty.emplace(builtinTypes->unknownType); + ft->generics.clear(); + + for (TypePackId gen : ft->genericPacks) + asMutable(gen)->ty.emplace(builtinTypes->unknownTypePack); + ft->genericPacks.clear(); + } + } + } + return true; } @@ -1213,7 +1227,19 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul // deterministic. if (TypeId* cached = instantiatedAliases.find(signature)) { - bindResult(*cached); + // However, we might now be revealing a malformed mutually recursive + // alias. `instantiatedAliases` can change from underneath us in a + // way that can cause a cached type id to bind to itself if we don't + // do this check. + if (FFlag::LuauGuardAgainstMalformedTypeAliasExpansion && occursCheck(follow(c.target), *cached)) + { + reportError(OccursCheckFailed{}, constraint->location); + bindResult(errorRecoveryType()); + } + else + { + bindResult(*cached); + } return true; } @@ -1633,45 +1659,13 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNullgenerics) { replacements[generic] = builtinTypes->unknownType; - if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes) - containsGenerics.generics.insert(generic); + containsGenerics.generics.insert(generic); } for (auto genericPack : ftv->genericPacks) { replacementPacks[genericPack] = builtinTypes->unknownTypePack; - if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes) - containsGenerics.generics.insert(genericPack); - } - - // If the type of the function has generics, we don't actually want to push any of the generics themselves - // into the argument types as expected types because this creates an unnecessary loop. Instead, we want to - // replace these types with `unknown` (and `...unknown`) to keep any structure but not create the cycle. - if (!FFlag::LuauBidirectionalInferenceCollectIndexerTypes) - { - if (!replacements.empty() || !replacementPacks.empty()) - { - Replacer replacer{arena, std::move(replacements), std::move(replacementPacks)}; - - std::optional res = replacer.substitute(fn); - if (res) - { - if (*res != fn) - { - FunctionType* ftvMut = getMutable(*res); - LUAU_ASSERT(ftvMut); - ftvMut->generics.clear(); - ftvMut->genericPacks.clear(); - } - - fn = *res; - ftv = get(*res); - LUAU_ASSERT(ftv); - - // we've potentially copied type functions here, so we need to reproduce their reduce constraint. - reproduceConstraints(constraint->scope, constraint->location, replacer); - } - } + containsGenerics.generics.insert(genericPack); } const std::vector expectedArgs = flatten(ftv->argTypes).first; @@ -1690,7 +1684,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull(expectedArgTy); @@ -1857,7 +1851,7 @@ bool ConstraintSolver::tryDispatchHasIndexer( return true; } - if (auto ft = get(subjectType)) + if (auto ft = getMutable(subjectType)) { if (auto tbl = get(follow(ft->upperBound)); tbl && tbl->indexer) { @@ -1874,7 +1868,19 @@ bool ConstraintSolver::tryDispatchHasIndexer( TypeId upperBound = arena->addType(TableType{/* props */ {}, TableIndexer{indexType, resultType}, TypeLevel{}, ft->scope, TableState::Unsealed}); - unify(constraint, subjectType, upperBound); + if (FFlag::DebugLuauGreedyGeneralization) + { + TypeId sr = follow(simplifyIntersection(constraint->scope, constraint->location, ft->upperBound, upperBound)); + + if (get(sr)) + bind(constraint, resultType, builtinTypes->errorType); + else + ft->upperBound = sr; + } + else + { + unify(constraint, subjectType, upperBound); + } return true; } diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index 1d1c924a..7fcbcd81 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -133,10 +133,7 @@ struct ErrorConverter size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength); if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength) return "Type " + given + " could not be converted into " + wanted; - if (FFlag::LuauImproveTypePathsInErrors) - return "Type\n\t" + given + "\ncould not be converted into\n\t" + wanted; - else - return "Type\n " + given + "\ncould not be converted into\n " + wanted; + return "Type\n\t" + given + "\ncould not be converted into\n\t" + wanted; }; if (givenTypeName == wantedTypeName) diff --git a/Analysis/src/FragmentAutocomplete.cpp b/Analysis/src/FragmentAutocomplete.cpp index 56f3f684..9a03d89c 100644 --- a/Analysis/src/FragmentAutocomplete.cpp +++ b/Analysis/src/FragmentAutocomplete.cpp @@ -33,7 +33,6 @@ LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection) LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes) LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes) LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings) -LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteDemandBasedCloning) LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval) LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection) @@ -41,51 +40,11 @@ LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection) LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak) LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) - -namespace -{ -template -void copyModuleVec(std::vector& result, const std::vector& input) -{ - result.insert(result.end(), input.begin(), input.end()); -} - -template -void copyModuleMap(Luau::DenseHashMap& result, const Luau::DenseHashMap& input) -{ - for (auto [k, v] : input) - result[k] = v; -} - -} // namespace +LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteIfRecommendations) namespace Luau { -template -void cloneModuleMap_DEPRECATED(TypeArena& destArena, CloneState& cloneState, const Luau::DenseHashMap& source, Luau::DenseHashMap& dest) -{ - for (auto [k, v] : source) - { - dest[k] = Luau::clone(v, destArena, cloneState); - } -} - -template -void cloneModuleMap( - TypeArena& destArena, - CloneState& cloneState, - const Luau::DenseHashMap& source, - Luau::DenseHashMap& dest, - Scope* freshScopeForFreeType -) -{ - for (auto [k, v] : source) - { - dest[k] = Luau::cloneIncremental(v, destArena, cloneState, freshScopeForFreeType); - } -} - static std::pair getDocumentOffsets(std::string_view src, const Position& startPos, const Position& endPos); // when typing a function partially, get the span of the first line @@ -141,6 +100,29 @@ Location getAstStatForExtents(AstStatFor* forStat) return Location{begin, end}; } +// Traverses the linkedlist of else if statements to find the one that matches the cursor position the best +AstStatIf* getNearestIfToCursor(AstStat* stmt, const Position& cursorPos) +{ + AstStatIf* current = stmt->as(); + if (!current) + return nullptr; + while (current->is()) + { + if (current->elsebody && current->elsebody->location.containsClosed(cursorPos)) + { + if (auto elseIfS = current->elsebody->as()) + { + current = elseIfS; + } + else + break; + } + else + break; + } + return current; +} + Location getFragmentLocation(AstStat* nearestStatement, const Position& cursorPosition) { Location empty{cursorPosition, cursorPosition}; @@ -199,25 +181,59 @@ Location getFragmentLocation(AstStat* nearestStatement, const Position& cursorPo else return empty; } - - if (auto ifS = nearestStatement->as()) + if (FFlag::LuauFragmentAutocompleteIfRecommendations) { - auto conditionExtents = Location{ifS->location.begin, ifS->condition->location.end}; - if (conditionExtents.containsClosed(cursorPosition)) - return nonEmpty; - else if (ifS->thenbody->location.containsClosed(cursorPosition)) - return empty; - else if (auto elseS = ifS->elsebody) + if (auto ifS = getNearestIfToCursor(nearestStatement, cursorPosition)) { - if (auto elseIf = ifS->elsebody->as()) + auto conditionExtents = Location{ifS->condition->location.begin, ifS->condition->location.end}; + if (conditionExtents.containsClosed(cursorPosition) || !ifS->thenLocation) { - - if (elseIf->thenbody->hasEnd) + // CLI-152249 - the condition parse location can sometimes be after the body of the if + // statement. This is a bug that results returning locations like {3,0 - 2,0} which is + // wrong. + if (ifS->condition->location.begin > cursorPosition) return empty; - else - return {elseS->location.begin, cursorPosition}; + return Location{ifS->condition->location.begin, cursorPosition}; + } + + else if (ifS->thenbody->location.containsClosed(cursorPosition)) + return empty; + else if (auto elseS = ifS->elsebody) + { + if (auto elseIf = ifS->elsebody->as()) + { + auto elseIfConditionExtents = Location{elseIf->location.begin, elseIf->condition->location.end}; + if (elseIfConditionExtents.containsClosed(cursorPosition)) + return {elseIf->condition->location.begin, cursorPosition}; + if (elseIf->thenbody->hasEnd) + return empty; + else + return {elseS->location.begin, cursorPosition}; + } + return empty; + } + } + } + else + { + if (auto ifS = nearestStatement->as()) + { + auto conditionExtents = Location{ifS->location.begin, ifS->condition->location.end}; + if (conditionExtents.containsClosed(cursorPosition)) + return nonEmpty; + else if (ifS->thenbody->location.containsClosed(cursorPosition)) + return empty; + else if (auto elseS = ifS->elsebody) + { + if (auto elseIf = ifS->elsebody->as()) + { + if (elseIf->thenbody->hasEnd) + return empty; + else + return {elseS->location.begin, cursorPosition}; + } + return empty; } - return empty; } } @@ -504,9 +520,31 @@ std::optional parseFragment( if (p.root == nullptr) return std::nullopt; - std::vector fabricatedAncestry = std::move(result.ancestry); + std::vector fabricatedAncestry; + // Moves cannot be on the rhs of a ? : statement, so the assignment is placed in an if else stmt + // Make sure to trim this comment after you remove FFlag::LuauFragmentAutocompleteIfRecommendations + if (FFlag::LuauFragmentAutocompleteIfRecommendations) + fabricatedAncestry = findAncestryAtPositionForAutocomplete(mostRecentParse, cursorPos); + else + fabricatedAncestry = std::move(result.ancestry); std::vector fragmentAncestry = findAncestryAtPositionForAutocomplete(p.root, cursorPos); - fabricatedAncestry.insert(fabricatedAncestry.end(), fragmentAncestry.begin(), fragmentAncestry.end()); + + // Computes the accurate ancestry and then replaces the nodes that correspond to the fragment ancestry + // Needed because we look up types by pointer identity + if (FFlag::LuauFragmentAutocompleteIfRecommendations) + { + LUAU_ASSERT(!fabricatedAncestry.empty()); + auto back = fabricatedAncestry.size() - 1; + for (auto it = fragmentAncestry.rbegin(); it != fragmentAncestry.rend(); ++it) + { + if (back >= 0 && back < fabricatedAncestry.size() && (*it)->classIndex == fabricatedAncestry[back]->classIndex) + fabricatedAncestry[back] = *it; + back--; + } + } + else + fabricatedAncestry.insert(fabricatedAncestry.end(), fragmentAncestry.begin(), fragmentAncestry.end()); + if (nearestStatement == nullptr) nearestStatement = p.root; fragmentResult.root = p.root; @@ -718,179 +756,6 @@ void cloneTypesFromFragment( destScope->returnType = Luau::cloneIncremental(staleScope->returnType, *destArena, cloneState, destScope); } - -struct MixedModeIncrementalTCDefFinder : public AstVisitor -{ - - bool visit(AstExprLocal* local) override - { - referencedLocalDefs.emplace_back(local->local, local); - return true; - } - - bool visit(AstTypeTypeof* node) override - { - // We need to traverse typeof expressions because they may refer to locals that we need - // to populate the local environment for fragment typechecking. For example, `typeof(m)` - // requires that we find the local/global `m` and place it in the environment. - // The default behaviour here is to return false, and have individual visitors override - // the specific behaviour they need. - return true; - } - - bool visit(AstStatTypeAlias* alias) override - { - if (FFlag::LuauCloneTypeAliasBindings) - declaredAliases.insert(std::string(alias->name.value)); - return true; - } - - // ast defs is just a mapping from expr -> def in general - // will get built up by the dfg builder - - // localDefs, we need to copy over - std::vector> referencedLocalDefs; - DenseHashSet declaredAliases{""}; -}; - -void cloneAndSquashScopes_DEPRECATED( - CloneState& cloneState, - const Scope* staleScope, - const ModulePtr& staleModule, - NotNull destArena, - NotNull dfg, - AstStatBlock* program, - Scope* destScope -) -{ - LUAU_TIMETRACE_SCOPE("Luau::cloneAndSquashScopes", "FragmentAutocomplete"); - std::vector scopes; - for (const Scope* current = staleScope; current; current = current->parent.get()) - { - scopes.emplace_back(current); - } - - // in reverse order (we need to clone the parents and override defs as we go down the list) - for (auto it = scopes.rbegin(); it != scopes.rend(); ++it) - { - const Scope* curr = *it; - // Clone the lvalue types - for (const auto& [def, ty] : curr->lvalueTypes) - destScope->lvalueTypes[def] = Luau::clone(ty, *destArena, cloneState); - // Clone the rvalueRefinements - for (const auto& [def, ty] : curr->rvalueRefinements) - destScope->rvalueRefinements[def] = Luau::clone(ty, *destArena, cloneState); - for (const auto& [n, m] : curr->importedTypeBindings) - { - std::unordered_map importedBindingTypes; - for (const auto& [v, tf] : m) - importedBindingTypes[v] = Luau::clone(tf, *destArena, cloneState); - destScope->importedTypeBindings[n] = m; - } - - // Finally, clone up the bindings - for (const auto& [s, b] : curr->bindings) - { - destScope->bindings[s] = Luau::clone(b, *destArena, cloneState); - } - } - - // The above code associates defs with TypeId's in the scope - // so that lookup to locals will succeed. - MixedModeIncrementalTCDefFinder finder; - program->visit(&finder); - std::vector> locals = std::move(finder.referencedLocalDefs); - for (auto [loc, expr] : locals) - { - if (std::optional binding = staleScope->linearSearchForBinding(loc->name.value, true)) - { - destScope->lvalueTypes[dfg->getDef(expr)] = Luau::clone(binding->typeId, *destArena, cloneState); - } - } - return; -} - -void cloneAndSquashScopes( - CloneState& cloneState, - const Scope* staleScope, - const ModulePtr& staleModule, - NotNull destArena, - NotNull dfg, - AstStatBlock* program, - Scope* destScope -) -{ - LUAU_TIMETRACE_SCOPE("Luau::cloneAndSquashScopes", "FragmentAutocomplete"); - std::vector scopes; - for (const Scope* current = staleScope; current; current = current->parent.get()) - { - scopes.emplace_back(current); - } - - MixedModeIncrementalTCDefFinder finder; - - if (FFlag::LuauCloneTypeAliasBindings) - program->visit(&finder); - // in reverse order (we need to clone the parents and override defs as we go down the list) - for (auto it = scopes.rbegin(); it != scopes.rend(); ++it) - { - const Scope* curr = *it; - // Clone the lvalue types - for (const auto& [def, ty] : curr->lvalueTypes) - destScope->lvalueTypes[def] = Luau::cloneIncremental(ty, *destArena, cloneState, destScope); - // Clone the rvalueRefinements - for (const auto& [def, ty] : curr->rvalueRefinements) - destScope->rvalueRefinements[def] = Luau::cloneIncremental(ty, *destArena, cloneState, destScope); - - if (FFlag::LuauCloneTypeAliasBindings) - { - for (const auto& [n, tf] : curr->exportedTypeBindings) - { - if (!finder.declaredAliases.contains(n)) - destScope->exportedTypeBindings[n] = Luau::cloneIncremental(tf, *destArena, cloneState, destScope); - } - - for (const auto& [n, tf] : curr->privateTypeBindings) - { - if (!finder.declaredAliases.contains(n)) - destScope->privateTypeBindings[n] = Luau::cloneIncremental(tf, *destArena, cloneState, destScope); - } - } - for (const auto& [n, m] : curr->importedTypeBindings) - { - std::unordered_map importedBindingTypes; - for (const auto& [v, tf] : m) - importedBindingTypes[v] = Luau::cloneIncremental(tf, *destArena, cloneState, destScope); - destScope->importedTypeBindings[n] = std::move(importedBindingTypes); - } - - // Finally, clone up the bindings - for (const auto& [s, b] : curr->bindings) - { - destScope->bindings[s] = Luau::cloneIncremental(b, *destArena, cloneState, destScope); - } - } - - if (!FFlag::LuauCloneTypeAliasBindings) - program->visit(&finder); - // The above code associates defs with TypeId's in the scope - // so that lookup to locals will succeed. - - std::vector> locals = std::move(finder.referencedLocalDefs); - for (auto [loc, expr] : locals) - { - if (std::optional binding = staleScope->linearSearchForBinding(loc->name.value, true)) - { - destScope->lvalueTypes[dfg->getDef(expr)] = Luau::cloneIncremental(binding->typeId, *destArena, cloneState, destScope); - } - } - - if (destScope->returnType) - destScope->returnType = Luau::cloneIncremental(destScope->returnType, *destArena, cloneState, destScope); - - return; -} - static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::optional options) { if (FFlag::LuauSolverV2 || !options) @@ -1031,8 +896,11 @@ static std::pair getDocumentOffsets(std::string_view src, const if (endPos.line == lineCount && endPos.column == colCount) { endOffset = docOffset; - while (endOffset < src.size() && src[endOffset] != '\n') - endOffset++; + if (!FFlag::LuauFragmentAutocompleteIfRecommendations) + { + while (endOffset < src.size() && src[endOffset] != '\n') + endOffset++; + } foundEnd = true; } @@ -1177,80 +1045,6 @@ std::optional parseFragment_DEPRECATED( return fragmentResult; } -ModulePtr cloneModule_DEPRECATED(CloneState& cloneState, const ModulePtr& source, std::unique_ptr alloc) -{ - LUAU_TIMETRACE_SCOPE("Luau::cloneModule", "FragmentAutocomplete"); - freeze(source->internalTypes); - freeze(source->interfaceTypes); - ModulePtr incremental = std::make_shared(); - incremental->name = source->name; - incremental->humanReadableName = source->humanReadableName; - incremental->allocator = std::move(alloc); - // Clone types - cloneModuleMap_DEPRECATED(incremental->internalTypes, cloneState, source->astTypes, incremental->astTypes); - cloneModuleMap_DEPRECATED(incremental->internalTypes, cloneState, source->astTypePacks, incremental->astTypePacks); - cloneModuleMap_DEPRECATED(incremental->internalTypes, cloneState, source->astExpectedTypes, incremental->astExpectedTypes); - - cloneModuleMap_DEPRECATED(incremental->internalTypes, cloneState, source->astOverloadResolvedTypes, incremental->astOverloadResolvedTypes); - - cloneModuleMap_DEPRECATED(incremental->internalTypes, cloneState, source->astForInNextTypes, incremental->astForInNextTypes); - - copyModuleMap(incremental->astScopes, source->astScopes); - - return incremental; -} - -ModulePtr cloneModule(CloneState& cloneState, const ModulePtr& source, std::unique_ptr alloc, Scope* freeTypeFreshScope) -{ - LUAU_TIMETRACE_SCOPE("Luau::cloneModule", "FragmentAutocomplete"); - freeze(source->internalTypes); - freeze(source->interfaceTypes); - ModulePtr incremental = std::make_shared(); - incremental->name = source->name; - incremental->humanReadableName = source->humanReadableName; - incremental->allocator = std::move(alloc); - // Clone types - cloneModuleMap(incremental->internalTypes, cloneState, source->astTypes, incremental->astTypes, freeTypeFreshScope); - cloneModuleMap(incremental->internalTypes, cloneState, source->astTypePacks, incremental->astTypePacks, freeTypeFreshScope); - cloneModuleMap(incremental->internalTypes, cloneState, source->astExpectedTypes, incremental->astExpectedTypes, freeTypeFreshScope); - - cloneModuleMap( - incremental->internalTypes, cloneState, source->astOverloadResolvedTypes, incremental->astOverloadResolvedTypes, freeTypeFreshScope - ); - - cloneModuleMap(incremental->internalTypes, cloneState, source->astForInNextTypes, incremental->astForInNextTypes, freeTypeFreshScope); - - copyModuleMap(incremental->astScopes, source->astScopes); - - return incremental; -} - -void mixedModeCompatibility( - const ScopePtr& bottomScopeStale, - const ScopePtr& myFakeScope, - const ModulePtr& stale, - NotNull dfg, - AstStatBlock* program -) -{ - // This code does the following - // traverse program - // look for ast refs for locals - // ask for the corresponding defId from dfg - // given that defId, and that expression, in the incremental module, map lvalue types from defID to - - MixedModeIncrementalTCDefFinder finder; - program->visit(&finder); - std::vector> locals = std::move(finder.referencedLocalDefs); - for (auto [loc, expr] : locals) - { - if (std::optional binding = bottomScopeStale->linearSearchForBinding(loc->name.value, true)) - { - myFakeScope->lvalueTypes[dfg->getDef(expr)] = binding->typeId; - } - } -} - static void reportWaypoint(IFragmentAutocompleteReporter* reporter, FragmentAutocompleteWaypoint type) { if (!reporter) @@ -1267,156 +1061,6 @@ static void reportFragmentString(IFragmentAutocompleteReporter* reporter, std::s reporter->reportFragmentString(fragment); } -FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED( - Frontend& frontend, - AstStatBlock* root, - const ModulePtr& stale, - const ScopePtr& closestScope, - const Position& cursorPos, - std::unique_ptr astAllocator, - const FrontendOptions& opts, - IFragmentAutocompleteReporter* reporter -) -{ - LUAU_TIMETRACE_SCOPE("Luau::typecheckFragment_", "FragmentAutocomplete"); - - freeze(stale->internalTypes); - freeze(stale->interfaceTypes); - reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneModuleStart); - CloneState cloneState{frontend.builtinTypes}; - std::shared_ptr freshChildOfNearestScope = std::make_shared(closestScope); - ModulePtr incrementalModule = nullptr; - if (FFlag::LuauAllFreeTypesHaveScopes) - incrementalModule = cloneModule(cloneState, stale, std::move(astAllocator), freshChildOfNearestScope.get()); - else - incrementalModule = cloneModule_DEPRECATED(cloneState, stale, std::move(astAllocator)); - - reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneModuleEnd); - incrementalModule->checkedInNewSolver = true; - unfreeze(incrementalModule->internalTypes); - unfreeze(incrementalModule->interfaceTypes); - - /// Setup typecheck limits - TypeCheckLimits limits; - if (opts.moduleTimeLimitSec) - limits.finishTime = TimeTrace::getClock() + *opts.moduleTimeLimitSec; - else - limits.finishTime = std::nullopt; - limits.cancellationToken = opts.cancellationToken; - - /// Icehandler - NotNull iceHandler{&frontend.iceHandler}; - /// Make the shared state for the unifier (recursion + iteration limits) - UnifierSharedState unifierState{iceHandler}; - unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit; - unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit); - - /// Initialize the normalizer - Normalizer normalizer{&incrementalModule->internalTypes, frontend.builtinTypes, NotNull{&unifierState}}; - - /// User defined type functions runtime - TypeFunctionRuntime typeFunctionRuntime(iceHandler, NotNull{&limits}); - - /// Create a DataFlowGraph just for the surrounding context - DataFlowGraph dfg = DataFlowGraphBuilder::build(root, NotNull{&incrementalModule->defArena}, NotNull{&incrementalModule->keyArena}, iceHandler); - reportWaypoint(reporter, FragmentAutocompleteWaypoint::DfgBuildEnd); - - SimplifierPtr simplifier = newSimplifier(NotNull{&incrementalModule->internalTypes}, frontend.builtinTypes); - - FrontendModuleResolver& resolver = getModuleResolver(frontend, opts); - - /// Contraint Generator - ConstraintGenerator cg{ - incrementalModule, - NotNull{&normalizer}, - NotNull{simplifier.get()}, - NotNull{&typeFunctionRuntime}, - NotNull{&resolver}, - frontend.builtinTypes, - iceHandler, - stale->getModuleScope(), - frontend.globals.globalTypeFunctionScope, - nullptr, - nullptr, - NotNull{&dfg}, - {} - }; - - reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart); - incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope); - cg.rootScope = freshChildOfNearestScope.get(); - - if (FFlag::LuauAllFreeTypesHaveScopes) - cloneAndSquashScopes( - cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get() - ); - else - cloneAndSquashScopes_DEPRECATED( - cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get() - ); - - reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeEnd); - cg.visitFragmentRoot(freshChildOfNearestScope, root); - - if (FFlag::LuauPersistConstraintGenerationScopes) - { - for (auto p : cg.scopes) - incrementalModule->scopes.emplace_back(std::move(p)); - } - - reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverStart); - - if (FFlag::LuauAllFreeTypesHaveScopes) - { - if (Scope* sc = freshChildOfNearestScope.get()) - { - if (!sc->interiorFreeTypes.has_value()) - sc->interiorFreeTypes.emplace(); - if (!sc->interiorFreeTypePacks.has_value()) - sc->interiorFreeTypePacks.emplace(); - } - } - - /// Initialize the constraint solver and run it - ConstraintSolver cs{ - NotNull{&normalizer}, - NotNull{simplifier.get()}, - NotNull{&typeFunctionRuntime}, - NotNull(cg.rootScope), - borrowConstraints(cg.constraints), - NotNull{&cg.scopeToFunction}, - incrementalModule->name, - NotNull{&resolver}, - {}, - nullptr, - NotNull{&dfg}, - limits - }; - - try - { - cs.run(); - } - catch (const TimeLimitError&) - { - stale->timeout = true; - } - catch (const UserCancelError&) - { - stale->cancelled = true; - } - - reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverEnd); - - // In frontend we would forbid internal types - // because this is just for autocomplete, we don't actually care - // We also don't even need to typecheck - just synthesize types as best as we can - - freeze(incrementalModule->internalTypes); - freeze(incrementalModule->interfaceTypes); - return {std::move(incrementalModule), std::move(freshChildOfNearestScope)}; -} - FragmentTypeCheckResult typecheckFragment_( Frontend& frontend, AstStatBlock* root, @@ -1609,12 +1253,7 @@ std::pair typecheckFragment( FrontendOptions frontendOptions = opts.value_or(frontend.options); const ScopePtr& closestScope = FFlag::LuauBetterScopeSelection ? findClosestScope(module, parseResult.scopePos) : findClosestScope_DEPRECATED(module, parseResult.nearestStatement); - FragmentTypeCheckResult result = - FFlag::LuauIncrementalAutocompleteDemandBasedCloning - ? typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter) - : typecheckFragmentHelper_DEPRECATED( - frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter - ); + FragmentTypeCheckResult result = typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter); result.ancestry = std::move(parseResult.ancestry); reportFragmentString(reporter, tryParse->fragmentToParse); return {FragmentTypeCheckStatus::Success, result}; diff --git a/Analysis/src/Generalization.cpp b/Analysis/src/Generalization.cpp index 5971c927..a977b033 100644 --- a/Analysis/src/Generalization.cpp +++ b/Analysis/src/Generalization.cpp @@ -1627,6 +1627,10 @@ void pruneUnnecessaryGenerics( return true; seen.insert(ty); + auto state = counter.generics.find(ty); + if (state && state->count == 0) + return true; + return !get(ty); } ); @@ -1650,6 +1654,10 @@ void pruneUnnecessaryGenerics( return true; seen2.insert(tp); + auto state = counter.genericPacks.find(tp); + if (state && state->count == 0) + return true; + return !get(tp); } ); diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index 795d0fc8..3ac099ca 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -19,7 +19,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauAttribute) LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute) -LUAU_FASTFLAG(LuauDeprecatedAttribute) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) namespace Luau @@ -2295,53 +2294,36 @@ private: bool visit(AstExprLocal* node) override { - if (FFlag::LuauDeprecatedAttribute) - { - const FunctionType* fty = getFunctionType(node); bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty); if (shouldReport) report(node->location, node->local->name.value); - } return true; } bool visit(AstExprGlobal* node) override { - if (FFlag::LuauDeprecatedAttribute) - { const FunctionType* fty = getFunctionType(node); bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty); if (shouldReport) report(node->location, node->name.value); - } return true; } bool visit(AstStatLocalFunction* node) override { - if (FFlag::LuauDeprecatedAttribute) - { check(node->func); return false; - } - else - return true; } bool visit(AstStatFunction* node) override { - if (FFlag::LuauDeprecatedAttribute) - { check(node->func); return false; - } - else - return true; } bool visit(AstExprIndexName* node) override @@ -2385,13 +2367,13 @@ private: { if (const ExternType* cty = get(ty)) { - const Property* prop = lookupExternTypeProp(cty, node->index.value); - - if (prop && prop->deprecated) - report(node->location, *prop, cty->name.c_str(), node->index.value); - else if (FFlag::LuauDeprecatedAttribute && prop) + if (const Property* prop = lookupExternTypeProp(cty, node->index.value)) { - if (std::optional ty = prop->readTy) + if (prop->deprecated) + { + report(node->location, *prop, cty->name.c_str(), node->index.value); + } + else if (std::optional ty = prop->readTy) { const FunctionType* fty = get(follow(ty)); bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty); @@ -2423,7 +2405,7 @@ private: else report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value); } - else if (FFlag::LuauDeprecatedAttribute) + else { if (std::optional ty = prop->second.readTy) { @@ -2462,7 +2444,6 @@ private: void check(AstExprFunction* func) { - LUAU_ASSERT(FFlag::LuauDeprecatedAttribute); LUAU_ASSERT(func); const FunctionType* fty = getFunctionType(func); @@ -2492,8 +2473,6 @@ private: void report(const Location& location, const char* tableName, const char* functionName) { - LUAU_ASSERT(FFlag::LuauDeprecatedAttribute); - if (tableName) emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s.%s' is deprecated", tableName, functionName); else @@ -2502,8 +2481,6 @@ private: void report(const Location& location, const char* functionName) { - LUAU_ASSERT(FFlag::LuauDeprecatedAttribute); - emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Function '%s' is deprecated", functionName); } @@ -2511,7 +2488,6 @@ private: void pushScope(const FunctionType* fty) { - LUAU_ASSERT(FFlag::LuauDeprecatedAttribute); LUAU_ASSERT(fty); functionTypeScopeStack.push_back(fty); @@ -2519,7 +2495,6 @@ private: void popScope(const FunctionType* fty) { - LUAU_ASSERT(FFlag::LuauDeprecatedAttribute); LUAU_ASSERT(fty); LUAU_ASSERT(fty == functionTypeScopeStack.back()); @@ -2528,7 +2503,6 @@ private: bool inScope(const FunctionType* fty) const { - LUAU_ASSERT(FFlag::LuauDeprecatedAttribute); LUAU_ASSERT(fty); return std::find(functionTypeScopeStack.begin(), functionTypeScopeStack.end(), fty) != functionTypeScopeStack.end(); @@ -2536,8 +2510,6 @@ private: const FunctionType* getFunctionType(AstExpr* node) { - LUAU_ASSERT(FFlag::LuauDeprecatedAttribute); - std::optional ty = context->getType(node); if (!ty) return nullptr; diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index 791109c8..27a889fc 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -11,7 +11,6 @@ LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256) LUAU_FASTFLAG(LuauSyntheticErrors) -LUAU_FASTFLAG(LuauDeprecatedAttribute) namespace Luau { @@ -101,8 +100,7 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log) clone.tags = a.tags; clone.argNames = a.argNames; clone.isCheckedFunction = a.isCheckedFunction; - if (FFlag::LuauDeprecatedAttribute) - clone.isDeprecatedFunction = a.isDeprecatedFunction; + clone.isDeprecatedFunction = a.isDeprecatedFunction; return dest.addType(std::move(clone)); } else if constexpr (std::is_same_v) diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index 0dbad322..53b1028e 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit) +LUAU_FASTFLAGVARIABLE(LuauSubtypeGenericsAndNegations) namespace Luau { @@ -669,6 +670,18 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub result = {false}; else if (get(subTy)) result = {true}; + else if (auto subGeneric = get(subTy); FFlag::LuauSubtypeGenericsAndNegations && subGeneric && variance == Variance::Covariant) + { + bool ok = bindGeneric(env, subTy, superTy); + result.isSubtype = ok; + result.isCacheable = false; + } + else if (auto superGeneric = get(superTy); FFlag::LuauSubtypeGenericsAndNegations && superGeneric && variance == Variance::Contravariant) + { + bool ok = bindGeneric(env, subTy, superTy); + result.isSubtype = ok; + result.isCacheable = false; + } else if (auto p = get2(subTy, superTy)) result = isCovariantWith(env, p.first->ty, p.second->ty, scope).withBothComponent(TypePath::TypeField::Negated); else if (auto subNegation = get(subTy)) @@ -711,13 +724,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub result = isCovariantWith(env, subTy, superTypeFunctionInstance, scope); } - else if (auto subGeneric = get(subTy); subGeneric && variance == Variance::Covariant) + else if (auto subGeneric = get(subTy); !FFlag::LuauSubtypeGenericsAndNegations && subGeneric && variance == Variance::Covariant) { bool ok = bindGeneric(env, subTy, superTy); result.isSubtype = ok; result.isCacheable = false; } - else if (auto superGeneric = get(superTy); superGeneric && variance == Variance::Contravariant) + else if (auto superGeneric = get(superTy); !FFlag::LuauSubtypeGenericsAndNegations && superGeneric && variance == Variance::Contravariant) { bool ok = bindGeneric(env, subTy, superTy); result.isSubtype = ok; diff --git a/Analysis/src/TableLiteralInference.cpp b/Analysis/src/TableLiteralInference.cpp index a94eaf59..90340bcc 100644 --- a/Analysis/src/TableLiteralInference.cpp +++ b/Analysis/src/TableLiteralInference.cpp @@ -13,8 +13,6 @@ #include "Luau/TypeUtils.h" #include "Luau/Unifier2.h" -LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceCollectIndexerTypes) -LUAU_FASTFLAGVARIABLE(LuauBidirectionalFailsafe) LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert) namespace Luau @@ -148,22 +146,13 @@ TypeId matchLiteralType( expectedType = follow(expectedType); exprType = follow(exprType); - if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes) - { - // The intent of `matchLiteralType` is to upcast values when it's safe - // to do so. it's always safe to upcast to `any` or `unknown`, so we - // can unconditionally do so here. - if (is(expectedType)) - return expectedType; - } - else - { - if (get(expectedType) || get(expectedType)) - { - // "Narrowing" to unknown or any is not going to do anything useful. - return exprType; - } - } + + // The intent of `matchLiteralType` is to upcast values when it's safe + // to do so. it's always safe to upcast to `any` or `unknown`, so we + // can unconditionally do so here. + if (is(expectedType)) + return expectedType; + if (expr->is()) @@ -250,7 +239,7 @@ TypeId matchLiteralType( // { x = {}, x = 42 } // // The type of this will be `{ x: number }` - if (FFlag::LuauBidirectionalFailsafe && !tableTy) + if (!tableTy) return exprType; LUAU_ASSERT(tableTy); @@ -291,7 +280,7 @@ TypeId matchLiteralType( auto it = tableTy->props.find(keyStr); // This can occur, potentially, if we are re-entrant. - if (FFlag::LuauBidirectionalFailsafe && it == tableTy->props.end()) + if (it == tableTy->props.end()) continue; LUAU_ASSERT(it != tableTy->props.end()); @@ -330,18 +319,8 @@ TypeId matchLiteralType( toBlock ); - if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes) - { - indexerKeyTypes.insert(arena->addType(SingletonType{StringSingleton{keyStr}})); - indexerValueTypes.insert(matchedType); - } - else - { - if (tableTy->indexer) - unifier->unify(matchedType, tableTy->indexer->indexResultType); - else - tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType}; - } + indexerKeyTypes.insert(arena->addType(SingletonType{StringSingleton{keyStr}})); + indexerValueTypes.insert(matchedType); keysToDelete.insert(item.key->as()); } @@ -409,7 +388,7 @@ TypeId matchLiteralType( } else if (item.kind == AstExprTable::Item::List) { - if (!FFlag::LuauBidirectionalInferenceCollectIndexerTypes || !FFlag::LuauBidirectionalInferenceElideAssert) + if (!FFlag::LuauBidirectionalInferenceElideAssert) LUAU_ASSERT(tableTy->indexer); if (expectedTableTy->indexer) @@ -431,17 +410,8 @@ TypeId matchLiteralType( toBlock ); - if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes) - { - indexerKeyTypes.insert(builtinTypes->numberType); - indexerValueTypes.insert(matchedType); - } - else - { - // if the index result type is the prop type, we can replace it with the matched type here. - if (tableTy->indexer->indexResultType == *propTy) - tableTy->indexer->indexResultType = matchedType; - } + indexerKeyTypes.insert(builtinTypes->numberType); + indexerValueTypes.insert(matchedType); } } else if (item.kind == AstExprTable::Item::General) @@ -464,11 +434,8 @@ TypeId matchLiteralType( if (!item.key->as() && expectedTableTy->indexer) (*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType; - if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes) - { - indexerKeyTypes.insert(tKey); - indexerValueTypes.insert(tProp); - } + indexerKeyTypes.insert(tKey); + indexerValueTypes.insert(tProp); } else LUAU_ASSERT(!"Unexpected"); @@ -530,7 +497,7 @@ TypeId matchLiteralType( // have one too. // TODO: If the expected table also has an indexer, we might want to // push the expected indexer's types into it. - if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes && expectedTableTy->indexer) + if (expectedTableTy->indexer) { if (indexerValueTypes.size() > 0 && indexerKeyTypes.size() > 0) { diff --git a/Analysis/src/Transpiler.cpp b/Analysis/src/Transpiler.cpp index b3cf82a1..9ff31fff 100644 --- a/Analysis/src/Transpiler.cpp +++ b/Analysis/src/Transpiler.cpp @@ -11,10 +11,9 @@ #include LUAU_FASTFLAG(LuauStoreCSTData2) -LUAU_FASTFLAG(LuauAstTypeGroup3) -LUAU_FASTFLAG(LuauParseOptionalAsNode2) LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) +LUAU_FASTFLAG(LuauStoreLocalAnnotationColonPositions) namespace { @@ -368,7 +367,7 @@ struct Printer_DEPRECATED else if (typeCount == 1) { bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is()); - if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize) + if (shouldParenthesize) writer.symbol("("); // Only variadic tail @@ -381,7 +380,7 @@ struct Printer_DEPRECATED visualizeTypeAnnotation(*list.types.data[0]); } - if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize) + if (shouldParenthesize) writer.symbol(")"); } else @@ -1233,18 +1232,9 @@ struct Printer_DEPRECATED AstType* l = a->types.data[0]; AstType* r = a->types.data[1]; - if (FFlag::LuauParseOptionalAsNode2) - { - auto lta = l->as(); - if (lta && lta->name == "nil" && !r->is()) - std::swap(l, r); - } - else - { - auto lta = l->as(); - if (lta && lta->name == "nil") - std::swap(l, r); - } + auto lta = l->as(); + if (lta && lta->name == "nil" && !r->is()) + std::swap(l, r); // it's still possible that we had a (T | U) or (T | nil) and not (nil | T) auto rta = r->as(); @@ -1267,13 +1257,10 @@ struct Printer_DEPRECATED for (size_t i = 0; i < a->types.size; ++i) { - if (FFlag::LuauParseOptionalAsNode2) + if (a->types.data[i]->is()) { - if (a->types.data[i]->is()) - { - writer.symbol("?"); - continue; - } + writer.symbol("?"); + continue; } if (i > 0) @@ -1359,14 +1346,15 @@ struct Printer return nullptr; } - void visualize(const AstLocal& local) + void visualize(const AstLocal& local, Position colonPosition) { advance(local.location.begin); writer.identifier(local.name.value); if (writeTypes && local.annotation) { - // TODO: handle spacing for type annotation + if (FFlag::LuauStoreLocalAnnotationColonPositions) + advance(colonPosition); writer.symbol(":"); visualizeTypeAnnotation(*local.annotation); } @@ -1428,7 +1416,7 @@ struct Printer else if (typeCount == 1) { bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is()); - if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize) + if (shouldParenthesize) { if (openParenthesesPosition) advance(*openParenthesesPosition); @@ -1447,7 +1435,7 @@ struct Printer visualizeTypeAnnotation(*list.types.data[0]); } - if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize) + if (shouldParenthesize) { if (closeParenthesesPosition) advance(*closeParenthesesPosition); @@ -1973,10 +1961,16 @@ struct Printer writer.keyword("local"); CommaSeparatorInserter varComma(writer, cstNode ? cstNode->varsCommaPositions.begin() : nullptr); - for (const auto& local : a->vars) + for (size_t i = 0; i < a->vars.size; i++) { varComma(); - visualize(*local); + if (FFlag::LuauStoreLocalAnnotationColonPositions && cstNode) + { + LUAU_ASSERT(cstNode->varsAnnotationColonPositions.size > i); + visualize(*a->vars.data[i], cstNode->varsAnnotationColonPositions.data[i]); + } + else + visualize(*a->vars.data[i], Position{0, 0}); } if (a->equalsSignLocation) @@ -1999,7 +1993,11 @@ struct Printer writer.keyword("for"); - visualize(*a->var); + if (FFlag::LuauStoreLocalAnnotationColonPositions) + visualize(*a->var, cstNode ? cstNode->annotationColonPosition : Position{0, 0}); + else + visualize(*a->var, Position{0,0}); + if (cstNode) advance(cstNode->equalsPosition); writer.symbol("="); @@ -2029,10 +2027,16 @@ struct Printer writer.keyword("for"); CommaSeparatorInserter varComma(writer, cstNode ? cstNode->varsCommaPositions.begin() : nullptr); - for (const auto& var : a->vars) + for (size_t i = 0; i < a->vars.size; i++) { varComma(); - visualize(*var); + if (FFlag::LuauStoreLocalAnnotationColonPositions && cstNode) + { + LUAU_ASSERT(cstNode->varsAnnotationColonPositions.size > i); + visualize(*a->vars.data[i], cstNode->varsAnnotationColonPositions.data[i]); + } + else + visualize(*a->vars.data[i], Position{0, 0}); } advance(a->inLocation.begin); @@ -2363,6 +2367,11 @@ struct Printer writer.identifier(local->name.value); if (writeTypes && local->annotation) { + if (FFlag::LuauStoreReturnTypesAsPackOnAst && FFlag::LuauStoreLocalAnnotationColonPositions && cstNode) + { + LUAU_ASSERT(cstNode->argsAnnotationColonPositions.size > i); + advance(cstNode->argsAnnotationColonPositions.data[i]); + } writer.symbol(":"); visualizeTypeAnnotation(*local->annotation); } @@ -2376,6 +2385,11 @@ struct Printer if (func.varargAnnotation) { + if (FFlag::LuauStoreReturnTypesAsPackOnAst && FFlag::LuauStoreLocalAnnotationColonPositions && cstNode) + { + LUAU_ASSERT(cstNode->varargAnnotationColonPosition != Position({0, 0})); + advance(cstNode->varargAnnotationColonPosition); + } writer.symbol(":"); visualizeTypePackAnnotation(*func.varargAnnotation, true); } @@ -2755,18 +2769,9 @@ struct Printer AstType* l = a->types.data[0]; AstType* r = a->types.data[1]; - if (FFlag::LuauParseOptionalAsNode2) - { - auto lta = l->as(); - if (lta && lta->name == "nil" && !r->is()) - std::swap(l, r); - } - else - { - auto lta = l->as(); - if (lta && lta->name == "nil") - std::swap(l, r); - } + auto lta = l->as(); + if (lta && lta->name == "nil" && !r->is()) + std::swap(l, r); // it's still possible that we had a (T | U) or (T | nil) and not (nil | T) auto rta = r->as(); @@ -2796,21 +2801,17 @@ struct Printer size_t separatorIndex = 0; for (size_t i = 0; i < a->types.size; ++i) { - if (FFlag::LuauParseOptionalAsNode2) + if (const auto optional = a->types.data[i]->as()) { - if (const auto optional = a->types.data[i]->as()) - { - advance(optional->location.begin); - writer.symbol("?"); - continue; - } + advance(optional->location.begin); + writer.symbol("?"); + continue; } if (i > 0) { - if (cstNode && FFlag::LuauParseOptionalAsNode2) + if (cstNode) { - // separatorIndex is only valid if `?` is handled as an AstTypeOptional advance(cstNode->separatorPositions.data[separatorIndex]); separatorIndex++; } diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 8648ec7e..d132a411 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -30,11 +30,11 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats) LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) +LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors) namespace Luau { @@ -1323,8 +1323,21 @@ void TypeChecker2::visit(AstExprConstantBool* expr) NotNull scope{findInnermostScope(expr->location)}; const SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope); - if (!r.isSubtype && !isErrorSuppressing(expr->location, inferredType)) - reportError(TypeMismatch{inferredType, bestType}, expr->location); + if (FFlag::LuauReportSubtypingErrors) + { + if (!isErrorSuppressing(expr->location, inferredType)) + { + if (!r.isSubtype) + reportError(TypeMismatch{inferredType, bestType}, expr->location); + + reportErrors(r.errors); + } + } + else + { + if (!r.isSubtype && !isErrorSuppressing(expr->location, inferredType)) + reportError(TypeMismatch{inferredType, bestType}, expr->location); + } } void TypeChecker2::visit(AstExprConstantNumber* expr) @@ -1348,8 +1361,21 @@ void TypeChecker2::visit(AstExprConstantString* expr) NotNull scope{findInnermostScope(expr->location)}; const SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope); - if (!r.isSubtype && !isErrorSuppressing(expr->location, inferredType)) - reportError(TypeMismatch{inferredType, bestType}, expr->location); + if (FFlag::LuauReportSubtypingErrors) + { + if (!isErrorSuppressing(expr->location, inferredType)) + { + if (!r.isSubtype) + reportError(TypeMismatch{inferredType, bestType}, expr->location); + + reportErrors(r.errors); + } + } + else + { + if (!r.isSubtype && !isErrorSuppressing(expr->location, inferredType)) + reportError(TypeMismatch{inferredType, bestType}, expr->location); + } } void TypeChecker2::visit(AstExprLocal* expr) @@ -1417,6 +1443,12 @@ void TypeChecker2::visitCall(AstExprCall* call) if (result.isSubtype) fnTy = follow(*selectedOverloadTy); + if (FFlag::LuauReportSubtypingErrors) + { + if (!isErrorSuppressing(call->location, *selectedOverloadTy)) + reportErrors(std::move(result.errors)); + } + if (result.normalizationTooComplex) { reportError(NormalizationTooComplex{}, call->func->location); @@ -2728,61 +2760,41 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc if (!subLeafTy && !superLeafTy && !subLeafTp && !superLeafTp) ice->ice("Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack.", location); - if (FFlag::LuauImproveTypePathsInErrors) - { - std::string relation = "a subtype of"; - if (reasoning.variance == SubtypingVariance::Invariant) - relation = "exactly"; - else if (reasoning.variance == SubtypingVariance::Contravariant) - relation = "a supertype of"; + std::string relation = "a subtype of"; + if (reasoning.variance == SubtypingVariance::Invariant) + relation = "exactly"; + else if (reasoning.variance == SubtypingVariance::Contravariant) + relation = "a supertype of"; - std::string subLeafAsString = toString(subLeaf); - // if the string is empty, it must be an empty type pack - if (subLeafAsString.empty()) - subLeafAsString = "()"; + std::string subLeafAsString = toString(subLeaf); + // if the string is empty, it must be an empty type pack + if (subLeafAsString.empty()) + subLeafAsString = "()"; - std::string superLeafAsString = toString(superLeaf); - // if the string is empty, it must be an empty type pack - if (superLeafAsString.empty()) - superLeafAsString = "()"; + std::string superLeafAsString = toString(superLeaf); + // if the string is empty, it must be an empty type pack + if (superLeafAsString.empty()) + superLeafAsString = "()"; - std::stringstream baseReasonBuilder; - baseReasonBuilder << "`" << subLeafAsString << "` is not " << relation << " `" << superLeafAsString << "`"; - std::string baseReason = baseReasonBuilder.str(); + std::stringstream baseReasonBuilder; + baseReasonBuilder << "`" << subLeafAsString << "` is not " << relation << " `" << superLeafAsString << "`"; + std::string baseReason = baseReasonBuilder.str(); - std::stringstream reason; + std::stringstream reason; - if (reasoning.subPath == reasoning.superPath) - reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "` in the former type and `" << superLeafAsString - << "` in the latter type, and " << baseReason; - else if (!reasoning.subPath.empty() && !reasoning.superPath.empty()) - reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "` and " << toStringHuman(reasoning.superPath) << "`" - << superLeafAsString << "`, and " << baseReason; - else if (!reasoning.subPath.empty()) - reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "`, which is not " << relation << " `" << superLeafAsString - << "`"; - else - reason << toStringHuman(reasoning.superPath) << "`" << superLeafAsString << "`, and " << baseReason; - - reasons.push_back(reason.str()); - } + if (reasoning.subPath == reasoning.superPath) + reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "` in the former type and `" << superLeafAsString + << "` in the latter type, and " << baseReason; + else if (!reasoning.subPath.empty() && !reasoning.superPath.empty()) + reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "` and " << toStringHuman(reasoning.superPath) << "`" + << superLeafAsString << "`, and " << baseReason; + else if (!reasoning.subPath.empty()) + reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "`, which is not " << relation << " `" << superLeafAsString + << "`"; else - { - std::string relation = "a subtype of"; - if (reasoning.variance == SubtypingVariance::Invariant) - relation = "exactly"; - else if (reasoning.variance == SubtypingVariance::Contravariant) - relation = "a supertype of"; + reason << toStringHuman(reasoning.superPath) << "`" << superLeafAsString << "`, and " << baseReason; - std::string reason; - if (reasoning.subPath == reasoning.superPath) - reason = "at " + toString(reasoning.subPath) + ", " + toString(subLeaf) + " is not " + relation + " " + toString(superLeaf); - else - reason = "type " + toString(subTy) + toString(reasoning.subPath, /* prefixDot */ true) + " (" + toString(subLeaf) + ") is not " + - relation + " " + toString(superTy) + toString(reasoning.superPath, /* prefixDot */ true) + " (" + toString(superLeaf) + ")"; - - reasons.push_back(reason); - } + reasons.push_back(reason.str()); // if we haven't already proved this isn't suppressing, we have to keep checking. if (suppressed) @@ -2850,6 +2862,12 @@ bool TypeChecker2::testIsSubtype(TypeId subTy, TypeId superTy, Location location NotNull scope{findInnermostScope(location)}; SubtypingResult r = subtyping->isSubtype(subTy, superTy, scope); + if (FFlag::LuauReportSubtypingErrors) + { + if (!isErrorSuppressing(location, subTy)) + reportErrors(std::move(r.errors)); + } + if (r.normalizationTooComplex) reportError(NormalizationTooComplex{}, location); @@ -2864,6 +2882,12 @@ bool TypeChecker2::testIsSubtype(TypePackId subTy, TypePackId superTy, Location NotNull scope{findInnermostScope(location)}; SubtypingResult r = subtyping->isSubtype(subTy, superTy, scope); + if (FFlag::LuauReportSubtypingErrors) + { + if (!isErrorSuppressing(location, subTy)) + reportErrors(std::move(r.errors)); + } + if (r.normalizationTooComplex) reportError(NormalizationTooComplex{}, location); diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index 3bd05335..14a1831a 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -56,17 +56,16 @@ LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions) LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionImprovements) LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionFunctionMetamethods) LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil) -LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement) LUAU_FASTFLAGVARIABLE(LuauMetatablesHaveLength) LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny) LUAU_FASTFLAGVARIABLE(LuauFixCyclicIndexInIndexer) LUAU_FASTFLAGVARIABLE(LuauSimplyRefineNotNil) LUAU_FASTFLAGVARIABLE(LuauIndexDeferPendingIndexee) LUAU_FASTFLAGVARIABLE(LuauNewTypeFunReductionChecks2) -LUAU_FASTFLAGVARIABLE(LuauReduceUnionFollowUnionType) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers) LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget) +LUAU_FASTFLAGVARIABLE(LuauNotAllBinaryTypeFunsHaveDefaults) namespace Luau { @@ -2492,25 +2491,14 @@ struct CollectUnionTypeOptions : TypeOnceVisitor bool visit(TypeId ty, const UnionType& ut) override { - if (FFlag::LuauReduceUnionFollowUnionType) - { - // If we have something like: - // - // union - // - // We probably just want to consider this to be the same as - // - // union - return true; - } - else - { - // Copy of the default visit method. - options.insert(ty); - if (isPending(ty, ctx->solver)) - blockingTypes.insert(ty); - return false; - } + // If we have something like: + // + // union + // + // We probably just want to consider this to be the same as + // + // union + return true; } bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override @@ -3600,8 +3588,8 @@ void BuiltinTypeFunctions::addToScope(NotNull arena, NotNull s return TypeFun{{genericT}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t}, {}})}; }; - // make a type function for a two-argument type function - auto mkBinaryTypeFunction = [&](const TypeFunction* tf) + // make a type function for a two-argument type function with a default argument for the second type being the first + auto mkBinaryTypeFunctionWithDefault = [&](const TypeFunction* tf) { TypeId t = arena->addType(GenericType{"T", Polarity::Negative}); TypeId u = arena->addType(GenericType{"U", Polarity::Negative}); @@ -3611,31 +3599,53 @@ void BuiltinTypeFunctions::addToScope(NotNull arena, NotNull s return TypeFun{{genericT, genericU}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t, u}, {}})}; }; + // make a two-argument type function without the default arguments + auto mkBinaryTypeFunction = [&](const TypeFunction* tf) + { + TypeId t = arena->addType(GenericType{"T", Polarity::Negative}); + TypeId u = arena->addType(GenericType{"U", Polarity::Negative}); + GenericTypeDefinition genericT{t}; + GenericTypeDefinition genericU{u}; + + return TypeFun{{genericT, genericU}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t, u}, {}})}; + }; + scope->exportedTypeBindings[lenFunc.name] = mkUnaryTypeFunction(&lenFunc); scope->exportedTypeBindings[unmFunc.name] = mkUnaryTypeFunction(&unmFunc); - scope->exportedTypeBindings[addFunc.name] = mkBinaryTypeFunction(&addFunc); - scope->exportedTypeBindings[subFunc.name] = mkBinaryTypeFunction(&subFunc); - scope->exportedTypeBindings[mulFunc.name] = mkBinaryTypeFunction(&mulFunc); - scope->exportedTypeBindings[divFunc.name] = mkBinaryTypeFunction(&divFunc); - scope->exportedTypeBindings[idivFunc.name] = mkBinaryTypeFunction(&idivFunc); - scope->exportedTypeBindings[powFunc.name] = mkBinaryTypeFunction(&powFunc); - scope->exportedTypeBindings[modFunc.name] = mkBinaryTypeFunction(&modFunc); - scope->exportedTypeBindings[concatFunc.name] = mkBinaryTypeFunction(&concatFunc); + scope->exportedTypeBindings[addFunc.name] = mkBinaryTypeFunctionWithDefault(&addFunc); + scope->exportedTypeBindings[subFunc.name] = mkBinaryTypeFunctionWithDefault(&subFunc); + scope->exportedTypeBindings[mulFunc.name] = mkBinaryTypeFunctionWithDefault(&mulFunc); + scope->exportedTypeBindings[divFunc.name] = mkBinaryTypeFunctionWithDefault(&divFunc); + scope->exportedTypeBindings[idivFunc.name] = mkBinaryTypeFunctionWithDefault(&idivFunc); + scope->exportedTypeBindings[powFunc.name] = mkBinaryTypeFunctionWithDefault(&powFunc); + scope->exportedTypeBindings[modFunc.name] = mkBinaryTypeFunctionWithDefault(&modFunc); + scope->exportedTypeBindings[concatFunc.name] = mkBinaryTypeFunctionWithDefault(&concatFunc); - scope->exportedTypeBindings[ltFunc.name] = mkBinaryTypeFunction(<Func); - scope->exportedTypeBindings[leFunc.name] = mkBinaryTypeFunction(&leFunc); - scope->exportedTypeBindings[eqFunc.name] = mkBinaryTypeFunction(&eqFunc); + scope->exportedTypeBindings[ltFunc.name] = mkBinaryTypeFunctionWithDefault(<Func); + scope->exportedTypeBindings[leFunc.name] = mkBinaryTypeFunctionWithDefault(&leFunc); + scope->exportedTypeBindings[eqFunc.name] = mkBinaryTypeFunctionWithDefault(&eqFunc); scope->exportedTypeBindings[keyofFunc.name] = mkUnaryTypeFunction(&keyofFunc); scope->exportedTypeBindings[rawkeyofFunc.name] = mkUnaryTypeFunction(&rawkeyofFunc); - scope->exportedTypeBindings[indexFunc.name] = mkBinaryTypeFunction(&indexFunc); - scope->exportedTypeBindings[rawgetFunc.name] = mkBinaryTypeFunction(&rawgetFunc); + if (FFlag::LuauNotAllBinaryTypeFunsHaveDefaults) + { + scope->exportedTypeBindings[indexFunc.name] = mkBinaryTypeFunction(&indexFunc); + scope->exportedTypeBindings[rawgetFunc.name] = mkBinaryTypeFunction(&rawgetFunc); + } + else + { + scope->exportedTypeBindings[indexFunc.name] = mkBinaryTypeFunctionWithDefault(&indexFunc); + scope->exportedTypeBindings[rawgetFunc.name] = mkBinaryTypeFunctionWithDefault(&rawgetFunc); + } if (FFlag::LuauMetatableTypeFunctions) { - scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunction(&setmetatableFunc); + if (FFlag::LuauNotAllBinaryTypeFunsHaveDefaults) + scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunction(&setmetatableFunc); + else + scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunctionWithDefault(&setmetatableFunc); scope->exportedTypeBindings[getmetatableFunc.name] = mkUnaryTypeFunction(&getmetatableFunc); } } diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 2456efd5..9e9e93d7 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -37,7 +37,6 @@ LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAGVARIABLE(LuauStatForInFix) LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure) -LUAU_FASTFLAGVARIABLE(LuauLimitIterationWhenCheckingArgumentCounts) namespace Luau { @@ -4120,15 +4119,12 @@ void TypeChecker::checkArgumentList( int loopCount = 0; auto exceedsLoopCount = [&]() { - if (FFlag::LuauLimitIterationWhenCheckingArgumentCounts) + ++loopCount; + if (loopCount > FInt::LuauTypeInferTypePackLoopLimit) { - ++loopCount; - if (loopCount > FInt::LuauTypeInferTypePackLoopLimit) - { - state.reportError(TypeError{state.location, CodeTooComplex{}}); - reportErrorCodeTooComplex(state.location); - return true; - } + state.reportError(TypeError{state.location, CodeTooComplex{}}); + reportErrorCodeTooComplex(state.location); + return true; } return false; diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index 467deb75..983e4be2 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -19,6 +19,7 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauNonReentrantGeneralization2) +LUAU_FASTFLAG(DebugLuauGreedyGeneralization) namespace Luau { @@ -330,6 +331,10 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn) { if (FFlag::LuauNonReentrantGeneralization2) { + if (FFlag::DebugLuauGreedyGeneralization) + genericPack = follow(genericPack); + + // TODO: Clip this follow() with DebugLuauGreedyGeneralization const GenericTypePack* gen = get(follow(genericPack)); if (gen) genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity); diff --git a/Ast/include/Luau/Cst.h b/Ast/include/Luau/Cst.h index 70148c50..f5cd5a5e 100644 --- a/Ast/include/Luau/Cst.h +++ b/Ast/include/Luau/Cst.h @@ -116,7 +116,9 @@ public: Position openGenericsPosition{0, 0}; AstArray genericsCommaPositions; Position closeGenericsPosition{0, 0}; + AstArray argsAnnotationColonPositions; AstArray argsCommaPositions; + Position varargAnnotationColonPosition{0, 0}; Position returnSpecifierPosition{0, 0}; }; @@ -224,8 +226,9 @@ class CstStatLocal : public CstNode public: LUAU_CST_RTTI(CstStatLocal) - CstStatLocal(AstArray varsCommaPositions, AstArray valuesCommaPositions); + CstStatLocal(AstArray varsAnnotationColonPositions, AstArray varsCommaPositions, AstArray valuesCommaPositions); + AstArray varsAnnotationColonPositions; AstArray varsCommaPositions; AstArray valuesCommaPositions; }; @@ -235,8 +238,9 @@ class CstStatFor : public CstNode public: LUAU_CST_RTTI(CstStatFor) - CstStatFor(Position equalsPosition, Position endCommaPosition, std::optional stepCommaPosition); + CstStatFor(Position annotationColonPosition, Position equalsPosition, Position endCommaPosition, std::optional stepCommaPosition); + Position annotationColonPosition; Position equalsPosition; Position endCommaPosition; std::optional stepCommaPosition; @@ -247,8 +251,9 @@ class CstStatForIn : public CstNode public: LUAU_CST_RTTI(CstStatForIn) - CstStatForIn(AstArray varsCommaPositions, AstArray valuesCommaPositions); + CstStatForIn(AstArray varsAnnotationColonPositions, AstArray varsCommaPositions, AstArray valuesCommaPositions); + AstArray varsAnnotationColonPositions; AstArray varsCommaPositions; AstArray valuesCommaPositions; }; diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index a4ba540b..52dd76ea 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -196,6 +196,7 @@ private: // binding ::= Name [`:` Type] Binding parseBinding(); + AstArray extractAnnotationColonPositions(const TempVector& bindings); // bindinglist ::= (binding | `...') {`,' bindinglist} // Returns the location of the vararg ..., or std::nullopt if the function is not vararg. @@ -203,7 +204,8 @@ private: TempVector& result, bool allowDot3 = false, AstArray* commaPositions = nullptr, - std::optional initialCommaPosition = std::nullopt + Position* initialCommaPosition = nullptr, + Position* varargAnnotationColonPosition = nullptr ); AstType* parseOptionalType(); @@ -452,10 +454,12 @@ private: { Name name; AstType* annotation; + Position colonPosition; - explicit Binding(const Name& name, AstType* annotation = nullptr) + explicit Binding(const Name& name, AstType* annotation = nullptr, Position colonPosition = {0,0}) : name(name) , annotation(annotation) + , colonPosition(colonPosition) { } }; diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index 7ded45c9..9b081dcc 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -3,7 +3,6 @@ #include "Luau/Common.h" -LUAU_FASTFLAG(LuauDeprecatedAttribute); LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) namespace Luau @@ -11,8 +10,6 @@ namespace Luau static bool hasAttributeInArray(const AstArray attributes, AstAttr::Type attributeType) { - LUAU_ASSERT(FFlag::LuauDeprecatedAttribute); - for (const auto attribute : attributes) { if (attribute->type == attributeType) @@ -338,8 +335,6 @@ bool AstExprFunction::hasNativeAttribute() const bool AstExprFunction::hasAttribute(const AstAttr::Type attributeType) const { - LUAU_ASSERT(FFlag::LuauDeprecatedAttribute); - return hasAttributeInArray(attributes, attributeType); } @@ -1026,8 +1021,6 @@ bool AstStatDeclareFunction::isCheckedFunction() const bool AstStatDeclareFunction::hasAttribute(AstAttr::Type attributeType) const { - LUAU_ASSERT(FFlag::LuauDeprecatedAttribute); - return hasAttributeInArray(attributes, attributeType); } @@ -1244,8 +1237,6 @@ bool AstTypeFunction::isCheckedFunction() const bool AstTypeFunction::hasAttribute(AstAttr::Type attributeType) const { - LUAU_ASSERT(FFlag::LuauDeprecatedAttribute); - return hasAttributeInArray(attributes, attributeType); } diff --git a/Ast/src/Cst.cpp b/Ast/src/Cst.cpp index e374b189..7616311c 100644 --- a/Ast/src/Cst.cpp +++ b/Ast/src/Cst.cpp @@ -94,23 +94,26 @@ CstStatReturn::CstStatReturn(AstArray commaPositions) { } -CstStatLocal::CstStatLocal(AstArray varsCommaPositions, AstArray valuesCommaPositions) +CstStatLocal::CstStatLocal(AstArray varsAnnotationColonPositions, AstArray varsCommaPositions, AstArray valuesCommaPositions) : CstNode(CstClassIndex()) + , varsAnnotationColonPositions(varsAnnotationColonPositions) , varsCommaPositions(varsCommaPositions) , valuesCommaPositions(valuesCommaPositions) { } -CstStatFor::CstStatFor(Position equalsPosition, Position endCommaPosition, std::optional stepCommaPosition) +CstStatFor::CstStatFor(Position annotationColonPosition, Position equalsPosition, Position endCommaPosition, std::optional stepCommaPosition) : CstNode(CstClassIndex()) + , annotationColonPosition(annotationColonPosition) , equalsPosition(equalsPosition) , endCommaPosition(endCommaPosition) , stepCommaPosition(stepCommaPosition) { } -CstStatForIn::CstStatForIn(AstArray varsCommaPositions, AstArray valuesCommaPositions) +CstStatForIn::CstStatForIn(AstArray varsAnnotationColonPositions, AstArray varsCommaPositions, AstArray valuesCommaPositions) : CstNode(CstClassIndex()) + , varsAnnotationColonPositions(varsAnnotationColonPositions) , varsCommaPositions(varsCommaPositions) , valuesCommaPositions(valuesCommaPositions) { diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index ad0c3297..521a187b 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -19,14 +19,12 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) // See docs/SyntaxChanges.md for an explanation. LUAU_FASTFLAGVARIABLE(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauStoreCSTData2) -LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3) -LUAU_FASTFLAGVARIABLE(LuauParseOptionalAsNode2) LUAU_FASTFLAGVARIABLE(LuauDeclareExternType) LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer) LUAU_FASTFLAGVARIABLE(LuauTypeFunResultInAutocomplete) -LUAU_FASTFLAGVARIABLE(LuauDeprecatedAttribute) LUAU_FASTFLAGVARIABLE(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAGVARIABLE(LuauFixFunctionWithAttributesStartLocation) +LUAU_FASTFLAGVARIABLE(LuauStoreLocalAnnotationColonPositions) LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false) // Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix @@ -644,7 +642,8 @@ AstStat* Parser::parseFor() { AstStatFor* node = allocator.alloc(Location(start, end), var, from, to, step, body, hasDo, matchDo.location); if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(equalsPosition, endCommaPosition, stepCommaPosition); + cstNodeMap[node] = allocator.alloc(varname.colonPosition, equalsPosition, endCommaPosition, stepCommaPosition); + return node; } else @@ -664,7 +663,7 @@ AstStat* Parser::parseFor() { Position initialCommaPosition = lexer.current().location.begin; nextLexeme(); - parseBindingList(names, false, &varsCommaPosition, initialCommaPosition); + parseBindingList(names, false, &varsCommaPosition, &initialCommaPosition); } else { @@ -709,7 +708,12 @@ AstStat* Parser::parseFor() AstStatForIn* node = allocator.alloc(Location(start, end), copy(vars), copy(values), body, hasIn, inLocation, hasDo, matchDo.location); if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(varsCommaPosition, copy(valuesCommaPositions)); + { + if (FFlag::LuauStoreLocalAnnotationColonPositions) + cstNodeMap[node] = allocator.alloc(extractAnnotationColonPositions(names), varsCommaPosition, copy(valuesCommaPositions)); + else + cstNodeMap[node] = allocator.alloc(AstArray{}, varsCommaPosition, copy(valuesCommaPositions)); + } return node; } else @@ -825,7 +829,7 @@ std::pair Parser::validateAttribute(const char* attributeNa } } - if (!found || (!FFlag::LuauDeprecatedAttribute && type == AstAttr::Type::Deprecated)) + if (!found) { if (strlen(attributeName) == 1) report(lexer.current().location, "Attribute name is missing"); @@ -1010,7 +1014,13 @@ AstStat* Parser::parseLocal(const AstArray& attributes) { AstStatLocal* node = allocator.alloc(Location(start, end), copy(vars), copy(values), equalsSignLocation); if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(varsCommaPositions, copy(valuesCommaPositions)); + { + if (FFlag::LuauStoreLocalAnnotationColonPositions) + cstNodeMap[node] = allocator.alloc(extractAnnotationColonPositions(names), varsCommaPositions, copy(valuesCommaPositions)); + else + cstNodeMap[node] = allocator.alloc(AstArray{}, varsCommaPositions, copy(valuesCommaPositions)); + } + return node; } else @@ -1140,8 +1150,6 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported, Positio AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod(const AstArray& attributes) { - LUAU_ASSERT(FFlag::LuauDeprecatedAttribute); - Location start = lexer.current().location; nextLexeme(); @@ -1225,85 +1233,6 @@ AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod(const AstArr return AstDeclaredExternTypeProperty{fnName.name, fnName.location, fnType, true, Location(start, end)}; } -AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod_DEPRECATED() -{ - Location start = lexer.current().location; - - nextLexeme(); - - Name fnName = parseName("function name"); - - // TODO: generic method declarations CLI-39909 - AstArray generics; - AstArray genericPacks; - generics.size = 0; - generics.data = nullptr; - genericPacks.size = 0; - genericPacks.data = nullptr; - - MatchLexeme matchParen = lexer.current(); - expectAndConsume('(', "function parameter list start"); - - TempVector args(scratchBinding); - - bool vararg = false; - Location varargLocation; - AstTypePack* varargAnnotation = nullptr; - if (lexer.current().type != ')') - std::tie(vararg, varargLocation, varargAnnotation) = parseBindingList(args, /* allowDot3 */ true); - - expectMatchAndConsume(')', matchParen); - - AstTypePack* retTypes; - AstTypeList retTypes_DEPRECATED; - if (FFlag::LuauStoreReturnTypesAsPackOnAst) - { - retTypes = parseOptionalReturnType(); - if (!retTypes) - retTypes = allocator.alloc(lexer.current().location, AstTypeList{copy(nullptr, 0), nullptr}); - } - else - { - retTypes_DEPRECATED = parseOptionalReturnType_DEPRECATED().value_or(AstTypeList{copy(nullptr, 0), nullptr}); - } - Location end = lexer.previousLocation(); - - TempVector vars(scratchType); - TempVector> varNames(scratchOptArgName); - - if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr) - { - return AstDeclaredExternTypeProperty{ - fnName.name, fnName.location, reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true - }; - } - - // Skip the first index. - for (size_t i = 1; i < args.size(); ++i) - { - varNames.push_back(AstArgumentName{args[i].name.name, args[i].name.location}); - - if (args[i].annotation) - vars.push_back(args[i].annotation); - else - vars.push_back(reportTypeError(Location(start, end), {}, "All declaration parameters aside from 'self' must be annotated")); - } - - if (vararg && !varargAnnotation) - report(start, "All declaration parameters aside from 'self' must be annotated"); - - AstType* fnType = - FFlag::LuauStoreReturnTypesAsPackOnAst - ? allocator.alloc( - Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes - ) - : allocator.alloc( - Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes_DEPRECATED - ); - - return AstDeclaredExternTypeProperty{fnName.name, fnName.location, fnType, true, Location(start, end)}; -} - AstStat* Parser::parseDeclaration(const Location& start, const AstArray& attributes) { // `declare` token is already parsed at this point @@ -1445,7 +1374,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray attributes{nullptr, 0}; - if (FFlag::LuauDeprecatedAttribute && (lexer.current().type == Lexeme::Attribute)) + if (lexer.current().type == Lexeme::Attribute) { attributes = Parser::parseAttributes(); @@ -1464,10 +1393,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray Parser::parseFunctionBody( AstTypePack* varargAnnotation = nullptr; if (lexer.current().type != ')') - std::tie(vararg, varargLocation, varargAnnotation) = - parseBindingList(args, /* allowDot3= */ true, cstNode ? &cstNode->argsCommaPositions : nullptr); + { + if (FFlag::LuauStoreLocalAnnotationColonPositions) + { + if (cstNode) + std::tie(vararg, varargLocation, varargAnnotation) = + parseBindingList(args, /* allowDot3= */ true, &cstNode->argsCommaPositions, nullptr, &cstNode->varargAnnotationColonPosition); + else + std::tie(vararg, varargLocation, varargAnnotation) = parseBindingList(args, /* allowDot3= */ true); + } + else + std::tie(vararg, varargLocation, varargAnnotation) = + parseBindingList(args, /* allowDot3= */ true, cstNode ? &cstNode->argsCommaPositions : nullptr); + } std::optional argLocation; @@ -1846,6 +1780,8 @@ std::pair Parser::parseFunctionBody( if (options.storeCstData) { cstNode->functionKeywordPosition = matchFunction.location.begin; + if (FFlag::LuauStoreLocalAnnotationColonPositions) + cstNode->argsAnnotationColonPositions = extractAnnotationColonPositions(args); cstNodeMap[node] = cstNode; } @@ -2042,9 +1978,22 @@ Parser::Binding Parser::parseBinding() if (!name) name = Name(nameError, lexer.current().location); + Position colonPosition = lexer.current().location.begin; AstType* annotation = parseOptionalType(); - return Binding(*name, annotation); + if (FFlag::LuauStoreLocalAnnotationColonPositions && options.storeCstData) + return Binding(*name, annotation, colonPosition); + else + return Binding(*name, annotation); +} + +AstArray Parser::extractAnnotationColonPositions(const TempVector& bindings) +{ + LUAU_ASSERT(FFlag::LuauStoreLocalAnnotationColonPositions); + TempVector annotationColonPositions(scratchPosition); + for (size_t i = 0; i < bindings.size(); ++i) + annotationColonPositions.push_back(bindings[i].colonPosition); + return copy(annotationColonPositions); } // bindinglist ::= (binding | `...') [`,' bindinglist] @@ -2052,7 +2001,8 @@ std::tuple Parser::parseBindingList( TempVector& result, bool allowDot3, AstArray* commaPositions, - std::optional initialCommaPosition + Position* initialCommaPosition, + Position* varargAnnotationColonPosition ) { TempVector localCommaPositions(scratchPosition); @@ -2070,6 +2020,9 @@ std::tuple Parser::parseBindingList( AstTypePack* tailAnnotation = nullptr; if (lexer.current().type == ':') { + if (FFlag::LuauStoreLocalAnnotationColonPositions && varargAnnotationColonPosition) + *varargAnnotationColonPosition = lexer.current().location.begin; + nextLexeme(); tailAnnotation = parseVariadicArgumentTypePack(); } @@ -2256,8 +2209,6 @@ AstTypePack* Parser::parseReturnType() nextLexeme(); - Location innerBegin = lexer.current().location; - matchRecoveryStopOnToken[Lexeme::SkinnyArrow]++; TempVector result(scratchType); @@ -2283,47 +2234,25 @@ AstTypePack* Parser::parseReturnType() if (lexer.current().type != Lexeme::SkinnyArrow && resultNames.empty()) { // If it turns out that it's just '(A)', it's possible that there are unions/intersections to follow, so fold over it. - if (FFlag::LuauAstTypeGroup3) + if (result.size() == 1) { - if (result.size() == 1) - { - // TODO(CLI-140667): stop parsing type suffix when varargAnnotation != nullptr - this should be a parse error - AstType* inner = varargAnnotation == nullptr ? allocator.alloc(location, result[0]) : result[0]; - AstType* returnType = parseTypeSuffix(inner, begin.location); + // TODO(CLI-140667): stop parsing type suffix when varargAnnotation != nullptr - this should be a parse error + AstType* inner = varargAnnotation == nullptr ? allocator.alloc(location, result[0]) : result[0]; + AstType* returnType = parseTypeSuffix(inner, begin.location); - if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr && - (returnType->is() || returnType->is())) - luau_telemetry_parsed_return_type_variadic_with_type_suffix = true; + if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr && + (returnType->is() || returnType->is())) + luau_telemetry_parsed_return_type_variadic_with_type_suffix = true; - // If parseType parses nothing, then returnType->location.end only points at the last non-type-pack - // type to successfully parse. We need the span of the whole annotation. - Position endPos = result.size() == 1 ? location.end : returnType->location.end; + // If parseType parses nothing, then returnType->location.end only points at the last non-type-pack + // type to successfully parse. We need the span of the whole annotation. + Position endPos = result.size() == 1 ? location.end : returnType->location.end; - AstTypePackExplicit* node = allocator.alloc(Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation}); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(); - return node; - } - } - else - { - if (result.size() == 1) - { - AstType* returnType = parseTypeSuffix(result[0], innerBegin); - - if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr && - (returnType->is() || returnType->is())) - luau_telemetry_parsed_return_type_variadic_with_type_suffix = true; - - // If parseType parses nothing, then returnType->location.end only points at the last non-type-pack - // type to successfully parse. We need the span of the whole annotation. - Position endPos = result.size() == 1 ? location.end : returnType->location.end; - - AstTypePackExplicit* node = allocator.alloc(Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation}); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(); - return node; - } + AstTypePackExplicit* node = + allocator.alloc(Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation}); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(); + return node; } AstTypePackExplicit* node = allocator.alloc(location, AstTypeList{copy(result), varargAnnotation}); @@ -2366,8 +2295,6 @@ std::pair Parser::parseReturnType_DEPRECATED() nextLexeme(); - Location innerBegin = lexer.current().location; - matchRecoveryStopOnToken[Lexeme::SkinnyArrow]++; TempVector result(scratchType); @@ -2387,42 +2314,23 @@ std::pair Parser::parseReturnType_DEPRECATED() if (lexer.current().type != Lexeme::SkinnyArrow && resultNames.empty()) { // If it turns out that it's just '(A)', it's possible that there are unions/intersections to follow, so fold over it. - if (FFlag::LuauAstTypeGroup3) + if (result.size() == 1) { - if (result.size() == 1) - { - // TODO(CLI-140667): stop parsing type suffix when varargAnnotation != nullptr - this should be a parse error - AstType* inner = varargAnnotation == nullptr ? allocator.alloc(location, result[0]) : result[0]; - AstType* returnType = parseTypeSuffix(inner, begin.location); + // TODO(CLI-140667): stop parsing type suffix when varargAnnotation != nullptr - this should be a parse error + AstType* inner = varargAnnotation == nullptr ? allocator.alloc(location, result[0]) : result[0]; + AstType* returnType = parseTypeSuffix(inner, begin.location); - if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr && - (returnType->is() || returnType->is())) - luau_telemetry_parsed_return_type_variadic_with_type_suffix = true; + if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr && + (returnType->is() || returnType->is())) + luau_telemetry_parsed_return_type_variadic_with_type_suffix = true; - // If parseType parses nothing, then returnType->location.end only points at the last non-type-pack - // type to successfully parse. We need the span of the whole annotation. - Position endPos = result.size() == 1 ? location.end : returnType->location.end; + // If parseType parses nothing, then returnType->location.end only points at the last non-type-pack + // type to successfully parse. We need the span of the whole annotation. + Position endPos = result.size() == 1 ? location.end : returnType->location.end; - return {Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation}}; - } + return {Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation}}; } - else - { - if (result.size() == 1) - { - AstType* returnType = parseTypeSuffix(result[0], innerBegin); - if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr && - (returnType->is() || returnType->is())) - luau_telemetry_parsed_return_type_variadic_with_type_suffix = true; - - // If parseType parses nothing, then returnType->location.end only points at the last non-type-pack - // type to successfully parse. We need the span of the whole annotation. - Position endPos = result.size() == 1 ? location.end : returnType->location.end; - - return {Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation}}; - } - } return {location, AstTypeList{copy(result), varargAnnotation}}; } @@ -2888,10 +2796,7 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray } else { - if (FFlag::LuauAstTypeGroup3) - return {allocator.alloc(Location(parameterStart.location, closeArgsLocation), params[0]), {}}; - else - return {params[0], {}}; + return {allocator.alloc(Location(parameterStart.location, closeArgsLocation), params[0]), {}}; } } @@ -3012,8 +2917,6 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin) bool isUnion = false; bool isIntersection = false; - // Clip with FFlag::LuauParseOptionalAsNode2 - bool hasOptional_DEPRECATED = false; unsigned int optionalCount = 0; Location location = begin; @@ -3047,19 +2950,10 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin) Location loc = lexer.current().location; nextLexeme(); - if (FFlag::LuauParseOptionalAsNode2) - { - parts.push_back(allocator.alloc(Location(loc))); - optionalCount++; - } - else - { - if (!hasOptional_DEPRECATED) - parts.push_back(allocator.alloc(loc, std::nullopt, nameNil, std::nullopt, loc)); - } + parts.push_back(allocator.alloc(Location(loc))); + optionalCount++; isUnion = true; - hasOptional_DEPRECATED = true; } else if (c == '&') { @@ -3087,16 +2981,8 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin) else break; - if (FFlag::LuauParseOptionalAsNode2) - { - if (parts.size() > unsigned(FInt::LuauTypeLengthLimit) + optionalCount) - ParseError::raise(parts.back()->location, "Exceeded allowed type length; simplify your type annotation to make the code compile"); - } - else - { - if (parts.size() > unsigned(FInt::LuauTypeLengthLimit) + hasOptional_DEPRECATED) - ParseError::raise(parts.back()->location, "Exceeded allowed type length; simplify your type annotation to make the code compile"); - } + if (parts.size() > unsigned(FInt::LuauTypeLengthLimit) + optionalCount) + ParseError::raise(parts.back()->location, "Exceeded allowed type length; simplify your type annotation to make the code compile"); } if (parts.size() == 1 && !isUnion && !isIntersection) @@ -4520,15 +4406,10 @@ AstArray Parser::parseTypeParams(Position* openingPosition, TempV // the next lexeme is one that follows a type // (&, |, ?), then assume that this was actually a // parenthesized type. - if (FFlag::LuauAstTypeGroup3) - { - auto parenthesizedType = explicitTypePack->typeList.types.data[0]; - parameters.push_back( - {parseTypeSuffix(allocator.alloc(parenthesizedType->location, parenthesizedType), begin), {}} - ); - } - else - parameters.push_back({parseTypeSuffix(explicitTypePack->typeList.types.data[0], begin), {}}); + auto parenthesizedType = explicitTypePack->typeList.types.data[0]; + parameters.push_back( + {parseTypeSuffix(allocator.alloc(parenthesizedType->location, parenthesizedType), begin), {}} + ); } else { diff --git a/CLI/include/Luau/Repl.h b/CLI/include/Luau/Repl.h index cd54b7e0..21b6ab1f 100644 --- a/CLI/include/Luau/Repl.h +++ b/CLI/include/Luau/Repl.h @@ -10,6 +10,7 @@ using AddCompletionCallback = std::function #include void requireConfigInit(luarequire_Configuration* config); struct ReplRequirer { + using CompileOptions = Luau::CompileOptions(*)(); + using BoolCheck = bool(*)(); + using Coverage = void(*)(lua_State*, int); + ReplRequirer( - std::function copts, - std::function coverageActive, - std::function codegenEnabled, - std::function coverageTrack + CompileOptions copts, + BoolCheck coverageActive, + BoolCheck codegenEnabled, + Coverage coverageTrack ); - std::function copts; - std::function coverageActive; - std::function codegenEnabled; - std::function coverageTrack; + CompileOptions copts; + BoolCheck coverageActive; + BoolCheck codegenEnabled; + Coverage coverageTrack; std::string absPath; std::string relPath; diff --git a/CLI/src/Repl.cpp b/CLI/src/Repl.cpp index 9fb1221f..770bdec7 100644 --- a/CLI/src/Repl.cpp +++ b/CLI/src/Repl.cpp @@ -164,7 +164,7 @@ static int lua_callgrind(lua_State* L) } #endif -static void* createCliRequireContext(lua_State* L) +void* createCliRequireContext(lua_State* L) { void* ctx = lua_newuserdatadtor( L, diff --git a/CLI/src/ReplRequirer.cpp b/CLI/src/ReplRequirer.cpp index 7d405a7e..631f91eb 100644 --- a/CLI/src/ReplRequirer.cpp +++ b/CLI/src/ReplRequirer.cpp @@ -3,6 +3,7 @@ #include "Luau/CodeGen.h" #include "Luau/CodeGenOptions.h" +#include "Luau/FileUtils.h" #include "Luau/Require.h" #include "Luau/RequirerUtils.h" @@ -101,18 +102,18 @@ static bool is_module_present(lua_State* L, void* ctx) return isFilePresent(req->absPath, req->suffix); } -static luarequire_WriteResult get_contents(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out) -{ - ReplRequirer* req = static_cast(ctx); - return write(getFileContents(req->absPath, req->suffix), buffer, buffer_size, size_out); -} - static luarequire_WriteResult get_chunkname(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out) { ReplRequirer* req = static_cast(ctx); return write("@" + req->relPath, buffer, buffer_size, size_out); } +static luarequire_WriteResult get_loadname(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out) +{ + ReplRequirer* req = static_cast(ctx); + return write(req->absPath + req->suffix, buffer, buffer_size, size_out); +} + static luarequire_WriteResult get_cache_key(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out) { ReplRequirer* req = static_cast(ctx); @@ -131,7 +132,7 @@ static luarequire_WriteResult get_config(lua_State* L, void* ctx, char* buffer, return write(getFileContents(req->absPath, "/.luaurc"), buffer, buffer_size, size_out); } -static int load(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* contents) +static int load(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* loadname) { ReplRequirer* req = static_cast(ctx); @@ -144,8 +145,12 @@ static int load(lua_State* L, void* ctx, const char* path, const char* chunkname // new thread needs to have the globals sandboxed luaL_sandboxthread(ML); + std::optional contents = readFile(loadname); + if (!contents) + luaL_error(L, "could not read file '%s'", loadname); + // now we can compile & run module on the new thread - std::string bytecode = Luau::compile(contents, req->copts()); + std::string bytecode = Luau::compile(*contents, req->copts()); if (luau_load(ML, chunkname, bytecode.data(), bytecode.size(), 0) == 0) { if (req->codegenEnabled()) @@ -199,23 +204,18 @@ void requireConfigInit(luarequire_Configuration* config) config->to_parent = to_parent; config->to_child = to_child; config->is_module_present = is_module_present; - config->get_contents = get_contents; config->is_config_present = is_config_present; config->get_chunkname = get_chunkname; + config->get_loadname = get_loadname; config->get_cache_key = get_cache_key; config->get_config = get_config; config->load = load; } -ReplRequirer::ReplRequirer( - std::function copts, - std::function coverageActive, - std::function codegenEnabled, - std::function coverageTrack -) - : copts(std::move(copts)) - , coverageActive(std::move(coverageActive)) - , codegenEnabled(std::move(codegenEnabled)) - , coverageTrack(std::move(coverageTrack)) +ReplRequirer::ReplRequirer(CompileOptions copts, BoolCheck coverageActive, BoolCheck codegenEnabled, Coverage coverageTrack) + : copts(copts) + , coverageActive(coverageActive) + , codegenEnabled(codegenEnabled) + , coverageTrack(coverageTrack) { } diff --git a/Require/Runtime/include/Luau/Require.h b/Require/Runtime/include/Luau/Require.h index 5df7e41e..1686e24e 100644 --- a/Require/Runtime/include/Luau/Require.h +++ b/Require/Runtime/include/Luau/Require.h @@ -83,15 +83,15 @@ struct luarequire_Configuration // Returns whether the context is currently pointing at a module. bool (*is_module_present)(lua_State* L, void* ctx); - // Provides the contents of the current module. This function is only called - // if is_module_present returns true. - luarequire_WriteResult (*get_contents)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out); - // Provides a chunkname for the current module. This will be accessible // through the debug library. This function is only called if // is_module_present returns true. luarequire_WriteResult (*get_chunkname)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out); + // Provides a loadname that identifies the current module and is passed to + // load. This function is only called if is_module_present returns true. + luarequire_WriteResult (*get_loadname)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out); + // Provides a cache key representing the current module. This function is // only called if is_module_present returns true. luarequire_WriteResult (*get_cache_key)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out); @@ -109,7 +109,7 @@ struct luarequire_Configuration // number of results placed on the stack. Returning -1 directs the requiring // thread to yield. In this case, this thread should be resumed with the // module result pushed onto its stack. - int (*load)(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* contents); + int (*load)(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* loadname); }; // Populates function pointers in the given luarequire_Configuration. diff --git a/Require/Runtime/src/Navigation.cpp b/Require/Runtime/src/Navigation.cpp index fd54bd61..251707a9 100644 --- a/Require/Runtime/src/Navigation.cpp +++ b/Require/Runtime/src/Navigation.cpp @@ -62,16 +62,16 @@ bool RuntimeNavigationContext::isModulePresent() const return config->is_module_present(L, ctx); } -std::optional RuntimeNavigationContext::getContents() const -{ - return getStringFromCWriter(config->get_contents, initalFileBufferSize); -} - std::optional RuntimeNavigationContext::getChunkname() const { return getStringFromCWriter(config->get_chunkname, initalIdentifierBufferSize); } +std::optional RuntimeNavigationContext::getLoadname() const +{ + return getStringFromCWriter(config->get_loadname, initalIdentifierBufferSize); +} + std::optional RuntimeNavigationContext::getCacheKey() const { return getStringFromCWriter(config->get_cache_key, initalIdentifierBufferSize); diff --git a/Require/Runtime/src/Navigation.h b/Require/Runtime/src/Navigation.h index 7b97c75e..bbb69118 100644 --- a/Require/Runtime/src/Navigation.h +++ b/Require/Runtime/src/Navigation.h @@ -31,8 +31,8 @@ public: // Custom capabilities bool isModulePresent() const; - std::optional getContents() const; std::optional getChunkname() const; + std::optional getLoadname() const; std::optional getCacheKey() const; private: diff --git a/Require/Runtime/src/Require.cpp b/Require/Runtime/src/Require.cpp index b08b8e05..7a3b3f01 100644 --- a/Require/Runtime/src/Require.cpp +++ b/Require/Runtime/src/Require.cpp @@ -21,10 +21,10 @@ static void validateConfig(lua_State* L, const luarequire_Configuration& config) luaL_error(L, "require configuration is missing required function pointer: to_child"); if (!config.is_module_present) luaL_error(L, "require configuration is missing required function pointer: is_module_present"); - if (!config.get_contents) - luaL_error(L, "require configuration is missing required function pointer: get_contents"); if (!config.get_chunkname) luaL_error(L, "require configuration is missing required function pointer: get_chunkname"); + if (!config.get_loadname) + luaL_error(L, "require configuration is missing required function pointer: get_loadname"); if (!config.get_cache_key) luaL_error(L, "require configuration is missing required function pointer: get_cache_key"); if (!config.is_config_present) diff --git a/Require/Runtime/src/RequireImpl.cpp b/Require/Runtime/src/RequireImpl.cpp index b8f5c980..257f66ba 100644 --- a/Require/Runtime/src/RequireImpl.cpp +++ b/Require/Runtime/src/RequireImpl.cpp @@ -29,8 +29,8 @@ struct ResolvedRequire }; Status status; - std::string contents; std::string chunkname; + std::string loadname; std::string cacheKey; }; @@ -89,17 +89,17 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State* return ResolvedRequire{ResolvedRequire::Status::ErrorReported}; } - std::optional contents = navigationContext.getContents(); - if (!contents) + std::optional loadname = navigationContext.getLoadname(); + if (!loadname) { - errorHandler.reportError("could not get contents for module"); + errorHandler.reportError("could not get loadname for module"); return ResolvedRequire{ResolvedRequire::Status::ErrorReported}; } return ResolvedRequire{ ResolvedRequire::Status::ModuleRead, - std::move(*contents), std::move(*chunkname), + std::move(*loadname), std::move(*cacheKey), }; } @@ -181,7 +181,7 @@ int lua_requireinternal(lua_State* L, const char* requirerChunkname) lua_pushinteger(L, numArgsBeforeLoad); lua_insert(L, 1); - int numResults = lrc->load(L, ctx, path, resolvedRequire.chunkname.c_str(), resolvedRequire.contents.c_str()); + int numResults = lrc->load(L, ctx, path, resolvedRequire.chunkname.c_str(), resolvedRequire.loadname.c_str()); if (numResults == -1) { if (lua_gettop(L) != numArgsBeforeLoad) diff --git a/VM/src/laux.cpp b/VM/src/laux.cpp index 8e708d16..ba1281ba 100644 --- a/VM/src/laux.cpp +++ b/VM/src/laux.cpp @@ -11,7 +11,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauLibWhereErrorAutoreserve) LUAU_FASTFLAG(LuauYieldableContinuations) // convert a stack index to positive @@ -80,9 +79,7 @@ void luaL_where(lua_State* L, int level) return; } - if (FFlag::LuauLibWhereErrorAutoreserve) - lua_rawcheckstack(L, 1); - + lua_rawcheckstack(L, 1); lua_pushliteral(L, ""); // else, no information available... } diff --git a/tests/AstJsonEncoder.test.cpp b/tests/AstJsonEncoder.test.cpp index 00e9c8c3..306481e8 100644 --- a/tests/AstJsonEncoder.test.cpp +++ b/tests/AstJsonEncoder.test.cpp @@ -11,7 +11,6 @@ using namespace Luau; -LUAU_FASTFLAG(LuauAstTypeGroup3) LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) @@ -505,28 +504,16 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation") { AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())"); - if (FFlag::LuauStoreReturnTypesAsPackOnAst && FFlag::LuauAstTypeGroup3) + if (FFlag::LuauStoreReturnTypesAsPackOnAst) { std::string_view expected = R"({"type":"AstStatTypeAlias","location":"0,0 - 0,56","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,56","types":[{"type":"AstTypeGroup","location":"0,9 - 0,37","inner":{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypePackExplicit","location":"0,22 - 0,36","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeGroup","location":"0,22 - 0,36","inner":{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}}]}}}},{"type":"AstTypeGroup","location":"0,40 - 0,56","inner":{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypePackExplicit","location":"0,53 - 0,55","typeList":{"type":"AstTypeList","types":[]}}}}]},"exported":false})"; CHECK(toJson(statement) == expected); } - else if (FFlag::LuauStoreReturnTypesAsPackOnAst) - { - std::string_view expected = - R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypePackExplicit","location":"0,22 - 0,36","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}]}}},{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypePackExplicit","location":"0,53 - 0,55","typeList":{"type":"AstTypeList","types":[]}}}]},"exported":false})"; - CHECK(toJson(statement) == expected); - } - else if (FFlag::LuauAstTypeGroup3) - { - std::string_view expected = - R"({"type":"AstStatTypeAlias","location":"0,0 - 0,56","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,56","types":[{"type":"AstTypeGroup","location":"0,9 - 0,37","inner":{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeGroup","location":"0,22 - 0,36","inner":{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}}]}}},{"type":"AstTypeGroup","location":"0,40 - 0,56","inner":{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}}]},"exported":false})"; - CHECK(toJson(statement) == expected); - } else { std::string_view expected = - R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}]}},{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}]},"exported":false})"; + R"({"type":"AstStatTypeAlias","location":"0,0 - 0,56","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,56","types":[{"type":"AstTypeGroup","location":"0,9 - 0,37","inner":{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeGroup","location":"0,22 - 0,36","inner":{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}}]}}},{"type":"AstTypeGroup","location":"0,40 - 0,56","inner":{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}}]},"exported":false})"; CHECK(toJson(statement) == expected); } } diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 67fb7981..7c0ebf2f 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -31,7 +31,6 @@ extern int optimizationLevel; void luaC_fullgc(lua_State* L); void luaC_validate(lua_State* L); -LUAU_FASTFLAG(LuauLibWhereErrorAutoreserve) LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC) @@ -1942,8 +1941,6 @@ int slowlyOverflowStack(lua_State* L) TEST_CASE("ApiStack") { - ScopedFastFlag luauLibWhereErrorAutoreserve{FFlag::LuauLibWhereErrorAutoreserve, true}; - StateRef globalState(luaL_newstate(), lua_close); lua_State* L = globalState.get(); diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index 30a920d8..f022d1c9 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -33,12 +33,12 @@ LUAU_FASTFLAG(LuauClonedTableAndFunctionTypesMustHaveScopes) LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode) LUAU_FASTFLAG(LuauCloneTypeAliasBindings) LUAU_FASTFLAG(LuauDoNotClonePersistentBindings) -LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning) LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauBetterScopeSelection) LUAU_FASTFLAG(LuauBlockDiffFragmentSelection) LUAU_FASTFLAG(LuauFragmentAcMemoryLeak) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) +LUAU_FASTFLAG(LuauFragmentAutocompleteIfRecommendations) static std::optional nullCallback(std::string tag, std::optional ptr, std::optional contents) { @@ -73,12 +73,12 @@ struct FragmentAutocompleteFixtureImpl : BaseType ScopedFastFlag luauDisableNewSolverAssertsInMixedMode{FFlag::LuauDisableNewSolverAssertsInMixedMode, true}; ScopedFastFlag luauCloneTypeAliasBindings{FFlag::LuauCloneTypeAliasBindings, true}; ScopedFastFlag luauDoNotClonePersistentBindings{FFlag::LuauDoNotClonePersistentBindings, true}; - ScopedFastFlag luauIncrementalAutocompleteDemandBasedCloning{FFlag::LuauIncrementalAutocompleteDemandBasedCloning, true}; ScopedFastFlag luauBetterScopeSelection{FFlag::LuauBetterScopeSelection, true}; ScopedFastFlag luauBlockDiffFragmentSelection{FFlag::LuauBlockDiffFragmentSelection, true}; ScopedFastFlag luauAutocompleteUsesModuleForTypeCompatibility{FFlag::LuauAutocompleteUsesModuleForTypeCompatibility, true}; ScopedFastFlag luauFragmentAcMemoryLeak{FFlag::LuauFragmentAcMemoryLeak, true}; ScopedFastFlag luauGlobalVariableModuleIsolation{FFlag::LuauGlobalVariableModuleIsolation, true}; + ScopedFastFlag luauFragmentAutocompleteIfRecommendations{FFlag::LuauFragmentAutocompleteIfRecommendations, true}; FragmentAutocompleteFixtureImpl() : BaseType(true) @@ -746,11 +746,10 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_partial") { auto region = getAutocompleteRegion( R"( -if -)", - Position{1, 3} +if)", + Position{1, 2} ); - CHECK_EQ(Location{{1, 0}, {1, 3}}, region.fragmentLocation); + CHECK_EQ(Location{{1, 2}, {1, 2}}, region.fragmentLocation); REQUIRE(region.parentBlock); CHECK(region.nearestStatement->as()); } @@ -763,7 +762,7 @@ if true )", Position{1, 7} ); - CHECK_EQ(Location{{1, 0}, {1, 7}}, region.fragmentLocation); + CHECK_EQ(Location{{1, 3}, {1, 7}}, region.fragmentLocation); REQUIRE(region.parentBlock); CHECK(region.nearestStatement->as()); } @@ -776,7 +775,7 @@ if true )", Position{1, 8} ); - CHECK_EQ(Location{{1, 8}, {1, 8}}, region.fragmentLocation); + CHECK_EQ(Location{{1, 3}, {1, 8}}, region.fragmentLocation); REQUIRE(region.parentBlock); CHECK(region.nearestStatement->as()); } @@ -829,7 +828,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_else_if") auto region = getAutocompleteRegion( R"( if true then -elseif +elseif end )", @@ -849,7 +848,7 @@ elseif )", Position{2, 8} ); - CHECK_EQ(Location{{2, 0}, {2, 8}}, region.fragmentLocation); + CHECK_EQ(Location{{2, 8}, {2, 8}}, region.fragmentLocation); REQUIRE(region.parentBlock); CHECK(region.nearestStatement->as()); } @@ -1071,7 +1070,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "statement_in_empty_fragment_is_n ); REQUIRE(fragment.has_value()); CHECK_EQ("", fragment->fragmentToParse); - CHECK_EQ(2, fragment->ancestry.size()); + CHECK_EQ(1, fragment->ancestry.size()); REQUIRE(fragment->root); CHECK_EQ(0, fragment->root->body.size); auto statBody = fragment->root->as(); @@ -1104,7 +1103,7 @@ local z = x + y CHECK_EQ(Location{Position{3, 0}, Position{3, 15}}, fragment->root->location); CHECK_EQ("local z = x + y", fragment->fragmentToParse); - CHECK_EQ(5, fragment->ancestry.size()); + CHECK_EQ(4, fragment->ancestry.size()); REQUIRE(fragment->root); CHECK_EQ(1, fragment->root->body.size); auto stat = fragment->root->body.data[0]->as(); @@ -1149,7 +1148,7 @@ local y = 5 REQUIRE(fragment.has_value()); CHECK_EQ("local z = x + y", fragment->fragmentToParse); - CHECK_EQ(5, fragment->ancestry.size()); + CHECK_EQ(4, fragment->ancestry.size()); REQUIRE(fragment->root); CHECK_EQ(Location{Position{2, 0}, Position{2, 15}}, fragment->root->location); CHECK_EQ(1, fragment->root->body.size); @@ -1244,7 +1243,7 @@ abc("bar") REQUIRE(stringFragment.has_value()); - CHECK_EQ("abc(\"foo\")", stringFragment->fragmentToParse); + CHECK_EQ("abc(\"foo\"", stringFragment->fragmentToParse); CHECK(stringFragment->nearestStatement); CHECK(stringFragment->nearestStatement->is()); @@ -3605,6 +3604,126 @@ end) )"; autocompleteFragmentInBothSolvers(source, dest, Position{5, 11}, [](auto& result) {}); } + +TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_cond_no_then_recs_then") +{ + const std::string source = R"( + + )"; + + const std::string dest = R"( +if x t + )"; + + autocompleteFragmentInBothSolvers( + source, + dest, + Position{1, 6}, + [](auto& result) + { + REQUIRE(result.result); + CHECK(result.result->acResults.entryMap.count("then")); + } + ); +} + +TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_then_recs_else") +{ + const std::string source = R"( +if x then + +end + )"; + + const std::string dest = R"( +if x then +e +end + )"; + + autocompleteFragmentInBothSolvers( + source, + dest, + Position{2, 1}, + [](auto& result) + { + REQUIRE(result.result); + CHECK(result.result->acResults.entryMap.count("else")); + CHECK(result.result->acResults.entryMap.count("elseif")); + } + ); +} + +TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_else_if_table_prop_recs_no_then") +{ + const std::string source = R"( +type T = {xa : number, y : number} +local t : T = {xa = 3, y = 3} + +if t.x then +elseif +end +)"; + + const std::string dest = R"( +type T = {xa : number, y : number} +local t : T = {xa = 3, y = 3} + +if t.x then +elseif t.xa t +end + )"; + + autocompleteFragmentInBothSolvers( + source, + dest, + Position{5, 13}, + [](auto& result) + { + REQUIRE(result.result); + CHECK(!result.result->acResults.entryMap.empty()); + CHECK(!result.result->acResults.entryMap.count("xa")); + CHECK(!result.result->acResults.entryMap.count("y")); + CHECK(result.result->acResults.entryMap.count("then")); + } + ); +} + +TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_else_if_table_prop_recs_with_then") +{ + const std::string source = R"( +type T = {xa : number, y : number} +local t : T = {xa = 3, y = 3} + +if t.x then +elseif then +end +)"; + + const std::string dest = R"( +type T = {xa : number, y : number} +local t : T = {xa = 3, y = 3} + +if t.x then +elseif t. then +end +)"; + + autocompleteFragmentInBothSolvers( + source, + dest, + Position{5, 9}, + [](auto& result) + { + REQUIRE(result.result); + CHECK(!result.result->acResults.entryMap.empty()); + CHECK(result.result->acResults.entryMap.count("xa")); + CHECK(result.result->acResults.entryMap.count("y")); + CHECK(!result.result->acResults.entryMap.count("then")); + } + ); +} + // NOLINTEND(bugprone-unchecked-optional-access) TEST_SUITE_END(); diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index 76debed8..50e3fe8d 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -16,40 +16,10 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2) namespace { - -struct NaiveModuleResolver : ModuleResolver -{ - std::optional resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override - { - if (auto name = pathExprToModuleName(currentModuleName, pathExpr)) - return {{*name, false}}; - - return std::nullopt; - } - - const ModulePtr getModule(const ModuleName& moduleName) const override - { - return nullptr; - } - - bool moduleExists(const ModuleName& moduleName) const override - { - return false; - } - - std::string getHumanReadableModuleName(const ModuleName& moduleName) const override - { - return moduleName; - } -}; - -NaiveModuleResolver naiveModuleResolver; - struct NaiveFileResolver : NullFileResolver { std::optional resolveModule(const ModuleInfo* context, AstExpr* expr) override @@ -920,7 +890,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f // When this test fails, it is because the TypeIds needed by the error have been deallocated. // It is thus basically impossible to predict what will happen when this assert is evaluated. // It could segfault, or you could see weird type names like the empty string or - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { REQUIRE_EQ( "Type\n\t" @@ -930,14 +900,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f toString(result.errors[0]) ); } - else if (FFlag::LuauSolverV2) - REQUIRE_EQ( - R"(Type - '{ count: string }' -could not be converted into - '{ Count: number }')", - toString(result.errors[0]) - ); else REQUIRE_EQ( "Table type 'a' not compatible with type '{| Count: number |}' because the former is missing field 'Count'", toString(result.errors[0]) diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index 1e89fdeb..ac32d827 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -1628,7 +1628,7 @@ static void checkDeprecatedWarning(const Luau::LintWarning& warning, const Luau: TEST_CASE_FIXTURE(Fixture, "DeprecatedAttribute") { - ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}}; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; // @deprecated works on local functions { @@ -1877,7 +1877,7 @@ end TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeFunctionDeclaration") { - ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}}; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; // @deprecated works on function type declarations @@ -1895,7 +1895,7 @@ bar(2) TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeTableDeclaration") { - ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}}; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; // @deprecated works on table type declarations @@ -1915,7 +1915,7 @@ print(Hooty:tooty(2.0)) TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeMethodDeclaration") { - ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}}; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; // @deprecated works on table type declarations diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 5a20670a..96a69a62 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -17,8 +17,6 @@ LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauTypeLengthLimit) LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauAstTypeGroup3) -LUAU_FASTFLAG(LuauParseOptionalAsNode2) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauParseStringIndexer) LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation) @@ -457,10 +455,7 @@ TEST_CASE_FIXTURE(Fixture, "return_type_is_an_intersection_type_if_led_with_one_ returnAnnotation = annotation->returnTypes_DEPRECATED.types.data[0]->as(); } REQUIRE(returnAnnotation != nullptr); - if (FFlag::LuauAstTypeGroup3) - CHECK(returnAnnotation->types.data[0]->as()); - else - CHECK(returnAnnotation->types.data[0]->as()); + CHECK(returnAnnotation->types.data[0]->as()); CHECK(returnAnnotation->types.data[1]->as()); } @@ -2780,8 +2775,6 @@ TEST_CASE_FIXTURE(Fixture, "leading_union_intersection_with_single_type_preserve TEST_CASE_FIXTURE(Fixture, "parse_simple_ast_type_group") { - ScopedFastFlag _{FFlag::LuauAstTypeGroup3, true}; - AstStatBlock* stat = parse(R"( type Foo = (string) )"); @@ -2798,8 +2791,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_simple_ast_type_group") TEST_CASE_FIXTURE(Fixture, "parse_nested_ast_type_group") { - ScopedFastFlag _{FFlag::LuauAstTypeGroup3, true}; - AstStatBlock* stat = parse(R"( type Foo = ((string)) )"); @@ -2819,8 +2810,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_nested_ast_type_group") TEST_CASE_FIXTURE(Fixture, "parse_return_type_ast_type_group") { - ScopedFastFlag _{FFlag::LuauAstTypeGroup3, true}; - AstStatBlock* stat = parse(R"( type Foo = () -> (string) )"); @@ -4206,18 +4195,10 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type") auto unionTy = paramTy.type->as(); LUAU_ASSERT(unionTy); CHECK_EQ(unionTy->types.size, 2); - if (FFlag::LuauAstTypeGroup3) - { - auto groupTy = unionTy->types.data[0]->as(); // (() -> ()) - REQUIRE(groupTy); - CHECK(groupTy->type->is()); // () -> () - } - else - CHECK(unionTy->types.data[0]->is()); // () -> () - if (FFlag::LuauParseOptionalAsNode2) - CHECK(unionTy->types.data[1]->is()); // ? - else - CHECK(unionTy->types.data[1]->is()); // nil + auto groupTy = unionTy->types.data[0]->as(); // (() -> ()) + REQUIRE(groupTy); + CHECK(groupTy->type->is()); // () -> () + CHECK(unionTy->types.data[1]->is()); // ? } TEST_CASE_FIXTURE(Fixture, "complex_union_in_generic_ty") diff --git a/tests/RequireByString.test.cpp b/tests/RequireByString.test.cpp index 1a6c06f7..a9c2ea93 100644 --- a/tests/RequireByString.test.cpp +++ b/tests/RequireByString.test.cpp @@ -7,6 +7,7 @@ #include "lualib.h" #include "Luau/Repl.h" +#include "Luau/ReplRequirer.h" #include "Luau/Require.h" #include "Luau/FileUtils.h" @@ -546,6 +547,16 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RegisterRuntimeModule") assertOutputContainsAll({"true"}); } +TEST_CASE_FIXTURE(ReplWithPathFixture, "ProxyRequire") +{ + luarequire_pushproxyrequire(L, requireConfigInit, createCliRequireContext(L)); + lua_setglobal(L, "proxyrequire"); + + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/proxy_requirer"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "result from dependency", "required into proxy_requirer"}); +} + TEST_CASE_FIXTURE(ReplWithPathFixture, "LoadStringRelative") { runCode(L, "return pcall(function() return loadstring(\"require('a/relative/path')\")() end)"); diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 4b5f6876..c7116d74 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -16,6 +16,7 @@ #include LUAU_FASTFLAG(LuauSolverV2) +LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations) using namespace Luau; @@ -1615,4 +1616,35 @@ TEST_CASE_FIXTURE(SubtypeFixture, "multiple_reasonings") ); } +TEST_CASE_FIXTURE(SubtypeFixture, "substitute_a_generic_for_a_negation") +{ + ScopedFastFlag sff{FFlag::LuauSubtypeGenericsAndNegations, true}; + + // (x: A, y: B) -> (A & ~(false?)) | B + // (~(false?), ~(false?)) -> (~(false?) & ~(false?)) | ~(false?) + + TypeId aTy = arena.addType(GenericType{"A"}); + getMutable(aTy)->scope = moduleScope.get(); + TypeId bTy = arena.addType(GenericType{"B"}); + getMutable(bTy)->scope = moduleScope.get(); + + TypeId genericFunctionTy = arena.addType(FunctionType{ + {aTy, bTy}, + {}, + arena.addTypePack({aTy, bTy}), + arena.addTypePack({join(meet(aTy, builtinTypes->truthyType), bTy)}) + }); + + const TypeId truthyTy = builtinTypes->truthyType; + + TypeId actualFunctionTy = fn( + {truthyTy, truthyTy}, + {join(meet(truthyTy, builtinTypes->truthyType), truthyTy)} + ); + + SubtypingResult result = isSubtype(genericFunctionTy, actualFunctionTy); + + CHECK(result.isSubtype); +} + TEST_SUITE_END(); diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index 1a8c3af7..3cd344e7 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -14,7 +14,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauAttributeSyntax) -LUAU_FASTFLAG(LuauImproveTypePathsInErrors) TEST_SUITE_BEGIN("ToString"); @@ -873,15 +872,12 @@ TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch") )"); std::string expected; - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) expected = "Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; \n" "this is because in the 1st entry in the type pack, accessing `c.d` results in `string` in the former type and `number` in the latter " "type, and `string` is not exactly `number`"; - else if (FFlag::LuauSolverV2) - expected = - R"(Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; at [0][read "c"][read "d"], string is not exactly number)"; - else if (FFlag::LuauImproveTypePathsInErrors) + else expected = R"(Type '{ a: number, b: string, c: { d: string } }' could not be converted into @@ -895,20 +891,6 @@ could not be converted into caused by: Property 'd' is not compatible. Type 'string' could not be converted into 'number' in an invariant context)"; - else - expected = R"(Type - '{ a: number, b: string, c: { d: string } }' -could not be converted into - '{| a: number, b: string, c: {| d: number |} |}' -caused by: - Property 'c' is not compatible. -Type - '{ d: string }' -could not be converted into - '{| d: number |}' -caused by: - Property 'd' is not compatible. -Type 'string' could not be converted into 'number' in an invariant context)"; LUAU_REQUIRE_ERROR_COUNT(1, result); diff --git a/tests/Transpiler.test.cpp b/tests/Transpiler.test.cpp index 1775336d..9c3f14b1 100644 --- a/tests/Transpiler.test.cpp +++ b/tests/Transpiler.test.cpp @@ -13,9 +13,8 @@ using namespace Luau; LUAU_FASTFLAG(LuauStoreCSTData2) -LUAU_FASTFLAG(LuauAstTypeGroup3); -LUAU_FASTFLAG(LuauParseOptionalAsNode2) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) +LUAU_FASTFLAG(LuauStoreLocalAnnotationColonPositions) TEST_SUITE_BEGIN("TranspilerTests"); @@ -343,7 +342,8 @@ TEST_CASE("function_with_types_spaces_around_tokens") { ScopedFastFlag sffs[] = { {FFlag::LuauStoreCSTData2, true}, - {FFlag::LuauStoreReturnTypesAsPackOnAst, true} + {FFlag::LuauStoreReturnTypesAsPackOnAst, true}, + {FFlag::LuauStoreLocalAnnotationColonPositions, true}, }; std::string code = R"( function p(o: string, m: number, ...: any): string end )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -369,9 +369,8 @@ TEST_CASE("function_with_types_spaces_around_tokens") code = R"( function p (o: string, m: number, ...: any): string end )"; CHECK_EQ(code, transpile(code, {}, true).code); - // TODO(CLI-139347): re-enable test once colon positions are supported - // code = R"( function p(o : string, m: number, ...: any): string end )"; - // CHECK_EQ(code, transpile(code, {}, true).code); + code = R"( function p(o : string, m: number, ...: any): string end )"; + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( function p(o: string, m: number, ...: any): string end )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -385,9 +384,8 @@ TEST_CASE("function_with_types_spaces_around_tokens") code = R"( function p(o: string, m: number, ...: any): string end )"; CHECK_EQ(code, transpile(code, {}, true).code); - // TODO(CLI-139347): re-enable test once colon positions are supported - // code = R"( function p(o: string, m: number, ... : any): string end )"; - // CHECK_EQ(code, transpile(code, {}, true).code); + code = R"( function p(o: string, m: number, ... : any): string end )"; + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( function p(o: string, m: number, ...: any): string end )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1249,6 +1247,59 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_reference_spaces_around_tokens") CHECK_EQ(code, transpile(code, {}, true).code); } +TEST_CASE_FIXTURE(Fixture, "transpile_type_annotation_spaces_around_tokens") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauStoreCSTData2, true}, + {FFlag::LuauStoreLocalAnnotationColonPositions, true}, + }; + std::string code = R"( local _: Type )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( local _ : Type )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( local _: Type )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( local x: Type, y = 1 )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( local x : Type, y = 1 )"; + CHECK_EQ(code, transpile(code, {}, true).code); +} + +TEST_CASE_FIXTURE(Fixture, "transpile_for_loop_annotation_spaces_around_tokens") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauStoreCSTData2, true}, + {FFlag::LuauStoreLocalAnnotationColonPositions, true}, + }; + std::string code = R"( for i: number = 1, 10 do end )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( for i : number = 1, 10 do end )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( for i: number = 1, 10 do end )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( for x: number, y: number in ... do end )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( for x : number, y: number in ... do end )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( for x: number, y: number in ... do end )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( for x: number, y : number in ... do end )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( for x: number, y: number in ... do end )"; + CHECK_EQ(code, transpile(code, {}, true).code); +} + TEST_CASE_FIXTURE(Fixture, "transpile_type_packs") { std::string code = R"( @@ -1323,10 +1374,8 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested_3") if (FFlag::LuauStoreCSTData2) CHECK_EQ(code, transpile(code, {}, true).code); - else if (FFlag::LuauAstTypeGroup3) - CHECK_EQ("local a: (string & number)?", transpile(code, {}, true).code); else - CHECK_EQ("local a: ( string & number)?", transpile(code, {}, true).code); + CHECK_EQ("local a: (string & number)?", transpile(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_nested") @@ -1358,7 +1407,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_leading_union_pipe") { ScopedFastFlag flags[] = { {FFlag::LuauStoreCSTData2, true}, - {FFlag::LuauParseOptionalAsNode2, true}, }; std::string code = "local a: | string | number"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1371,7 +1419,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_spaces_around_tokens") { ScopedFastFlag flags[] = { {FFlag::LuauStoreCSTData2, true}, - {FFlag::LuauParseOptionalAsNode2, true}, }; std::string code = "local a: string | number"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1408,8 +1455,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_mixed_union_intersection") { ScopedFastFlag flags[] = { {FFlag::LuauStoreCSTData2, true}, - {FFlag::LuauAstTypeGroup3, true}, - {FFlag::LuauParseOptionalAsNode2, true}, }; std::string code = "local a: string | (Foo & Bar)"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1437,7 +1482,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_preserve_union_optional_style") { ScopedFastFlag flags[] = { {FFlag::LuauStoreCSTData2, true}, - {FFlag::LuauParseOptionalAsNode2, true}, }; std::string code = "local a: string | nil"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -2029,7 +2073,6 @@ TEST_CASE("transpile_types_preserve_parentheses_style") { ScopedFastFlag flags[] = { {FFlag::LuauStoreCSTData2, true}, - {FFlag::LuauAstTypeGroup3, true}, }; std::string code = R"( type Foo = number )"; @@ -2176,7 +2219,6 @@ TEST_CASE("transpile_type_function_return_types") { ScopedFastFlag fflags[] = { {FFlag::LuauStoreCSTData2, true}, - {FFlag::LuauAstTypeGroup3, true}, {FFlag::LuauStoreReturnTypesAsPackOnAst, true}, }; std::string code = R"( type Foo = () -> () )"; @@ -2224,8 +2266,6 @@ TEST_CASE("transpile_type_function_return_types") TEST_CASE("fuzzer_nil_optional") { - ScopedFastFlag _{FFlag::LuauParseOptionalAsNode2, true}; - const std::string code = R"( local x: nil? )"; CHECK_EQ(code, transpile(code, {}, true).code); } diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index 0a10b7a7..a2d9b12c 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -10,7 +10,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauTypeFunReadWriteParents) -LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2) LUAU_FASTFLAG(LuauNoTypeFunctionsNamedTypeOf) @@ -1337,35 +1336,21 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tag_field") LUAU_REQUIRE_ERROR_COUNT(3, result); - if (FFlag::LuauImproveTypePathsInErrors) - { - - CHECK( - toString(result.errors[0]) == - "Type pack '\"number\"' could not be converted into 'never'; \n" - R"(this is because the 1st entry in the type pack is `"number"` in the former type and `never` in the latter type, and `"number"` is not a subtype of `never`)" - ); - CHECK( - toString(result.errors[1]) == - "Type pack '\"string\"' could not be converted into 'never'; \n" - R"(this is because the 1st entry in the type pack is `"string"` in the former type and `never` in the latter type, and `"string"` is not a subtype of `never`)" - ); - CHECK( - toString(result.errors[2]) == - "Type pack '\"table\"' could not be converted into 'never'; \n" - R"(this is because the 1st entry in the type pack is `"table"` in the former type and `never` in the latter type, and `"table"` is not a subtype of `never`)" - ); - } - else - { - CHECK( - toString(result.errors[0]) == R"(Type pack '"number"' could not be converted into 'never'; at [0], "number" is not a subtype of never)" - ); - CHECK( - toString(result.errors[1]) == R"(Type pack '"string"' could not be converted into 'never'; at [0], "string" is not a subtype of never)" - ); - CHECK(toString(result.errors[2]) == R"(Type pack '"table"' could not be converted into 'never'; at [0], "table" is not a subtype of never)"); - } + CHECK( + toString(result.errors[0]) == + "Type pack '\"number\"' could not be converted into 'never'; \n" + R"(this is because the 1st entry in the type pack is `"number"` in the former type and `never` in the latter type, and `"number"` is not a subtype of `never`)" + ); + CHECK( + toString(result.errors[1]) == + "Type pack '\"string\"' could not be converted into 'never'; \n" + R"(this is because the 1st entry in the type pack is `"string"` in the former type and `never` in the latter type, and `"string"` is not a subtype of `never`)" + ); + CHECK( + toString(result.errors[2]) == + "Type pack '\"table\"' could not be converted into 'never'; \n" + R"(this is because the 1st entry in the type pack is `"table"` in the former type and `never` in the latter type, and `"table"` is not a subtype of `never`)" + ); } TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_serialization") diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index 899c2a19..2e8b26dd 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -11,10 +11,9 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization) -LUAU_FASTFLAG(LuauImproveTypePathsInErrors) -LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes) LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2) +LUAU_FASTFLAG(LuauGuardAgainstMalformedTypeAliasExpansion) TEST_SUITE_BEGIN("TypeAliases"); @@ -220,11 +219,10 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = (FFlag::LuauImproveTypePathsInErrors) - ? "Type '{ v: string }' could not be converted into 'T'; \n" - "this is because accessing `v` results in `string` in the former type and `number` in the latter type, and " - "`string` is not exactly `number`" - : R"(Type '{ v: string }' could not be converted into 'T'; at [read "v"], string is not exactly number)"; + const std::string expected = + "Type '{ v: string }' could not be converted into 'T'; \n" + "this is because accessing `v` results in `string` in the former type and `number` in the latter type, and " + "`string` is not exactly `number`"; CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}}); CHECK_EQ(expected, toString(result.errors[0])); } @@ -244,11 +242,9 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases") LUAU_REQUIRE_ERROR_COUNT(1, result); const std::string expected = - (FFlag::LuauImproveTypePathsInErrors) - ? "Type '{ t: { v: string } }' could not be converted into 'U'; \n" - "this is because accessing `t.v` results in `string` in the former type and `number` in the latter type, and `string` is not exactly " - "`number`" - : R"(Type '{ t: { v: string } }' could not be converted into 'U'; at [read "t"][read "v"], string is not exactly number)"; + "Type '{ t: { v: string } }' could not be converted into 'U'; \n" + "this is because accessing `t.v` results in `string` in the former type and `number` in the latter type, and `string` is not exactly " + "`number`"; CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}}); CHECK_EQ(expected, toString(result.errors[0])); @@ -256,8 +252,6 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases") TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases") { - ScopedFastFlag _{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true}; - CheckResult result = check(R"( --!strict type T = { f: a, g: U } @@ -1268,4 +1262,17 @@ TEST_CASE_FIXTURE(Fixture, "exported_type_function_location_is_accessible_on_mod CHECK_EQ(tfun->second.definitionLocation, Location{{1, 8}, {2, 11}}); } +TEST_CASE_FIXTURE(Fixture, "fuzzer_cursed_type_aliases") +{ + ScopedFastFlag _{FFlag::LuauGuardAgainstMalformedTypeAliasExpansion, true}; + + // This used to crash under the new solver: we would like this to continue + // to not crash. + LUAU_REQUIRE_ERRORS(check(R"( + export type t1 = t4 + export type t4 = t4 + )")); +} + + TEST_SUITE_END(); diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index e58e8e42..c80467c8 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -11,7 +11,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauTableCloneClonesType3) -LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) @@ -147,32 +146,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t" - "'(number, number) -> boolean'" - "\ncould not be converted into\n\t" - "'((string, string) -> boolean)?'" - "\ncaused by:\n" - " None of the union options are compatible. For example:\n" - "Type\n\t" - "'(number, number) -> boolean'" - "\ncould not be converted into\n\t" - "'(string, string) -> boolean'" - "\ncaused by:\n" - " Argument #1 type is not compatible.\n" - "Type 'string' could not be converted into 'number'" - : R"(Type - '(number, number) -> boolean' -could not be converted into - '((string, string) -> boolean)?' -caused by: - None of the union options are compatible. For example: -Type - '(number, number) -> boolean' -could not be converted into - '(string, string) -> boolean' -caused by: - Argument #1 type is not compatible. -Type 'string' could not be converted into 'number')"; + const std::string expected = + "Type\n\t" + "'(number, number) -> boolean'" + "\ncould not be converted into\n\t" + "'((string, string) -> boolean)?'" + "\ncaused by:\n" + " None of the union options are compatible. For example:\n" + "Type\n\t" + "'(number, number) -> boolean'" + "\ncould not be converted into\n\t" + "'(string, string) -> boolean'" + "\ncaused by:\n" + " Argument #1 type is not compatible.\n" + "Type 'string' could not be converted into 'number'"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -1008,17 +995,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tonumber_returns_optional_number_type") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) CHECK_EQ( "Type 'number?' could not be converted into 'number'; \n" "this is because the 2nd component of the union is `nil`, which is not a subtype of `number`", toString(result.errors[0]) ); - else if (FFlag::LuauSolverV2) - CHECK_EQ( - "Type 'number?' could not be converted into 'number'; type number?[1] (nil) is not a subtype of number (number)", - toString(result.errors[0]) - ); else CHECK_EQ("Type 'number?' could not be converted into 'number'", toString(result.errors[0])); } diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index 6ed3f7ea..14a61025 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -15,7 +15,6 @@ using namespace Luau; using std::nullopt; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauImproveTypePathsInErrors) TEST_SUITE_BEGIN("TypeInferExternTypes"); @@ -547,14 +546,12 @@ local b: B = a LUAU_REQUIRE_ERRORS(result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) CHECK( "Type 'A' could not be converted into 'B'; \n" "this is because accessing `x` results in `ChildClass` in the former type and `BaseClass` in the latter type, and `ChildClass` is not " "exactly `BaseClass`" == toString(result.errors.at(0)) ); - else if (FFlag::LuauSolverV2) - CHECK(toString(result.errors.at(0)) == "Type 'A' could not be converted into 'B'; at [read \"x\"], ChildClass is not exactly BaseClass"); else { const std::string expected = R"(Type 'A' could not be converted into 'B' diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 24d69663..128b9b4f 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -23,9 +23,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauUngeneralizedTypesForRecursiveFunctions) -LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) -LUAU_FASTFLAG(LuauReduceUnionFollowUnionType) LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) LUAU_FASTFLAG(LuauHasPropProperBlock) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) @@ -1313,7 +1311,7 @@ f(function(a, b, c, ...) return a + b end) LUAU_REQUIRE_ERRORS(result); std::string expected; - if (FFlag::LuauInstantiateInSubtyping && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauInstantiateInSubtyping) { expected = "Type\n\t" "'(number, number, a) -> number'" @@ -1322,16 +1320,7 @@ f(function(a, b, c, ...) return a + b end) "\ncaused by:\n" " Argument count mismatch. Function expects 3 arguments, but only 2 are specified"; } - else if (FFlag::LuauInstantiateInSubtyping) - { - expected = R"(Type - '(number, number, a) -> number' -could not be converted into - '(number, number) -> number' -caused by: - Argument count mismatch. Function expects 3 arguments, but only 2 are specified)"; - } - else if (FFlag::LuauImproveTypePathsInErrors) + else { expected = "Type\n\t" "'(number, number, *error-type*) -> number'" @@ -1340,15 +1329,6 @@ caused by: "\ncaused by:\n" " Argument count mismatch. Function expects 3 arguments, but only 2 are specified"; } - else - { - expected = R"(Type - '(number, number, *error-type*) -> number' -could not be converted into - '(number, number) -> number' -caused by: - Argument count mismatch. Function expects 3 arguments, but only 2 are specified)"; - } CHECK_EQ(expected, toString(result.errors[0])); @@ -1544,19 +1524,13 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = (FFlag::LuauImproveTypePathsInErrors) - ? "Type\n\t" - "'(number, number) -> string'" - "\ncould not be converted into\n\t" - "'(number) -> string'" - "\ncaused by:\n" - " Argument count mismatch. Function expects 2 arguments, but only 1 is specified" - : R"(Type - '(number, number) -> string' -could not be converted into - '(number) -> string' -caused by: - Argument count mismatch. Function expects 2 arguments, but only 1 is specified)"; + const std::string expected = + "Type\n\t" + "'(number, number) -> string'" + "\ncould not be converted into\n\t" + "'(number) -> string'" + "\ncaused by:\n" + " Argument count mismatch. Function expects 2 arguments, but only 1 is specified"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -1574,20 +1548,14 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t" - "'(number, number) -> string'" - "\ncould not be converted into\n\t" - "'(number, string) -> string'" - "\ncaused by:\n" - " Argument #2 type is not compatible.\n" - "Type 'string' could not be converted into 'number'" - : R"(Type - '(number, number) -> string' -could not be converted into - '(number, string) -> string' -caused by: - Argument #2 type is not compatible. -Type 'string' could not be converted into 'number')"; + const std::string expected = + "Type\n\t" + "'(number, number) -> string'" + "\ncould not be converted into\n\t" + "'(number, string) -> string'" + "\ncaused by:\n" + " Argument #2 type is not compatible.\n" + "Type 'string' could not be converted into 'number'"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -1605,18 +1573,13 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t" - "'(number, number) -> number'" - "\ncould not be converted into\n\t" - "'(number, number) -> (number, boolean)'" - "\ncaused by:\n" - " Function only returns 1 value, but 2 are required here" - : R"(Type - '(number, number) -> number' -could not be converted into - '(number, number) -> (number, boolean)' -caused by: - Function only returns 1 value, but 2 are required here)"; + const std::string expected = + "Type\n\t" + "'(number, number) -> number'" + "\ncould not be converted into\n\t" + "'(number, number) -> (number, boolean)'" + "\ncaused by:\n" + " Function only returns 1 value, but 2 are required here"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -1634,20 +1597,14 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t" - "'(number, number) -> string'" - "\ncould not be converted into\n\t" - "'(number, number) -> number'" - "\ncaused by:\n" - " Return type is not compatible.\n" - "Type 'string' could not be converted into 'number'" - : R"(Type - '(number, number) -> string' -could not be converted into - '(number, number) -> number' -caused by: - Return type is not compatible. -Type 'string' could not be converted into 'number')"; + const std::string expected = + "Type\n\t" + "'(number, number) -> string'" + "\ncould not be converted into\n\t" + "'(number, number) -> number'" + "\ncaused by:\n" + " Return type is not compatible.\n" + "Type 'string' could not be converted into 'number'"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -1665,20 +1622,14 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t" - "'(number, number) -> (number, string)'" - "\ncould not be converted into\n\t" - "'(number, number) -> (number, boolean)'" - "\ncaused by:\n" - " Return #2 type is not compatible.\n" - "Type 'string' could not be converted into 'boolean'" - : R"(Type - '(number, number) -> (number, string)' -could not be converted into - '(number, number) -> (number, boolean)' -caused by: - Return #2 type is not compatible. -Type 'string' could not be converted into 'boolean')"; + const std::string expected = + "Type\n\t" + "'(number, number) -> (number, string)'" + "\ncould not be converted into\n\t" + "'(number, number) -> (number, boolean)'" + "\ncaused by:\n" + " Return #2 type is not compatible.\n" + "Type 'string' could not be converted into 'boolean'"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -1828,7 +1779,7 @@ end R"(Type function instance add depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)" ); } - else if (FFlag::LuauImproveTypePathsInErrors) + else { LUAU_REQUIRE_ERROR_COUNT(2, result); CHECK_EQ(toString(result.errors[0]), R"(Type @@ -1843,24 +1794,6 @@ could not be converted into '(number) -> number' caused by: Argument #1 type is not compatible. -Type 'number' could not be converted into 'string')"); - CHECK_EQ(toString(result.errors[1]), R"(Type 'string' could not be converted into 'number')"); - } - else - { - LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK_EQ(toString(result.errors[0]), R"(Type - '(string) -> string' -could not be converted into - '((number) -> number)?' -caused by: - None of the union options are compatible. For example: -Type - '(string) -> string' -could not be converted into - '(number) -> number' -caused by: - Argument #1 type is not compatible. Type 'number' could not be converted into 'string')"); CHECK_EQ(toString(result.errors[1]), R"(Type 'string' could not be converted into 'number')"); } @@ -1890,30 +1823,15 @@ function t:b() return 2 end -- not OK LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauImproveTypePathsInErrors) - { - CHECK_EQ( - "Type\n\t" - "'(*error-type*) -> number'" - "\ncould not be converted into\n\t" - "'() -> number'\n" - "caused by:\n" - " Argument count mismatch. Function expects 1 argument, but none are specified", - toString(result.errors[0]) - ); - } - else - { - CHECK_EQ( - R"(Type - '(*error-type*) -> number' -could not be converted into - '() -> number' -caused by: - Argument count mismatch. Function expects 1 argument, but none are specified)", - toString(result.errors[0]) - ); - } + CHECK_EQ( + "Type\n\t" + "'(*error-type*) -> number'" + "\ncould not be converted into\n\t" + "'() -> number'\n" + "caused by:\n" + " Argument count mismatch. Function expects 1 argument, but none are specified", + toString(result.errors[0]) + ); } TEST_CASE_FIXTURE(Fixture, "too_few_arguments_variadic") @@ -2187,15 +2105,10 @@ z = y -- Not OK, so the line is colorable LUAU_REQUIRE_ERROR_COUNT(1, result); const std::string expected = - (FFlag::LuauImproveTypePathsInErrors) - ? "Type\n\t" - R"('(("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> boolean) & (("blue" | "red") -> ("blue") -> ("blue") -> false) & (("blue" | "red") -> ("red") -> ("red") -> false) & (("blue") -> ("blue") -> ("blue" | "red") -> false) & (("red") -> ("red") -> ("blue" | "red") -> false)')" - "\ncould not be converted into\n\t" - R"('("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> false'; none of the intersection parts are compatible)" - : R"(Type - '(("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> boolean) & (("blue" | "red") -> ("blue") -> ("blue") -> false) & (("blue" | "red") -> ("red") -> ("red") -> false) & (("blue") -> ("blue") -> ("blue" | "red") -> false) & (("red") -> ("red") -> ("blue" | "red") -> false)' -could not be converted into - '("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> false'; none of the intersection parts are compatible)"; + "Type\n\t" + R"('(("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> boolean) & (("blue" | "red") -> ("blue") -> ("blue") -> false) & (("blue" | "red") -> ("red") -> ("red") -> false) & (("blue") -> ("blue") -> ("blue" | "red") -> false) & (("red") -> ("red") -> ("blue" | "red") -> false)')" + "\ncould not be converted into\n\t" + R"('("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> false'; none of the intersection parts are compatible)"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -2525,14 +2438,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_before_num_or_str") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) CHECK( "Type pack 'string' could not be converted into 'number'; \n" "this is because the 1st entry in the type pack is `string` in the former type and `number` in the latter type, and `string` is not a " "subtype of `number`" == toString(result.errors.at(0)) ); - else if (FFlag::LuauSolverV2) - CHECK(toString(result.errors.at(0)) == "Type pack 'string' could not be converted into 'number'; at [0], string is not a subtype of number"); else CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); @@ -2556,14 +2467,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_after_num_or_str") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) CHECK( "Type pack 'string' could not be converted into 'number'; \n" "this is because the 1st entry in the type pack is `string` in the former type and `number` in the latter type, and `string` is not a " "subtype of `number`" == toString(result.errors.at(0)) ); - else if (FFlag::LuauSolverV2) - CHECK(toString(result.errors.at(0)) == "Type pack 'string' could not be converted into 'number'; at [0], string is not a subtype of number"); else CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); CHECK_EQ("() -> number", toString(requireType("num_or_str"))); diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index 69057ca9..e90a33a2 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -12,7 +12,6 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauIntersectNotNil) @@ -867,12 +866,10 @@ y.a.c = y CHECK_EQ(toString(mismatch->givenType), "{ a: { c: T?, d: number }, b: number }"); CHECK_EQ(toString(mismatch->wantedType), "T"); std::string reason = - (FFlag::LuauImproveTypePathsInErrors) - ? "\nthis is because \n\t" - " * accessing `a.d` results in `number` in the former type and `string` in the latter type, and `number` is not exactly " - "`string`\n\t" - " * accessing `b` results in `number` in the former type and `string` in the latter type, and `number` is not exactly `string`" - : "at [read \"a\"][read \"d\"], number is not exactly string\n\tat [read \"b\"], number is not exactly string"; + "\nthis is because \n\t" + " * accessing `a.d` results in `number` in the former type and `string` in the latter type, and `number` is not exactly " + "`string`\n\t" + " * accessing `b` results in `number` in the former type and `string` in the latter type, and `number` is not exactly `string`"; CHECK_EQ(mismatch->reason, reason); } else diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index fb178b6f..adbe972f 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -10,7 +10,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauNarrowIntersectionNevers) TEST_SUITE_BEGIN("IntersectionTypes"); @@ -360,19 +359,13 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect") { LUAU_REQUIRE_ERROR_COUNT(4, result); - const std::string expected = (FFlag::LuauImproveTypePathsInErrors) - ? "Type\n\t" - "'(string, number) -> string'" - "\ncould not be converted into\n\t" - "'(string) -> string'\n" - "caused by:\n" - " Argument count mismatch. Function expects 2 arguments, but only 1 is specified" - : R"(Type - '(string, number) -> string' -could not be converted into - '(string) -> string' -caused by: - Argument count mismatch. Function expects 2 arguments, but only 1 is specified)"; + const std::string expected = + "Type\n\t" + "'(string, number) -> string'" + "\ncould not be converted into\n\t" + "'(string) -> string'\n" + "caused by:\n" + " Argument count mismatch. Function expects 2 arguments, but only 1 is specified"; CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'X & Y'"); @@ -398,19 +391,13 @@ TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect") )"); LUAU_REQUIRE_ERROR_COUNT(4, result); - const std::string expected = (FFlag::LuauImproveTypePathsInErrors) - ? "Type\n\t" - "'(string, number) -> string'" - "\ncould not be converted into\n\t" - "'(string) -> string'\n" - "caused by:\n" - " Argument count mismatch. Function expects 2 arguments, but only 1 is specified" - : R"(Type - '(string, number) -> string' -could not be converted into - '(string) -> string' -caused by: - Argument count mismatch. Function expects 2 arguments, but only 1 is specified)"; + const std::string expected = + "Type\n\t" + "'(string, number) -> string'" + "\ncould not be converted into\n\t" + "'(string) -> string'\n" + "caused by:\n" + " Argument count mismatch. Function expects 2 arguments, but only 1 is specified"; CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'XY'"); @@ -440,31 +427,30 @@ local a: XYZ = 3 )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type 'number' could not be converted into 'X & Y & Z' + + if (FFlag::LuauSolverV2) + { + const std::string expected = + "Type " + "'number'" + " could not be converted into " + "'X & Y & Z'; \n" + "this is because \n\t" + " * the 1st component of the intersection is `X`, and `number` is not a subtype of `X`\n\t" + " * the 2nd component of the intersection is `Y`, and `number` is not a subtype of `Y`\n\t" + " * the 3rd component of the intersection is `Z`, and `number` is not a subtype of `Z`"; + + CHECK_EQ(expected, toString(result.errors[0])); + } + else + { + const std::string expected = R"(Type 'number' could not be converted into 'X & Y & Z' caused by: Not all intersection parts are compatible. Type 'number' could not be converted into 'X')"; - const std::string dcrExprected = - R"(Type 'number' could not be converted into 'X & Y & Z'; type number (number) is not a subtype of X & Y & Z[0] (X) - type number (number) is not a subtype of X & Y & Z[1] (Y) - type number (number) is not a subtype of X & Y & Z[2] (Z))"; - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) - { - const std::string expected = "Type " - "'number'" - " could not be converted into " - "'X & Y & Z'; \n" - "this is because \n\t" - " * the 1st component of the intersection is `X`, and `number` is not a subtype of `X`\n\t" - " * the 2nd component of the intersection is `Y`, and `number` is not a subtype of `Y`\n\t" - " * the 3rd component of the intersection is `Z`, and `number` is not a subtype of `Z`"; CHECK_EQ(expected, toString(result.errors[0])); } - else if (FFlag::LuauSolverV2) - CHECK_EQ(dcrExprected, toString(result.errors[0])); - else - CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_all") @@ -482,30 +468,22 @@ end LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { - const std::string expected = "Type pack " - "'X & Y & Z'" - " could not be converted into " - "'number'; \n" - "this is because \n\t" - " * in the 1st entry in the type pack has the 1st component of the intersection as `X` and the 1st entry in the " - "type pack is `number`, and `X` is not a subtype of `number`\n\t" - " * in the 1st entry in the type pack has the 2nd component of the intersection as `Y` and the 1st entry in the " - "type pack is `number`, and `Y` is not a subtype of `number`\n\t" - " * in the 1st entry in the type pack has the 3rd component of the intersection as `Z` and the 1st entry in the " - "type pack is `number`, and `Z` is not a subtype of `number`"; + const std::string expected = + "Type pack " + "'X & Y & Z'" + " could not be converted into " + "'number'; \n" + "this is because \n\t" + " * in the 1st entry in the type pack has the 1st component of the intersection as `X` and the 1st entry in the " + "type pack is `number`, and `X` is not a subtype of `number`\n\t" + " * in the 1st entry in the type pack has the 2nd component of the intersection as `Y` and the 1st entry in the " + "type pack is `number`, and `Y` is not a subtype of `number`\n\t" + " * in the 1st entry in the type pack has the 3rd component of the intersection as `Z` and the 1st entry in the " + "type pack is `number`, and `Z` is not a subtype of `number`"; CHECK_EQ(expected, toString(result.errors[0])); } - else if (FFlag::LuauSolverV2) - { - CHECK_EQ( - R"(Type pack 'X & Y & Z' could not be converted into 'number'; type X & Y & Z[0][0] (X) is not a subtype of number[0] (number) - type X & Y & Z[0][1] (Y) is not a subtype of number[0] (number) - type X & Y & Z[0][2] (Z) is not a subtype of number[0] (number))", - toString(result.errors[0]) - ); - } else CHECK_EQ( toString(result.errors[0]), R"(Type 'X & Y & Z' could not be converted into 'number'; none of the intersection parts are compatible)" @@ -551,25 +529,18 @@ TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { - const std::string expected = "Type " - "'boolean & false'" - " could not be converted into " - "'true'; \n" - "this is because \n\t" - " * the 1st component of the intersection is `boolean`, which is not a subtype of `true`\n\t" - " * the 2nd component of the intersection is `false`, which is not a subtype of `true`"; + const std::string expected = + "Type " + "'boolean & false'" + " could not be converted into " + "'true'; \n" + "this is because \n\t" + " * the 1st component of the intersection is `boolean`, which is not a subtype of `true`\n\t" + " * the 2nd component of the intersection is `false`, which is not a subtype of `true`"; CHECK_EQ(expected, toString(result.errors[0])); } - else if (FFlag::LuauSolverV2) - { - CHECK_EQ( - R"(Type 'boolean & false' could not be converted into 'true'; type boolean & false[0] (boolean) is not a subtype of true (true) - type boolean & false[1] (false) is not a subtype of true (true))", - toString(result.errors[0]) - ); - } else CHECK_EQ( toString(result.errors[0]), "Type 'boolean & false' could not be converted into 'true'; none of the intersection parts are compatible" @@ -588,25 +559,19 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false") LUAU_REQUIRE_ERROR_COUNT(1, result); // TODO: odd stringification of `false & (boolean & false)`.) - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { - const std::string expected = "Type " - "'boolean & false & false'" - " could not be converted into " - "'true'; \n" - "this is because \n\t" - " * the 1st component of the intersection is `false`, which is not a subtype of `true`\n\t" - " * the 2nd component of the intersection is `boolean`, which is not a subtype of `true`\n\t" - " * the 3rd component of the intersection is `false`, which is not a subtype of `true`"; + const std::string expected = + "Type " + "'boolean & false & false'" + " could not be converted into " + "'true'; \n" + "this is because \n\t" + " * the 1st component of the intersection is `false`, which is not a subtype of `true`\n\t" + " * the 2nd component of the intersection is `boolean`, which is not a subtype of `true`\n\t" + " * the 3rd component of the intersection is `false`, which is not a subtype of `true`"; CHECK_EQ(expected, toString(result.errors[0])); } - else if (FFlag::LuauSolverV2) - CHECK_EQ( - R"(Type 'boolean & false & false' could not be converted into 'true'; type boolean & false & false[0] (false) is not a subtype of true (true) - type boolean & false & false[1] (boolean) is not a subtype of true (true) - type boolean & false & false[2] (false) is not a subtype of true (true))", - toString(result.errors[0]) - ); else CHECK_EQ( toString(result.errors[0]), @@ -623,7 +588,7 @@ TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions") end )"); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { const std::string expected1 = "Type\n\t" @@ -654,25 +619,7 @@ TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions") CHECK_EQ(expected1, toString(result.errors[0])); CHECK_EQ(expected2, toString(result.errors[1])); } - else if (FFlag::LuauSolverV2) - { - LUAU_REQUIRE_ERROR_COUNT(2, result); - const std::string expected1 = R"(Type - '((number?) -> number?) & ((string?) -> string?)' -could not be converted into - '(nil) -> nil'; type ((number?) -> number?) & ((string?) -> string?)[0].returns()[0][0] (number) is not a subtype of (nil) -> nil.returns()[0] (nil) - type ((number?) -> number?) & ((string?) -> string?)[1].returns()[0][0] (string) is not a subtype of (nil) -> nil.returns()[0] (nil))"; - const std::string expected2 = R"(Type - '((number?) -> number?) & ((string?) -> string?)' -could not be converted into - '(number) -> number'; type ((number?) -> number?) & ((string?) -> string?)[0].returns()[0][1] (nil) is not a subtype of (number) -> number.returns()[0] (number) - type ((number?) -> number?) & ((string?) -> string?)[1].arguments()[0] (string?) is not a supertype of (number) -> number.arguments()[0] (number) - type ((number?) -> number?) & ((string?) -> string?)[1].returns()[0][0] (string) is not a subtype of (number) -> number.returns()[0] (number) - type ((number?) -> number?) & ((string?) -> string?)[1].returns()[0][1] (nil) is not a subtype of (number) -> number.returns()[0] (number))"; - CHECK_EQ(expected1, toString(result.errors[0])); - CHECK_EQ(expected2, toString(result.errors[1])); - } - else if (FFlag::LuauImproveTypePathsInErrors) + else { LUAU_REQUIRE_ERROR_COUNT(1, result); const std::string expected = R"(Type @@ -681,15 +628,6 @@ could not be converted into '(number) -> number'; none of the intersection parts are compatible)"; CHECK_EQ(expected, toString(result.errors[0])); } - else - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type - '((number?) -> number?) & ((string?) -> string?)' -could not be converted into - '(number) -> number'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } } TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions") @@ -706,22 +644,13 @@ TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauImproveTypePathsInErrors) - { - const std::string expected = "Type\n\t" - "'((number) -> number) & ((string) -> string)'" - "\ncould not be converted into\n\t" - "'(boolean | number) -> boolean | number'; none of the intersection parts are compatible"; - CHECK_EQ(expected, toString(result.errors[0])); - } - else - { - const std::string expected = R"(Type - '((number) -> number) & ((string) -> string)' -could not be converted into - '(boolean | number) -> boolean | number'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } + const std::string expected = + "Type\n\t" + "'((number) -> number) & ((string) -> string)'" + "\ncould not be converted into\n\t" + "'(boolean | number) -> boolean | number'; none of the intersection parts are compatible"; + CHECK_EQ(expected, toString(result.errors[0])); + } TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") @@ -735,20 +664,21 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { - const std::string expected = "Type " - "'{ p: number?, q: number?, r: number? } & { p: number?, q: string? }'" - " could not be converted into " - "'{ p: nil }'; \n" - "this is because \n\t" - " * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and " - "accessing `p` results in `nil`, and `number` is not exactly `nil`\n\t" - " * in the 2nd component of the intersection, accessing `p` has the 1st component of the union as `number` and " - "accessing `p` results in `nil`, and `number` is not exactly `nil`"; + const std::string expected = + "Type " + "'{ p: number?, q: number?, r: number? } & { p: number?, q: string? }'" + " could not be converted into " + "'{ p: nil }'; \n" + "this is because \n\t" + " * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and " + "accessing `p` results in `nil`, and `number` is not exactly `nil`\n\t" + " * in the 2nd component of the intersection, accessing `p` has the 1st component of the union as `number` and " + "accessing `p` results in `nil`, and `number` is not exactly `nil`"; CHECK_EQ(expected, toString(result.errors[0])); } - else if (FFlag::LuauImproveTypePathsInErrors) + else { const std::string expected = R"(Type @@ -757,19 +687,6 @@ could not be converted into '{| p: nil |}'; none of the intersection parts are compatible)"; CHECK_EQ(expected, toString(result.errors[0])); } - else - { - const std::string expected = - (FFlag::LuauSolverV2) - ? R"(Type '{ p: number?, q: number?, r: number? } & { p: number?, q: string? }' could not be converted into '{ p: nil }'; type { p: number?, q: number?, r: number? } & { p: number?, q: string? }[0][read "p"][0] (number) is not exactly { p: nil }[read "p"] (nil) - type { p: number?, q: number?, r: number? } & { p: number?, q: string? }[1][read "p"][0] (number) is not exactly { p: nil }[read "p"] (nil))" - : - R"(Type - '{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}' -could not be converted into - '{| p: nil |}'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } } TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties") @@ -781,59 +698,35 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties") end )"); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { - const std::string expected = "Type\n\t" - "'{ p: number?, q: any } & { p: unknown, q: string? }'" - "\ncould not be converted into\n\t" - "'{ p: string?, q: number? }'; \n" - "this is because \n\t" - " * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and " - "accessing `p` results in `string?`, and `number` is not exactly `string?`\n\t" - " * in the 1st component of the intersection, accessing `p` results in `number?` and accessing `p` has the 1st " - "component of the union as `string`, and `number?` is not exactly `string`\n\t" - " * in the 1st component of the intersection, accessing `q` results in `any` and accessing `q` results in " - "`number?`, and `any` is not exactly `number?`\n\t" - " * in the 2nd component of the intersection, accessing `p` results in `unknown` and accessing `p` results in " - "`string?`, and `unknown` is not exactly `string?`\n\t" - " * in the 2nd component of the intersection, accessing `q` has the 1st component of the union as `string` and " - "accessing `q` results in `number?`, and `string` is not exactly `number?`\n\t" - " * in the 2nd component of the intersection, accessing `q` results in `string?` and accessing `q` has the 1st " - "component of the union as `number`, and `string?` is not exactly `number`"; - CHECK_EQ(expected, toString(result.errors[0])); - } - else if (FFlag::LuauSolverV2) - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ( - R"(Type - '{ p: number?, q: any } & { p: unknown, q: string? }' -could not be converted into - '{ p: string?, q: number? }'; type { p: number?, q: any } & { p: unknown, q: string? }[0][read "p"] (number?) is not exactly { p: string?, q: number? }[read "p"][0] (string) - type { p: number?, q: any } & { p: unknown, q: string? }[0][read "p"][0] (number) is not exactly { p: string?, q: number? }[read "p"] (string?) - type { p: number?, q: any } & { p: unknown, q: string? }[0][read "q"] (any) is not exactly { p: string?, q: number? }[read "q"] (number?) - type { p: number?, q: any } & { p: unknown, q: string? }[1][read "p"] (unknown) is not exactly { p: string?, q: number? }[read "p"] (string?) - type { p: number?, q: any } & { p: unknown, q: string? }[1][read "q"] (string?) is not exactly { p: string?, q: number? }[read "q"][0] (number) - type { p: number?, q: any } & { p: unknown, q: string? }[1][read "q"][0] (string) is not exactly { p: string?, q: number? }[read "q"] (number?))", - toString(result.errors[0]) - ); - } - else if (FFlag::LuauImproveTypePathsInErrors) - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type - '{| p: number?, q: any |} & {| p: unknown, q: string? |}' -could not be converted into - '{| p: string?, q: number? |}'; none of the intersection parts are compatible)"; + const std::string expected = + "Type\n\t" + "'{ p: number?, q: any } & { p: unknown, q: string? }'" + "\ncould not be converted into\n\t" + "'{ p: string?, q: number? }'; \n" + "this is because \n\t" + " * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and " + "accessing `p` results in `string?`, and `number` is not exactly `string?`\n\t" + " * in the 1st component of the intersection, accessing `p` results in `number?` and accessing `p` has the 1st " + "component of the union as `string`, and `number?` is not exactly `string`\n\t" + " * in the 1st component of the intersection, accessing `q` results in `any` and accessing `q` results in " + "`number?`, and `any` is not exactly `number?`\n\t" + " * in the 2nd component of the intersection, accessing `p` results in `unknown` and accessing `p` results in " + "`string?`, and `unknown` is not exactly `string?`\n\t" + " * in the 2nd component of the intersection, accessing `q` has the 1st component of the union as `string` and " + "accessing `q` results in `number?`, and `string` is not exactly `number?`\n\t" + " * in the 2nd component of the intersection, accessing `q` results in `string?` and accessing `q` has the 1st " + "component of the union as `number`, and `string?` is not exactly `number`"; CHECK_EQ(expected, toString(result.errors[0])); } else { LUAU_REQUIRE_ERROR_COUNT(1, result); const std::string expected = R"(Type - '{| p: number?, q: any |} & {| p: unknown, q: string? |}' + '{| p: number?, q: any |} & {| p: unknown, q: string? |}' could not be converted into - '{| p: string?, q: number? |}'; none of the intersection parts are compatible)"; + '{| p: string?, q: number? |}'; none of the intersection parts are compatible)"; CHECK_EQ(expected, toString(result.errors[0])); } } @@ -859,7 +752,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections") end )"); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { const std::string expected1 = "Type\n\t" @@ -904,32 +797,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections") CHECK_EQ(expected1, toString(result.errors[0])); CHECK_EQ(expected2, toString(result.errors[1])); } - else if (FFlag::LuauSolverV2) - { - LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK_EQ( - R"(Type - '((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })' -could not be converted into - '(nil) -> { p: number, q: number, r: number }'; type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[0].returns()[0][0] ({ p: number }) is not a subtype of (nil) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }) - type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[0].returns()[0][1] ({ q: number }) is not a subtype of (nil) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }) - type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].returns()[0][0] ({ p: number }) is not a subtype of (nil) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }) - type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].returns()[0][1] ({ r: number }) is not a subtype of (nil) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }))", - toString(result.errors[0]) - ); - CHECK_EQ( - R"(Type - '((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })' -could not be converted into - '(number?) -> { p: number, q: number, r: number }'; type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[0].returns()[0][0] ({ p: number }) is not a subtype of (number?) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }) - type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[0].returns()[0][1] ({ q: number }) is not a subtype of (number?) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }) - type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].arguments()[0] (string?) is not a supertype of (number?) -> { p: number, q: number, r: number }.arguments()[0][0] (number) - type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].returns()[0][0] ({ p: number }) is not a subtype of (number?) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }) - type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].returns()[0][1] ({ r: number }) is not a subtype of (number?) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }))", - toString(result.errors[1]) - ); - } - else if (FFlag::LuauImproveTypePathsInErrors) + else { LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ( @@ -940,17 +808,6 @@ could not be converted into toString(result.errors[0]) ); } - else - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ( - R"(Type - '((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})' -could not be converted into - '(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible)", - toString(result.errors[0]) - ); - } } TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic") @@ -967,7 +824,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic") { LUAU_REQUIRE_ERROR_COUNT(0, result); } - else if (FFlag::LuauImproveTypePathsInErrors) + else { LUAU_REQUIRE_ERROR_COUNT(1, result); const std::string expected = R"(Type @@ -976,15 +833,6 @@ could not be converted into '(number?) -> a'; none of the intersection parts are compatible)"; CHECK_EQ(expected, toString(result.errors[0])); } - else - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type - '((number?) -> a | number) & ((string?) -> a | string)' -could not be converted into - '(number?) -> a'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } } TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics") @@ -1003,7 +851,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics") { LUAU_REQUIRE_NO_ERRORS(result); } - else if (FFlag::LuauImproveTypePathsInErrors) + else { LUAU_REQUIRE_ERROR_COUNT(1, result); const std::string expected = R"(Type @@ -1012,15 +860,6 @@ could not be converted into '(a?) -> (a & c) | b'; none of the intersection parts are compatible)"; CHECK_EQ(expected, toString(result.errors[0])); } - else - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type - '((a?) -> a | b) & ((c?) -> b | c)' -could not be converted into - '(a?) -> (a & c) | b'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } } TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") @@ -1034,7 +873,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") end )"); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { const std::string expected1 = "Type\n\t" @@ -1061,27 +900,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") CHECK_EQ(expected1, toString(result.errors[0])); CHECK_EQ(expected2, toString(result.errors[1])); } - else if (FFlag::LuauSolverV2) - { - LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK_EQ( - R"(Type - '((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))' -could not be converted into - '(nil, a...) -> (nil, b...)'; type ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))[0].returns()[0][0] (number) is not a subtype of (nil, a...) -> (nil, b...).returns()[0] (nil) - type ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))[1].returns()[0][0] (string) is not a subtype of (nil, a...) -> (nil, b...).returns()[0] (nil))", - toString(result.errors[0]) - ); - CHECK_EQ( - R"(Type - '((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))' -could not be converted into - '(nil, b...) -> (nil, a...)'; type ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))[0].returns()[0][0] (number) is not a subtype of (nil, b...) -> (nil, a...).returns()[0] (nil) - type ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))[1].returns()[0][0] (string) is not a subtype of (nil, b...) -> (nil, a...).returns()[0] (nil))", - toString(result.errors[1]) - ); - } - else if (FFlag::LuauImproveTypePathsInErrors) + else { LUAU_REQUIRE_ERROR_COUNT(1, result); const std::string expected = R"(Type @@ -1090,15 +909,6 @@ could not be converted into '(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible)"; CHECK_EQ(expected, toString(result.errors[0])); } - else - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type - '((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))' -could not be converted into - '(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result") @@ -1117,22 +927,11 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauImproveTypePathsInErrors) - { - const std::string expected = "Type\n\t" - "'((nil) -> unknown) & ((number) -> number)'" - "\ncould not be converted into\n\t" - "'(number?) -> number?'; none of the intersection parts are compatible"; - CHECK_EQ(expected, toString(result.errors[0])); - } - else - { - const std::string expected = R"(Type - '((nil) -> unknown) & ((number) -> number)' -could not be converted into - '(number?) -> number?'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } + const std::string expected = "Type\n\t" + "'((nil) -> unknown) & ((number) -> number)'" + "\ncould not be converted into\n\t" + "'(number?) -> number?'; none of the intersection parts are compatible"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments") @@ -1151,22 +950,12 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauImproveTypePathsInErrors) - { - const std::string expected = "Type\n\t" - "'((number) -> number?) & ((unknown) -> string?)'" - "\ncould not be converted into\n\t" - "'(number?) -> nil'; none of the intersection parts are compatible"; - CHECK_EQ(expected, toString(result.errors[0])); - } - else - { - const std::string expected = R"(Type - '((number) -> number?) & ((unknown) -> string?)' -could not be converted into - '(number?) -> nil'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } + const std::string expected = + "Type\n\t" + "'((number) -> number?) & ((unknown) -> string?)'" + "\ncould not be converted into\n\t" + "'(number?) -> nil'; none of the intersection parts are compatible"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result") @@ -1180,7 +969,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result") end )"); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { const std::string expected1 = "Type\n\t" @@ -1209,28 +998,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result") CHECK_EQ(expected1, toString(result.errors[0])); CHECK_EQ(expected2, toString(result.errors[1])); } - else if (FFlag::LuauSolverV2) - { - LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK_EQ( - R"(Type - '((nil) -> never) & ((number) -> number)' -could not be converted into - '(number?) -> number'; type ((nil) -> never) & ((number) -> number)[0].arguments()[0] (number) is not a supertype of (number?) -> number.arguments()[0][1] (nil) - type ((nil) -> never) & ((number) -> number)[1].arguments()[0] (nil) is not a supertype of (number?) -> number.arguments()[0][0] (number))", - toString(result.errors[0]) - ); - CHECK_EQ( - R"(Type - '((nil) -> never) & ((number) -> number)' -could not be converted into - '(number?) -> never'; type ((nil) -> never) & ((number) -> number)[0].arguments()[0] (number) is not a supertype of (number?) -> never.arguments()[0][1] (nil) - type ((nil) -> never) & ((number) -> number)[0].returns()[0] (number) is not a subtype of (number?) -> never.returns()[0] (never) - type ((nil) -> never) & ((number) -> number)[1].arguments()[0] (nil) is not a supertype of (number?) -> never.arguments()[0][0] (number))", - toString(result.errors[1]) - ); - } - else if (FFlag::LuauImproveTypePathsInErrors) + else { LUAU_REQUIRE_ERROR_COUNT(1, result); const std::string expected = R"(Type @@ -1239,15 +1007,6 @@ could not be converted into '(number?) -> never'; none of the intersection parts are compatible)"; CHECK_EQ(expected, toString(result.errors[0])); } - else - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type - '((nil) -> never) & ((number) -> number)' -could not be converted into - '(number?) -> never'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments") @@ -1261,7 +1020,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments") end )"); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { const std::string expected1 = "Type\n\t" @@ -1294,26 +1053,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments") CHECK_EQ(expected1, toString(result.errors[0])); CHECK_EQ(expected2, toString(result.errors[1])); } - else if (FFlag::LuauSolverV2) - { - LUAU_REQUIRE_ERROR_COUNT(2, result); - const std::string expected1 = R"(Type - '((never) -> string?) & ((number) -> number?)' -could not be converted into - '(never) -> nil'; type ((never) -> string?) & ((number) -> number?)[0].returns()[0][0] (number) is not a subtype of (never) -> nil.returns()[0] (nil) - type ((never) -> string?) & ((number) -> number?)[1].returns()[0][0] (string) is not a subtype of (never) -> nil.returns()[0] (nil))"; - const std::string expected2 = R"(Type - '((never) -> string?) & ((number) -> number?)' -could not be converted into - '(number?) -> nil'; type ((never) -> string?) & ((number) -> number?)[0].arguments()[0] (number) is not a supertype of (number?) -> nil.arguments()[0][1] (nil) - type ((never) -> string?) & ((number) -> number?)[0].returns()[0][0] (number) is not a subtype of (number?) -> nil.returns()[0] (nil) - type ((never) -> string?) & ((number) -> number?)[1].arguments()[0] (never) is not a supertype of (number?) -> nil.arguments()[0][0] (number) - type ((never) -> string?) & ((number) -> number?)[1].arguments()[0] (never) is not a supertype of (number?) -> nil.arguments()[0][1] (nil) - type ((never) -> string?) & ((number) -> number?)[1].returns()[0][0] (string) is not a subtype of (number?) -> nil.returns()[0] (nil))"; - CHECK_EQ(expected1, toString(result.errors[0])); - CHECK_EQ(expected2, toString(result.errors[1])); - } - else if (FFlag::LuauImproveTypePathsInErrors) + else { LUAU_REQUIRE_ERROR_COUNT(1, result); const std::string expected = R"(Type @@ -1322,15 +1062,6 @@ could not be converted into '(number?) -> nil'; none of the intersection parts are compatible)"; CHECK_EQ(expected, toString(result.errors[0])); } - else - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type - '((never) -> string?) & ((number) -> number?)' -could not be converted into - '(number?) -> nil'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_variadics") @@ -1347,22 +1078,11 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_ LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauImproveTypePathsInErrors) - { - const std::string expected = "Type\n\t" - "'((number?) -> (...number)) & ((string?) -> number | string)'" - "\ncould not be converted into\n\t" - "'(number | string) -> (number, number?)'; none of the intersection parts are compatible"; - CHECK(expected == toString(result.errors[0])); - } - else - { - const std::string expected = R"(Type - '((number?) -> (...number)) & ((string?) -> number | string)' -could not be converted into - '(number | string) -> (number, number?)'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } + const std::string expected = "Type\n\t" + "'((number?) -> (...number)) & ((string?) -> number | string)'" + "\ncould not be converted into\n\t" + "'(number | string) -> (number, number?)'; none of the intersection parts are compatible"; + CHECK(expected == toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1") @@ -1430,7 +1150,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3") { LUAU_REQUIRE_NO_ERRORS(result); } - else if (FFlag::LuauImproveTypePathsInErrors) + else { LUAU_REQUIRE_ERROR_COUNT(1, result); const std::string expected = R"(Type @@ -1439,15 +1159,6 @@ could not be converted into '() -> number'; none of the intersection parts are compatible)"; CHECK_EQ(expected, toString(result.errors[0])); } - else - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type - '(() -> (a...)) & (() -> (number?, a...))' -could not be converted into - '() -> number'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") @@ -1463,31 +1174,21 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { - const std::string expected = "Type\n\t" - "'((a...) -> ()) & ((number, a...) -> number)'" - "\ncould not be converted into\n\t" - "'((a...) -> ()) & ((number, a...) -> number)'; \n" - "this is because \n\t" - " * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in " - "the latter type, and `()` is not a subtype of `number`\n\t" - " * in the 2nd component of the intersection, the function takes a tail of `a...` and in the 1st component of " - "the intersection, the function takes a tail of `a...`, and `a...` is not a supertype of `a...`"; + const std::string expected = + "Type\n\t" + "'((a...) -> ()) & ((number, a...) -> number)'" + "\ncould not be converted into\n\t" + "'((a...) -> ()) & ((number, a...) -> number)'; \n" + "this is because \n\t" + " * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in " + "the latter type, and `()` is not a subtype of `number`\n\t" + " * in the 2nd component of the intersection, the function takes a tail of `a...` and in the 1st component of " + "the intersection, the function takes a tail of `a...`, and `a...` is not a supertype of `a...`"; CHECK(expected == toString(result.errors[0])); } - else if (FFlag::LuauSolverV2) - { - CHECK_EQ( - R"(Type - '((a...) -> ()) & ((number, a...) -> number)' -could not be converted into - '((a...) -> ()) & ((number, a...) -> number)'; at [0].returns(), is not a subtype of number - type ((a...) -> ()) & ((number, a...) -> number)[1].arguments().tail() (a...) is not a supertype of ((a...) -> ()) & ((number, a...) -> number)[0].arguments().tail() (a...))", - toString(result.errors[0]) - ); - } - else if (FFlag::LuauImproveTypePathsInErrors) + else { CHECK_EQ( R"(Type @@ -1497,16 +1198,6 @@ could not be converted into toString(result.errors[0]) ); } - else - { - CHECK_EQ( - R"(Type - '((a...) -> ()) & ((number, a...) -> number)' -could not be converted into - '(number?) -> ()'; none of the intersection parts are compatible)", - toString(result.errors[0]) - ); - } } TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables") diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index 212a3dc5..4f15091b 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -12,7 +12,6 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) @@ -465,20 +464,14 @@ local b: B.T = a CheckResult result = frontend.check("game/C"); LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { - const std::string expected = "Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'; \n" - "this is because accessing `x` results in `number` in the former type and `string` in the latter type, and " - "`number` is not exactly `string`"; + const std::string expected = + "Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'; \n" + "this is because accessing `x` results in `number` in the former type and `string` in the latter type, and " + "`number` is not exactly `string`"; CHECK(expected == toString(result.errors[0])); } - else if (FFlag::LuauSolverV2) - { - CHECK( - toString(result.errors.at(0)) == - "Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'; at [read \"x\"], number is not exactly string" - ); - } else { const std::string expected = R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B' @@ -518,20 +511,14 @@ local b: B.T = a CheckResult result = frontend.check("game/D"); LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { - const std::string expected = "Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'; \n" - "this is because accessing `x` results in `number` in the former type and `string` in the latter type, and " - "`number` is not exactly `string`"; + const std::string expected = + "Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'; \n" + "this is because accessing `x` results in `number` in the former type and `string` in the latter type, and " + "`number` is not exactly `string`"; CHECK(expected == toString(result.errors[0])); } - else if (FFlag::LuauSolverV2) - { - CHECK( - toString(result.errors.at(0)) == - "Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'; at [read \"x\"], number is not exactly string" - ); - } else { const std::string expected = R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C' diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index c1ecfbf2..b31476f9 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -19,7 +19,6 @@ LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) -LUAU_FASTFLAG(LuauImproveTypePathsInErrors) TEST_SUITE_BEGIN("ProvisionalTests"); @@ -874,20 +873,13 @@ TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_ty else { LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? - R"(Type + const std::string expected = + R"(Type '{| x: number? |}' could not be converted into '{| x: number |}' caused by: Property 'x' is not compatible. -Type 'number?' could not be converted into 'number' in an invariant context)" - : R"(Type - '{| x: number? |}' -could not be converted into - '{| x: number |}' -caused by: - Property 'x' is not compatible. Type 'number?' could not be converted into 'number' in an invariant context)"; CHECK_EQ(expected, toString(result.errors[0])); } diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 7dfebb7e..a1b04daa 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -11,13 +11,13 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauIntersectNotNil) -LUAU_FASTFLAG(LuauSkipNoRefineDuringRefinement) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable) LUAU_FASTFLAG(LuauSimplyRefineNotNil) LUAU_FASTFLAG(LuauWeakNilRefinementType) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauSimplificationTableExternType) +LUAU_FASTFLAG(LuauAvoidDoubleNegation) using namespace Luau; @@ -1243,6 +1243,8 @@ TEST_CASE_FIXTURE(Fixture, "apply_refinements_on_astexprindexexpr_whose_subscrip TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x") { + ScopedFastFlag _{FFlag::LuauAvoidDoubleNegation, true}; + CheckResult result = check(R"( type T = {tag: "missing", x: nil} | {tag: "exists", x: string} @@ -1259,9 +1261,12 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x") if (FFlag::LuauSolverV2) { - // CLI-115281 Types produced by refinements do not consistently get simplified + // CLI-115281 Types produced by refinements do not consistently get + // simplified. Sometimes this is due to not refining at the correct + // time, sometimes this is due to hitting the simplifier rather than + // normalization. CHECK("{ tag: \"exists\", x: string } & { x: ~(false?) }" == toString(requireTypeAtPosition({5, 28}))); - CHECK("({ tag: \"exists\", x: string } & { x: ~~(false?) }) | { tag: \"missing\", x: nil }" == toString(requireTypeAtPosition({7, 28}))); + CHECK(R"(({ tag: "exists", x: string } & { x: false? }) | ({ tag: "missing", x: nil } & { x: false? }))" == toString(requireTypeAtPosition({7, 28}))); } else { @@ -2552,8 +2557,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "truthy_refinement_on_generic") TEST_CASE_FIXTURE(Fixture, "truthy_call_of_function_with_table_value_as_argument_should_not_refine_value_as_never") { - ScopedFastFlag sff{FFlag::LuauSkipNoRefineDuringRefinement, true}; - CheckResult result = check(R"( type Item = {} diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index 9d3e0338..32c522e0 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -8,7 +8,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauPropagateExpectedTypesForCalls) -LUAU_FASTFLAG(LuauImproveTypePathsInErrors) TEST_SUITE_BEGIN("TypeSingletons"); @@ -387,7 +386,7 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { const std::string expected = "Type\n\t" "'{ [\"\\n\"]: number }'" @@ -395,13 +394,6 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes") "'{ [\"<>\"]: number }'"; CHECK(expected == toString(result.errors[0])); } - else if (FFlag::LuauSolverV2) - CHECK( - "Type\n" - " '{ [\"\\n\"]: number }'\n" - "could not be converted into\n" - " '{ [\"<>\"]: number }'" == toString(result.errors[0]) - ); else CHECK_EQ( R"(Table type '{ ["\n"]: number }' not compatible with type '{| ["<>"]: number |}' because the former is missing field '<>')", @@ -471,23 +463,12 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauImproveTypePathsInErrors) - { - const std::string expectedError = "Type\n\t" - "'{ result: string, success: boolean }'" - "\ncould not be converted into\n\t" - "'Err | Ok'"; - CHECK(toString(result.errors[0]) == expectedError); - } - else - { - const std::string expectedError = R"(Type - '{ result: string, success: boolean }' -could not be converted into - 'Err | Ok')"; + const std::string expectedError = "Type\n\t" + "'{ result: string, success: boolean }'" + "\ncould not be converted into\n\t" + "'Err | Ok'"; + CHECK(toString(result.errors[0]) == expectedError); - CHECK(toString(result.errors[0]) == expectedError); - } } TEST_CASE_FIXTURE(Fixture, "if_then_else_expression_singleton_options") diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 3d884e8c..446ad606 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -24,12 +24,10 @@ LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) -LUAU_FASTFLAG(LuauImproveTypePathsInErrors) -LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes) -LUAU_FASTFLAG(LuauBidirectionalFailsafe) LUAU_FASTFLAG(LuauBidirectionalInferenceElideAssert) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck) +LUAU_FASTFLAG(LuauReportSubtypingErrors) TEST_SUITE_BEGIN("TableTests"); @@ -924,7 +922,7 @@ TEST_CASE_FIXTURE(Fixture, "sealed_table_indexers_must_unify") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { CHECK( "Type pack '{number}' could not be converted into '{string}'; \n" @@ -933,14 +931,6 @@ TEST_CASE_FIXTURE(Fixture, "sealed_table_indexers_must_unify") "and `number` is not exactly `string`" == toString(result.errors[0]) ); } - else if (FFlag::LuauSolverV2) - { - // CLI-114879 - Error path reporting is not great - CHECK( - toString(result.errors[0]) == - "Type pack '{number}' could not be converted into '{string}'; at [0].indexResult(), number is not exactly string" - ); - } else CHECK_MESSAGE(nullptr != get(result.errors[0]), "Expected a TypeMismatch but got " << result.errors[0]); } @@ -1816,7 +1806,7 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multi LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { CHECK_EQ( "Type pack '{ x: number }' could not be converted into '{ x: number, y: number, z: number }'; \n" @@ -1825,14 +1815,6 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multi toString(result.errors[0]) ); } - else if (FFlag::LuauSolverV2) - { - CHECK_EQ( - "Type pack '{ x: number }' could not be converted into '{ x: number, y: number, z: number }';" - " at [0], { x: number } is not a subtype of { x: number, y: number, z: number }", - toString(result.errors[0]) - ); - } else { MissingProperties* mp = get(result.errors[0]); @@ -2455,7 +2437,7 @@ local b: B = a LUAU_REQUIRE_ERRORS(result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { CHECK( "Type 'A' could not be converted into 'B'; \n" @@ -2463,8 +2445,6 @@ local b: B = a "`string`" == toString(result.errors.at(0)) ); } - else if (FFlag::LuauSolverV2) - CHECK(toString(result.errors.at(0)) == R"(Type 'A' could not be converted into 'B'; at [read "y"], number is not exactly string)"); else { const std::string expected = R"(Type 'A' could not be converted into 'B' @@ -2490,7 +2470,7 @@ local b: B = a LUAU_REQUIRE_ERRORS(result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { CHECK( "Type 'A' could not be converted into 'B'; \n" @@ -2498,8 +2478,6 @@ local b: B = a "`string`" == toString(result.errors.at(0)) ); } - else if (FFlag::LuauSolverV2) - CHECK(toString(result.errors.at(0)) == R"(Type 'A' could not be converted into 'B'; at [read "b"][read "y"], number is not exactly string)"); else { const std::string expected = R"(Type 'A' could not be converted into 'B' @@ -2525,8 +2503,8 @@ local b2 = setmetatable({ x = 2, y = 4 }, { __call = function(s, t) end }); local c2: typeof(a2) = b2 )"); - const std::string expected1 = (FFlag::LuauImproveTypePathsInErrors) ? - R"(Type 'b1' could not be converted into 'a1' + const std::string expected1 = + R"(Type 'b1' could not be converted into 'a1' caused by: Type '{ x: number, y: string }' @@ -2534,18 +2512,9 @@ could not be converted into '{ x: number, y: number }' caused by: Property 'y' is not compatible. -Type 'string' could not be converted into 'number' in an invariant context)" - : R"(Type 'b1' could not be converted into 'a1' -caused by: - Type - '{ x: number, y: string }' -could not be converted into - '{ x: number, y: number }' -caused by: - Property 'y' is not compatible. Type 'string' could not be converted into 'number' in an invariant context)"; - const std::string expected2 = (FFlag::LuauImproveTypePathsInErrors) ? - R"(Type 'b2' could not be converted into 'a2' + const std::string expected2 = + R"(Type 'b2' could not be converted into 'a2' caused by: Type '{ __call: (a, b) -> () }' @@ -2556,21 +2525,9 @@ caused by: Type '(a, b) -> ()' could not be converted into - '(a) -> ()'; different number of generic type parameters)" - : R"(Type 'b2' could not be converted into 'a2' -caused by: - Type - '{ __call: (a, b) -> () }' -could not be converted into - '{ __call: (a) -> () }' -caused by: - Property '__call' is not compatible. -Type - '(a, b) -> ()' -could not be converted into - '(a) -> ()'; different number of generic type parameters)"; + '(a) -> ()'; different number of generic type parameters)"; - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { // The assignment of c2 to b2 is, surprisingly, allowed under the new // solver for two reasons: @@ -2586,18 +2543,6 @@ could not be converted into "`string` is not exactly `number`" == toString(result.errors[0]) ); } - else if (FFlag::LuauSolverV2) - { - // The assignment of c2 to b2 is, surprisingly, allowed under the new - // solver for two reasons: - // - // First, both of the __call functions have hidden ...any arguments - // because their exact definition is available. - // - // Second, nil <: unknown, so we consider that parameter to be optional. - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK("Type 'b1' could not be converted into 'a1'; at table()[read \"y\"], string is not exactly number" == toString(result.errors[0])); - } else if (FFlag::LuauInstantiateInSubtyping) { LUAU_REQUIRE_ERROR_COUNT(2, result); @@ -2606,15 +2551,15 @@ could not be converted into const std::string expected3 = R"(Type 'b2' could not be converted into 'a2' caused by: Type - '{ __call: (a, b) -> () }' + '{ __call: (a, b) -> () }' could not be converted into - '{ __call: (a) -> () }' + '{ __call: (a) -> () }' caused by: Property '__call' is not compatible. Type - '(a, b) -> ()' + '(a, b) -> ()' could not be converted into - '(a) -> ()'; different number of generic type parameters)"; + '(a) -> ()'; different number of generic type parameters)"; CHECK_EQ(expected2, toString(result.errors[1])); } @@ -2626,15 +2571,15 @@ could not be converted into std::string expected3 = R"(Type 'b2' could not be converted into 'a2' caused by: Type - '{ __call: (a, b) -> () }' + '{ __call: (a, b) -> () }' could not be converted into - '{ __call: (a) -> () }' + '{ __call: (a) -> () }' caused by: Property '__call' is not compatible. Type - '(a, b) -> ()' + '(a, b) -> ()' could not be converted into - '(a) -> ()'; different number of generic type parameters)"; + '(a) -> ()'; different number of generic type parameters)"; CHECK_EQ(expected3, toString(result.errors[1])); } } @@ -2651,7 +2596,7 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key") LUAU_REQUIRE_ERRORS(result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { CHECK( "Type 'A' could not be converted into 'B'; \n" @@ -2659,10 +2604,6 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key") toString(result.errors[0]) ); } - else if (FFlag::LuauSolverV2) - { - CHECK("Type 'A' could not be converted into 'B'; at indexer(), number is not exactly string" == toString(result.errors[0])); - } else { const std::string expected = R"(Type 'A' could not be converted into 'B' @@ -2685,7 +2626,7 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value") LUAU_REQUIRE_ERRORS(result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { CHECK( "Type 'A' could not be converted into 'B'; \n" @@ -2693,10 +2634,6 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value") "`string`" == toString(result.errors[0]) ); } - else if (FFlag::LuauSolverV2) - { - CHECK("Type 'A' could not be converted into 'B'; at indexResult(), number is not exactly string" == toString(result.errors[0])); - } else { const std::string expected = R"(Type 'A' could not be converted into 'B' @@ -2742,17 +2679,12 @@ local y: number = tmp.p.y LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) CHECK( "Type 'tmp' could not be converted into 'HasSuper'; \n" "this is because accessing `p` results in `{ x: number, y: number }` in the former type and `Super` in the latter type, and `{ x: " "number, y: number }` is not exactly `Super`" == toString(result.errors[0]) ); - else if (FFlag::LuauSolverV2) - CHECK( - "Type 'tmp' could not be converted into 'HasSuper'; at [read \"p\"], { x: number, y: number } is not exactly Super" == - toString(result.errors[0]) - ); else { const std::string expected = R"(Type 'tmp' could not be converted into 'HasSuper' @@ -3712,21 +3644,13 @@ TEST_CASE_FIXTURE(Fixture, "mixed_tables_with_implicit_numbered_keys") local t: { [string]: number } = { 5, 6, 7 } )"); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { std::string expected = "Type '{number}' could not be converted into '{ [string]: number }'; \n" "this is because the index type is `number` in the former type and `string` in the latter type, and `number` is not exactly `string`"; CHECK(toString(result.errors[0]) == expected); } - else if (FFlag::LuauSolverV2) - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK( - "Type '{number}' could not be converted into '{ [string]: number }'; at indexer(), number is not exactly string" == - toString(result.errors[0]) - ); - } else { LUAU_REQUIRE_ERROR_COUNT(3, result); @@ -3824,6 +3748,8 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_a_subtype_of_a_compatible_polymorphic_shap TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type") { + ScopedFastFlag sffs[] = {{FFlag::LuauNonReentrantGeneralization2, true}, {FFlag::LuauReportSubtypingErrors, true}}; + CheckResult result = check(R"( local function f(s) return s:absolutely_no_scalar_has_this_method() @@ -3861,7 +3787,7 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_ CHECK("typeof(string)" == toString(tm4->givenType)); CHECK("t1 where t1 = { read absolutely_no_scalar_has_this_method: (t1) -> (a...) }" == toString(tm4->wantedType)); } - else if (FFlag::LuauImproveTypePathsInErrors) + else { LUAU_REQUIRE_ERROR_COUNT(3, result); @@ -3886,36 +3812,6 @@ could not be converted into caused by: Not all union options are compatible. Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' -caused by: - The former's metatable does not satisfy the requirements. -Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')"; - CHECK_EQ(expected3, toString(result.errors[2])); - } - else - { - LUAU_REQUIRE_ERROR_COUNT(3, result); - - const std::string expected1 = - R"(Type 'string' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' -caused by: - The former's metatable does not satisfy the requirements. -Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')"; - CHECK_EQ(expected1, toString(result.errors[0])); - - const std::string expected2 = - R"(Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' -caused by: - The former's metatable does not satisfy the requirements. -Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')"; - CHECK_EQ(expected2, toString(result.errors[1])); - - const std::string expected3 = R"(Type - '"bar" | "baz"' -could not be converted into - 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' -caused by: - Not all union options are compatible. -Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' caused by: The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')"; @@ -4467,25 +4363,13 @@ TEST_CASE_FIXTURE(Fixture, "identify_all_problematic_table_fields") LUAU_REQUIRE_ERROR_COUNT(1, result); - - if (FFlag::LuauImproveTypePathsInErrors) - { - std::string expected = - "Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; \n" - "this is because \n\t" - " * accessing `a` results in `string` in the former type and `number` in the latter type, and `string` is not exactly `number`\n\t" - " * accessing `b` results in `boolean` in the former type and `string` in the latter type, and `boolean` is not exactly `string`\n\t" - " * accessing `c` results in `number` in the former type and `boolean` in the latter type, and `number` is not exactly `boolean`"; - CHECK(toString(result.errors[0]) == expected); - } - else - { - std::string expected = - "Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; at [read \"a\"], string is not exactly number" - "\n\tat [read \"b\"], boolean is not exactly string" - "\n\tat [read \"c\"], number is not exactly boolean"; - CHECK(toString(result.errors[0]) == expected); - } + std::string expected = + "Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; \n" + "this is because \n\t" + " * accessing `a` results in `string` in the former type and `number` in the latter type, and `string` is not exactly `number`\n\t" + " * accessing `b` results in `boolean` in the former type and `string` in the latter type, and `boolean` is not exactly `string`\n\t" + " * accessing `c` results in `number` in the former type and `boolean` in the latter type, and `number` is not exactly `boolean`"; + CHECK(toString(result.errors[0]) == expected); } TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported") @@ -5125,29 +5009,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "subtyping_with_a_metatable_table_path") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauImproveTypePathsInErrors) - { - CHECK_EQ( - "Type pack '{ @metatable { }, { } & { } }' could not be converted into 'Class'; \n" - "this is because \n\t" - " * in the 1st entry in the type pack, the metatable portion is `{ }` in the former type and `nil` in the latter type, and `{ }` " - "is not a subtype of `nil`\n\t" - " * in the 1st entry in the type pack, the table portion has the 1st component of the intersection as `{ }` and in the 1st entry " - "in the type pack, the table portion is `nil`, and `{ }` is not a subtype of `nil`\n\t" - " * in the 1st entry in the type pack, the table portion has the 2nd component of the intersection as `{ }` and in the 1st entry " - "in the type pack, the table portion is `nil`, and `{ }` is not a subtype of `nil`", - toString(result.errors[0]) - ); - } - else - { - CHECK_EQ( - "Type pack '{ @metatable { }, { } & { } }' could not be converted into 'Class'; at [0].metatable(), { } is not a subtype of nil\n" - "\ttype { @metatable { }, { } & { } }[0].table()[0] ({ }) is not a subtype of Class[0].table() (nil)\n" - "\ttype { @metatable { }, { } & { } }[0].table()[1] ({ }) is not a subtype of Class[0].table() (nil)", - toString(result.errors[0]) - ); - } + CHECK_EQ( + "Type pack '{ @metatable { }, { } & { } }' could not be converted into 'Class'; \n" + "this is because \n\t" + " * in the 1st entry in the type pack, the metatable portion is `{ }` in the former type and `nil` in the latter type, and `{ }` " + "is not a subtype of `nil`\n\t" + " * in the 1st entry in the type pack, the table portion has the 1st component of the intersection as `{ }` and in the 1st entry " + "in the type pack, the table portion is `nil`, and `{ }` is not a subtype of `nil`\n\t" + " * in the 1st entry in the type pack, the table portion has the 2nd component of the intersection as `{ }` and in the 1st entry " + "in the type pack, the table portion is `nil`, and `{ }` is not a subtype of `nil`", + toString(result.errors[0]) + ); } TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type") @@ -5178,10 +5050,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type") TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true}, - }; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; CheckResult result = check(R"( local function doTheThing(_: { [string]: unknown }) end @@ -5221,10 +5090,7 @@ TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager") TEST_CASE_FIXTURE(BuiltinsFixture, "magic_functions_bidirectionally_inferred") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true}, - }; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; CheckResult result = check(R"( local function getStuff(): (string, number, string) @@ -5484,10 +5350,7 @@ TEST_CASE_FIXTURE(Fixture, "oss_1543_optional_generic_param") TEST_CASE_FIXTURE(Fixture, "missing_fields_bidirectional_inference") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true}, - }; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; auto result = check(R"( type Book = { title: string, author: string } @@ -5515,10 +5378,7 @@ TEST_CASE_FIXTURE(Fixture, "missing_fields_bidirectional_inference") TEST_CASE_FIXTURE(Fixture, "generic_index_syntax_bidirectional_infer_with_tables") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true}, - }; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; auto result = check((R"( local function getStatus(): string @@ -5572,7 +5432,6 @@ TEST_CASE_FIXTURE(Fixture, "bigger_nested_table_causes_big_type_error") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauImproveTypePathsInErrors, true}, }; auto result = check(R"( @@ -5606,22 +5465,22 @@ TEST_CASE_FIXTURE(Fixture, "bigger_nested_table_causes_big_type_error") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - std::string expected = "Type\n\t" - "'{Dir | File | { children: ({Dir | File | { content: string?, path: string, type: \"file\" }} | {Dir | File})?, name: " - "string, type: \"dir\" }}'" - "\ncould not be converted into\n\t" - "'DirectoryChildren'; \n" - "this is because in the result of indexing has the 3rd component of the union as `{ children: ({Dir | File | { content: " - "string?, path: string, type: \"file\" }} | {Dir | File})?, name: string, type: \"dir\" }` and the result of indexing is " - "`Dir | File`, and `{ children: ({Dir | File | { content: string?, path: string, type: \"file\" }} | {Dir | File})?, " - "name: string, type: \"dir\" }` is not exactly `Dir | File`"; + std::string expected = + "Type\n\t" + "'{Dir | File | { children: ({Dir | File | { content: string?, path: string, type: \"file\" }} | {Dir | File})?, name: " + "string, type: \"dir\" }}'" + "\ncould not be converted into\n\t" + "'DirectoryChildren'; \n" + "this is because in the result of indexing has the 3rd component of the union as `{ children: ({Dir | File | { content: " + "string?, path: string, type: \"file\" }} | {Dir | File})?, name: string, type: \"dir\" }` and the result of indexing is " + "`Dir | File`, and `{ children: ({Dir | File | { content: string?, path: string, type: \"file\" }} | {Dir | File})?, " + "name: string, type: \"dir\" }` is not exactly `Dir | File`"; CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "unsafe_bidirectional_mutation") { - ScopedFastFlag _{FFlag::LuauBidirectionalFailsafe, true}; // It's kind of suspect that we allow multiple definitions of keys in // a single table. LUAU_REQUIRE_NO_ERRORS(check(R"( @@ -5641,7 +5500,6 @@ TEST_CASE_FIXTURE(Fixture, "unsafe_bidirectional_mutation") TEST_CASE_FIXTURE(BuiltinsFixture, "function_call_in_indexer_with_compound_assign") { - ScopedFastFlag _{FFlag::LuauBidirectionalFailsafe, true}; // This has a bunch of errors, we really just need it to not crash / assert. std::ignore = check(R"( --!strict @@ -5676,10 +5534,8 @@ TEST_CASE_FIXTURE(Fixture, "stop_refining_new_table_indices_for_non_primitive_ta TEST_CASE_FIXTURE(Fixture, "fuzz_match_literal_type_crash_again") { - ScopedFastFlag sffs[] = { - {FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true}, - {FFlag::LuauBidirectionalInferenceElideAssert, true}, - }; + ScopedFastFlag _{FFlag::LuauBidirectionalInferenceElideAssert, true}; + CheckResult result = check(R"( function f(_: { [string]: {unknown}} ) end f( @@ -5694,8 +5550,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_match_literal_type_crash_again") TEST_CASE_FIXTURE(Fixture, "type_mismatch_in_dict") { - ScopedFastFlag sff{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true}; - CheckResult result = check(R"( --!strict local dict: {[string]: boolean} = { diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index a333073e..a7c37341 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -24,20 +24,19 @@ LUAU_FASTINT(LuauNormalizeCacheLimit) LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) -LUAU_FASTFLAG(LuauAstTypeGroup3) LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals) -LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauTypeCheckerAcceptNumberConcats) LUAU_FASTFLAG(LuauPreprocessTypestatedArgument) LUAU_FASTFLAG(LuauCacheInferencePerAstExpr) -LUAU_FASTFLAG(LuauLimitIterationWhenCheckingArgumentCounts) -LUAU_FASTFLAG(LuauMagicFreezeCheckBlocked) +LUAU_FASTFLAG(LuauMagicFreezeCheckBlocked2) LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauHasPropProperBlock) LUAU_FASTFLAG(LuauStringPartLengthLimit) LUAU_FASTFLAG(LuauSimplificationRecheckAssumption) LUAU_FASTFLAG(LuauAlwaysResolveAstTypes) +LUAU_FASTFLAG(LuauReportSubtypingErrors) +LUAU_FASTFLAG(LuauAvoidDoubleNegation) using namespace Luau; @@ -1144,8 +1143,12 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error") end )"); - if (FFlag::LuauInstantiateInSubtyping && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauInstantiateInSubtyping) { + // though this didn't error before the flag, it seems as though it should error since fields of a table are invariant. + // the user's intent would likely be that these "method" fields would be read-only, but without an annotation, accepting this should be + // unsound. + LUAU_REQUIRE_ERROR_COUNT(1, result); const std::string expected = "Type 'Policies' from 'MainModule' could not be converted into 'Policies' from 'MainModule'" @@ -1166,31 +1169,6 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error") "Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName'"; CHECK_EQ(expected, toString(result.errors[0])); } - else if (FFlag::LuauInstantiateInSubtyping) - { - // though this didn't error before the flag, it seems as though it should error since fields of a table are invariant. - // the user's intent would likely be that these "method" fields would be read-only, but without an annotation, accepting this should be - // unsound. - - LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type 'Policies' from 'MainModule' could not be converted into 'Policies' from 'MainModule' -caused by: - Property 'getStoreFieldName' is not compatible. -Type - '(Policies, FieldSpecifier & {| from: number? |}) -> (a, b...)' -could not be converted into - '(Policies, FieldSpecifier) -> string' -caused by: - Argument #2 type is not compatible. -Type - 'FieldSpecifier' -could not be converted into - 'FieldSpecifier & {| from: number? |}' -caused by: - Not all intersection parts are compatible. -Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName')"; - CHECK_EQ(expected, toString(result.errors[0])); - } else { LUAU_REQUIRE_NO_ERRORS(result); @@ -1235,19 +1213,27 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer") if (FFlag::LuauSolverV2) { - CHECK(3 == result.errors.size()); - if (FFlag::LuauAstTypeGroup3) + if (FFlag::LuauReportSubtypingErrors) + { + CHECK(4 == result.errors.size()); CHECK(Location{{2, 22}, {2, 42}} == result.errors[0].location); + CHECK(Location{{3, 22}, {3, 42}} == result.errors[1].location); + CHECK(Location{{3, 45}, {3, 46}} == result.errors[2].location); + CHECK(Location{{3, 22}, {3, 41}} == result.errors[3].location); + + for (const TypeError& e : result.errors) + CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(e)); + } else - CHECK(Location{{2, 22}, {2, 41}} == result.errors[0].location); - CHECK(Location{{3, 22}, {3, 42}} == result.errors[1].location); - if (FFlag::LuauAstTypeGroup3) + { + CHECK(3 == result.errors.size()); + CHECK(Location{{2, 22}, {2, 42}} == result.errors[0].location); + CHECK(Location{{3, 22}, {3, 42}} == result.errors[1].location); CHECK(Location{{3, 22}, {3, 41}} == result.errors[2].location); - else - CHECK(Location{{3, 23}, {3, 40}} == result.errors[2].location); - CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[0])); - CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[1])); - CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[2])); + CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[0])); + CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[1])); + CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[2])); + } } else { @@ -1994,7 +1980,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_table_freeze_constraint_solving") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauMagicFreezeCheckBlocked, true} + {FFlag::LuauMagicFreezeCheckBlocked2, true} }; LUAU_REQUIRE_NO_ERRORS(check(R"( local f = table.freeze @@ -2006,7 +1992,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_assert_table_freeze_constraint_solving" { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauMagicFreezeCheckBlocked, true} + {FFlag::LuauMagicFreezeCheckBlocked2, true} }; // This is the original fuzzer version of the above issue. CheckResult results = check(R"( @@ -2029,7 +2015,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_unification_aborts_eventually" * doct ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, false}, {FFlag::LuauInstantiateInSubtyping, true}, - {FFlag::LuauLimitIterationWhenCheckingArgumentCounts, true}, }; ScopedFastInt sfi{FInt::LuauTypeInferTypePackLoopLimit, 100}; @@ -2131,4 +2116,55 @@ local _ )")); } +TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_missing_follow_table_freeze") +{ + ScopedFastFlag _{FFlag::LuauMagicFreezeCheckBlocked2, true}; + + LUAU_REQUIRE_ERRORS(check(R"( + if _:freeze(_)[_][_] then + else + do end + end + if _:freeze((nil))[_][_] then + else + do end + end + _ = table,true,_(lower) + do end + _:freeze()[_] += {} > _ + )")); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_avoid_double_negation" * doctest::timeout(0.5)) +{ + ScopedFastFlag _{FFlag::LuauAvoidDoubleNegation, true}; + // We don't care about errors, only that we don't OOM during typechecking. + LUAU_REQUIRE_ERRORS(check(R"( +local _ = _ +repeat +do end +while 0 do +do +_ = _[0] +_._ *= _ +end +if _ then +elseif "" then +end +_ = _[0] +_ = "" +end +_ = "" +until _ +while false do +do +_ = _[0] +do end +end +_ = "" +return if _ then _,_ +end + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.typePacks.test.cpp b/tests/TypeInfer.typePacks.test.cpp index f323c407..231d537b 100644 --- a/tests/TypeInfer.typePacks.test.cpp +++ b/tests/TypeInfer.typePacks.test.cpp @@ -12,8 +12,9 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauInstantiateInSubtyping) -LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) +LUAU_FASTFLAG(LuauReportSubtypingErrors) +LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall) TEST_SUITE_BEGIN("TypePackTests"); @@ -957,43 +958,25 @@ a = b LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { - const std::string expected = "Type\n\t" - "'() -> (number, ...boolean)'" - "\ncould not be converted into\n\t" - "'() -> (number, ...string)'; \n" - "this is because it returns a tail of the variadic `boolean` in the former type and `string` in the latter " - "type, and `boolean` is not a subtype of `string`"; + const std::string expected = + "Type\n\t" + "'() -> (number, ...boolean)'" + "\ncould not be converted into\n\t" + "'() -> (number, ...string)'; \n" + "this is because it returns a tail of the variadic `boolean` in the former type and `string` in the latter " + "type, and `boolean` is not a subtype of `string`"; CHECK(expected == toString(result.errors[0])); } - else if (FFlag::LuauSolverV2) - { - const std::string expected = "Type\n" - " '() -> (number, ...boolean)'\n" - "could not be converted into\n" - " '() -> (number, ...string)'; at returns().tail().variadic(), boolean is not a subtype of string"; - - CHECK(expected == toString(result.errors[0])); - } - else if (FFlag::LuauImproveTypePathsInErrors) + else { const std::string expected = R"(Type '() -> (number, ...boolean)' could not be converted into '() -> (number, ...string)' -caused by: - Type 'boolean' could not be converted into 'string')"; - CHECK_EQ(expected, toString(result.errors[0])); - } - else - { - const std::string expected = R"(Type - '() -> (number, ...boolean)' -could not be converted into - '() -> (number, ...string)' caused by: Type 'boolean' could not be converted into 'string')"; CHECK_EQ(expected, toString(result.errors[0])); @@ -1077,6 +1060,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks") TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks2") { + ScopedFastFlag sffs[] = {{FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauTrackInferredFunctionTypeFromCall, true}}; + CheckResult result = check(R"( function _(l0:((typeof((pcall)))|((((t0)->())|(typeof(-67108864)))|(any)))|(any),...):(((typeof(0))|(any))|(any),typeof(-67108864),any) xpcall(_,_,_) @@ -1121,16 +1106,11 @@ TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments_free") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) CHECK( toString(result.errors.at(0)) == "Type pack '...number' could not be converted into 'boolean'; \nthis is because it has a tail of " "`...number`, which is not a subtype of `boolean`" ); - else if (FFlag::LuauSolverV2) - CHECK( - toString(result.errors.at(0)) == - "Type pack '...number' could not be converted into 'boolean'; type ...number.tail() (...number) is not a subtype of boolean (boolean)" - ); else CHECK_EQ(toString(result.errors[0]), "Type 'number' could not be converted into 'boolean'"); } diff --git a/tests/TypeInfer.typestates.test.cpp b/tests/TypeInfer.typestates.test.cpp index 92015370..3f08652a 100644 --- a/tests/TypeInfer.typestates.test.cpp +++ b/tests/TypeInfer.typestates.test.cpp @@ -7,6 +7,8 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget) LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow) +LUAU_FASTFLAG(LuauReportSubtypingErrors) +LUAU_FASTFLAG(LuauNonReentrantGeneralization2) using namespace Luau; @@ -407,9 +409,7 @@ TEST_CASE_FIXTURE(TypeStateFixture, "prototyped_recursive_functions") TEST_CASE_FIXTURE(BuiltinsFixture, "prototyped_recursive_functions_but_has_future_assignments") { - // early return if the flag isn't set since this is blocking gated commits - if (!FFlag::LuauSolverV2) - return; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauNonReentrantGeneralization2, true}}; CheckResult result = check(R"( local f @@ -846,4 +846,4 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assign_in_an_if_branch_without_else") CHECK_EQ("string?", toString(requireTypeAtPosition({9, 14}))); } -TEST_SUITE_END(); \ No newline at end of file +TEST_SUITE_END(); diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 17d35fd1..7f1a888b 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -11,7 +11,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) -LUAU_FASTFLAG(LuauImproveTypePathsInErrors) TEST_SUITE_BEGIN("UnionTypes"); @@ -541,7 +540,7 @@ end LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { CHECK_EQ( @@ -553,16 +552,6 @@ end " * the 3rd component of the union is `Z`, which is not a subtype of `{ w: number }`" ); } - else if (FFlag::LuauSolverV2) - { - CHECK_EQ( - toString(result.errors[0]), - "Type 'X | Y | Z' could not be converted into '{ w: number }'; type X | Y | Z[0] (X) is not a subtype " - "of { w: number } ({ w: number })\n\t" - "type X | Y | Z[1] (Y) is not a subtype of { w: number } ({ w: number })\n\t" - "type X | Y | Z[2] (Z) is not a subtype of { w: number } ({ w: number })" - ); - } else { CHECK_EQ(toString(result.errors[0]), R"(Type 'X | Y | Z' could not be converted into '{| w: number |}' @@ -683,22 +672,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect") LUAU_REQUIRE_ERROR_COUNT(1, result); // NOTE: union normalization will improve this message - if (FFlag::LuauImproveTypePathsInErrors) - { - const std::string expected = "Type\n\t" - "'(string) -> number'" - "\ncould not be converted into\n\t" - "'((number) -> string) | ((number) -> string)'; none of the union options are compatible"; - CHECK_EQ(expected, toString(result.errors[0])); - } - else - { - const std::string expected = R"(Type - '(string) -> number' -could not be converted into - '((number) -> string) | ((number) -> string)'; none of the union options are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } + const std::string expected = + "Type\n\t" + "'(string) -> number'" + "\ncould not be converted into\n\t" + "'((number) -> string) | ((number) -> string)'; none of the union options are compatible"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_true_and_false") @@ -785,22 +764,12 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauImproveTypePathsInErrors) - { - const std::string expected = "Type\n\t" - "'(number, a...) -> (number?, a...)'" - "\ncould not be converted into\n\t" - "'((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible"; - CHECK_EQ(expected, toString(result.errors[0])); - } - else - { - const std::string expected = R"(Type - '(number, a...) -> (number?, a...)' -could not be converted into - '((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } + const std::string expected = + "Type\n\t" + "'(number, a...) -> (number?, a...)'" + "\ncould not be converted into\n\t" + "'((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities") @@ -816,22 +785,12 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauImproveTypePathsInErrors) - { - const std::string expected = "Type\n\t" - "'(number) -> number?'" - "\ncould not be converted into\n\t" - "'((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible"; - CHECK_EQ(expected, toString(result.errors[0])); - } - else - { - const std::string expected = R"(Type - '(number) -> number?' -could not be converted into - '((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } + const std::string expected = + "Type\n\t" + "'(number) -> number?'" + "\ncould not be converted into\n\t" + "'((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities") @@ -847,22 +806,12 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauImproveTypePathsInErrors) - { - const std::string expected = "Type\n\t" - "'() -> number | string'" - "\ncould not be converted into\n\t" - "'(() -> (string, string)) | (() -> number)'; none of the union options are compatible"; - CHECK_EQ(expected, toString(result.errors[0])); - } - else - { - const std::string expected = R"(Type - '() -> number | string' -could not be converted into - '(() -> (string, string)) | (() -> number)'; none of the union options are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } + const std::string expected = + "Type\n\t" + "'() -> number | string'" + "\ncould not be converted into\n\t" + "'(() -> (string, string)) | (() -> number)'; none of the union options are compatible"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics") @@ -878,22 +827,12 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauImproveTypePathsInErrors) - { - const std::string expected = "Type\n\t" - "'(...nil) -> (...number?)'" - "\ncould not be converted into\n\t" - "'((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible"; - CHECK_EQ(expected, toString(result.errors[0])); - } - else - { - const std::string expected = R"(Type - '(...nil) -> (...number?)' -could not be converted into - '((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } + const std::string expected = + "Type\n\t" + "'(...nil) -> (...number?)'" + "\ncould not be converted into\n\t" + "'((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics") @@ -906,22 +845,16 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors) + if (FFlag::LuauSolverV2) { - const std::string expected = "Type\n\t" - "'(number) -> ()'" - "\ncould not be converted into\n\t" - "'((...number?) -> ()) | ((number?) -> ())'"; + const std::string expected = + "Type\n\t" + "'(number) -> ()'" + "\ncould not be converted into\n\t" + "'((...number?) -> ()) | ((number?) -> ())'"; CHECK(expected == toString(result.errors[0])); } - else if (FFlag::LuauSolverV2) - { - CHECK(R"(Type - '(number) -> ()' -could not be converted into - '((...number?) -> ()) | ((number?) -> ())')" == toString(result.errors[0])); - } - else if (FFlag::LuauImproveTypePathsInErrors) + else { const std::string expected = R"(Type '(number) -> ()' @@ -929,14 +862,6 @@ could not be converted into '((...number?) -> ()) | ((number?) -> ())'; none of the union options are compatible)"; CHECK_EQ(expected, toString(result.errors[0])); } - else - { - const std::string expected = R"(Type - '(number) -> ()' -could not be converted into - '((...number?) -> ()) | ((number?) -> ())'; none of the union options are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } } TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics") @@ -952,22 +877,12 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauImproveTypePathsInErrors) - { - const std::string expected = "Type\n\t" - "'() -> (number?, ...number)'" - "\ncould not be converted into\n\t" - "'(() -> (...number)) | (() -> number)'; none of the union options are compatible"; - CHECK_EQ(expected, toString(result.errors[0])); - } - else - { - const std::string expected = R"(Type - '() -> (number?, ...number)' -could not be converted into - '(() -> (...number)) | (() -> number)'; none of the union options are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); - } + const std::string expected = + "Type\n\t" + "'() -> (number?, ...number)'" + "\ncould not be converted into\n\t" + "'(() -> (...number)) | (() -> number)'; none of the union options are compatible"; + CHECK_EQ(expected, toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types") diff --git a/tests/require/without_config/proxy_requirer.luau b/tests/require/without_config/proxy_requirer.luau new file mode 100644 index 00000000..ab8b6511 --- /dev/null +++ b/tests/require/without_config/proxy_requirer.luau @@ -0,0 +1,3 @@ +local result = proxyrequire("./dependency", "@"..debug.info(1, "s")) +result[#result+1] = "required into proxy_requirer" +return result