diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h index a8e1c76f..d6a4c800 100644 --- a/Analysis/include/Luau/Error.h +++ b/Analysis/include/Luau/Error.h @@ -1,11 +1,12 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once +#include "Luau/Ast.h" #include "Luau/Location.h" #include "Luau/NotNull.h" #include "Luau/Type.h" +#include "Luau/TypeIds.h" #include "Luau/Variant.h" -#include "Luau/Ast.h" #include @@ -513,6 +514,18 @@ struct RecursiveRestraintViolation } }; +// Error during subtyping when the inferred bounds of a generic type are incompatible +struct GenericBoundsMismatch +{ + std::string_view genericName; + std::vector lowerBounds; + std::vector upperBounds; + + GenericBoundsMismatch(std::string_view genericName, TypeIds lowerBoundSet, TypeIds upperBoundSet); + + bool operator==(const GenericBoundsMismatch& rhs) const; +}; + using TypeErrorData = Variant< TypeMismatch, UnknownSymbol, @@ -569,7 +582,8 @@ using TypeErrorData = Variant< GenericTypeCountMismatch, GenericTypePackCountMismatch, MultipleNonviableOverloads, - RecursiveRestraintViolation>; + RecursiveRestraintViolation, + GenericBoundsMismatch>; struct TypeErrorSummary { diff --git a/Analysis/include/Luau/Instantiation.h b/Analysis/include/Luau/Instantiation.h index b3482ecd..acca6780 100644 --- a/Analysis/include/Luau/Instantiation.h +++ b/Analysis/include/Luau/Instantiation.h @@ -7,6 +7,8 @@ #include "Luau/Unifiable.h" #include "Luau/VisitType.h" +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) + namespace Luau { @@ -93,7 +95,7 @@ struct GenericTypeFinder : TypeOnceVisitor bool found = false; GenericTypeFinder() - : TypeOnceVisitor("GenericTypeFinder") + : TypeOnceVisitor("GenericTypeFinder", FFlag::LuauExplicitSkipBoundTypes) { } diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index 648db98c..357c800c 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -68,6 +68,9 @@ struct SubtypingResult // those assumptions are recorded here. std::vector assumedConstraints; + /// If any generic bounds were invalid, report them here + std::vector genericBoundsMismatches; + SubtypingResult& andAlso(const SubtypingResult& other); SubtypingResult& orElse(const SubtypingResult& other); SubtypingResult& withBothComponent(TypePath::Component component); @@ -358,7 +361,15 @@ private: NotNull scope, SubtypingResult& original); - SubtypingResult checkGenericBounds(const SubtypingEnvironment::GenericBounds& bounds, SubtypingEnvironment& env, NotNull scope); + SubtypingResult checkGenericBounds( + const SubtypingEnvironment::GenericBounds& bounds, + SubtypingEnvironment& env, + NotNull scope, + std::string_view genericName + ); + + // TODO: Clip with LuauSubtypingReportGenericBoundMismatches + SubtypingResult checkGenericBounds_DEPRECATED(const SubtypingEnvironment::GenericBounds& bounds, SubtypingEnvironment& env, NotNull scope); static void maybeUpdateBounds( TypeId here, diff --git a/Analysis/include/Luau/TxnLog.h b/Analysis/include/Luau/TxnLog.h index 1d5e1a47..1d016cbb 100644 --- a/Analysis/include/Luau/TxnLog.h +++ b/Analysis/include/Luau/TxnLog.h @@ -112,7 +112,6 @@ struct TxnLog // If both logs talk about the same type, pack, or table, the rhs takes // priority. void concat(TxnLog rhs); - void concatAsIntersections(TxnLog rhs, NotNull arena); void concatAsUnion(TxnLog rhs, NotNull arena); // Commits the TxnLog, rebinding all type pointers to their pending states. @@ -269,8 +268,6 @@ struct TxnLog return Luau::get_if(&ty->ty) != nullptr; } - std::pair, std::vector> getChanges() const; - private: // unique_ptr is used to give us stable pointers across insertions into the // map. Otherwise, it would be really easy to accidentally invalidate the @@ -291,12 +288,6 @@ private: void popSeen(TypeOrPackId lhs, TypeOrPackId rhs); public: - // There is one spot in the code where TxnLog has to reconcile collisions - // between parallel logs. In that codepath, we have to work out which of two - // FreeTypes subsumes the other. If useScopes is false, the TypeLevel is - // used. Else we use the embedded Scope*. - bool useScopes = false; - // It is sometimes the case under DCR that we speculatively rebind // GenericTypes to other types as though they were free. We mark logs that // contain these kinds of substitutions as radioactive so that we know that diff --git a/Analysis/include/Luau/Unifier.h b/Analysis/include/Luau/Unifier.h index eb978fba..7e1ef957 100644 --- a/Analysis/include/Luau/Unifier.h +++ b/Analysis/include/Luau/Unifier.h @@ -80,9 +80,6 @@ struct Unifier bool checkInhabited = true; // Normalize types to check if they are inhabited CountMismatch::Context ctx = CountMismatch::Arg; - // If true, generics act as free types when unifying. - bool hideousFixMeGenericsAreActuallyFree = false; - UnifierSharedState& sharedState; // When the Unifier is forced to unify two blocked types (or packs), they diff --git a/Analysis/include/Luau/VisitType.h b/Analysis/include/Luau/VisitType.h index 6c03fb29..3d52f0e7 100644 --- a/Analysis/include/Luau/VisitType.h +++ b/Analysis/include/Luau/VisitType.h @@ -556,7 +556,7 @@ struct GenericTypeVisitor */ struct TypeVisitor : GenericTypeVisitor> { - explicit TypeVisitor(const std::string visitorName, bool skipBoundTypes = false) + explicit TypeVisitor(const std::string visitorName, bool skipBoundTypes) : GenericTypeVisitor{visitorName, {}, skipBoundTypes} { } @@ -565,7 +565,7 @@ struct TypeVisitor : GenericTypeVisitor> /// Visit each type under a given type. Each type will only be checked once even if there are multiple paths to it. struct TypeOnceVisitor : GenericTypeVisitor> { - explicit TypeOnceVisitor(const std::string visitorName, bool skipBoundTypes = false) + explicit TypeOnceVisitor(const std::string visitorName, bool skipBoundTypes) : GenericTypeVisitor{visitorName, DenseHashSet{nullptr}, skipBoundTypes} { } diff --git a/Analysis/src/AutocompleteCore.cpp b/Analysis/src/AutocompleteCore.cpp index 66908920..d680a31a 100644 --- a/Analysis/src/AutocompleteCore.cpp +++ b/Analysis/src/AutocompleteCore.cpp @@ -25,6 +25,7 @@ LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames) LUAU_FASTFLAG(LuauImplicitTableIndexerKeys3) +LUAU_FASTFLAGVARIABLE(LuauIncludeBreakContinueStatements) static const std::unordered_set kStatementStartingKeywords = {"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; @@ -1210,6 +1211,28 @@ static bool isBindingLegalAtCurrentPosition(const Symbol& symbol, const Binding& return binding.location == Location() || !binding.location.containsClosed(pos); } +static bool isValidBreakContinueContext(const std::vector& ancestry, Position position) +{ + LUAU_ASSERT(FFlag::LuauIncludeBreakContinueStatements); + for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it) + { + if ((*it)->is() || (*it)->is() || (*it)->is() || (*it)->is() || + (*it)->is()) + return false; + + if (auto statWhile = (*it)->as(); statWhile && statWhile->body->location.contains(position)) + return true; + else if (auto statFor = (*it)->as(); statFor && statFor->body->location.contains(position)) + return true; + else if (auto statForIn = (*it)->as(); statForIn && statForIn->body->location.contains(position)) + return true; + else if (auto statRepeat = (*it)->as(); statRepeat && statRepeat->body->location.contains(position)) + return true; + } + + return false; +} + static AutocompleteEntryMap autocompleteStatement( const Module& module, const std::vector& ancestry, @@ -1254,8 +1277,20 @@ static AutocompleteEntryMap autocompleteStatement( scope = scope->parent; } - for (const auto& kw : kStatementStartingKeywords) - result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword}); + if (FFlag::LuauIncludeBreakContinueStatements) + { + bool shouldIncludeBreakAndContinue = isValidBreakContinueContext(ancestry, position); + for (const auto& kw : kStatementStartingKeywords) + { + if ((kw != "break" && kw != "continue") || shouldIncludeBreakAndContinue) + result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword}); + } + } + else + { + for (const auto& kw : kStatementStartingKeywords) + result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword}); + } for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it) { diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 15c7a528..de8f9911 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -34,7 +34,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) -LUAU_FASTFLAGVARIABLE(LuauUpdateSetMetatableTypeSignature) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAG(LuauEmplaceNotPushBack) @@ -409,32 +408,14 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC if (frontend.getLuauSolverMode() == SolverMode::New) { - TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT}); - - if (FFlag::LuauUpdateSetMetatableTypeSignature) - { - // setmetatable(T, MT) -> setmetatable - TypeId setmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions().setmetatableFunc, {genericT, genericMT}}); - addGlobalBinding( - globals, "setmetatable", makeFunction(arena, std::nullopt, {genericT, genericMT}, {}, {genericT, genericMT}, {setmtReturn}), "@luau" - ); - } - else - { - // clang-format off - // setmetatable(T, MT) -> { @metatable MT, T } - addGlobalBinding(globals, "setmetatable", - arena.addType( - FunctionType{ - {genericT, genericMT}, - {}, - arena.addTypePack(TypePack{{genericT, genericMT}}), - arena.addTypePack(TypePack{{tMetaMT}}) - } - ), "@luau" - ); - // clang-format on - } + // setmetatable(T, MT) -> setmetatable + TypeId setmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions().setmetatableFunc, {genericT, genericMT}}); + addGlobalBinding( + globals, + "setmetatable", + makeFunction(arena, std::nullopt, {genericT, genericMT}, {}, {genericT, genericMT}, {setmtReturn}), + "@luau" + ); } else { diff --git a/Analysis/src/BuiltinTypeFunctions.cpp b/Analysis/src/BuiltinTypeFunctions.cpp index e7bba4de..8d50d31b 100644 --- a/Analysis/src/BuiltinTypeFunctions.cpp +++ b/Analysis/src/BuiltinTypeFunctions.cpp @@ -27,6 +27,7 @@ LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAGVARIABLE(LuauRefineNoRefineAlways) LUAU_FASTFLAGVARIABLE(LuauRefineDistributesOverUnions) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) namespace Luau { @@ -1063,7 +1064,7 @@ struct FindRefinementBlockers : TypeOnceVisitor DenseHashSet found{nullptr}; FindRefinementBlockers() - : TypeOnceVisitor("FindRefinementBlockers") + : TypeOnceVisitor("FindRefinementBlockers", FFlag::LuauExplicitSkipBoundTypes) { } diff --git a/Analysis/src/Constraint.cpp b/Analysis/src/Constraint.cpp index 877908d0..a7083e10 100644 --- a/Analysis/src/Constraint.cpp +++ b/Analysis/src/Constraint.cpp @@ -5,8 +5,8 @@ #include "Luau/VisitType.h" LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauForceSimplifyConstraint2) +LUAU_FASTFLAGVARIABLE(LuauExplicitSkipBoundTypes) namespace Luau { @@ -24,7 +24,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor bool traverseIntoTypeFunctions = true; explicit ReferenceCountInitializer(NotNull result) - : TypeOnceVisitor("ReferenceCountInitializer") + : TypeOnceVisitor("ReferenceCountInitializer", FFlag::LuauExplicitSkipBoundTypes) , result(result) { } @@ -178,12 +178,10 @@ TypeIds Constraint::getMaybeMutatedFreeTypes() const rci.traverse(tcc->exprType); } - if (FFlag::LuauPushFunctionTypesInFunctionStatement) + // NOTE: this should probably be in an if-else chain with the above. + if (auto pftc = get(*this)) { - if (auto pftc = get(*this)) - { - rci.traverse(pftc->functionType); - } + rci.traverse(pftc->functionType); } return types; diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 1c1395ea..e6dd494d 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -39,15 +39,17 @@ LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties) LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500) LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTracksRValueRefinements) -LUAU_FASTFLAGVARIABLE(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAGVARIABLE(LuauInferActualIfElseExprType2) LUAU_FASTFLAGVARIABLE(LuauDoNotPrototypeTableIndex) +LUAU_FASTFLAGVARIABLE(LuauTypeFunNoScopeMapRef) LUAU_FASTFLAGVARIABLE(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAGVARIABLE(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) +LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes) namespace Luau { @@ -137,7 +139,7 @@ struct HasFreeType : TypeOnceVisitor bool result = false; HasFreeType() - : TypeOnceVisitor("TypeOnceVisitor") + : TypeOnceVisitor("TypeOnceVisitor", FFlag::LuauExplicitSkipBoundTypes) { } @@ -643,7 +645,7 @@ struct FindSimplificationBlockers : TypeOnceVisitor bool found = false; FindSimplificationBlockers() - : TypeOnceVisitor("FindSimplificationBlockers") + : TypeOnceVisitor("FindSimplificationBlockers", FFlag::LuauExplicitSkipBoundTypes) { } @@ -1565,49 +1567,42 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f sig.bodyScope->rvalueRefinements[def] = sig.signature; } - if (FFlag::LuauPushFunctionTypesInFunctionStatement) + if (auto indexName = function->name->as()) { - if (auto indexName = function->name->as()) - { - auto beginProp = checkpoint(this); - auto [fn, _] = check(scope, indexName); - auto endProp = checkpoint(this); - auto pftc = addConstraint( - sig.signatureScope, - function->func->location, - PushFunctionTypeConstraint{ - fn, - sig.signature, - NotNull{function->func}, - /* isSelf */ indexName->op == ':', - } - ); - forEachConstraint( - beginProp, - endProp, - this, - [pftc](const ConstraintPtr& c) - { - pftc->dependencies.emplace_back(c.get()); - } - ); - auto beginBody = checkpoint(this); - checkFunctionBody(sig.bodyScope, function->func); - auto endBody = checkpoint(this); - forEachConstraint( - beginBody, - endBody, - this, - [pftc](const ConstraintPtr& c) - { - c->dependencies.push_back(pftc); - } - ); - } - else - { - checkFunctionBody(sig.bodyScope, function->func); - } + auto beginProp = checkpoint(this); + auto [fn, _] = check(scope, indexName); + auto endProp = checkpoint(this); + auto pftc = addConstraint( + sig.signatureScope, + function->func->location, + PushFunctionTypeConstraint{ + fn, + sig.signature, + NotNull{function->func}, + /* isSelf */ indexName->op == ':', + } + ); + forEachConstraint( + beginProp, + endProp, + this, + [pftc](const ConstraintPtr& c) + { + pftc->dependencies.emplace_back(c.get()); + } + ); + auto beginBody = checkpoint(this); + checkFunctionBody(sig.bodyScope, function->func); + auto endBody = checkpoint(this); + forEachConstraint( + beginBody, + endBody, + this, + [pftc](const ConstraintPtr& c) + { + c->dependencies.push_back(pftc); + } + ); } else { @@ -1906,85 +1901,172 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio reportError(function->location, ReservedIdentifier{"typeof"}); } - auto scopePtr = astTypeFunctionEnvironmentScopes.find(function); - LUAU_ASSERT(scopePtr); - - Checkpoint startCheckpoint = checkpoint(this); - FunctionSignature sig = checkFunctionSignature(*scopePtr, function->body, /* expectedType */ std::nullopt); - - // Place this function as a child of the non-type function scope - if (FFlag::LuauEmplaceNotPushBack) - scope->children.emplace_back(sig.signatureScope.get()); - else - scope->children.push_back(NotNull{sig.signatureScope.get()}); - - if (FFlag::LuauTrackFreeInteriorTypePacks) - interiorFreeTypes.emplace_back(); - else - DEPRECATED_interiorTypes.push_back(std::vector{}); - checkFunctionBody(sig.bodyScope, function->body); - Checkpoint endCheckpoint = checkpoint(this); - - TypeId generalizedTy = arena->addType(BlockedType{}); - NotNull gc = addConstraint( - sig.signatureScope, - function->location, - GeneralizationConstraint{ - generalizedTy, - sig.signature, - std::vector{}, - } - ); - - if (FFlag::LuauTrackFreeInteriorTypePacks) + if (FFlag::LuauTypeFunNoScopeMapRef) { - sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); - sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); + auto scopeIt = astTypeFunctionEnvironmentScopes.find(function); + LUAU_ASSERT(scopeIt); + + ScopePtr environmentScope = *scopeIt; + + Checkpoint startCheckpoint = checkpoint(this); + FunctionSignature sig = checkFunctionSignature(environmentScope, function->body, /* expectedType */ std::nullopt); + + // Place this function as a child of the non-type function scope + if (FFlag::LuauEmplaceNotPushBack) + scope->children.emplace_back(sig.signatureScope.get()); + else + scope->children.push_back(NotNull{sig.signatureScope.get()}); + + if (FFlag::LuauTrackFreeInteriorTypePacks) + interiorFreeTypes.emplace_back(); + else + DEPRECATED_interiorTypes.push_back(std::vector{}); + checkFunctionBody(sig.bodyScope, function->body); + Checkpoint endCheckpoint = checkpoint(this); + + TypeId generalizedTy = arena->addType(BlockedType{}); + NotNull gc = addConstraint( + sig.signatureScope, + function->location, + GeneralizationConstraint{ + generalizedTy, + sig.signature, + std::vector{}, + } + ); + + if (FFlag::LuauTrackFreeInteriorTypePacks) + { + sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); + sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); + } + else + sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); + + getMutable(generalizedTy)->setOwner(gc); + if (FFlag::LuauTrackFreeInteriorTypePacks) + interiorFreeTypes.pop_back(); + else + DEPRECATED_interiorTypes.pop_back(); + + Constraint* previous = nullptr; + forEachConstraint( + startCheckpoint, + endCheckpoint, + this, + [gc, &previous](const ConstraintPtr& constraint) + { + gc->dependencies.emplace_back(constraint.get()); + + if (auto psc = get(*constraint); psc && psc->returns) + { + if (previous) + { + if (FFlag::LuauEmplaceNotPushBack) + constraint->dependencies.emplace_back(previous); + else + constraint->dependencies.push_back(NotNull{previous}); + } + + previous = constraint.get(); + } + } + ); + + std::optional existingFunctionTy = environmentScope->lookup(function->name); + + if (!existingFunctionTy) + ice->ice("checkAliases did not populate type function name", function->nameLocation); + + TypeId unpackedTy = follow(*existingFunctionTy); + + if (auto bt = get(unpackedTy); bt && nullptr == bt->getOwner()) + emplaceType(asMutable(unpackedTy), generalizedTy); + + return ControlFlow::None; } else - sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); + { + auto scopePtr = astTypeFunctionEnvironmentScopes.find(function); + LUAU_ASSERT(scopePtr); - getMutable(generalizedTy)->setOwner(gc); - if (FFlag::LuauTrackFreeInteriorTypePacks) - interiorFreeTypes.pop_back(); - else - DEPRECATED_interiorTypes.pop_back(); + Checkpoint startCheckpoint = checkpoint(this); + FunctionSignature sig = checkFunctionSignature(*scopePtr, function->body, /* expectedType */ std::nullopt); - Constraint* previous = nullptr; - forEachConstraint( - startCheckpoint, - endCheckpoint, - this, - [gc, &previous](const ConstraintPtr& constraint) - { - gc->dependencies.emplace_back(constraint.get()); + // Place this function as a child of the non-type function scope + if (FFlag::LuauEmplaceNotPushBack) + scope->children.emplace_back(sig.signatureScope.get()); + else + scope->children.push_back(NotNull{sig.signatureScope.get()}); - if (auto psc = get(*constraint); psc && psc->returns) - { - if (previous) - { - if (FFlag::LuauEmplaceNotPushBack) - constraint->dependencies.emplace_back(previous); - else - constraint->dependencies.push_back(NotNull{previous}); - } + if (FFlag::LuauTrackFreeInteriorTypePacks) + interiorFreeTypes.emplace_back(); + else + DEPRECATED_interiorTypes.push_back(std::vector{}); + checkFunctionBody(sig.bodyScope, function->body); + Checkpoint endCheckpoint = checkpoint(this); - previous = constraint.get(); + TypeId generalizedTy = arena->addType(BlockedType{}); + NotNull gc = addConstraint( + sig.signatureScope, + function->location, + GeneralizationConstraint{ + generalizedTy, + sig.signature, + std::vector{}, } + ); + + if (FFlag::LuauTrackFreeInteriorTypePacks) + { + sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); + sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); } - ); + else + sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); - std::optional existingFunctionTy = (*scopePtr)->lookup(function->name); + getMutable(generalizedTy)->setOwner(gc); + if (FFlag::LuauTrackFreeInteriorTypePacks) + interiorFreeTypes.pop_back(); + else + DEPRECATED_interiorTypes.pop_back(); - if (!existingFunctionTy) - ice->ice("checkAliases did not populate type function name", function->nameLocation); + Constraint* previous = nullptr; + forEachConstraint( + startCheckpoint, + endCheckpoint, + this, + [gc, &previous](const ConstraintPtr& constraint) + { + gc->dependencies.emplace_back(constraint.get()); - TypeId unpackedTy = follow(*existingFunctionTy); + if (auto psc = get(*constraint); psc && psc->returns) + { + if (previous) + { + if (FFlag::LuauEmplaceNotPushBack) + constraint->dependencies.emplace_back(previous); + else + constraint->dependencies.push_back(NotNull{previous}); + } - if (auto bt = get(unpackedTy); bt && nullptr == bt->getOwner()) - emplaceType(asMutable(unpackedTy), generalizedTy); + previous = constraint.get(); + } + } + ); - return ControlFlow::None; + std::optional existingFunctionTy = (*scopePtr)->lookup(function->name); + + if (!existingFunctionTy) + ice->ice("checkAliases did not populate type function name", function->nameLocation); + + TypeId unpackedTy = follow(*existingFunctionTy); + + if (auto bt = get(unpackedTy); bt && nullptr == bt->getOwner()) + emplaceType(asMutable(unpackedTy), generalizedTy); + + return ControlFlow::None; + } } ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareGlobal* global) @@ -2690,6 +2772,15 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExpr* expr, std:: Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantString* string, std::optional expectedType, bool forceSingleton) { + + if (FFlag::DebugLuauStringSingletonBasedOnQuotes) + { + if (string->quoteStyle == AstExprConstantString::QuotedSingle || forceSingleton) + return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})}; + + return Inference{builtinTypes->stringType}; + } + if (forceSingleton) return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})}; @@ -2725,6 +2816,16 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool* boolExpr, std::optional expectedType, bool forceSingleton) { + if (FFlag::DebugLuauStringSingletonBasedOnQuotes) + { + const TypeId singletonType = boolExpr->value ? builtinTypes->trueType : builtinTypes->falseType; + if (forceSingleton) + return Inference{singletonType}; + + return Inference { builtinTypes->booleanType }; + } + + const TypeId singletonType = boolExpr->value ? builtinTypes->trueType : builtinTypes->falseType; if (forceSingleton) return Inference{singletonType}; @@ -4321,65 +4422,6 @@ TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location locati return resultType; } -struct FragmentTypeCheckGlobalPrepopulator_DEPRECATED : AstVisitor -{ - const NotNull globalScope; - const NotNull currentScope; - const NotNull dfg; - const NotNull arena; - - FragmentTypeCheckGlobalPrepopulator_DEPRECATED( - NotNull globalScope, - NotNull currentScope, - NotNull dfg, - NotNull arena - ) - : globalScope(globalScope) - , currentScope(currentScope) - , dfg(dfg) - , arena(arena) - { - } - - bool visit(AstExprGlobal* global) override - { - if (auto ty = globalScope->lookup(global->name)) - { - DefId def = dfg->getDef(global); - // We only want to write into the current scope the type of the global - currentScope->lvalueTypes[def] = *ty; - } - else if (auto ty = currentScope->lookup(global->name)) - { - // We are trying to create a binding for a brand new function, so we actually do have to write it into the scope. - DefId def = dfg->getDef(global); - // We only want to write into the current scope the type of the global - currentScope->lvalueTypes[def] = *ty; - } - - return true; - } - - bool visit(AstStatFunction* function) override - { - if (AstExprGlobal* g = function->name->as()) - { - if (auto ty = globalScope->lookup(g->name)) - { - currentScope->bindings[g->name] = Binding{*ty}; - } - else - { - // Hasn't existed since a previous typecheck - TypeId bt = arena->addType(BlockedType{}); - currentScope->bindings[g->name] = Binding{bt}; - } - } - - return true; - } -}; - struct GlobalPrepopulator : AstVisitor { const NotNull globalScope; diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 30ab72e0..d163b073 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -36,8 +36,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies) LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeCheckFunctionCalls) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying) LUAU_FASTFLAG(LuauLimitUnification) LUAU_FASTFLAGVARIABLE(LuauForceSimplifyConstraint2) @@ -49,6 +47,8 @@ LUAU_FASTFLAGVARIABLE(LuauExtendSealedTableUpperBounds) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) LUAU_FASTFLAGVARIABLE(LuauNameConstraintRestrictRecursiveTypes) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) +LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes) namespace Luau { @@ -346,7 +346,7 @@ struct InfiniteTypeFinder : TypeOnceVisitor bool foundInfiniteType = false; explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull scope) - : TypeOnceVisitor("InfiniteTypeFinder") + : TypeOnceVisitor("InfiniteTypeFinder", FFlag::LuauExplicitSkipBoundTypes) , solver(solver) , signature(signature) , scope(scope) @@ -683,7 +683,7 @@ struct TypeSearcher : TypeVisitor } explicit TypeSearcher(TypeId needle, Polarity initialPolarity) - : TypeVisitor("TypeSearcher") + : TypeVisitor("TypeSearcher", FFlag::LuauExplicitSkipBoundTypes) , needle(needle) , current(initialPolarity) { @@ -1664,7 +1664,7 @@ struct ContainsGenerics_DEPRECATED : public TypeOnceVisitor bool found = false; ContainsGenerics_DEPRECATED() - : TypeOnceVisitor("ContainsGenerics_DEPRECATED") + : TypeOnceVisitor("ContainsGenerics_DEPRECATED", FFlag::LuauExplicitSkipBoundTypes) { } @@ -1748,7 +1748,7 @@ struct ContainsGenerics : public TypeOnceVisitor NotNull> generics; explicit ContainsGenerics(NotNull> generics) - : TypeOnceVisitor("ContainsGenerics") + : TypeOnceVisitor("ContainsGenerics", FFlag::LuauExplicitSkipBoundTypes) , generics{generics} { } @@ -1887,7 +1887,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNullis() || expr->is() || expr->is() || - expr->is() || (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls && expr->is())) + expr->is() || expr->is()) { if (ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks})) { @@ -1901,37 +1901,24 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNullis() && - !ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks})) - { - Subtyping sp{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}}; - std::vector toBlock; - (void)matchLiteralType( - c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, NotNull{&sp}, expectedArgTy, actualArgTy, expr, toBlock - ); - LUAU_ASSERT(toBlock.empty()); - } } - if (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls) + // Consider: + // + // local Direction = { Left = 1, Right = 2 } + // type Direction = keyof + // + // local function move(dirs: { Direction }) --[[...]] end + // + // move({ "Left", "Right", "Left", "Right" }) + // + // We need `keyof` to reduce prior to inferring that the + // arguments to `move` must generalize to their lower bounds. This + // is how we ensure that ordering. + for (auto& c : u2.incompleteSubtypes) { - // Consider: - // - // local Direction = { Left = 1, Right = 2 } - // type Direction = keyof - // - // local function move(dirs: { Direction }) --[[...]] end - // - // move({ "Left", "Right", "Left", "Right" }) - // - // We need `keyof` to reduce prior to inferring that the - // arguments to `move` must generalize to their lower bounds. This - // is how we ensure that ordering. - for (auto& c : u2.incompleteSubtypes) - { - NotNull addition = pushConstraint(constraint->scope, constraint->location, std::move(c)); - inheritBlocks(constraint, addition); - } + NotNull addition = pushConstraint(constraint->scope, constraint->location, std::move(c)); + inheritBlocks(constraint, addition); } return true; @@ -1962,6 +1949,7 @@ bool ConstraintSolver::tryDispatch(const TableCheckConstraint& c, NotNull constraint) { + LUAU_ASSERT(!FFlag::DebugLuauStringSingletonBasedOnQuotes); std::optional expectedType = c.expectedType ? std::make_optional(follow(*c.expectedType)) : std::nullopt; if (expectedType && (isBlocked(*expectedType) || get(*expectedType))) return block(*expectedType, constraint); @@ -2213,7 +2201,7 @@ struct BlockedTypeFinder : TypeOnceVisitor std::optional blocked; BlockedTypeFinder() - : TypeOnceVisitor("ContainsGenerics_DEPRECATED") + : TypeOnceVisitor("ContainsGenerics_DEPRECATED", FFlag::LuauExplicitSkipBoundTypes) { } @@ -2444,8 +2432,7 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNulllocation); + unblock(lhsType, constraint->location); } return true; @@ -3568,7 +3555,7 @@ struct Blocker : TypeOnceVisitor bool blocked = false; explicit Blocker(NotNull solver, NotNull constraint) - : TypeOnceVisitor("Blocker") + : TypeOnceVisitor("Blocker", FFlag::LuauExplicitSkipBoundTypes) , solver(solver) , constraint(constraint) { diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index e255451e..88e5c5ec 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -1,6 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/BuiltinDefinitions.h" +LUAU_FASTFLAGVARIABLE(LuauTypeCheckerVectorLerp) + namespace Luau { @@ -268,6 +270,37 @@ declare extern type vector with z: number end +declare vector: { + create: @checked (x: number, y: number, z: number?) -> vector, + magnitude: @checked (vec: vector) -> number, + normalize: @checked (vec: vector) -> vector, + cross: @checked (vec1: vector, vec2: vector) -> vector, + dot: @checked (vec1: vector, vec2: vector) -> number, + angle: @checked (vec1: vector, vec2: vector, axis: vector?) -> number, + floor: @checked (vec: vector) -> vector, + ceil: @checked (vec: vector) -> vector, + abs: @checked (vec: vector) -> vector, + sign: @checked (vec: vector) -> vector, + clamp: @checked (vec: vector, min: vector, max: vector) -> vector, + max: @checked (vector, ...vector) -> vector, + min: @checked (vector, ...vector) -> vector, + lerp: @checked (vec1: vector, vec2: vector, t: number) -> number, + + zero: vector, + one: vector, +} + +)BUILTIN_SRC"; + +static const char* const kBuiltinDefinitionVectorSrc_DEPRECATED = R"BUILTIN_SRC( + +-- While vector would have been better represented as a built-in primitive type, type solver extern type handling covers most of the properties +declare extern type vector with + x: number + y: number + z: number +end + declare vector: { create: @checked (x: number, y: number, z: number?) -> vector, magnitude: @checked (vec: vector) -> number, @@ -301,7 +334,14 @@ std::string getBuiltinDefinitionSource() result += kBuiltinDefinitionDebugSrc; result += kBuiltinDefinitionUtf8Src; result += kBuiltinDefinitionBufferSrc; - result += kBuiltinDefinitionVectorSrc; + if (FFlag::LuauTypeCheckerVectorLerp) + { + result += kBuiltinDefinitionVectorSrc; + } + else + { + result += kBuiltinDefinitionVectorSrc_DEPRECATED; + } return result; } diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index d771b2d4..be163e44 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -894,6 +894,27 @@ struct ErrorConverter { return "Recursive type being used with different parameters."; } + + std::string operator()(const GenericBoundsMismatch& e) const + { + std::string lowerBounds; + for (size_t i = 0; i < e.lowerBounds.size(); ++i) + { + if (i > 0) + lowerBounds += ", "; + lowerBounds += Luau::toString(e.lowerBounds[i]); + } + std::string upperBounds; + for (size_t i = 0; i < e.upperBounds.size(); ++i) + { + if (i > 0) + upperBounds += ", "; + upperBounds += Luau::toString(e.upperBounds[i]); + } + + return "The generic type parameter " + std::string{e.genericName} + "was found to have invalid bounds. Its lower bounds were [" + + lowerBounds + "], and its upper bounds were [" + upperBounds + "]."; + } }; struct InvalidNameChecker @@ -1297,6 +1318,18 @@ bool MultipleNonviableOverloads::operator==(const MultipleNonviableOverloads& rh return attemptedArgCount == rhs.attemptedArgCount; } +GenericBoundsMismatch::GenericBoundsMismatch(const std::string_view genericName, TypeIds lowerBoundSet, TypeIds upperBoundSet) + : genericName(genericName) + , lowerBounds(lowerBoundSet.take()) + , upperBounds(upperBoundSet.take()) +{ +} + +bool GenericBoundsMismatch::operator==(const GenericBoundsMismatch& rhs) const +{ + return genericName == rhs.genericName && lowerBounds == rhs.lowerBounds && upperBounds == rhs.upperBounds; +} + std::string toString(const TypeError& error) { return toString(error, TypeErrorToStringOptions{}); @@ -1523,6 +1556,13 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState) else if constexpr (std::is_same_v) { } + else if constexpr (std::is_same_v) + { + for (auto& lowerBound : e.lowerBounds) + lowerBound = clone(lowerBound); + for (auto& upperBound : e.upperBounds) + upperBound = clone(upperBound); + } else static_assert(always_false_v, "Non-exhaustive type switch"); } diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 36ed4a71..46696040 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -47,6 +47,7 @@ LUAU_FASTFLAGVARIABLE(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) LUAU_FASTFLAGVARIABLE(DebugLuauAlwaysShowConstraintSolvingIncomplete) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauEmplaceNotPushBack) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) namespace Luau { @@ -1369,7 +1370,7 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons struct InternalTypeFinder : TypeOnceVisitor { InternalTypeFinder() - : TypeOnceVisitor("InternalTypeFinder") + : TypeOnceVisitor("InternalTypeFinder", FFlag::LuauExplicitSkipBoundTypes) { } diff --git a/Analysis/src/Generalization.cpp b/Analysis/src/Generalization.cpp index cd7b73e3..1f246f1f 100644 --- a/Analysis/src/Generalization.cpp +++ b/Analysis/src/Generalization.cpp @@ -18,6 +18,7 @@ LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauReduceSetTypeStackPressure) LUAU_FASTINTVARIABLE(LuauGenericCounterMaxDepth, 15) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) namespace Luau { @@ -1406,7 +1407,7 @@ struct GenericCounter : TypeVisitor bool hitLimits = false; explicit GenericCounter(NotNull> cachedTypes) - : TypeVisitor("GenericCounter") + : TypeVisitor("GenericCounter", FFlag::LuauExplicitSkipBoundTypes) , cachedTypes(cachedTypes) { } diff --git a/Analysis/src/InferPolarity.cpp b/Analysis/src/InferPolarity.cpp index e95d76d9..40c1fa5c 100644 --- a/Analysis/src/InferPolarity.cpp +++ b/Analysis/src/InferPolarity.cpp @@ -6,6 +6,7 @@ #include "Luau/VisitType.h" LUAU_FASTFLAG(LuauEagerGeneralization4) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) namespace Luau { @@ -21,7 +22,7 @@ struct InferPolarity : TypeVisitor Polarity polarity = Polarity::Positive; explicit InferPolarity(NotNull arena, NotNull scope) - : TypeVisitor("InferPolarity") + : TypeVisitor("InferPolarity", FFlag::LuauExplicitSkipBoundTypes) , arena(arena) , scope(scope) { diff --git a/Analysis/src/IostreamHelpers.cpp b/Analysis/src/IostreamHelpers.cpp index fd1435c7..7f245005 100644 --- a/Analysis/src/IostreamHelpers.cpp +++ b/Analysis/src/IostreamHelpers.cpp @@ -269,6 +269,24 @@ static void errorToString(std::ostream& stream, const T& err) stream << "MultipleNonviableOverloads { attemptedArgCount = " << err.attemptedArgCount << " }"; else if constexpr (std::is_same_v) stream << "RecursiveRestraintViolation"; + else if constexpr (std::is_same_v) + { + stream << "GenericBoundsMismatch { genericName = " << std::string{err.genericName} << ", lowerBounds = ["; + for (size_t i = 0; i < err.lowerBounds.size(); ++i) + { + if (i > 0) + stream << ", "; + stream << toString(err.lowerBounds[i]); + } + stream << "], upperBounds = ["; + for (size_t i = 0; i < err.upperBounds.size(); ++i) + { + if (i > 0) + stream << ", "; + stream << toString(err.upperBounds[i]); + } + stream << "] }"; + } else static_assert(always_false_v, "Non-exhaustive type switch"); } diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index b313acc0..d9e718bd 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -13,6 +13,8 @@ LUAU_FASTFLAG(LuauLimitUnification) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) +LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) namespace Luau { @@ -487,6 +489,13 @@ std::pair OverloadResolver::checkOverload_ argLocation = argExprs->at(argExprs->size() - 1)->location; // TODO extract location from the SubtypingResult path and argExprs + if (FFlag::LuauVariadicAnyPackShouldBeErrorSuppressing) + { + auto errorSuppression = shouldSuppressErrors(normalizer, *failedSubPack).orElse(shouldSuppressErrors(normalizer, *failedSuperPack)); + if (errorSuppression == ErrorSuppression::Suppress) + break; + } + switch (reason.variance) { case SubtypingVariance::Covariant: @@ -505,6 +514,12 @@ std::pair OverloadResolver::checkOverload_ } } + if (FFlag::LuauSubtypingReportGenericBoundMismatches) + { + for (GenericBoundsMismatch& mismatch : sr.genericBoundsMismatches) + errors.emplace_back(fnExpr->location, std::move(mismatch)); + } + return {Analysis::OverloadIsNonviable, std::move(errors)}; } diff --git a/Analysis/src/Quantify.cpp b/Analysis/src/Quantify.cpp index 8d5cfbbf..d02cc00c 100644 --- a/Analysis/src/Quantify.cpp +++ b/Analysis/src/Quantify.cpp @@ -8,6 +8,8 @@ #include "Luau/Type.h" #include "Luau/VisitType.h" +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) + namespace Luau { @@ -21,7 +23,7 @@ struct Quantifier final : TypeOnceVisitor bool seenMutableType = false; explicit Quantifier(TypeLevel level) - : TypeOnceVisitor("Quantifier") + : TypeOnceVisitor("Quantifier", /* skipBoundTypes */ false) , level(level) { } diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index f2052b28..b63be0f2 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -26,6 +26,7 @@ LUAU_FASTFLAGVARIABLE(LuauMissingFollowMappedGenericPacks) LUAU_FASTFLAGVARIABLE(LuauSubtypingNegationsChecksNormalizationComplexity) LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauEmplaceNotPushBack) +LUAU_FASTFLAGVARIABLE(LuauSubtypingReportGenericBoundMismatches) namespace Luau { @@ -174,6 +175,8 @@ SubtypingResult& SubtypingResult::andAlso(const SubtypingResult& other) normalizationTooComplex |= other.normalizationTooComplex; isCacheable &= other.isCacheable; errors.insert(errors.end(), other.errors.begin(), other.errors.end()); + if (FFlag::LuauSubtypingReportGenericBoundMismatches) + genericBoundsMismatches.insert(genericBoundsMismatches.end(), other.genericBoundsMismatches.begin(), other.genericBoundsMismatches.end()); return *this; } @@ -196,6 +199,8 @@ SubtypingResult& SubtypingResult::orElse(const SubtypingResult& other) normalizationTooComplex |= other.normalizationTooComplex; isCacheable &= other.isCacheable; errors.insert(errors.end(), other.errors.begin(), other.errors.end()); + if (FFlag::LuauSubtypingReportGenericBoundMismatches) + genericBoundsMismatches.insert(genericBoundsMismatches.end(), other.genericBoundsMismatches.begin(), other.genericBoundsMismatches.end()); return *this; } @@ -710,8 +715,15 @@ SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp, NotNu { // Bounds should have exactly one entry LUAU_ASSERT(bounds->size() == 1); - if (!bounds->empty()) - result.andAlso(checkGenericBounds(bounds->back(), env, scope)); + if (FFlag::LuauSubtypingReportGenericBoundMismatches) + { + if (bounds->empty()) + continue; + if (const GenericType* gen = get(bg)) + result.andAlso(checkGenericBounds(bounds->back(), env, scope, gen->name)); + } + else if (!bounds->empty()) + result.andAlso(checkGenericBounds_DEPRECATED(bounds->back(), env, scope)); } } } @@ -2034,12 +2046,15 @@ SubtypingResult Subtyping::isCovariantWith( for (TypeId g : subFunction->generics) { g = follow(g); - if (get(g)) + if (const GenericType* gen = get(g)) { auto bounds = env.mappedGenerics.find(g); LUAU_ASSERT(bounds && !bounds->empty()); // Check the bounds are valid - result.andAlso(checkGenericBounds(bounds->back(), env, scope)); + if (FFlag::LuauSubtypingReportGenericBoundMismatches) + result.andAlso(checkGenericBounds(bounds->back(), env, scope, gen->name)); + else + result.andAlso(checkGenericBounds_DEPRECATED(bounds->back(), env, scope)); bounds->pop_back(); } @@ -2524,9 +2539,16 @@ SubtypingResult Subtyping::trySemanticSubtyping(SubtypingEnvironment& env, return original; } -SubtypingResult Subtyping::checkGenericBounds(const SubtypingEnvironment::GenericBounds& bounds, SubtypingEnvironment& env, NotNull scope) + +SubtypingResult Subtyping::checkGenericBounds( + const SubtypingEnvironment::GenericBounds& bounds, + SubtypingEnvironment& env, + NotNull scope, + std::string_view genericName +) { LUAU_ASSERT(FFlag::LuauSubtypingGenericsDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingReportGenericBoundMismatches); SubtypingResult result{true}; @@ -2607,6 +2629,101 @@ SubtypingResult Subtyping::checkGenericBounds(const SubtypingEnvironment::Generi SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope); boundsResult.reasoning.clear(); + if (res == NormalizationResult::False || !boundsResult.isSubtype) + result.genericBoundsMismatches.emplace_back(genericName, bounds.lowerBound, bounds.upperBound); + + result.andAlso(boundsResult); + + return result; +} + +SubtypingResult Subtyping::checkGenericBounds_DEPRECATED( + const SubtypingEnvironment::GenericBounds& bounds, + SubtypingEnvironment& env, + NotNull scope +) +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericsDoesntUseVariance); + LUAU_ASSERT(!FFlag::LuauSubtypingReportGenericBoundMismatches); + + SubtypingResult result{true}; + + const auto& [lb, ub] = bounds; + + TypeIds lbTypes; + for (TypeId t : lb) + { + t = follow(t); + if (const auto mappedBounds = env.mappedGenerics.find(t)) + { + if (mappedBounds->empty()) // If the generic is no longer in scope, we don't have any info about it + continue; + + auto& [lowerBound, upperBound] = mappedBounds->back(); + // We're populating the lower bounds, so we prioritize the upper bounds of a mapped generic + if (!upperBound.empty()) + lbTypes.insert(upperBound.begin(), upperBound.end()); + else if (!lowerBound.empty()) + lbTypes.insert(lowerBound.begin(), lowerBound.end()); + else + lbTypes.insert(builtinTypes->unknownType); + } + else + lbTypes.insert(t); + } + + TypeIds ubTypes; + for (TypeId t : ub) + { + t = follow(t); + if (const auto mappedBounds = env.mappedGenerics.find(t)) + { + if (mappedBounds->empty()) // If the generic is no longer in scope, we don't have any info about it + continue; + + auto& [lowerBound, upperBound] = mappedBounds->back(); + // We're populating the upper bounds, so we prioritize the lower bounds of a mapped generic + if (!lowerBound.empty()) + ubTypes.insert(lowerBound.begin(), lowerBound.end()); + else if (!upperBound.empty()) + ubTypes.insert(upperBound.begin(), upperBound.end()); + else + ubTypes.insert(builtinTypes->unknownType); + } + else + ubTypes.insert(t); + } + TypeId lowerBound = makeAggregateType(lbTypes.take(), builtinTypes->neverType); + TypeId upperBound = makeAggregateType(ubTypes.take(), builtinTypes->unknownType); + + std::shared_ptr nt = normalizer->normalize(upperBound); + // we say that the result is true if normalization failed because complex types are likely to be inhabited. + NormalizationResult res = nt ? normalizer->isInhabited(nt.get()) : NormalizationResult::True; + + if (!nt || res == NormalizationResult::HitLimits) + result.normalizationTooComplex = true; + else if (res == NormalizationResult::False) + { + /* If the normalized upper bound we're mapping to a generic is + * uninhabited, then we must consider the subtyping relation not to + * hold. + * + * This happens eg in () -> (T, T) <: () -> (string, number) + * + * T appears in covariant position and would have to be both string + * and number at once. + * + * No actual value is both a string and a number, so the test fails. + * + * TODO: We'll need to add explanitory context here. + */ + result.isSubtype = false; + } + + SubtypingEnvironment boundsEnv; + boundsEnv.parent = &env; + SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope); + boundsResult.reasoning.clear(); result.andAlso(boundsResult); return result; diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index fb0786c1..2c2a4503 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) /* * Enables increasing levels of verbosity for Luau type names when stringifying. @@ -51,7 +52,7 @@ namespace struct FindCyclicTypes final : TypeVisitor { FindCyclicTypes() - : TypeVisitor("FindCyclicTypes") + : TypeVisitor("FindCyclicTypes", FFlag::LuauExplicitSkipBoundTypes) { } diff --git a/Analysis/src/TxnLog.cpp b/Analysis/src/TxnLog.cpp index 572f38a6..cee6427f 100644 --- a/Analysis/src/TxnLog.cpp +++ b/Analysis/src/TxnLog.cpp @@ -9,6 +9,8 @@ #include #include +LUAU_FASTFLAGVARIABLE(LuauOccursCheckInCommit) + namespace Luau { @@ -84,29 +86,6 @@ void TxnLog::concat(TxnLog rhs) radioactive |= rhs.radioactive; } -void TxnLog::concatAsIntersections(TxnLog rhs, NotNull arena) -{ - for (auto& [ty, rightRep] : rhs.typeVarChanges) - { - if (rightRep->dead) - continue; - - if (auto leftRep = typeVarChanges.find(ty); leftRep && !(*leftRep)->dead) - { - TypeId leftTy = arena->addType((*leftRep)->pending.clone()); - TypeId rightTy = arena->addType(rightRep->pending.clone()); - typeVarChanges[ty]->pending.ty = IntersectionType{{leftTy, rightTy}}; - } - else - typeVarChanges[ty] = std::move(rightRep); - } - - for (auto& [tp, rep] : rhs.typePackChanges) - typePackChanges[tp] = std::move(rep); - - radioactive |= rhs.radioactive; -} - void TxnLog::concatAsUnion(TxnLog rhs, NotNull arena) { /* @@ -154,7 +133,7 @@ void TxnLog::concatAsUnion(TxnLog rhs, NotNull arena) // leftTy has been bound to rightTy, but rightTy has also been bound // to leftTy. We find the one that belongs to the more deeply nested // scope and remove it from the log. - const bool discardLeft = useScopes ? subsumes(lf->scope, rf->scope) : lf->level.subsumes(rf->level); + const bool discardLeft = lf->level.subsumes(rf->level); if (discardLeft) (*leftRep)->dead = true; @@ -188,6 +167,57 @@ void TxnLog::concatAsUnion(TxnLog rhs, NotNull arena) radioactive |= rhs.radioactive; } +// Like follow(), but only takes a single step. +// +// This is potentailly performance sensitive, so we use nullptr rather than an +// optional for the return type here. +static TypeId followOnce(TxnLog& log, TypeId ty) +{ + if (auto bound = log.get(ty)) + return bound->boundTo; + if (auto tt = log.get(ty)) + return tt->boundTo.value_or(nullptr); + + return nullptr; +} + +// We must take extra care not to replace a type with a BoundType to itself. We +// check each BoundType along the chain +// +// This function returns true if any of the bound types pointed at by 'needle' +// point at 'haystack'. +static bool occurs(TxnLog& log, TypeId needle, TypeId haystack) +{ + TypeId tortoise = needle; + TypeId hare = needle; + + while (true) + { + if (tortoise == haystack) + return true; + + TypeId g = followOnce(log, tortoise); + if (!g) + return false; + tortoise = g; + + // Cycle detection: The hare steps twice for each step that the tortoise takes. + // If ever the two meet, it can only be because the track is cyclic. + // When we hit the end of the chain, hare becomes nullptr. + if (hare) + { + hare = followOnce(log, hare); + if (hare) + { + hare = followOnce(log, hare); + + if (hare == tortoise) + return true; + } + } + } +} + void TxnLog::commit() { LUAU_ASSERT(!radioactive); @@ -195,7 +225,17 @@ void TxnLog::commit() for (auto& [ty, rep] : typeVarChanges) { if (!rep->dead) - asMutable(ty)->reassign(rep.get()->pending); + { + const TypeId unfollowed = &rep.get()->pending; + + if (FFlag::LuauOccursCheckInCommit) + { + if (!occurs(*this, unfollowed, ty)) + asMutable(ty)->reassign(*unfollowed); + } + else + asMutable(ty)->reassign(*unfollowed); + } } for (auto& [tp, rep] : typePackChanges) @@ -474,16 +514,4 @@ TypePackId TxnLog::follow(TypePackId tp) const ); } -std::pair, std::vector> TxnLog::getChanges() const -{ - std::pair, std::vector> result; - - for (const auto& [typeId, _newState] : typeVarChanges) - result.first.push_back(typeId); - for (const auto& [typePackId, _newState] : typePackChanges) - result.second.push_back(typePackId); - - return result; -} - } // namespace Luau diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 048fdba4..f4a1d3c1 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -30,7 +30,6 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauInferActualIfElseExprType2) @@ -40,6 +39,8 @@ LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) LUAU_FASTFLAGVARIABLE(LuauIceLess) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) +LUAU_FASTFLAGVARIABLE(LuauAllowMixedTables) namespace Luau { @@ -162,7 +163,7 @@ struct TypeFunctionFinder : TypeOnceVisitor DenseHashSet mentionedFunctionPacks{nullptr}; TypeFunctionFinder() - : TypeOnceVisitor("TypeFunctionFinder") + : TypeOnceVisitor("TypeFunctionFinder", FFlag::LuauExplicitSkipBoundTypes) { } @@ -187,7 +188,7 @@ struct InternalTypeFunctionFinder : TypeOnceVisitor DenseHashSet mentionedFunctionPacks{nullptr}; explicit InternalTypeFunctionFinder(std::vector& declStack) - : TypeOnceVisitor("InternalTypeFunctionFinder") + : TypeOnceVisitor("InternalTypeFunctionFinder", FFlag::LuauExplicitSkipBoundTypes) { TypeFunctionFinder f; for (TypeId fn : declStack) @@ -1545,80 +1546,53 @@ void TypeChecker2::visitCall(AstExprCall* call) argExprs.push_back(indexExpr->expr); } - if (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls) + // FIXME: Similar to bidirectional inference prior, this does not support + // overloaded functions nor generic types (yet). + if (auto fty = get(fnTy); fty && fty->generics.empty() && fty->genericPacks.empty() && call->args.size > 0) { - // FIXME: Similar to bidirectional inference prior, this does not support - // overloaded functions nor generic types (yet). - if (auto fty = get(fnTy); fty && fty->generics.empty() && fty->genericPacks.empty() && call->args.size > 0) + size_t selfOffset = call->self ? 1 : 0; + + auto [paramsHead, _] = extendTypePack(module->internalTypes, builtinTypes, fty->argTypes, call->args.size + selfOffset); + + for (size_t idx = 0; idx < call->args.size - 1; ++idx) { - size_t selfOffset = call->self ? 1 : 0; - - auto [paramsHead, _] = extendTypePack(module->internalTypes, builtinTypes, fty->argTypes, call->args.size + selfOffset); - - for (size_t idx = 0; idx < call->args.size - 1; ++idx) + auto argExpr = call->args.data[idx]; + auto argExprType = lookupType(argExpr); + argExprs.push_back(argExpr); + if (idx + selfOffset >= paramsHead.size() || isErrorSuppressing(argExpr->location, argExprType)) { - auto argExpr = call->args.data[idx]; - auto argExprType = lookupType(argExpr); - argExprs.push_back(argExpr); - if (idx + selfOffset >= paramsHead.size() || isErrorSuppressing(argExpr->location, argExprType)) - { - args.head.push_back(argExprType); - continue; - } - testLiteralOrAstTypeIsSubtype(argExpr, paramsHead[idx + selfOffset]); - args.head.push_back(paramsHead[idx + selfOffset]); + args.head.push_back(argExprType); + continue; } + testLiteralOrAstTypeIsSubtype(argExpr, paramsHead[idx + selfOffset]); + args.head.push_back(paramsHead[idx + selfOffset]); + } - auto lastExpr = call->args.data[call->args.size - 1]; - argExprs.push_back(lastExpr); + auto lastExpr = call->args.data[call->args.size - 1]; + argExprs.push_back(lastExpr); - if (auto argTail = module->astTypePacks.find(lastExpr)) + if (auto argTail = module->astTypePacks.find(lastExpr)) + { + auto [lastExprHead, lastExprTail] = flatten(*argTail); + args.head.insert(args.head.end(), lastExprHead.begin(), lastExprHead.end()); + args.tail = lastExprTail; + } + else if (paramsHead.size() >= call->args.size + selfOffset) + { + auto lastType = paramsHead[call->args.size - 1 + selfOffset]; + auto lastExprType = lookupType(lastExpr); + if (isErrorSuppressing(lastExpr->location, lastExprType)) { - auto [lastExprHead, lastExprTail] = flatten(*argTail); - args.head.insert(args.head.end(), lastExprHead.begin(), lastExprHead.end()); - args.tail = lastExprTail; - } - else if (paramsHead.size() >= call->args.size + selfOffset) - { - auto lastType = paramsHead[call->args.size - 1 + selfOffset]; - auto lastExprType = lookupType(lastExpr); - if (isErrorSuppressing(lastExpr->location, lastExprType)) - { - args.head.push_back(lastExprType); - } - else - { - testLiteralOrAstTypeIsSubtype(lastExpr, lastType); - args.head.push_back(lastType); - } + args.head.push_back(lastExprType); } else - args.tail = builtinTypes->anyTypePack; - } - else - { - for (size_t i = 0; i < call->args.size; ++i) { - AstExpr* arg = call->args.data[i]; - argExprs.push_back(arg); - TypeId* argTy = module->astTypes.find(arg); - if (argTy) - args.head.push_back(*argTy); - else if (i == call->args.size - 1) - { - if (auto argTail = module->astTypePacks.find(arg)) - { - auto [head, tail] = flatten(*argTail); - args.head.insert(args.head.end(), head.begin(), head.end()); - args.tail = tail; - } - else - args.tail = builtinTypes->anyTypePack; - } - else - args.head.push_back(builtinTypes->anyType); + testLiteralOrAstTypeIsSubtype(lastExpr, lastType); + args.head.push_back(lastType); } } + else + args.tail = builtinTypes->anyTypePack; } else { @@ -3157,8 +3131,17 @@ bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedT if (expectedTableType->indexer) { NotNull scope{findInnermostScope(expr->location)}; - auto result = subtyping->isSubtype(expectedTableType->indexer->indexType, builtinTypes->numberType, scope); - isArrayLike = result.isSubtype; + + if (FFlag::LuauAllowMixedTables) + { + auto result = subtyping->isSubtype(/* subTy */ builtinTypes->numberType, /* superTy */ expectedTableType->indexer->indexType, scope); + isArrayLike = result.isSubtype || isErrorSuppressing(expr->location, expectedTableType->indexer->indexType); + } + else + { + auto result = subtyping->isSubtype(/* subTy */ expectedTableType->indexer->indexType, /* superTy */ builtinTypes->numberType, scope); + isArrayLike = result.isSubtype; + } } bool isSubtype = true; diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index c0f0e353..728b5c60 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -35,6 +35,7 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) namespace Luau { @@ -53,7 +54,7 @@ struct InstanceCollector : TypeOnceVisitor InstanceCollector() - : TypeOnceVisitor("InstanceCollector") + : TypeOnceVisitor("InstanceCollector", FFlag::LuauExplicitSkipBoundTypes) { } @@ -144,7 +145,7 @@ struct UnscopedGenericFinder : TypeOnceVisitor bool foundUnscoped = false; UnscopedGenericFinder() - : TypeOnceVisitor("UnscopedGenericFinder") + : TypeOnceVisitor("UnscopedGenericFinder", FFlag::LuauExplicitSkipBoundTypes) { } diff --git a/Analysis/src/TypeFunctionReductionGuesser.cpp b/Analysis/src/TypeFunctionReductionGuesser.cpp index 12fcaa16..e419ab8a 100644 --- a/Analysis/src/TypeFunctionReductionGuesser.cpp +++ b/Analysis/src/TypeFunctionReductionGuesser.cpp @@ -15,6 +15,8 @@ LUAU_FASTFLAG(LuauEmplaceNotPushBack) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) + namespace Luau { struct InstanceCollector2 : TypeOnceVisitor @@ -25,7 +27,7 @@ struct InstanceCollector2 : TypeOnceVisitor DenseHashSet instanceArguments{nullptr}; InstanceCollector2() - : TypeOnceVisitor("InstanceCollector2") + : TypeOnceVisitor("InstanceCollector2", FFlag::LuauExplicitSkipBoundTypes) { } diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index 0a6c3340..53233dcb 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -8,6 +8,7 @@ #include "Luau/ToString.h" #include "Luau/Type.h" #include "Luau/TypeInfer.h" +#include "Luau/TypePack.h" #include @@ -15,6 +16,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauTidyTypeUtils) LUAU_FASTFLAG(LuauEmplaceNotPushBack) +LUAU_FASTFLAGVARIABLE(LuauVariadicAnyPackShouldBeErrorSuppressing) namespace Luau { @@ -502,6 +504,17 @@ ErrorSuppression shouldSuppressErrors(NotNull normalizer, TypeId ty) ErrorSuppression shouldSuppressErrors(NotNull normalizer, TypePackId tp) { + // Flatten t where t = ...any will produce a type pack [ {}, t] + // which trivially fails the tail check below, which is why we need to special case here + if (FFlag::LuauVariadicAnyPackShouldBeErrorSuppressing) + { + if (auto tpId = get(follow(tp))) + { + if (get(follow(tpId->ty))) + return ErrorSuppression::Suppress; + } + } + auto [tys, tail] = flatten(tp); // check the head, one type at a time diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index e632b03a..208c13e1 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -33,7 +33,7 @@ struct PromoteTypeLevels final : TypeOnceVisitor TypeLevel minLevel; PromoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel) - : TypeOnceVisitor("PromoteTypeLevels") + : TypeOnceVisitor("PromoteTypeLevels", /* skipBoundTypes */ false) , log(log) , typeArena(typeArena) , minLevel(minLevel) @@ -146,7 +146,7 @@ void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLev struct SkipCacheForType final : TypeOnceVisitor { SkipCacheForType(const DenseHashMap& skipCacheForType, const TypeArena* typeArena) - : TypeOnceVisitor("SkipCacheForType") + : TypeOnceVisitor("SkipCacheForType", /* skipBoundTypes */ false) , skipCacheForType(skipCacheForType) , typeArena(typeArena) { @@ -508,49 +508,6 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool return; } - if (hideousFixMeGenericsAreActuallyFree) - { - auto superGeneric = log.getMutable(superTy); - auto subGeneric = log.getMutable(subTy); - - if (superGeneric && subGeneric && subsumes(superGeneric, subGeneric)) - { - if (!occursCheck(subTy, superTy, /* reversed = */ false)) - log.replace(subTy, BoundType(superTy)); - - return; - } - else if (superGeneric && subGeneric) - { - if (!occursCheck(superTy, subTy, /* reversed = */ true)) - log.replace(superTy, BoundType(subTy)); - - return; - } - else if (superGeneric) - { - if (!occursCheck(superTy, subTy, /* reversed = */ true)) - { - Widen widen{types, builtinTypes}; - log.replace(superTy, BoundType(widen(subTy))); - } - - return; - } - else if (subGeneric) - { - // Normally, if the subtype is free, it should not be bound to any, unknown, or error types. - // But for bug compatibility, we'll only apply this rule to unknown. Doing this will silence cascading type errors. - if (log.get(superTy)) - return; - - if (!occursCheck(subTy, superTy, /* reversed = */ false)) - log.replace(subTy, BoundType(superTy)); - - return; - } - } - if (log.get(superTy)) return tryUnifyWithAny(subTy, builtinTypes->anyType); @@ -1510,21 +1467,6 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal log.replace(subTp, Unifiable::Bound(superTp)); } } - else if (hideousFixMeGenericsAreActuallyFree && log.getMutable(superTp)) - { - if (!occursCheck(superTp, subTp, /* reversed = */ true)) - { - Widen widen{types, builtinTypes}; - log.replace(superTp, Unifiable::Bound(widen(subTp))); - } - } - else if (hideousFixMeGenericsAreActuallyFree && log.getMutable(subTp)) - { - if (!occursCheck(subTp, superTp, /* reversed = */ false)) - { - log.replace(subTp, Unifiable::Bound(superTp)); - } - } else if (log.getMutable(superTp)) tryUnifyWithAny(subTp, superTp); else if (log.getMutable(subTp)) @@ -2557,12 +2499,7 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever tryUnify_(vtp->ty, variadicTy); } else if (get(tail)) - { - if (!hideousFixMeGenericsAreActuallyFree) - reportError(location, GenericError{"Cannot unify variadic and generic packs"}); - else - log.replace(tail, BoundTypePack{superTp}); - } + reportError(location, GenericError{"Cannot unify variadic and generic packs"}); else if (get(tail)) { // Nothing to do here. @@ -2755,13 +2692,13 @@ bool Unifier::occursCheck(DenseHashSet& seen, TypeId needle, TypeId hays if (log.getMutable(needle)) return false; - if (!log.getMutable(needle) && !(hideousFixMeGenericsAreActuallyFree && log.is(needle))) + if (!log.getMutable(needle)) ice("Expected needle to be free"); if (needle == haystack) return true; - if (log.getMutable(haystack) || (hideousFixMeGenericsAreActuallyFree && log.is(haystack))) + if (log.getMutable(haystack)) return false; else if (auto a = log.getMutable(haystack)) { @@ -2805,7 +2742,7 @@ bool Unifier::occursCheck(DenseHashSet& seen, TypePackId needle, Typ if (log.getMutable(needle)) return false; - if (!log.getMutable(needle) && !(hideousFixMeGenericsAreActuallyFree && log.is(needle))) + if (!log.getMutable(needle)) ice("Expected needle pack to be free"); RecursionLimiter _ra("Unifier::occursCheck", &sharedState.counters.recursionCount, sharedState.counters.recursionLimit); diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index ad1fa896..917217a4 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -339,9 +339,28 @@ public: enum QuoteStyle { + // A string created using double quotes or an interpolated string, + // as in: + // + // "foo", `My name is {protagonist}! / And I'm {antagonist}!` + // QuotedSimple, + // A string created using single quotes, as in: + // + // 'bar' + // + QuotedSingle, + // A string created using `[[ ... ]]` as in: + // + // [[ Gee, this sure is a long string. + // it even has a new line in it! ]] + // QuotedRaw, - Unquoted + // A "string" in the context of a table literal, as in: + // + // { foo = 42 } -- `foo` here is a "constant string" + // + Unquoted, }; AstExprConstantString(const Location& location, const AstArray& value, QuoteStyle quoteStyle); diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 8c7f7d00..8ace61b5 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -21,6 +21,7 @@ LUAU_FASTFLAGVARIABLE(LuauSolverV2) LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false) LUAU_FASTFLAGVARIABLE(LuauParseIncompleteInterpStringsWithLocation) LUAU_FASTFLAGVARIABLE(LuauParametrizedAttributeSyntax) +LUAU_FASTFLAGVARIABLE(DebugLuauStringSingletonBasedOnQuotes) // Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix bool luau_telemetry_parsed_return_type_variadic_with_type_suffix = false; @@ -3847,17 +3848,39 @@ AstExpr* Parser::parseString() Location location = lexer.current().location; AstExprConstantString::QuoteStyle style; - switch (lexer.current().type) + if (FFlag::DebugLuauStringSingletonBasedOnQuotes) { - case Lexeme::QuotedString: - case Lexeme::InterpStringSimple: - style = AstExprConstantString::QuotedSimple; - break; - case Lexeme::RawString: - style = AstExprConstantString::QuotedRaw; - break; - default: - LUAU_ASSERT(false && "Invalid string type"); + switch (lexer.current().type) + { + case Lexeme::InterpStringSimple: + style = AstExprConstantString::QuotedSimple; + break; + case Lexeme::RawString: + style = AstExprConstantString::QuotedRaw; + break; + case Lexeme::QuotedString: + style = lexer.current().getQuoteStyle() == Lexeme::QuoteStyle::Single + ? AstExprConstantString::QuotedSingle + : AstExprConstantString::QuotedSimple; + break; + default: + LUAU_ASSERT(false && "Invalid string type"); + } + } + else + { + switch (lexer.current().type) + { + case Lexeme::QuotedString: + case Lexeme::InterpStringSimple: + style = AstExprConstantString::QuotedSimple; + break; + case Lexeme::RawString: + style = AstExprConstantString::QuotedRaw; + break; + default: + LUAU_ASSERT(false && "Invalid string type"); + } } CstExprConstantString::QuoteStyle fullStyle; diff --git a/CodeGen/include/Luau/ConditionA64.h b/CodeGen/include/Luau/ConditionA64.h index 9e7d1fa9..b0bd3fe6 100644 --- a/CodeGen/include/Luau/ConditionA64.h +++ b/CodeGen/include/Luau/ConditionA64.h @@ -52,6 +52,35 @@ enum class ConditionA64 Count }; +// Returns a condition that for 'a op b' will result in 'b op a' +// Only a subset of conditions is allowed +inline ConditionA64 getInverseCondition(ConditionA64 cond) +{ + switch (cond) + { + case ConditionA64::Equal: + return ConditionA64::Equal; + case ConditionA64::NotEqual: + return ConditionA64::NotEqual; + case ConditionA64::UnsignedGreater: + return ConditionA64::CarryClear; + case ConditionA64::UnsignedLessEqual: + return ConditionA64::CarrySet; + case ConditionA64::GreaterEqual: + return ConditionA64::LessEqual; + case ConditionA64::Less: + return ConditionA64::Greater; + case ConditionA64::Greater: + return ConditionA64::Less; + case ConditionA64::LessEqual: + return ConditionA64::GreaterEqual; + default: + CODEGEN_ASSERT(!"invalid ConditionA64 value for getInverseCondition"); + } + + return ConditionA64::Count; +} + } // namespace A64 } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/include/Luau/ConditionX64.h b/CodeGen/include/Luau/ConditionX64.h index 39ee3f02..9c1d48bb 100644 --- a/CodeGen/include/Luau/ConditionX64.h +++ b/CodeGen/include/Luau/ConditionX64.h @@ -45,7 +45,8 @@ enum class ConditionX64 : uint8_t Count }; -inline ConditionX64 getReverseCondition(ConditionX64 cond) +// Returns a condition that for 'a op b' will result in '!(a op b)' +inline ConditionX64 getNegatedCondition(ConditionX64 cond) { switch (cond) { @@ -108,5 +109,54 @@ inline ConditionX64 getReverseCondition(ConditionX64 cond) return ConditionX64::Count; } +// Returns a condition that for 'a op b' will result in 'b op a' +// Only a subset of conditions is allowed +inline ConditionX64 getInverseCondition(ConditionX64 cond) +{ + switch (cond) + { + case ConditionX64::Below: + return ConditionX64::Above; + case ConditionX64::BelowEqual: + return ConditionX64::AboveEqual; + case ConditionX64::Above: + return ConditionX64::Above; + case ConditionX64::AboveEqual: + return ConditionX64::BelowEqual; + case ConditionX64::Equal: + return ConditionX64::Equal; + case ConditionX64::Less: + return ConditionX64::Greater; + case ConditionX64::LessEqual: + return ConditionX64::GreaterEqual; + case ConditionX64::Greater: + return ConditionX64::Less; + case ConditionX64::GreaterEqual: + return ConditionX64::LessEqual; + case ConditionX64::NotBelow: + return ConditionX64::NotAbove; + case ConditionX64::NotBelowEqual: + return ConditionX64::NotAboveEqual; + case ConditionX64::NotAbove: + return ConditionX64::NotBelow; + case ConditionX64::NotAboveEqual: + return ConditionX64::NotBelowEqual; + case ConditionX64::NotEqual: + return ConditionX64::NotEqual; + case ConditionX64::NotLess: + return ConditionX64::NotGreater; + case ConditionX64::NotLessEqual: + return ConditionX64::NotGreaterEqual; + case ConditionX64::NotGreater: + return ConditionX64::NotLess; + case ConditionX64::NotGreaterEqual: + return ConditionX64::NotLessEqual; + default: + CODEGEN_ASSERT(!"invalid ConditionX64 value for getInverseCondition"); + } + + return ConditionX64::Count; +} + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 2f02f994..4919f3b9 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -217,6 +217,11 @@ enum class IrCmd : uint8_t // C: condition CMP_ANY, + // Perform a comparison of two integer numbers. Result is an integer register containing 0 or 1 + // A, B: int + // C: condition + CMP_INT, + // Unconditional jump // A: block/vmexit/undef JUMP, diff --git a/CodeGen/include/Luau/IrUtils.h b/CodeGen/include/Luau/IrUtils.h index eb1cf079..520aec45 100644 --- a/CodeGen/include/Luau/IrUtils.h +++ b/CodeGen/include/Luau/IrUtils.h @@ -112,6 +112,7 @@ inline bool hasResult(IrCmd cmd) case IrCmd::UNM_VEC: case IrCmd::NOT_ANY: case IrCmd::CMP_ANY: + case IrCmd::CMP_INT: case IrCmd::TABLE_LEN: case IrCmd::TABLE_SETNUM: case IrCmd::STRING_LEN: diff --git a/CodeGen/src/BytecodeAnalysis.cpp b/CodeGen/src/BytecodeAnalysis.cpp index ddccf9ca..e0ed512e 100644 --- a/CodeGen/src/BytecodeAnalysis.cpp +++ b/CodeGen/src/BytecodeAnalysis.cpp @@ -548,6 +548,12 @@ static void applyBuiltinCall(LuauBuiltinFunction bfid, BytecodeTypes& types) types.b = LBC_TYPE_VECTOR; types.c = LBC_TYPE_VECTOR; // We can mark optional arguments break; + case LBF_VECTOR_LERP: + types.result = LBC_TYPE_VECTOR; + types.a = LBC_TYPE_VECTOR; + types.b = LBC_TYPE_VECTOR; + types.c = LBC_TYPE_NUMBER; + break; case LBF_MATH_LERP: types.result = LBC_TYPE_NUMBER; types.a = LBC_TYPE_NUMBER; diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index 1427e30e..4544c218 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -185,6 +185,8 @@ const char* getCmdName(IrCmd cmd) return "NOT_ANY"; case IrCmd::CMP_ANY: return "CMP_ANY"; + case IrCmd::CMP_INT: + return "CMP_INT"; case IrCmd::JUMP: return "JUMP"; case IrCmd::JUMP_IF_TRUTHY: diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 48c2b9a9..8941d5e6 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -12,6 +12,8 @@ #include "lstate.h" #include "lgc.h" +LUAU_FASTFLAG(LuauCodeGenDirectBtest) + namespace Luau { namespace CodeGen @@ -798,6 +800,30 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } break; } + case IrCmd::CMP_INT: + { + CODEGEN_ASSERT(FFlag::LuauCodeGenDirectBtest); + + inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a, inst.b}); + + IrCondition cond = conditionOp(inst.c); + + if (inst.a.kind == IrOpKind::Constant) + { + build.cmp(regOp(inst.b), intOp(inst.a)); + build.cset(inst.regA64, getInverseCondition(getConditionInt(cond))); + } + else if (inst.a.kind == IrOpKind::Inst) + { + build.cmp(regOp(inst.a), intOp(inst.b)); + build.cset(inst.regA64, getConditionInt(cond)); + } + else + { + CODEGEN_ASSERT(!"Unsupported instruction form"); + } + break; + } case IrCmd::CMP_ANY: { IrCondition cond = conditionOp(inst.c); diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 2c4d2837..257419cf 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -16,6 +16,8 @@ #include "lstate.h" #include "lgc.h" +LUAU_FASTFLAG(LuauCodeGenDirectBtest) + namespace Luau { namespace CodeGen @@ -759,6 +761,34 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.setLabel(exit); break; } + case IrCmd::CMP_INT: + { + CODEGEN_ASSERT(FFlag::LuauCodeGenDirectBtest); + + // Cannot reuse operand registers as a target because we have to modify it before the comparison + inst.regX64 = regs.allocReg(SizeX64::dword, index); + + // We are going to operate on byte register, those do not clear high bits on write + build.xor_(inst.regX64, inst.regX64); + + IrCondition cond = conditionOp(inst.c); + + if (inst.a.kind == IrOpKind::Constant) + { + build.cmp(regOp(inst.b), intOp(inst.a)); + build.setcc(getInverseCondition(getConditionInt(cond)), byteReg(inst.regX64)); + } + else if (inst.a.kind == IrOpKind::Inst) + { + build.cmp(regOp(inst.a), intOp(inst.b)); + build.setcc(getConditionInt(cond), byteReg(inst.regX64)); + } + else + { + CODEGEN_ASSERT(!"Unsupported instruction form"); + } + break; + } case IrCmd::CMP_ANY: { IrCondition cond = conditionOp(inst.c); @@ -2246,7 +2276,7 @@ void IrLoweringX64::jumpOrAbortOnUndef(ConditionX64 cond, IrOp target, const IrB } else { - build.jcc(getReverseCondition(cond), label); + build.jcc(getNegatedCondition(cond), label); build.ud2(); build.setLabel(label); } diff --git a/CodeGen/src/IrTranslateBuiltins.cpp b/CodeGen/src/IrTranslateBuiltins.cpp index 04f24378..1a5b8599 100644 --- a/CodeGen/src/IrTranslateBuiltins.cpp +++ b/CodeGen/src/IrTranslateBuiltins.cpp @@ -8,6 +8,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauCodeGenDirectBtest) + // TODO: when nresults is less than our actual result count, we can skip computing/writing unused results static const int kMinMaxUnrolledParams = 5; @@ -411,21 +413,30 @@ static BuiltinImplResult translateBuiltinBit32BinaryOp( if (btest) { - IrOp falsey = build.block(IrBlockKind::Internal); - IrOp truthy = build.block(IrBlockKind::Internal); - IrOp exit = build.block(IrBlockKind::Internal); - build.inst(IrCmd::JUMP_CMP_INT, res, build.constInt(0), build.cond(IrCondition::Equal), falsey, truthy); + if (FFlag::LuauCodeGenDirectBtest) + { + IrOp value = build.inst(IrCmd::CMP_INT, res, build.constInt(0), build.cond(IrCondition::NotEqual)); + build.inst(IrCmd::STORE_INT, build.vmReg(ra), value); + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TBOOLEAN)); + } + else + { + IrOp falsey = build.block(IrBlockKind::Internal); + IrOp truthy = build.block(IrBlockKind::Internal); + IrOp exit = build.block(IrBlockKind::Internal); + build.inst(IrCmd::JUMP_CMP_INT, res, build.constInt(0), build.cond(IrCondition::Equal), falsey, truthy); - build.beginBlock(falsey); - build.inst(IrCmd::STORE_INT, build.vmReg(ra), build.constInt(0)); - build.inst(IrCmd::JUMP, exit); + build.beginBlock(falsey); + build.inst(IrCmd::STORE_INT, build.vmReg(ra), build.constInt(0)); + build.inst(IrCmd::JUMP, exit); - build.beginBlock(truthy); - build.inst(IrCmd::STORE_INT, build.vmReg(ra), build.constInt(1)); - build.inst(IrCmd::JUMP, exit); + build.beginBlock(truthy); + build.inst(IrCmd::STORE_INT, build.vmReg(ra), build.constInt(1)); + build.inst(IrCmd::JUMP, exit); - build.beginBlock(exit); - build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TBOOLEAN)); + build.beginBlock(exit); + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TBOOLEAN)); + } } else { diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index f77ceb78..c8e1929e 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -16,6 +16,8 @@ #include #include +LUAU_FASTFLAG(LuauCodeGenDirectBtest) + namespace Luau { namespace CodeGen @@ -193,6 +195,7 @@ IrValueKind getCmdValueKind(IrCmd cmd) return IrValueKind::Double; case IrCmd::NOT_ANY: case IrCmd::CMP_ANY: + case IrCmd::CMP_INT: return IrValueKind::Int; case IrCmd::JUMP: case IrCmd::JUMP_IF_TRUTHY: @@ -790,6 +793,17 @@ void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint3 substitute(function, inst, build.constInt(function.intOp(inst.b) == 1 ? 0 : 1)); } break; + case IrCmd::CMP_INT: + CODEGEN_ASSERT(FFlag::LuauCodeGenDirectBtest); + + if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) + { + if (compare(function.intOp(inst.a), function.intOp(inst.b), conditionOp(inst.c))) + substitute(function, inst, build.constInt(1)); + else + substitute(function, inst, build.constInt(0)); + } + break; case IrCmd::JUMP_EQ_TAG: if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) { diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index 2104e288..5e05eb89 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -21,6 +21,7 @@ LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64) LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64) LUAU_FASTINTVARIABLE(LuauCodeGenLiveSlotReuseLimit, 8) LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks) +LUAU_FASTFLAG(LuauCodeGenDirectBtest) namespace Luau { @@ -607,6 +608,7 @@ static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid case LBF_VECTOR_CLAMP: case LBF_VECTOR_MIN: case LBF_VECTOR_MAX: + case LBF_VECTOR_LERP: case LBF_MATH_LERP: break; case LBF_TABLE_INSERT: @@ -1326,6 +1328,9 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::NOT_ANY: state.substituteOrRecord(inst, index); break; + case IrCmd::CMP_INT: + CODEGEN_ASSERT(FFlag::LuauCodeGenDirectBtest); + break; case IrCmd::CMP_ANY: state.invalidateUserCall(); break; diff --git a/Common/include/Luau/Bytecode.h b/Common/include/Luau/Bytecode.h index 3716d1f1..7d90d475 100644 --- a/Common/include/Luau/Bytecode.h +++ b/Common/include/Luau/Bytecode.h @@ -618,6 +618,8 @@ enum LuauBuiltinFunction // math.lerp LBF_MATH_LERP, + + LBF_VECTOR_LERP, }; // Capture type, used in LOP_CAPTURE diff --git a/Compiler/src/Builtins.cpp b/Compiler/src/Builtins.cpp index bc342bd3..c6ae9b71 100644 --- a/Compiler/src/Builtins.cpp +++ b/Compiler/src/Builtins.cpp @@ -7,6 +7,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauCompileVectorLerp) + namespace Luau { namespace Compile @@ -251,6 +253,8 @@ static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& op return LBF_VECTOR_MIN; if (builtin.method == "max") return LBF_VECTOR_MAX; + if (FFlag::LuauCompileVectorLerp && builtin.method == "lerp") + return LBF_VECTOR_LERP; } if (options.vectorCtor) @@ -552,6 +556,8 @@ BuiltinInfo getBuiltinInfo(int bfid) case LBF_VECTOR_MIN: case LBF_VECTOR_MAX: return {-1, 1}; // variadic + case LBF_VECTOR_LERP: + return {3, 1, BuiltinInfo::Flag_NoneSafe}; case LBF_MATH_LERP: return {3, 1, BuiltinInfo::Flag_NoneSafe}; diff --git a/Compiler/src/Types.cpp b/Compiler/src/Types.cpp index e984370e..4fcd984b 100644 --- a/Compiler/src/Types.cpp +++ b/Compiler/src/Types.cpp @@ -771,6 +771,7 @@ struct TypeMapVisitor : AstVisitor case LBF_VECTOR_CLAMP: case LBF_VECTOR_MIN: case LBF_VECTOR_MAX: + case LBF_VECTOR_LERP: recordResolvedType(node, &builtinTypes.vectorType); break; } diff --git a/VM/src/lbuiltins.cpp b/VM/src/lbuiltins.cpp index 3fc687e1..4c1a3c37 100644 --- a/VM/src/lbuiltins.cpp +++ b/VM/src/lbuiltins.cpp @@ -25,6 +25,8 @@ #endif #endif +LUAU_FASTFLAG(LuauVectorLerp) + // luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM // The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack. // If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path @@ -1701,6 +1703,26 @@ static int luauF_vectormax(lua_State* L, StkId res, TValue* arg0, int nresults, return -1; } +static int luauF_vectorlerp(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) +{ + if (FFlag::LuauVectorLerp && nparams >= 3 && nresults <= 1 && ttisvector(arg0) && ttisvector(args) && ttisnumber(args + 1)) + { + const float* a = vvalue(arg0); + const float* b = vvalue(args); + const float t = static_cast(nvalue(args + 1)); + +#if LUA_VECTOR_SIZE == 4 + setvvalue(res, luai_lerpf(a[0], b[0], t), luai_lerpf(a[1], b[1], t), luai_lerpf(a[2], b[2], t), luai_lerpf(a[3], b[3], t)); +#else + setvvalue(res, luai_lerpf(a[0], b[0], t), luai_lerpf(a[1], b[1], t), luai_lerpf(a[2], b[2], t), 0.0f); +#endif + + return 1; + } + + return -1; +} + static int luauF_lerp(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) { if (nparams >= 3 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1)) @@ -1915,6 +1937,8 @@ const luau_FastFunction luauF_table[256] = { luauF_lerp, + luauF_vectorlerp, + // When adding builtins, add them above this line; what follows is 64 "dummy" entries with luauF_missing fallback. // This is important so that older versions of the runtime that don't support newer builtins automatically fall back via luauF_missing. // Given the builtin addition velocity this should always provide a larger compatibility window than bytecode versions suggest. diff --git a/VM/src/lnumutils.h b/VM/src/lnumutils.h index de56bb09..53c563e7 100644 --- a/VM/src/lnumutils.h +++ b/VM/src/lnumutils.h @@ -58,6 +58,11 @@ inline double luai_numidiv(double a, double b) } LUAU_FASTMATH_END +inline float luai_lerpf(float a, float b, float t) +{ + return (t == 1.0) ? b : a + (b - a) * t; +} + #define luai_num2int(i, d) ((i) = (int)(d)) // On MSVC in 32-bit, double to unsigned cast compiles into a call to __dtoui3, so we invoke x87->int64 conversion path manually diff --git a/VM/src/lveclib.cpp b/VM/src/lveclib.cpp index f24dc8b6..13cfb847 100644 --- a/VM/src/lveclib.cpp +++ b/VM/src/lveclib.cpp @@ -6,6 +6,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauVectorLerp) + static int vector_create(lua_State* L) { // checking argument count to avoid accepting 'nil' as a valid value @@ -283,6 +285,21 @@ static int vector_index(lua_State* L) luaL_error(L, "attempt to index vector with '%s'", name); } +static int vector_lerp(lua_State* L) +{ + const float* a = luaL_checkvector(L, 1); + const float* b = luaL_checkvector(L, 2); + const float t = static_cast(luaL_checknumber(L, 3)); + +#if LUA_VECTOR_SIZE == 4 + lua_pushvector(L, luai_lerpf(a[0], b[0], t), luai_lerpf(a[1], b[1], t), luai_lerpf(a[2], b[2], t), luai_lerpf(a[3], b[3], t)); +#else + lua_pushvector(L, luai_lerpf(a[0], b[0], t), luai_lerpf(a[1], b[1], t), luai_lerpf(a[2], b[2], t)); +#endif + + return 1; +} + static const luaL_Reg vectorlib[] = { {"create", vector_create}, {"magnitude", vector_magnitude}, @@ -326,6 +343,13 @@ int luaopen_vector(lua_State* L) { luaL_register(L, LUA_VECLIBNAME, vectorlib); + if (FFlag::LuauVectorLerp) + { + // To unflag put {"lerp", vector_lerp} in the `vectorlib` table + lua_pushcfunction(L, vector_lerp, "lerp"); + lua_setfield(L, -2, "lerp"); + } + #if LUA_VECTOR_SIZE == 4 lua_pushvector(L, 0.0f, 0.0f, 0.0f, 0.0f); lua_setfield(L, -2, "zero"); diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 7f628abe..ae028d44 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -21,7 +21,7 @@ LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauImplicitTableIndexerKeys3) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) +LUAU_FASTFLAG(LuauIncludeBreakContinueStatements) using namespace Luau; @@ -4685,10 +4685,7 @@ TEST_CASE_FIXTURE(ACFixture, "bidirectional_autocomplete_in_function_call") TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_via_bidirectional_self") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; check(R"( type IAccount = { @@ -4722,4 +4719,163 @@ TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_via_bidirectional_self") CHECK_EQ(ac.entryMap.count("balance"), 1); } +TEST_CASE_FIXTURE(ACFixture, "autocomplete_include_break_continue_in_loop") +{ + ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; + + check(R"(for x in y do + @1 + if true then + @2 + end + end)"); + + auto ac = autocomplete('1'); + + CHECK(ac.entryMap.count("break") > 0); + CHECK(ac.entryMap.count("continue") > 0); + + ac = autocomplete('2'); + + CHECK(ac.entryMap.count("break") > 0); + CHECK(ac.entryMap.count("continue") > 0); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_outside_loop") +{ + ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; + + check(R"(@1if true then + @2 + end)"); + + auto ac = autocomplete('1'); + + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); + + ac = autocomplete('2'); + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_function_boundary") +{ + ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; + + check(R"(for i = 1, 10 do + local function helper() + @1 + end + end)"); + + auto ac = autocomplete('1'); + + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_in_param") +{ + ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; + + check(R"(while @1 do + end)"); + + auto ac = autocomplete('1'); + + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_incomplete_while") +{ + ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; + + check("while @1"); + + auto ac = autocomplete('1'); + + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_incomplete_for") +{ + ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; + + check("for @1 in @2 do"); + + auto ac = autocomplete('1'); + + ac = autocomplete('1'); + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); + + ac = autocomplete('2'); + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_expr_func") +{ + ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; + + check(R"(while true do + local _ = function () + @1 + end + end)"); + + auto ac = autocomplete('1'); + + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_include_break_continue_in_repeat") +{ + ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; + + check(R"(repeat + @1 + until foo())"); + + auto ac = autocomplete('1'); + + CHECK(ac.entryMap.count("break") > 0); + CHECK(ac.entryMap.count("continue") > 0); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_include_break_continue_in_nests") +{ + ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; + + check(R"(while ((function () + while true do + @1 + end + end)()) do + end)"); + + auto ac = autocomplete('1'); + + CHECK(ac.entryMap.count("break") > 0); + CHECK(ac.entryMap.count("continue") > 0); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_in_incomplete_loop") +{ + ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; + + check(R"(while foo() do + @1)"); + + auto ac = autocomplete('1'); + + // We'd like to include break/continue here but the incomplete loop ends immediately. + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); +} + TEST_SUITE_END(); diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index f1a1c91b..c6adc450 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -36,9 +36,13 @@ void luau_callhook(lua_State* L, lua_Hook hook, void* userdata); LUAU_FASTFLAG(LuauHeapDumpStringSizeOverhead) LUAU_FASTFLAG(DebugLuauAbortingChecks) +LUAU_FASTFLAG(LuauCodeGenDirectBtest) LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_DYNAMIC_FASTFLAG(LuauErrorYield) LUAU_DYNAMIC_FASTFLAG(LuauSafeStackCheck) +LUAU_FASTFLAG(LuauVectorLerp) +LUAU_FASTFLAG(LuauCompileVectorLerp) +LUAU_FASTFLAG(LuauTypeCheckerVectorLerp) static lua_CompileOptions defaultOptions() { @@ -1130,6 +1134,8 @@ TEST_CASE("Vector") TEST_CASE("VectorLibrary") { + ScopedFastFlag _[]{{FFlag::LuauCompileVectorLerp, true}, {FFlag::LuauTypeCheckerVectorLerp, true}, {FFlag::LuauVectorLerp, true}}; + lua_CompileOptions copts = defaultOptions(); SUBCASE("O0") @@ -3112,6 +3118,8 @@ TEST_CASE("SafeEnv") TEST_CASE("Native") { + ScopedFastFlag luauCodeGenDirectBtest{FFlag::LuauCodeGenDirectBtest, true}; + // This tests requires code to run natively, otherwise all 'is_native' checks will fail if (!codegen || !luau_codegen_supported()) return; diff --git a/tests/Fixture.h b/tests/Fixture.h index 4653e260..4bd2b891 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -29,7 +29,6 @@ LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests) -LUAU_FASTFLAG(LuauUpdateSetMetatableTypeSignature) LUAU_FASTFLAG(LuauTidyTypeUtils) LUAU_FASTFLAG(DebugLuauAlwaysShowConstraintSolvingIncomplete); @@ -153,7 +152,6 @@ struct Fixture // Most often those are changes related to builtin type definitions. // In that case, flag can be forced to 'true' using the example below: // ScopedFastFlag sff_LuauExampleFlagDefinition{FFlag::LuauExampleFlagDefinition, true}; - ScopedFastFlag sff_LuauUpdateSetMetatableTypeSignature{FFlag::LuauUpdateSetMetatableTypeSignature, true}; ScopedFastFlag sff_TypeUtilTidy{FFlag::LuauTidyTypeUtils, true}; diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index dbee4f81..0f76d5a2 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -31,7 +31,6 @@ LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauFragmentRequiresCanBeResolvedToAModule) LUAU_FASTFLAG(LuauFragmentAutocompleteTracksRValueRefinements) LUAU_FASTFLAG(LuauPopulateSelfTypesInFragment) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauParseIncompleteInterpStringsWithLocation) LUAU_FASTFLAG(LuauForInProvidesRecommendations) @@ -3948,8 +3947,6 @@ require(script.A). TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_types_provide_rich_autocomplete") { - ScopedFastFlag sff{FFlag::LuauPushFunctionTypesInFunctionStatement, true}; - const std::string source = R"( type Service = { Start: (self: Service) -> (), @@ -3990,7 +3987,6 @@ end TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_with_fancy_metatable_setting_new_solver") { - ScopedFastFlag sff{FFlag::LuauPushFunctionTypesInFunctionStatement, true}; const std::string source = R"( type IAccount = { __index: IAccount, @@ -4060,8 +4056,6 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_with_fancy_metatabl TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_with_colon_good_recommendations") { - ScopedFastFlag sff{FFlag::LuauPushFunctionTypesInFunctionStatement, true}; - const std::string source = R"( type Service = { Start: (self: Service) -> (), diff --git a/tests/Generalization.test.cpp b/tests/Generalization.test.cpp index 647d963a..6dc69cb4 100644 --- a/tests/Generalization.test.cpp +++ b/tests/Generalization.test.cpp @@ -19,6 +19,9 @@ LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(DebugLuauForbidInternalTypes) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) + +LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) TEST_SUITE_BEGIN("Generalization"); @@ -404,16 +407,27 @@ TEST_CASE_FIXTURE(Fixture, "generics_dont_leak_into_callback") TEST_CASE_FIXTURE(Fixture, "generics_dont_leak_into_callback_2") { - ScopedFastFlag _{FFlag::LuauSolverV2, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches, true}, {FFlag::LuauSubtypingGenericsDoesntUseVariance, true} + }; - // FIXME: CLI-156389: this is clearly wrong, but also predates this PR. - LUAU_REQUIRE_NO_ERRORS(check(R"( - local func: (T, (T) -> ()) -> () = nil :: any - local foobar: (number) -> () = nil :: any - func({}, function(obj) - foobar(obj) - end) - )")); + CheckResult result = check(R"( +local func: (T, (T) -> ()) -> () = nil :: any +local foobar: (number) -> () = nil :: any +func({}, function(obj) + foobar(obj) +end) + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + const GenericBoundsMismatch* gbm = get(result.errors[0]); + REQUIRE_MESSAGE(gbm, "Expected GenericBoundsMismatch but got: " << toString(result.errors[0])); + CHECK_EQ(gbm->genericName, "T"); + CHECK_EQ(gbm->lowerBounds.size(), 1); + CHECK_EQ(toString(gbm->lowerBounds[0]), "{ }"); + CHECK_EQ(gbm->upperBounds.size(), 1); + CHECK_EQ(toString(gbm->upperBounds[0]), "number"); + CHECK_EQ(result.errors[0].location, Location{Position{3, 0}, Position{3, 4}}); } TEST_CASE_FIXTURE(Fixture, "generic_argument_with_singleton_oss_1808") diff --git a/tests/IrLowering.test.cpp b/tests/IrLowering.test.cpp index 59e56c32..25df42aa 100644 --- a/tests/IrLowering.test.cpp +++ b/tests/IrLowering.test.cpp @@ -17,6 +17,7 @@ #include LUAU_FASTFLAG(LuauCodeGenSimplifyImport2) +LUAU_FASTFLAG(LuauCodeGenDirectBtest) static void luauLibraryConstantLookup(const char* library, const char* member, Luau::CompileConstant* constant) { @@ -2205,4 +2206,35 @@ bb_bytecode_0: ); } +TEST_CASE("Bit32BtestDirect") +{ + ScopedFastFlag luauCodeGenDirectBtest{FFlag::LuauCodeGenDirectBtest, true}; + + CHECK_EQ( + "\n" + getCodegenAssembly(R"( +local function foo(a: number) + return bit32.btest(a, 0x1f) +end +)"), + R"( +; function foo($arg0) line 2 +bb_0: + CHECK_TAG R0, tnumber, exit(entry) + JUMP bb_2 +bb_2: + JUMP bb_bytecode_1 +bb_bytecode_1: + CHECK_SAFE_ENV exit(2) + %7 = LOAD_DOUBLE R0 + %8 = NUM_TO_UINT %7 + %10 = BITAND_UINT %8, 31i + %11 = CMP_INT %10, 0i, not_eq + STORE_INT R1, %11 + STORE_TAG R1, tboolean + INTERRUPT 7u + RETURN R1, 1i +)" + ); +} + TEST_SUITE_END(); diff --git a/tests/RuntimeLimits.test.cpp b/tests/RuntimeLimits.test.cpp index 0809ead9..d5634fe5 100644 --- a/tests/RuntimeLimits.test.cpp +++ b/tests/RuntimeLimits.test.cpp @@ -24,7 +24,6 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauIceLess) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauSimplifyAnyAndUnion) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauDontDynamicallyCreateRedundantSubtypeConstraints) @@ -302,7 +301,6 @@ TEST_CASE_FIXTURE(LimitFixture, "Signal_exerpt" * doctest::timeout(0.5)) {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, {FFlag::LuauResetConditionalContextProperly, true}, - {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, // And this flag is the one that fixes it. {FFlag::LuauSimplifyAnyAndUnion, true}, @@ -387,7 +385,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "limit_number_of_dynamically_created_constrai {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, {FFlag::LuauResetConditionalContextProperly, true}, - {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, {FFlag::LuauDontDynamicallyCreateRedundantSubtypeConstraints, true}, }; diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 8d2b0397..ff970505 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -21,6 +21,7 @@ LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) +LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) using namespace Luau; @@ -1762,4 +1763,19 @@ TEST_CASE_FIXTURE(SubtypeFixture, "free_types_might_be_subtypes") REQUIRE(1 == result.assumedConstraints.size()); } +TEST_CASE_FIXTURE(Fixture, "variadic_any_pack_should_suppress_errors_during_overload_resolution") +{ + ScopedFastFlag sff{FFlag::LuauVariadicAnyPackShouldBeErrorSuppressing, true}; + auto res = check(R"( +type ActionCallback = (string) -> ...any + +function bindAction(callback: ActionCallback) + local _ = function(...) + callback(...) + end +end +)"); + LUAU_REQUIRE_NO_ERRORS(res); +} + TEST_SUITE_END(); diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index ee2d14bf..6fcea56b 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -15,6 +15,8 @@ using namespace Luau; LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) +LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) TEST_SUITE_BEGIN("ToString"); @@ -693,6 +695,11 @@ TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_cyclic_function_type_in_union" TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_cyclic_function_type_in_intersection") { + ScopedFastFlag sffs[] = { + {FFlag::LuauExplicitSkipBoundTypes, true}, + {FFlag::LuauReduceSetTypeStackPressure, true}, + }; + CheckResult result = check(R"( function f() return f end local a: ((number) -> ()) & typeof(f) @@ -700,10 +707,7 @@ TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_cyclic_function_type_in_inters LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauSolverV2) - CHECK("(() -> t1) & ((number) -> ()) where t1 = () -> t1" == toString(requireType("a"))); - else - CHECK_EQ("((number) -> ()) & t1 where t1 = () -> t1", toString(requireType("a"))); + CHECK_EQ("((number) -> ()) & t1 where t1 = () -> t1", toString(requireType("a"))); } TEST_CASE_FIXTURE(Fixture, "self_recursive_instantiated_param") diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index dabde854..62f4b24b 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -12,6 +12,7 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauResetConditionalContextProperly) +LUAU_FASTFLAG(LuauTypeFunNoScopeMapRef) TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); @@ -2451,4 +2452,35 @@ end CHECK(toString(result.errors[0]) == R"(Redefinition of type 't0', previously defined at line 2)"); } +TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_fuzz_environment_scope_crash") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauTypeFunNoScopeMapRef{FFlag::LuauTypeFunNoScopeMapRef, true}; + + CheckResult result = check(R"( +local _, running = ... +type function t255() end +if _ then + type function t1() end + type function t6(l0,...) end + type function t255() end + export type function t0() end +else + type function t1(...) end + type function t66(...) end + type function t255() end + if running then + export type function t255() end + type function t0(l0) end + end +end +type function t0(l0,...) end +export type function t66(...) + export type function t255() end +end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 3cafe58b..93e2bc14 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -13,7 +13,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) -LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads) TEST_SUITE_BEGIN("BuiltinTests"); @@ -1713,10 +1712,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "better_string_format_error_when_format_strin TEST_CASE_FIXTURE(Fixture, "write_only_table_assertion") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( local function accept(t: { write foo: number }) diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index c1c62cbd..4b49a583 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(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAG(LuauScopeMethodsAreSolverAgnostic) TEST_SUITE_BEGIN("TypeInferExternTypes"); @@ -443,8 +442,6 @@ b.X = 2 -- real Vector2.X is also read-only TEST_CASE_FIXTURE(ExternTypeFixture, "detailed_class_unification_error") { - ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}; - CheckResult result = check(R"( local function foo(v) return v.X :: number + string.len(v.Y) diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 9b5c78ca..40081a8c 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -27,11 +27,11 @@ LUAU_FASTFLAG(LuauCollapseShouldNotCrash) LUAU_FASTFLAG(LuauFormatUseLastPosition) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -1449,7 +1449,14 @@ local a = {{x=4}, {x=7}, {x=1}} table.sort(a, function(x, y) return x.x < y.x end) )"); - LUAU_REQUIRE_NO_ERRORS(result); + if (FFlag::LuauSubtypingReportGenericBoundMismatches) + { + // FIXME CLI-161355 + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); + } + else + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "variadic_any_is_compatible_with_a_generic_TypePack") @@ -3184,10 +3191,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_pack_variadic") TEST_CASE_FIXTURE(Fixture, "table_annotated_explicit_self") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; CheckResult results = check(R"( type MyObject = { @@ -3210,11 +3214,7 @@ TEST_CASE_FIXTURE(Fixture, "table_annotated_explicit_self") TEST_CASE_FIXTURE(Fixture, "oss_1871") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, - }; - + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( export type Test = { @@ -3233,10 +3233,7 @@ TEST_CASE_FIXTURE(Fixture, "oss_1871") TEST_CASE_FIXTURE(BuiltinsFixture, "io_manager_oop_ish") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( type IIOManager = { @@ -3268,10 +3265,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "io_manager_oop_ish") TEST_CASE_FIXTURE(BuiltinsFixture, "generic_function_statement") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( type Object = { diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index fafe9ee2..b13c8ee3 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -13,11 +13,11 @@ LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauIntersectNotNil) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauContainsAnyGenericFollowBeforeChecking) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) using namespace Luau; @@ -65,7 +65,7 @@ TEST_CASE_FIXTURE(Fixture, "check_generic_local_function2") CHECK_EQ(getBuiltins()->numberType, requireType("y")); } -TEST_CASE_FIXTURE(BuiltinsFixture, "unions_and_generics") +TEST_CASE_FIXTURE(Fixture, "unions_and_generics") { CheckResult result = check(R"( type foo = (T | {T}) -> T @@ -1220,12 +1220,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_table_method") TEST_CASE_FIXTURE(Fixture, "correctly_instantiate_polymorphic_member_functions") { - // Prior to `LuauPushFunctionTypesInFunctionStatement`, we _always_ forced - // a constraint when solving this block. - ScopedFastFlag sffs[] = { - {FFlag::DebugLuauAssertOnForcedConstraint, true}, - {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, - }; + ScopedFastFlag sff{FFlag::DebugLuauAssertOnForcedConstraint, true}; CheckResult result = check(R"( local T = {} @@ -1490,7 +1485,10 @@ TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded" g12({x=1}, {x=2}, function(x, y) return {x=x.x + y.x} end) )"); - LUAU_REQUIRE_NO_ERRORS(result); + if (FFlag::LuauSubtypingReportGenericBoundMismatches) + LUAU_REQUIRE_ERROR_COUNT(2, result); // FIXME CLI-161355 + else + LUAU_REQUIRE_NO_ERRORS(result); } // Important FIXME CLI-161128: This test exposes some problems with overload @@ -1892,4 +1890,42 @@ end )"); } +TEST_CASE_FIXTURE(Fixture, "ensure_that_invalid_generic_instantiations_error") +{ + ScopedFastFlag _[] = {{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches, true}}; + CheckResult res = check(R"( + local func: (T, (T) -> ()) -> () = nil :: any + local foobar: (number) -> () = nil :: any + func({}, foobar) + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, res); + if (FFlag::LuauSolverV2) + CHECK(get(res.errors[0])); + else + CHECK(get(res.errors[0])); +} + +TEST_CASE_FIXTURE(Fixture, "ensure_that_invalid_generic_instantiations_error_1") +{ + ScopedFastFlag _[] = {{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches, true}}; + CheckResult res = check(R"( + --!strict + + function insert(arr: {T}, value: T) + return arr + end + + local a: {number} = {} + + local b = insert(a, "five") + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, res); + if (FFlag::LuauSolverV2) + CHECK(get(res.errors[0])); + else + CHECK(get(res.errors[0])); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index 4049ecbf..980c6e3e 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -11,7 +11,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) TEST_SUITE_BEGIN("IntersectionTypes"); @@ -334,8 +333,6 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed") TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect") { - ScopedFastFlag sff{FFlag::LuauPushFunctionTypesInFunctionStatement, true}; - CheckResult result = check(R"( type X = { x: (number) -> number } type Y = { y: (string) -> string } diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 5e5cc386..242e8934 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -21,6 +21,7 @@ LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauNewNonStrictNoErrorsPassingNever) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) using namespace Luau; @@ -2350,7 +2351,14 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "mutate_prop_of_some_refined_symb end )"); - LUAU_REQUIRE_NO_ERRORS(result); + if (FFlag::LuauSubtypingReportGenericBoundMismatches) + { + // FIXME CLI-161355 + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); + } + else + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(BuiltinsFixture, "ensure_t_after_return_references_all_reachable_points") diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index ac6170ad..9abc8c80 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -24,17 +24,16 @@ LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit) -LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauNormalizationLimitTyvarUnionSize) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauExtendSealedTableUpperBounds) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) +LUAU_FASTFLAG(LuauAllowMixedTables) TEST_SUITE_BEGIN("TableTests"); @@ -491,8 +490,6 @@ TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_1") TEST_CASE_FIXTURE(BuiltinsFixture, "table_param_width_subtyping_2") { - ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}; - CheckResult result = check(R"( --!strict function foo(o) @@ -3879,10 +3876,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "a_free_shape_can_turn_into_a_scalar_directly TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_tables_in_call_is_unsound") { - ScopedFastFlag sff[]{ - {FFlag::LuauInstantiateInSubtyping, true}, - {FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}, - }; + ScopedFastFlag sff{FFlag::LuauInstantiateInSubtyping, true}; CheckResult result = check(R"( --!strict @@ -4436,8 +4430,6 @@ TEST_CASE_FIXTURE(Fixture, "new_solver_supports_read_write_properties") TEST_CASE_FIXTURE(Fixture, "table_subtyping_error_suppression") { - ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}; - CheckResult result = check(R"( function one(tbl: {x: any}) end function two(tbl: {x: string}) one(tbl) end -- ok, string <: any and any <: string @@ -5835,10 +5827,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1651") TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_call") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( local function take(_: { foo: string? }) end @@ -5849,10 +5838,7 @@ TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_call") TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_call_incorrect") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; CheckResult results = check(R"( local function take(_: { foo: string?, bing: number }) end @@ -5870,10 +5856,7 @@ TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_call_incorrect") TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_call_singleton") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; CheckResult results = check(R"( local function take(_: { foo: "foo" }) end @@ -5888,7 +5871,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1450") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}, {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, {FFlag::LuauResetConditionalContextProperly, true} @@ -6047,4 +6029,30 @@ TEST_CASE_FIXTURE(Fixture, "free_types_with_sealed_table_upper_bounds_can_still_ CHECK("({ read nope: () -> (...unknown) } & { x: number }) -> ()" == toString(requireType("foo"))); } +TEST_CASE_FIXTURE(Fixture, "mixed_tables_are_ok_when_explicit") +{ + ScopedFastFlag _{FFlag::LuauAllowMixedTables, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local foo: { [number | string]: unknown } = { + Key = "sorry", + "A", + "B", + } + )")); +} + +TEST_CASE_FIXTURE(Fixture, "mixed_tables_are_ok_for_any_key") +{ + ScopedFastFlag _{FFlag::LuauAllowMixedTables, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local foo: { [any]: unknown } = { + Key = "sorry", + "A", + "B", + } + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 3f4f3637..6a071b17 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -28,11 +28,11 @@ LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(LuauForceSimplifyConstraint2) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauMissingFollowMappedGenericPacks) +LUAU_FASTFLAG(LuauOccursCheckInCommit) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauResetConditionalContextProperly) @@ -2513,36 +2513,6 @@ TEST_CASE_FIXTURE(Fixture, "if_then_else_two_errors") CHECK_EQ("number", toString(err2->givenType)); } -TEST_CASE_FIXTURE(Fixture, "simplify_constraint_can_force") -{ - ScopedFastFlag sff[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauForceSimplifyConstraint2, true}, - // NOTE: Feel free to clip this test when this flag is clipped. - {FFlag::LuauPushFunctionTypesInFunctionStatement, false}, - }; - - CheckResult result = check(R"( - --!strict - - local foo = nil - - bar(function() - if foo then - foo.baz() - end - end) - - foo = {} - - foo.a = { - foo.b - } - )"); - - LUAU_CHECK_NO_ERROR(result, ConstraintSolvingIncompleteError); -} - TEST_CASE_FIXTURE(Fixture, "standalone_constraint_solving_incomplete_is_hidden") { ScopedFastFlag sffs[] = { @@ -2623,4 +2593,50 @@ do end } +TEST_CASE_FIXTURE(Fixture, "txnlog_checks_for_occurrence_before_self_binding_a_type") +{ + ScopedFastFlag sff[] = { + {FFlag::LuauSolverV2, false}, + {FFlag::LuauOccursCheckInCommit, true} + }; + + + CheckResult result = check(R"( + local any = nil :: any + + function f1(x) + x:m() + local _ = x.A.p.a + end + + function f2(x) + local _ = x.d + end + + function f3(x) + local a = "" + a = x.d.p + local _ = undef[x.a] + end + + function f4(x) + f2(x) + if undef and x and x:m() then + any(x) + return + end + f3(x) + for _, v in any.x do + local a = x[v].p + end + a.b = x + if x.q ~= nil then + f1(x) -- things go bad here + end + end + + return f4 + )"); +} + TEST_SUITE_END(); diff --git a/tests/TypeVar.test.cpp b/tests/TypeVar.test.cpp index 7274ed48..bf3b7452 100644 --- a/tests/TypeVar.test.cpp +++ b/tests/TypeVar.test.cpp @@ -366,7 +366,7 @@ struct VisitCountTracker final : TypeOnceVisitor std::unordered_map tpVisits; VisitCountTracker() - : TypeOnceVisitor("VisitCountTracker") + : TypeOnceVisitor("VisitCountTracker", /* skipBoundTypes */ true) { } diff --git a/tests/conformance/native.luau b/tests/conformance/native.luau index dae8ddf9..94d67d04 100644 --- a/tests/conformance/native.luau +++ b/tests/conformance/native.luau @@ -237,6 +237,17 @@ end assert(pcall(fuzzfail23) == false) +local function fuzzfail24(...) + local _ = l4 + repeat + local function l0() + end + until (...)[_][_]:n16((_),_,l4,bit32.btest(0,_,_,_) / bit32.btest(0,_,_,_)(_,_,_,0,((_)),_)) + do end +end + +assert(pcall(fuzzfail24) == false) + local function arraySizeInv1() local t = {1, 2, nil, nil, nil, nil, nil, nil, nil, true} diff --git a/tests/conformance/vector_library.luau b/tests/conformance/vector_library.luau index dd5f2d1b..09da41b8 100644 --- a/tests/conformance/vector_library.luau +++ b/tests/conformance/vector_library.luau @@ -166,6 +166,16 @@ assert(vector.clamp(vector.create(1, 1, 1), vector.create(0, 1, 2), vector.creat assert(vector.clamp(vector.create(1, 1, 1), vector.create(-1, -1, -1), vector.create(0, 1, 2)) == vector.create(0, 1, 1)) assert(select("#", vector.clamp(vector.zero, vector.zero, vector.one)) == 1) +-- lerp +assert(vector.lerp(vector.zero, vector.one, 0) == vector.zero) +assert(vector.lerp(vector.zero, vector.one, 1) == vector.one) +assert(vector.lerp(vector.zero, vector.one, 0.5) == vector.create(0.5, 0.5, 0.5)) +assert(vector.lerp(vector.one, vector.zero, 0.5) == vector.create(0.5, 0.5, 0.5)) +assert(vector.lerp(vector.one, vector.zero, 0.5) == vector.create(0.5, 0.5, 0.5)) +assert(vector.lerp(vector.create(1, 2, 3), vector.create(3, 2, 1), 0.5) == vector.create(2, 2, 2)) +assert(vector.lerp(vector.create(-1, -1, -3), vector.zero, 0.5) == vector.create(-0.5, -0.5, -1.5)) +assert(ecall(function() return vector.lerp(vector.zero, vector.one, vector.one) end) == "invalid argument #3 to 'lerp' (number expected, got vector)") + -- validate component access assert(vector.create(1, 2, 3).x == 1) assert(vector.create(1, 2, 3).X == 1) diff --git a/tests/main.cpp b/tests/main.cpp index 005a3e61..8ae42460 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -29,6 +29,8 @@ #include #endif +#include +#include #include #include @@ -426,6 +428,11 @@ int main(int argc, char** argv) doctest::String filter; if (doctest::parseOption(argc, argv, "--run_test", &filter) && filter[0] == '=') { + if (doctest::parseOption(argc, argv, "--run_tests_in_file")) + { + fprintf(stderr, "ERROR: Cannot pass both --run_test and --run_tests_in_file\n"); + return 1; + } const char* f = filter.c_str() + 1; const char* s = strchr(f, '/'); @@ -440,6 +447,15 @@ int main(int argc, char** argv) } } + doctest::String filter_path; + if (doctest::parseOption(argc, argv, "--run_tests_in_file", &filter_path) && filter_path[0] == '=') + { + filter_path = filter_path.substr(1, filter_path.size() - 1); + std::ifstream filter_stream(filter_path.c_str()); + std::string case_list((std::istreambuf_iterator(filter_stream)), std::istreambuf_iterator()); + context.addFilter("test-case", case_list.c_str()); + } + // These callbacks register unit tests that need runtime support to be // correctly set up. Running them here means that all command line flags // have been parsed, fast flags have been set, and we've potentially already