Sync to upstream/release/688 (#1968)

This commit is contained in:
ariel 2025-08-22 15:26:46 -07:00 committed by GitHub
parent 31725a6521
commit 0afee00064
Signed by: DevComp
GPG key ID: B5690EEEBB952194
69 changed files with 1481 additions and 599 deletions

View file

@ -1,11 +1,12 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once #pragma once
#include "Luau/Ast.h"
#include "Luau/Location.h" #include "Luau/Location.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeIds.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/Ast.h"
#include <set> #include <set>
@ -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<TypeId> lowerBounds;
std::vector<TypeId> upperBounds;
GenericBoundsMismatch(std::string_view genericName, TypeIds lowerBoundSet, TypeIds upperBoundSet);
bool operator==(const GenericBoundsMismatch& rhs) const;
};
using TypeErrorData = Variant< using TypeErrorData = Variant<
TypeMismatch, TypeMismatch,
UnknownSymbol, UnknownSymbol,
@ -569,7 +582,8 @@ using TypeErrorData = Variant<
GenericTypeCountMismatch, GenericTypeCountMismatch,
GenericTypePackCountMismatch, GenericTypePackCountMismatch,
MultipleNonviableOverloads, MultipleNonviableOverloads,
RecursiveRestraintViolation>; RecursiveRestraintViolation,
GenericBoundsMismatch>;
struct TypeErrorSummary struct TypeErrorSummary
{ {

View file

@ -7,6 +7,8 @@
#include "Luau/Unifiable.h" #include "Luau/Unifiable.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauExplicitSkipBoundTypes)
namespace Luau namespace Luau
{ {
@ -93,7 +95,7 @@ struct GenericTypeFinder : TypeOnceVisitor
bool found = false; bool found = false;
GenericTypeFinder() GenericTypeFinder()
: TypeOnceVisitor("GenericTypeFinder") : TypeOnceVisitor("GenericTypeFinder", FFlag::LuauExplicitSkipBoundTypes)
{ {
} }

View file

@ -68,6 +68,9 @@ struct SubtypingResult
// those assumptions are recorded here. // those assumptions are recorded here.
std::vector<SubtypeConstraint> assumedConstraints; std::vector<SubtypeConstraint> assumedConstraints;
/// If any generic bounds were invalid, report them here
std::vector<GenericBoundsMismatch> genericBoundsMismatches;
SubtypingResult& andAlso(const SubtypingResult& other); SubtypingResult& andAlso(const SubtypingResult& other);
SubtypingResult& orElse(const SubtypingResult& other); SubtypingResult& orElse(const SubtypingResult& other);
SubtypingResult& withBothComponent(TypePath::Component component); SubtypingResult& withBothComponent(TypePath::Component component);
@ -358,7 +361,15 @@ private:
NotNull<Scope> scope, NotNull<Scope> scope,
SubtypingResult& original); SubtypingResult& original);
SubtypingResult checkGenericBounds(const SubtypingEnvironment::GenericBounds& bounds, SubtypingEnvironment& env, NotNull<Scope> scope); SubtypingResult checkGenericBounds(
const SubtypingEnvironment::GenericBounds& bounds,
SubtypingEnvironment& env,
NotNull<Scope> scope,
std::string_view genericName
);
// TODO: Clip with LuauSubtypingReportGenericBoundMismatches
SubtypingResult checkGenericBounds_DEPRECATED(const SubtypingEnvironment::GenericBounds& bounds, SubtypingEnvironment& env, NotNull<Scope> scope);
static void maybeUpdateBounds( static void maybeUpdateBounds(
TypeId here, TypeId here,

View file

@ -112,7 +112,6 @@ struct TxnLog
// If both logs talk about the same type, pack, or table, the rhs takes // If both logs talk about the same type, pack, or table, the rhs takes
// priority. // priority.
void concat(TxnLog rhs); void concat(TxnLog rhs);
void concatAsIntersections(TxnLog rhs, NotNull<TypeArena> arena);
void concatAsUnion(TxnLog rhs, NotNull<TypeArena> arena); void concatAsUnion(TxnLog rhs, NotNull<TypeArena> arena);
// Commits the TxnLog, rebinding all type pointers to their pending states. // Commits the TxnLog, rebinding all type pointers to their pending states.
@ -269,8 +268,6 @@ struct TxnLog
return Luau::get_if<T>(&ty->ty) != nullptr; return Luau::get_if<T>(&ty->ty) != nullptr;
} }
std::pair<std::vector<TypeId>, std::vector<TypePackId>> getChanges() const;
private: private:
// unique_ptr is used to give us stable pointers across insertions into the // unique_ptr is used to give us stable pointers across insertions into the
// map. Otherwise, it would be really easy to accidentally invalidate the // map. Otherwise, it would be really easy to accidentally invalidate the
@ -291,12 +288,6 @@ private:
void popSeen(TypeOrPackId lhs, TypeOrPackId rhs); void popSeen(TypeOrPackId lhs, TypeOrPackId rhs);
public: 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 // It is sometimes the case under DCR that we speculatively rebind
// GenericTypes to other types as though they were free. We mark logs that // 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 // contain these kinds of substitutions as radioactive so that we know that

View file

@ -80,9 +80,6 @@ struct Unifier
bool checkInhabited = true; // Normalize types to check if they are inhabited bool checkInhabited = true; // Normalize types to check if they are inhabited
CountMismatch::Context ctx = CountMismatch::Arg; CountMismatch::Context ctx = CountMismatch::Arg;
// If true, generics act as free types when unifying.
bool hideousFixMeGenericsAreActuallyFree = false;
UnifierSharedState& sharedState; UnifierSharedState& sharedState;
// When the Unifier is forced to unify two blocked types (or packs), they // When the Unifier is forced to unify two blocked types (or packs), they

View file

@ -556,7 +556,7 @@ struct GenericTypeVisitor
*/ */
struct TypeVisitor : GenericTypeVisitor<std::unordered_set<void*>> struct TypeVisitor : GenericTypeVisitor<std::unordered_set<void*>>
{ {
explicit TypeVisitor(const std::string visitorName, bool skipBoundTypes = false) explicit TypeVisitor(const std::string visitorName, bool skipBoundTypes)
: GenericTypeVisitor{visitorName, {}, skipBoundTypes} : GenericTypeVisitor{visitorName, {}, skipBoundTypes}
{ {
} }
@ -565,7 +565,7 @@ struct TypeVisitor : GenericTypeVisitor<std::unordered_set<void*>>
/// Visit each type under a given type. Each type will only be checked once even if there are multiple paths to it. /// 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<DenseHashSet<void*>> struct TypeOnceVisitor : GenericTypeVisitor<DenseHashSet<void*>>
{ {
explicit TypeOnceVisitor(const std::string visitorName, bool skipBoundTypes = false) explicit TypeOnceVisitor(const std::string visitorName, bool skipBoundTypes)
: GenericTypeVisitor{visitorName, DenseHashSet<void*>{nullptr}, skipBoundTypes} : GenericTypeVisitor{visitorName, DenseHashSet<void*>{nullptr}, skipBoundTypes}
{ {
} }

View file

@ -25,6 +25,7 @@ LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames) LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames)
LUAU_FASTFLAG(LuauImplicitTableIndexerKeys3) LUAU_FASTFLAG(LuauImplicitTableIndexerKeys3)
LUAU_FASTFLAGVARIABLE(LuauIncludeBreakContinueStatements)
static const std::unordered_set<std::string> kStatementStartingKeywords = static const std::unordered_set<std::string> kStatementStartingKeywords =
{"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; {"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); return binding.location == Location() || !binding.location.containsClosed(pos);
} }
static bool isValidBreakContinueContext(const std::vector<AstNode*>& ancestry, Position position)
{
LUAU_ASSERT(FFlag::LuauIncludeBreakContinueStatements);
for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it)
{
if ((*it)->is<AstStatFunction>() || (*it)->is<AstStatLocalFunction>() || (*it)->is<AstExprFunction>() || (*it)->is<AstStatTypeFunction>() ||
(*it)->is<AstTypeFunction>())
return false;
if (auto statWhile = (*it)->as<AstStatWhile>(); statWhile && statWhile->body->location.contains(position))
return true;
else if (auto statFor = (*it)->as<AstStatFor>(); statFor && statFor->body->location.contains(position))
return true;
else if (auto statForIn = (*it)->as<AstStatForIn>(); statForIn && statForIn->body->location.contains(position))
return true;
else if (auto statRepeat = (*it)->as<AstStatRepeat>(); statRepeat && statRepeat->body->location.contains(position))
return true;
}
return false;
}
static AutocompleteEntryMap autocompleteStatement( static AutocompleteEntryMap autocompleteStatement(
const Module& module, const Module& module,
const std::vector<AstNode*>& ancestry, const std::vector<AstNode*>& ancestry,
@ -1254,8 +1277,20 @@ static AutocompleteEntryMap autocompleteStatement(
scope = scope->parent; scope = scope->parent;
} }
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) for (const auto& kw : kStatementStartingKeywords)
result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword}); result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword});
}
for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it) for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it)
{ {

View file

@ -34,7 +34,6 @@
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAGVARIABLE(LuauUpdateSetMetatableTypeSignature)
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauEmplaceNotPushBack)
@ -408,35 +407,17 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
} }
if (frontend.getLuauSolverMode() == SolverMode::New) if (frontend.getLuauSolverMode() == SolverMode::New)
{
TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT});
if (FFlag::LuauUpdateSetMetatableTypeSignature)
{ {
// setmetatable<T: {}, MT>(T, MT) -> setmetatable<T, MT> // setmetatable<T: {}, MT>(T, MT) -> setmetatable<T, MT>
TypeId setmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions().setmetatableFunc, {genericT, genericMT}}); TypeId setmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions().setmetatableFunc, {genericT, genericMT}});
addGlobalBinding( addGlobalBinding(
globals, "setmetatable", makeFunction(arena, std::nullopt, {genericT, genericMT}, {}, {genericT, genericMT}, {setmtReturn}), "@luau" globals,
"setmetatable",
makeFunction(arena, std::nullopt, {genericT, genericMT}, {}, {genericT, genericMT}, {setmtReturn}),
"@luau"
); );
} }
else else
{
// clang-format off
// setmetatable<T: {}, MT>(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
}
}
else
{ {
// clang-format off // clang-format off
// setmetatable<T: {}, MT>(T, MT) -> { @metatable MT, T } // setmetatable<T: {}, MT>(T, MT) -> { @metatable MT, T }

View file

@ -27,6 +27,7 @@ LUAU_FASTFLAG(LuauReduceSetTypeStackPressure)
LUAU_FASTFLAGVARIABLE(LuauRefineNoRefineAlways) LUAU_FASTFLAGVARIABLE(LuauRefineNoRefineAlways)
LUAU_FASTFLAGVARIABLE(LuauRefineDistributesOverUnions) LUAU_FASTFLAGVARIABLE(LuauRefineDistributesOverUnions)
LUAU_FASTFLAG(LuauExplicitSkipBoundTypes)
namespace Luau namespace Luau
{ {
@ -1063,7 +1064,7 @@ struct FindRefinementBlockers : TypeOnceVisitor
DenseHashSet<TypeId> found{nullptr}; DenseHashSet<TypeId> found{nullptr};
FindRefinementBlockers() FindRefinementBlockers()
: TypeOnceVisitor("FindRefinementBlockers") : TypeOnceVisitor("FindRefinementBlockers", FFlag::LuauExplicitSkipBoundTypes)
{ {
} }

View file

@ -5,8 +5,8 @@
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauForceSimplifyConstraint2) LUAU_FASTFLAG(LuauForceSimplifyConstraint2)
LUAU_FASTFLAGVARIABLE(LuauExplicitSkipBoundTypes)
namespace Luau namespace Luau
{ {
@ -24,7 +24,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor
bool traverseIntoTypeFunctions = true; bool traverseIntoTypeFunctions = true;
explicit ReferenceCountInitializer(NotNull<TypeIds> result) explicit ReferenceCountInitializer(NotNull<TypeIds> result)
: TypeOnceVisitor("ReferenceCountInitializer") : TypeOnceVisitor("ReferenceCountInitializer", FFlag::LuauExplicitSkipBoundTypes)
, result(result) , result(result)
{ {
} }
@ -178,13 +178,11 @@ TypeIds Constraint::getMaybeMutatedFreeTypes() const
rci.traverse(tcc->exprType); rci.traverse(tcc->exprType);
} }
if (FFlag::LuauPushFunctionTypesInFunctionStatement) // NOTE: this should probably be in an if-else chain with the above.
{
if (auto pftc = get<PushFunctionTypeConstraint>(*this)) if (auto pftc = get<PushFunctionTypeConstraint>(*this))
{ {
rci.traverse(pftc->functionType); rci.traverse(pftc->functionType);
} }
}
return types; return types;
} }

View file

@ -39,15 +39,17 @@ LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties) LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties)
LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500) LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500)
LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTracksRValueRefinements) LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTracksRValueRefinements)
LUAU_FASTFLAGVARIABLE(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAGVARIABLE(LuauInferActualIfElseExprType2) LUAU_FASTFLAGVARIABLE(LuauInferActualIfElseExprType2)
LUAU_FASTFLAGVARIABLE(LuauDoNotPrototypeTableIndex) LUAU_FASTFLAGVARIABLE(LuauDoNotPrototypeTableIndex)
LUAU_FASTFLAGVARIABLE(LuauTypeFunNoScopeMapRef)
LUAU_FASTFLAGVARIABLE(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAGVARIABLE(LuauTrackFreeInteriorTypePacks)
LUAU_FASTFLAGVARIABLE(LuauResetConditionalContextProperly) LUAU_FASTFLAGVARIABLE(LuauResetConditionalContextProperly)
LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3)
LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauEmplaceNotPushBack)
LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure)
LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) LUAU_FASTFLAG(LuauParametrizedAttributeSyntax)
LUAU_FASTFLAG(LuauExplicitSkipBoundTypes)
LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes)
namespace Luau namespace Luau
{ {
@ -137,7 +139,7 @@ struct HasFreeType : TypeOnceVisitor
bool result = false; bool result = false;
HasFreeType() HasFreeType()
: TypeOnceVisitor("TypeOnceVisitor") : TypeOnceVisitor("TypeOnceVisitor", FFlag::LuauExplicitSkipBoundTypes)
{ {
} }
@ -643,7 +645,7 @@ struct FindSimplificationBlockers : TypeOnceVisitor
bool found = false; bool found = false;
FindSimplificationBlockers() FindSimplificationBlockers()
: TypeOnceVisitor("FindSimplificationBlockers") : TypeOnceVisitor("FindSimplificationBlockers", FFlag::LuauExplicitSkipBoundTypes)
{ {
} }
@ -1565,8 +1567,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
sig.bodyScope->rvalueRefinements[def] = sig.signature; sig.bodyScope->rvalueRefinements[def] = sig.signature;
} }
if (FFlag::LuauPushFunctionTypesInFunctionStatement)
{
if (auto indexName = function->name->as<AstExprIndexName>()) if (auto indexName = function->name->as<AstExprIndexName>())
{ {
auto beginProp = checkpoint(this); auto beginProp = checkpoint(this);
@ -1608,11 +1608,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
{ {
checkFunctionBody(sig.bodyScope, function->func); checkFunctionBody(sig.bodyScope, function->func);
} }
}
else
{
checkFunctionBody(sig.bodyScope, function->func);
}
Checkpoint end = checkpoint(this); Checkpoint end = checkpoint(this);
@ -1906,6 +1901,92 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
reportError(function->location, ReservedIdentifier{"typeof"}); reportError(function->location, ReservedIdentifier{"typeof"});
} }
if (FFlag::LuauTypeFunNoScopeMapRef)
{
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<TypeId>{});
checkFunctionBody(sig.bodyScope, function->body);
Checkpoint endCheckpoint = checkpoint(this);
TypeId generalizedTy = arena->addType(BlockedType{});
NotNull<Constraint> gc = addConstraint(
sig.signatureScope,
function->location,
GeneralizationConstraint{
generalizedTy,
sig.signature,
std::vector<TypeId>{},
}
);
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<BlockedType>(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<PackSubtypeConstraint>(*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<TypeId> 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<BlockedType>(unpackedTy); bt && nullptr == bt->getOwner())
emplaceType<BoundType>(asMutable(unpackedTy), generalizedTy);
return ControlFlow::None;
}
else
{
auto scopePtr = astTypeFunctionEnvironmentScopes.find(function); auto scopePtr = astTypeFunctionEnvironmentScopes.find(function);
LUAU_ASSERT(scopePtr); LUAU_ASSERT(scopePtr);
@ -1986,6 +2067,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
return ControlFlow::None; return ControlFlow::None;
} }
}
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareGlobal* global) 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<TypeId> expectedType, bool forceSingleton) Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> 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) if (forceSingleton)
return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})}; 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<TypeId> expectedType, bool forceSingleton) Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool* boolExpr, std::optional<TypeId> 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; const TypeId singletonType = boolExpr->value ? builtinTypes->trueType : builtinTypes->falseType;
if (forceSingleton) if (forceSingleton)
return Inference{singletonType}; return Inference{singletonType};
@ -4321,65 +4422,6 @@ TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location locati
return resultType; return resultType;
} }
struct FragmentTypeCheckGlobalPrepopulator_DEPRECATED : AstVisitor
{
const NotNull<Scope> globalScope;
const NotNull<Scope> currentScope;
const NotNull<const DataFlowGraph> dfg;
const NotNull<TypeArena> arena;
FragmentTypeCheckGlobalPrepopulator_DEPRECATED(
NotNull<Scope> globalScope,
NotNull<Scope> currentScope,
NotNull<const DataFlowGraph> dfg,
NotNull<TypeArena> 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<AstExprGlobal>())
{
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 struct GlobalPrepopulator : AstVisitor
{ {
const NotNull<Scope> globalScope; const NotNull<Scope> globalScope;

View file

@ -36,8 +36,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies)
LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings) LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings)
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying) LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying)
LUAU_FASTFLAG(LuauLimitUnification) LUAU_FASTFLAG(LuauLimitUnification)
LUAU_FASTFLAGVARIABLE(LuauForceSimplifyConstraint2) LUAU_FASTFLAGVARIABLE(LuauForceSimplifyConstraint2)
@ -49,6 +47,8 @@ LUAU_FASTFLAGVARIABLE(LuauExtendSealedTableUpperBounds)
LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure)
LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) LUAU_FASTFLAG(LuauParametrizedAttributeSyntax)
LUAU_FASTFLAGVARIABLE(LuauNameConstraintRestrictRecursiveTypes) LUAU_FASTFLAGVARIABLE(LuauNameConstraintRestrictRecursiveTypes)
LUAU_FASTFLAG(LuauExplicitSkipBoundTypes)
LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes)
namespace Luau namespace Luau
{ {
@ -346,7 +346,7 @@ struct InfiniteTypeFinder : TypeOnceVisitor
bool foundInfiniteType = false; bool foundInfiniteType = false;
explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull<Scope> scope) explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull<Scope> scope)
: TypeOnceVisitor("InfiniteTypeFinder") : TypeOnceVisitor("InfiniteTypeFinder", FFlag::LuauExplicitSkipBoundTypes)
, solver(solver) , solver(solver)
, signature(signature) , signature(signature)
, scope(scope) , scope(scope)
@ -683,7 +683,7 @@ struct TypeSearcher : TypeVisitor
} }
explicit TypeSearcher(TypeId needle, Polarity initialPolarity) explicit TypeSearcher(TypeId needle, Polarity initialPolarity)
: TypeVisitor("TypeSearcher") : TypeVisitor("TypeSearcher", FFlag::LuauExplicitSkipBoundTypes)
, needle(needle) , needle(needle)
, current(initialPolarity) , current(initialPolarity)
{ {
@ -1664,7 +1664,7 @@ struct ContainsGenerics_DEPRECATED : public TypeOnceVisitor
bool found = false; bool found = false;
ContainsGenerics_DEPRECATED() ContainsGenerics_DEPRECATED()
: TypeOnceVisitor("ContainsGenerics_DEPRECATED") : TypeOnceVisitor("ContainsGenerics_DEPRECATED", FFlag::LuauExplicitSkipBoundTypes)
{ {
} }
@ -1748,7 +1748,7 @@ struct ContainsGenerics : public TypeOnceVisitor
NotNull<DenseHashSet<const void*>> generics; NotNull<DenseHashSet<const void*>> generics;
explicit ContainsGenerics(NotNull<DenseHashSet<const void*>> generics) explicit ContainsGenerics(NotNull<DenseHashSet<const void*>> generics)
: TypeOnceVisitor("ContainsGenerics") : TypeOnceVisitor("ContainsGenerics", FFlag::LuauExplicitSkipBoundTypes)
, generics{generics} , generics{generics}
{ {
} }
@ -1887,7 +1887,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
} }
} }
else if (expr->is<AstExprConstantBool>() || expr->is<AstExprConstantString>() || expr->is<AstExprConstantNumber>() || else if (expr->is<AstExprConstantBool>() || expr->is<AstExprConstantString>() || expr->is<AstExprConstantNumber>() ||
expr->is<AstExprConstantNil>() || (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls && expr->is<AstExprTable>())) expr->is<AstExprConstantNil>() || expr->is<AstExprTable>())
{ {
if (ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks})) if (ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks}))
{ {
@ -1901,20 +1901,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
} }
u2.unify(actualArgTy, expectedArgTy); u2.unify(actualArgTy, expectedArgTy);
} }
else if (!FFlag::LuauTableLiteralSubtypeCheckFunctionCalls && expr->is<AstExprTable>() &&
!ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks}))
{
Subtyping sp{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}};
std::vector<TypeId> 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: // Consider:
// //
// local Direction = { Left = 1, Right = 2 } // local Direction = { Left = 1, Right = 2 }
@ -1932,7 +1920,6 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
NotNull<Constraint> addition = pushConstraint(constraint->scope, constraint->location, std::move(c)); NotNull<Constraint> addition = pushConstraint(constraint->scope, constraint->location, std::move(c));
inheritBlocks(constraint, addition); inheritBlocks(constraint, addition);
} }
}
return true; return true;
} }
@ -1962,6 +1949,7 @@ bool ConstraintSolver::tryDispatch(const TableCheckConstraint& c, NotNull<const
bool ConstraintSolver::tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint) bool ConstraintSolver::tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint)
{ {
LUAU_ASSERT(!FFlag::DebugLuauStringSingletonBasedOnQuotes);
std::optional<TypeId> expectedType = c.expectedType ? std::make_optional<TypeId>(follow(*c.expectedType)) : std::nullopt; std::optional<TypeId> expectedType = c.expectedType ? std::make_optional<TypeId>(follow(*c.expectedType)) : std::nullopt;
if (expectedType && (isBlocked(*expectedType) || get<PendingExpansionType>(*expectedType))) if (expectedType && (isBlocked(*expectedType) || get<PendingExpansionType>(*expectedType)))
return block(*expectedType, constraint); return block(*expectedType, constraint);
@ -2213,7 +2201,7 @@ struct BlockedTypeFinder : TypeOnceVisitor
std::optional<TypeId> blocked; std::optional<TypeId> blocked;
BlockedTypeFinder() BlockedTypeFinder()
: TypeOnceVisitor("ContainsGenerics_DEPRECATED") : TypeOnceVisitor("ContainsGenerics_DEPRECATED", FFlag::LuauExplicitSkipBoundTypes)
{ {
} }
@ -2444,7 +2432,6 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
// was blocked on missing a member. In the above, we may // was blocked on missing a member. In the above, we may
// try to solve for `hasProp T "bar"`, block, then never // try to solve for `hasProp T "bar"`, block, then never
// wake up without forcing a constraint. // wake up without forcing a constraint.
if (FFlag::LuauPushFunctionTypesInFunctionStatement)
unblock(lhsType, constraint->location); unblock(lhsType, constraint->location);
} }
@ -3568,7 +3555,7 @@ struct Blocker : TypeOnceVisitor
bool blocked = false; bool blocked = false;
explicit Blocker(NotNull<ConstraintSolver> solver, NotNull<const Constraint> constraint) explicit Blocker(NotNull<ConstraintSolver> solver, NotNull<const Constraint> constraint)
: TypeOnceVisitor("Blocker") : TypeOnceVisitor("Blocker", FFlag::LuauExplicitSkipBoundTypes)
, solver(solver) , solver(solver)
, constraint(constraint) , constraint(constraint)
{ {

View file

@ -1,6 +1,8 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerVectorLerp)
namespace Luau namespace Luau
{ {
@ -268,6 +270,37 @@ declare extern type vector with
z: number z: number
end 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: { declare vector: {
create: @checked (x: number, y: number, z: number?) -> vector, create: @checked (x: number, y: number, z: number?) -> vector,
magnitude: @checked (vec: vector) -> number, magnitude: @checked (vec: vector) -> number,
@ -301,7 +334,14 @@ std::string getBuiltinDefinitionSource()
result += kBuiltinDefinitionDebugSrc; result += kBuiltinDefinitionDebugSrc;
result += kBuiltinDefinitionUtf8Src; result += kBuiltinDefinitionUtf8Src;
result += kBuiltinDefinitionBufferSrc; result += kBuiltinDefinitionBufferSrc;
if (FFlag::LuauTypeCheckerVectorLerp)
{
result += kBuiltinDefinitionVectorSrc; result += kBuiltinDefinitionVectorSrc;
}
else
{
result += kBuiltinDefinitionVectorSrc_DEPRECATED;
}
return result; return result;
} }

View file

@ -894,6 +894,27 @@ struct ErrorConverter
{ {
return "Recursive type being used with different parameters."; 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 struct InvalidNameChecker
@ -1297,6 +1318,18 @@ bool MultipleNonviableOverloads::operator==(const MultipleNonviableOverloads& rh
return attemptedArgCount == rhs.attemptedArgCount; 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) std::string toString(const TypeError& error)
{ {
return toString(error, TypeErrorToStringOptions{}); return toString(error, TypeErrorToStringOptions{});
@ -1523,6 +1556,13 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
else if constexpr (std::is_same_v<T, RecursiveRestraintViolation>) else if constexpr (std::is_same_v<T, RecursiveRestraintViolation>)
{ {
} }
else if constexpr (std::is_same_v<T, GenericBoundsMismatch>)
{
for (auto& lowerBound : e.lowerBounds)
lowerBound = clone(lowerBound);
for (auto& upperBound : e.upperBounds)
upperBound = clone(upperBound);
}
else else
static_assert(always_false_v<T>, "Non-exhaustive type switch"); static_assert(always_false_v<T>, "Non-exhaustive type switch");
} }

View file

@ -47,6 +47,7 @@ LUAU_FASTFLAGVARIABLE(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
LUAU_FASTFLAGVARIABLE(DebugLuauAlwaysShowConstraintSolvingIncomplete) LUAU_FASTFLAGVARIABLE(DebugLuauAlwaysShowConstraintSolvingIncomplete)
LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3)
LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauEmplaceNotPushBack)
LUAU_FASTFLAG(LuauExplicitSkipBoundTypes)
namespace Luau namespace Luau
{ {
@ -1369,7 +1370,7 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons
struct InternalTypeFinder : TypeOnceVisitor struct InternalTypeFinder : TypeOnceVisitor
{ {
InternalTypeFinder() InternalTypeFinder()
: TypeOnceVisitor("InternalTypeFinder") : TypeOnceVisitor("InternalTypeFinder", FFlag::LuauExplicitSkipBoundTypes)
{ {
} }

View file

@ -18,6 +18,7 @@
LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization4)
LUAU_FASTFLAGVARIABLE(LuauReduceSetTypeStackPressure) LUAU_FASTFLAGVARIABLE(LuauReduceSetTypeStackPressure)
LUAU_FASTINTVARIABLE(LuauGenericCounterMaxDepth, 15) LUAU_FASTINTVARIABLE(LuauGenericCounterMaxDepth, 15)
LUAU_FASTFLAG(LuauExplicitSkipBoundTypes)
namespace Luau namespace Luau
{ {
@ -1406,7 +1407,7 @@ struct GenericCounter : TypeVisitor
bool hitLimits = false; bool hitLimits = false;
explicit GenericCounter(NotNull<DenseHashSet<TypeId>> cachedTypes) explicit GenericCounter(NotNull<DenseHashSet<TypeId>> cachedTypes)
: TypeVisitor("GenericCounter") : TypeVisitor("GenericCounter", FFlag::LuauExplicitSkipBoundTypes)
, cachedTypes(cachedTypes) , cachedTypes(cachedTypes)
{ {
} }

View file

@ -6,6 +6,7 @@
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauExplicitSkipBoundTypes)
namespace Luau namespace Luau
{ {
@ -21,7 +22,7 @@ struct InferPolarity : TypeVisitor
Polarity polarity = Polarity::Positive; Polarity polarity = Polarity::Positive;
explicit InferPolarity(NotNull<TypeArena> arena, NotNull<Scope> scope) explicit InferPolarity(NotNull<TypeArena> arena, NotNull<Scope> scope)
: TypeVisitor("InferPolarity") : TypeVisitor("InferPolarity", FFlag::LuauExplicitSkipBoundTypes)
, arena(arena) , arena(arena)
, scope(scope) , scope(scope)
{ {

View file

@ -269,6 +269,24 @@ static void errorToString(std::ostream& stream, const T& err)
stream << "MultipleNonviableOverloads { attemptedArgCount = " << err.attemptedArgCount << " }"; stream << "MultipleNonviableOverloads { attemptedArgCount = " << err.attemptedArgCount << " }";
else if constexpr (std::is_same_v<T, RecursiveRestraintViolation>) else if constexpr (std::is_same_v<T, RecursiveRestraintViolation>)
stream << "RecursiveRestraintViolation"; stream << "RecursiveRestraintViolation";
else if constexpr (std::is_same_v<T, GenericBoundsMismatch>)
{
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 else
static_assert(always_false_v<T>, "Non-exhaustive type switch"); static_assert(always_false_v<T>, "Non-exhaustive type switch");
} }

View file

@ -13,6 +13,8 @@
LUAU_FASTFLAG(LuauLimitUnification) LUAU_FASTFLAG(LuauLimitUnification)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance)
LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing)
LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches)
namespace Luau namespace Luau
{ {
@ -487,6 +489,13 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
argLocation = argExprs->at(argExprs->size() - 1)->location; argLocation = argExprs->at(argExprs->size() - 1)->location;
// TODO extract location from the SubtypingResult path and argExprs // 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) switch (reason.variance)
{ {
case SubtypingVariance::Covariant: case SubtypingVariance::Covariant:
@ -505,6 +514,12 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
} }
} }
if (FFlag::LuauSubtypingReportGenericBoundMismatches)
{
for (GenericBoundsMismatch& mismatch : sr.genericBoundsMismatches)
errors.emplace_back(fnExpr->location, std::move(mismatch));
}
return {Analysis::OverloadIsNonviable, std::move(errors)}; return {Analysis::OverloadIsNonviable, std::move(errors)};
} }

View file

@ -8,6 +8,8 @@
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauExplicitSkipBoundTypes)
namespace Luau namespace Luau
{ {
@ -21,7 +23,7 @@ struct Quantifier final : TypeOnceVisitor
bool seenMutableType = false; bool seenMutableType = false;
explicit Quantifier(TypeLevel level) explicit Quantifier(TypeLevel level)
: TypeOnceVisitor("Quantifier") : TypeOnceVisitor("Quantifier", /* skipBoundTypes */ false)
, level(level) , level(level)
{ {
} }

View file

@ -26,6 +26,7 @@ LUAU_FASTFLAGVARIABLE(LuauMissingFollowMappedGenericPacks)
LUAU_FASTFLAGVARIABLE(LuauSubtypingNegationsChecksNormalizationComplexity) LUAU_FASTFLAGVARIABLE(LuauSubtypingNegationsChecksNormalizationComplexity)
LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericsDoesntUseVariance)
LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauEmplaceNotPushBack)
LUAU_FASTFLAGVARIABLE(LuauSubtypingReportGenericBoundMismatches)
namespace Luau namespace Luau
{ {
@ -174,6 +175,8 @@ SubtypingResult& SubtypingResult::andAlso(const SubtypingResult& other)
normalizationTooComplex |= other.normalizationTooComplex; normalizationTooComplex |= other.normalizationTooComplex;
isCacheable &= other.isCacheable; isCacheable &= other.isCacheable;
errors.insert(errors.end(), other.errors.begin(), other.errors.end()); 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; return *this;
} }
@ -196,6 +199,8 @@ SubtypingResult& SubtypingResult::orElse(const SubtypingResult& other)
normalizationTooComplex |= other.normalizationTooComplex; normalizationTooComplex |= other.normalizationTooComplex;
isCacheable &= other.isCacheable; isCacheable &= other.isCacheable;
errors.insert(errors.end(), other.errors.begin(), other.errors.end()); 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; return *this;
} }
@ -710,8 +715,15 @@ SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp, NotNu
{ {
// Bounds should have exactly one entry // Bounds should have exactly one entry
LUAU_ASSERT(bounds->size() == 1); LUAU_ASSERT(bounds->size() == 1);
if (!bounds->empty()) if (FFlag::LuauSubtypingReportGenericBoundMismatches)
result.andAlso(checkGenericBounds(bounds->back(), env, scope)); {
if (bounds->empty())
continue;
if (const GenericType* gen = get<GenericType>(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) for (TypeId g : subFunction->generics)
{ {
g = follow(g); g = follow(g);
if (get<GenericType>(g)) if (const GenericType* gen = get<GenericType>(g))
{ {
auto bounds = env.mappedGenerics.find(g); auto bounds = env.mappedGenerics.find(g);
LUAU_ASSERT(bounds && !bounds->empty()); LUAU_ASSERT(bounds && !bounds->empty());
// Check the bounds are valid // 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(); bounds->pop_back();
} }
@ -2524,9 +2539,16 @@ SubtypingResult Subtyping::trySemanticSubtyping(SubtypingEnvironment& env,
return original; return original;
} }
SubtypingResult Subtyping::checkGenericBounds(const SubtypingEnvironment::GenericBounds& bounds, SubtypingEnvironment& env, NotNull<Scope> scope)
SubtypingResult Subtyping::checkGenericBounds(
const SubtypingEnvironment::GenericBounds& bounds,
SubtypingEnvironment& env,
NotNull<Scope> scope,
std::string_view genericName
)
{ {
LUAU_ASSERT(FFlag::LuauSubtypingGenericsDoesntUseVariance); LUAU_ASSERT(FFlag::LuauSubtypingGenericsDoesntUseVariance);
LUAU_ASSERT(FFlag::LuauSubtypingReportGenericBoundMismatches);
SubtypingResult result{true}; SubtypingResult result{true};
@ -2607,6 +2629,101 @@ SubtypingResult Subtyping::checkGenericBounds(const SubtypingEnvironment::Generi
SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope); SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope);
boundsResult.reasoning.clear(); 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> 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<UnionType>(lbTypes.take(), builtinTypes->neverType);
TypeId upperBound = makeAggregateType<IntersectionType>(ubTypes.take(), builtinTypes->unknownType);
std::shared_ptr<const NormalizedType> 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, 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); result.andAlso(boundsResult);
return result; return result;

View file

@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticStringification) LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticStringification)
LUAU_FASTFLAG(LuauExplicitSkipBoundTypes)
/* /*
* Enables increasing levels of verbosity for Luau type names when stringifying. * Enables increasing levels of verbosity for Luau type names when stringifying.
@ -51,7 +52,7 @@ namespace
struct FindCyclicTypes final : TypeVisitor struct FindCyclicTypes final : TypeVisitor
{ {
FindCyclicTypes() FindCyclicTypes()
: TypeVisitor("FindCyclicTypes") : TypeVisitor("FindCyclicTypes", FFlag::LuauExplicitSkipBoundTypes)
{ {
} }

View file

@ -9,6 +9,8 @@
#include <algorithm> #include <algorithm>
#include <stdexcept> #include <stdexcept>
LUAU_FASTFLAGVARIABLE(LuauOccursCheckInCommit)
namespace Luau namespace Luau
{ {
@ -84,29 +86,6 @@ void TxnLog::concat(TxnLog rhs)
radioactive |= rhs.radioactive; radioactive |= rhs.radioactive;
} }
void TxnLog::concatAsIntersections(TxnLog rhs, NotNull<TypeArena> 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<TypeArena> arena) void TxnLog::concatAsUnion(TxnLog rhs, NotNull<TypeArena> arena)
{ {
/* /*
@ -154,7 +133,7 @@ void TxnLog::concatAsUnion(TxnLog rhs, NotNull<TypeArena> arena)
// leftTy has been bound to rightTy, but rightTy has also been bound // 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 // to leftTy. We find the one that belongs to the more deeply nested
// scope and remove it from the log. // 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) if (discardLeft)
(*leftRep)->dead = true; (*leftRep)->dead = true;
@ -188,6 +167,57 @@ void TxnLog::concatAsUnion(TxnLog rhs, NotNull<TypeArena> arena)
radioactive |= rhs.radioactive; radioactive |= rhs.radioactive;
} }
// Like follow(), but only takes a single step.
//
// This is potentailly performance sensitive, so we use nullptr rather than an
// optional<TypeId> for the return type here.
static TypeId followOnce(TxnLog& log, TypeId ty)
{
if (auto bound = log.get<BoundType>(ty))
return bound->boundTo;
if (auto tt = log.get<TableType>(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() void TxnLog::commit()
{ {
LUAU_ASSERT(!radioactive); LUAU_ASSERT(!radioactive);
@ -195,7 +225,17 @@ void TxnLog::commit()
for (auto& [ty, rep] : typeVarChanges) for (auto& [ty, rep] : typeVarChanges)
{ {
if (!rep->dead) 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) for (auto& [tp, rep] : typePackChanges)
@ -474,16 +514,4 @@ TypePackId TxnLog::follow(TypePackId tp) const
); );
} }
std::pair<std::vector<TypeId>, std::vector<TypePackId>> TxnLog::getChanges() const
{
std::pair<std::vector<TypeId>, std::vector<TypePackId>> 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 } // namespace Luau

View file

@ -30,7 +30,6 @@
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads) LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(LuauInferActualIfElseExprType2)
@ -40,6 +39,8 @@ LUAU_FASTFLAG(LuauResetConditionalContextProperly)
LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes)
LUAU_FASTFLAGVARIABLE(LuauIceLess) LUAU_FASTFLAGVARIABLE(LuauIceLess)
LUAU_FASTFLAG(LuauExplicitSkipBoundTypes)
LUAU_FASTFLAGVARIABLE(LuauAllowMixedTables)
namespace Luau namespace Luau
{ {
@ -162,7 +163,7 @@ struct TypeFunctionFinder : TypeOnceVisitor
DenseHashSet<TypePackId> mentionedFunctionPacks{nullptr}; DenseHashSet<TypePackId> mentionedFunctionPacks{nullptr};
TypeFunctionFinder() TypeFunctionFinder()
: TypeOnceVisitor("TypeFunctionFinder") : TypeOnceVisitor("TypeFunctionFinder", FFlag::LuauExplicitSkipBoundTypes)
{ {
} }
@ -187,7 +188,7 @@ struct InternalTypeFunctionFinder : TypeOnceVisitor
DenseHashSet<TypePackId> mentionedFunctionPacks{nullptr}; DenseHashSet<TypePackId> mentionedFunctionPacks{nullptr};
explicit InternalTypeFunctionFinder(std::vector<TypeId>& declStack) explicit InternalTypeFunctionFinder(std::vector<TypeId>& declStack)
: TypeOnceVisitor("InternalTypeFunctionFinder") : TypeOnceVisitor("InternalTypeFunctionFinder", FFlag::LuauExplicitSkipBoundTypes)
{ {
TypeFunctionFinder f; TypeFunctionFinder f;
for (TypeId fn : declStack) for (TypeId fn : declStack)
@ -1545,8 +1546,6 @@ void TypeChecker2::visitCall(AstExprCall* call)
argExprs.push_back(indexExpr->expr); argExprs.push_back(indexExpr->expr);
} }
if (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls)
{
// FIXME: Similar to bidirectional inference prior, this does not support // FIXME: Similar to bidirectional inference prior, this does not support
// overloaded functions nor generic types (yet). // overloaded functions nor generic types (yet).
if (auto fty = get<FunctionType>(fnTy); fty && fty->generics.empty() && fty->genericPacks.empty() && call->args.size > 0) if (auto fty = get<FunctionType>(fnTy); fty && fty->generics.empty() && fty->genericPacks.empty() && call->args.size > 0)
@ -1619,31 +1618,6 @@ void TypeChecker2::visitCall(AstExprCall* call)
args.head.push_back(builtinTypes->anyType); args.head.push_back(builtinTypes->anyType);
} }
} }
}
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);
}
}
TypePackId argsTp = module->internalTypes.addTypePack(args); TypePackId argsTp = module->internalTypes.addTypePack(args);
if (auto ftv = get<FunctionType>(follow(*originalCallTy))) if (auto ftv = get<FunctionType>(follow(*originalCallTy)))
@ -3157,9 +3131,18 @@ bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedT
if (expectedTableType->indexer) if (expectedTableType->indexer)
{ {
NotNull<Scope> scope{findInnermostScope(expr->location)}; NotNull<Scope> scope{findInnermostScope(expr->location)};
auto result = subtyping->isSubtype(expectedTableType->indexer->indexType, builtinTypes->numberType, scope);
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; isArrayLike = result.isSubtype;
} }
}
bool isSubtype = true; bool isSubtype = true;

View file

@ -35,6 +35,7 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies) LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
LUAU_FASTFLAG(LuauExplicitSkipBoundTypes)
namespace Luau namespace Luau
{ {
@ -53,7 +54,7 @@ struct InstanceCollector : TypeOnceVisitor
InstanceCollector() InstanceCollector()
: TypeOnceVisitor("InstanceCollector") : TypeOnceVisitor("InstanceCollector", FFlag::LuauExplicitSkipBoundTypes)
{ {
} }
@ -144,7 +145,7 @@ struct UnscopedGenericFinder : TypeOnceVisitor
bool foundUnscoped = false; bool foundUnscoped = false;
UnscopedGenericFinder() UnscopedGenericFinder()
: TypeOnceVisitor("UnscopedGenericFinder") : TypeOnceVisitor("UnscopedGenericFinder", FFlag::LuauExplicitSkipBoundTypes)
{ {
} }

View file

@ -15,6 +15,8 @@
LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauEmplaceNotPushBack)
LUAU_FASTFLAG(LuauExplicitSkipBoundTypes)
namespace Luau namespace Luau
{ {
struct InstanceCollector2 : TypeOnceVisitor struct InstanceCollector2 : TypeOnceVisitor
@ -25,7 +27,7 @@ struct InstanceCollector2 : TypeOnceVisitor
DenseHashSet<TypeId> instanceArguments{nullptr}; DenseHashSet<TypeId> instanceArguments{nullptr};
InstanceCollector2() InstanceCollector2()
: TypeOnceVisitor("InstanceCollector2") : TypeOnceVisitor("InstanceCollector2", FFlag::LuauExplicitSkipBoundTypes)
{ {
} }

View file

@ -8,6 +8,7 @@
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypePack.h"
#include <algorithm> #include <algorithm>
@ -15,6 +16,7 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAGVARIABLE(LuauTidyTypeUtils) LUAU_FASTFLAGVARIABLE(LuauTidyTypeUtils)
LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauEmplaceNotPushBack)
LUAU_FASTFLAGVARIABLE(LuauVariadicAnyPackShouldBeErrorSuppressing)
namespace Luau namespace Luau
{ {
@ -502,6 +504,17 @@ ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypeId ty)
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypePackId tp) ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> 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<VariadicTypePack>(follow(tp)))
{
if (get<AnyType>(follow(tpId->ty)))
return ErrorSuppression::Suppress;
}
}
auto [tys, tail] = flatten(tp); auto [tys, tail] = flatten(tp);
// check the head, one type at a time // check the head, one type at a time

View file

@ -33,7 +33,7 @@ struct PromoteTypeLevels final : TypeOnceVisitor
TypeLevel minLevel; TypeLevel minLevel;
PromoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel) PromoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel)
: TypeOnceVisitor("PromoteTypeLevels") : TypeOnceVisitor("PromoteTypeLevels", /* skipBoundTypes */ false)
, log(log) , log(log)
, typeArena(typeArena) , typeArena(typeArena)
, minLevel(minLevel) , minLevel(minLevel)
@ -146,7 +146,7 @@ void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLev
struct SkipCacheForType final : TypeOnceVisitor struct SkipCacheForType final : TypeOnceVisitor
{ {
SkipCacheForType(const DenseHashMap<TypeId, bool>& skipCacheForType, const TypeArena* typeArena) SkipCacheForType(const DenseHashMap<TypeId, bool>& skipCacheForType, const TypeArena* typeArena)
: TypeOnceVisitor("SkipCacheForType") : TypeOnceVisitor("SkipCacheForType", /* skipBoundTypes */ false)
, skipCacheForType(skipCacheForType) , skipCacheForType(skipCacheForType)
, typeArena(typeArena) , typeArena(typeArena)
{ {
@ -508,49 +508,6 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
return; return;
} }
if (hideousFixMeGenericsAreActuallyFree)
{
auto superGeneric = log.getMutable<GenericType>(superTy);
auto subGeneric = log.getMutable<GenericType>(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<UnknownType>(superTy))
return;
if (!occursCheck(subTy, superTy, /* reversed = */ false))
log.replace(subTy, BoundType(superTy));
return;
}
}
if (log.get<AnyType>(superTy)) if (log.get<AnyType>(superTy))
return tryUnifyWithAny(subTy, builtinTypes->anyType); return tryUnifyWithAny(subTy, builtinTypes->anyType);
@ -1510,21 +1467,6 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
log.replace(subTp, Unifiable::Bound<TypePackId>(superTp)); log.replace(subTp, Unifiable::Bound<TypePackId>(superTp));
} }
} }
else if (hideousFixMeGenericsAreActuallyFree && log.getMutable<GenericTypePack>(superTp))
{
if (!occursCheck(superTp, subTp, /* reversed = */ true))
{
Widen widen{types, builtinTypes};
log.replace(superTp, Unifiable::Bound<TypePackId>(widen(subTp)));
}
}
else if (hideousFixMeGenericsAreActuallyFree && log.getMutable<GenericTypePack>(subTp))
{
if (!occursCheck(subTp, superTp, /* reversed = */ false))
{
log.replace(subTp, Unifiable::Bound<TypePackId>(superTp));
}
}
else if (log.getMutable<ErrorTypePack>(superTp)) else if (log.getMutable<ErrorTypePack>(superTp))
tryUnifyWithAny(subTp, superTp); tryUnifyWithAny(subTp, superTp);
else if (log.getMutable<ErrorTypePack>(subTp)) else if (log.getMutable<ErrorTypePack>(subTp))
@ -2557,12 +2499,7 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever
tryUnify_(vtp->ty, variadicTy); tryUnify_(vtp->ty, variadicTy);
} }
else if (get<GenericTypePack>(tail)) else if (get<GenericTypePack>(tail))
{
if (!hideousFixMeGenericsAreActuallyFree)
reportError(location, GenericError{"Cannot unify variadic and generic packs"}); reportError(location, GenericError{"Cannot unify variadic and generic packs"});
else
log.replace(tail, BoundTypePack{superTp});
}
else if (get<ErrorTypePack>(tail)) else if (get<ErrorTypePack>(tail))
{ {
// Nothing to do here. // Nothing to do here.
@ -2755,13 +2692,13 @@ bool Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId hays
if (log.getMutable<ErrorType>(needle)) if (log.getMutable<ErrorType>(needle))
return false; return false;
if (!log.getMutable<FreeType>(needle) && !(hideousFixMeGenericsAreActuallyFree && log.is<GenericType>(needle))) if (!log.getMutable<FreeType>(needle))
ice("Expected needle to be free"); ice("Expected needle to be free");
if (needle == haystack) if (needle == haystack)
return true; return true;
if (log.getMutable<FreeType>(haystack) || (hideousFixMeGenericsAreActuallyFree && log.is<GenericType>(haystack))) if (log.getMutable<FreeType>(haystack))
return false; return false;
else if (auto a = log.getMutable<UnionType>(haystack)) else if (auto a = log.getMutable<UnionType>(haystack))
{ {
@ -2805,7 +2742,7 @@ bool Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
if (log.getMutable<ErrorTypePack>(needle)) if (log.getMutable<ErrorTypePack>(needle))
return false; return false;
if (!log.getMutable<FreeTypePack>(needle) && !(hideousFixMeGenericsAreActuallyFree && log.is<GenericTypePack>(needle))) if (!log.getMutable<FreeTypePack>(needle))
ice("Expected needle pack to be free"); ice("Expected needle pack to be free");
RecursionLimiter _ra("Unifier::occursCheck", &sharedState.counters.recursionCount, sharedState.counters.recursionLimit); RecursionLimiter _ra("Unifier::occursCheck", &sharedState.counters.recursionCount, sharedState.counters.recursionLimit);

View file

@ -339,9 +339,28 @@ public:
enum QuoteStyle enum QuoteStyle
{ {
// A string created using double quotes or an interpolated string,
// as in:
//
// "foo", `My name is {protagonist}! / And I'm {antagonist}!`
//
QuotedSimple, 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, 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<char>& value, QuoteStyle quoteStyle); AstExprConstantString(const Location& location, const AstArray<char>& value, QuoteStyle quoteStyle);

View file

@ -21,6 +21,7 @@ LUAU_FASTFLAGVARIABLE(LuauSolverV2)
LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false)
LUAU_FASTFLAGVARIABLE(LuauParseIncompleteInterpStringsWithLocation) LUAU_FASTFLAGVARIABLE(LuauParseIncompleteInterpStringsWithLocation)
LUAU_FASTFLAGVARIABLE(LuauParametrizedAttributeSyntax) LUAU_FASTFLAGVARIABLE(LuauParametrizedAttributeSyntax)
LUAU_FASTFLAGVARIABLE(DebugLuauStringSingletonBasedOnQuotes)
// Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix // Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix
bool luau_telemetry_parsed_return_type_variadic_with_type_suffix = false; bool luau_telemetry_parsed_return_type_variadic_with_type_suffix = false;
@ -3847,6 +3848,27 @@ AstExpr* Parser::parseString()
Location location = lexer.current().location; Location location = lexer.current().location;
AstExprConstantString::QuoteStyle style; AstExprConstantString::QuoteStyle style;
if (FFlag::DebugLuauStringSingletonBasedOnQuotes)
{
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) switch (lexer.current().type)
{ {
case Lexeme::QuotedString: case Lexeme::QuotedString:
@ -3859,6 +3881,7 @@ AstExpr* Parser::parseString()
default: default:
LUAU_ASSERT(false && "Invalid string type"); LUAU_ASSERT(false && "Invalid string type");
} }
}
CstExprConstantString::QuoteStyle fullStyle; CstExprConstantString::QuoteStyle fullStyle;
unsigned int blockDepth; unsigned int blockDepth;

View file

@ -52,6 +52,35 @@ enum class ConditionA64
Count 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 A64
} // namespace CodeGen } // namespace CodeGen
} // namespace Luau } // namespace Luau

View file

@ -45,7 +45,8 @@ enum class ConditionX64 : uint8_t
Count 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) switch (cond)
{ {
@ -108,5 +109,54 @@ inline ConditionX64 getReverseCondition(ConditionX64 cond)
return ConditionX64::Count; 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 CodeGen
} // namespace Luau } // namespace Luau

View file

@ -217,6 +217,11 @@ enum class IrCmd : uint8_t
// C: condition // C: condition
CMP_ANY, 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 // Unconditional jump
// A: block/vmexit/undef // A: block/vmexit/undef
JUMP, JUMP,

View file

@ -112,6 +112,7 @@ inline bool hasResult(IrCmd cmd)
case IrCmd::UNM_VEC: case IrCmd::UNM_VEC:
case IrCmd::NOT_ANY: case IrCmd::NOT_ANY:
case IrCmd::CMP_ANY: case IrCmd::CMP_ANY:
case IrCmd::CMP_INT:
case IrCmd::TABLE_LEN: case IrCmd::TABLE_LEN:
case IrCmd::TABLE_SETNUM: case IrCmd::TABLE_SETNUM:
case IrCmd::STRING_LEN: case IrCmd::STRING_LEN:

View file

@ -548,6 +548,12 @@ static void applyBuiltinCall(LuauBuiltinFunction bfid, BytecodeTypes& types)
types.b = LBC_TYPE_VECTOR; types.b = LBC_TYPE_VECTOR;
types.c = LBC_TYPE_VECTOR; // We can mark optional arguments types.c = LBC_TYPE_VECTOR; // We can mark optional arguments
break; 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: case LBF_MATH_LERP:
types.result = LBC_TYPE_NUMBER; types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER; types.a = LBC_TYPE_NUMBER;

View file

@ -185,6 +185,8 @@ const char* getCmdName(IrCmd cmd)
return "NOT_ANY"; return "NOT_ANY";
case IrCmd::CMP_ANY: case IrCmd::CMP_ANY:
return "CMP_ANY"; return "CMP_ANY";
case IrCmd::CMP_INT:
return "CMP_INT";
case IrCmd::JUMP: case IrCmd::JUMP:
return "JUMP"; return "JUMP";
case IrCmd::JUMP_IF_TRUTHY: case IrCmd::JUMP_IF_TRUTHY:

View file

@ -12,6 +12,8 @@
#include "lstate.h" #include "lstate.h"
#include "lgc.h" #include "lgc.h"
LUAU_FASTFLAG(LuauCodeGenDirectBtest)
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
@ -798,6 +800,30 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
} }
break; 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: case IrCmd::CMP_ANY:
{ {
IrCondition cond = conditionOp(inst.c); IrCondition cond = conditionOp(inst.c);

View file

@ -16,6 +16,8 @@
#include "lstate.h" #include "lstate.h"
#include "lgc.h" #include "lgc.h"
LUAU_FASTFLAG(LuauCodeGenDirectBtest)
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
@ -759,6 +761,34 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
build.setLabel(exit); build.setLabel(exit);
break; 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: case IrCmd::CMP_ANY:
{ {
IrCondition cond = conditionOp(inst.c); IrCondition cond = conditionOp(inst.c);
@ -2246,7 +2276,7 @@ void IrLoweringX64::jumpOrAbortOnUndef(ConditionX64 cond, IrOp target, const IrB
} }
else else
{ {
build.jcc(getReverseCondition(cond), label); build.jcc(getNegatedCondition(cond), label);
build.ud2(); build.ud2();
build.setLabel(label); build.setLabel(label);
} }

View file

@ -8,6 +8,8 @@
#include <math.h> #include <math.h>
LUAU_FASTFLAGVARIABLE(LuauCodeGenDirectBtest)
// TODO: when nresults is less than our actual result count, we can skip computing/writing unused results // TODO: when nresults is less than our actual result count, we can skip computing/writing unused results
static const int kMinMaxUnrolledParams = 5; static const int kMinMaxUnrolledParams = 5;
@ -410,6 +412,14 @@ static BuiltinImplResult translateBuiltinBit32BinaryOp(
} }
if (btest) if (btest)
{
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 falsey = build.block(IrBlockKind::Internal);
IrOp truthy = build.block(IrBlockKind::Internal); IrOp truthy = build.block(IrBlockKind::Internal);
@ -427,6 +437,7 @@ static BuiltinImplResult translateBuiltinBit32BinaryOp(
build.beginBlock(exit); build.beginBlock(exit);
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TBOOLEAN)); build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TBOOLEAN));
} }
}
else else
{ {
IrOp value = build.inst(IrCmd::UINT_TO_NUM, res); IrOp value = build.inst(IrCmd::UINT_TO_NUM, res);

View file

@ -16,6 +16,8 @@
#include <limits.h> #include <limits.h>
#include <math.h> #include <math.h>
LUAU_FASTFLAG(LuauCodeGenDirectBtest)
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
@ -193,6 +195,7 @@ IrValueKind getCmdValueKind(IrCmd cmd)
return IrValueKind::Double; return IrValueKind::Double;
case IrCmd::NOT_ANY: case IrCmd::NOT_ANY:
case IrCmd::CMP_ANY: case IrCmd::CMP_ANY:
case IrCmd::CMP_INT:
return IrValueKind::Int; return IrValueKind::Int;
case IrCmd::JUMP: case IrCmd::JUMP:
case IrCmd::JUMP_IF_TRUTHY: 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)); substitute(function, inst, build.constInt(function.intOp(inst.b) == 1 ? 0 : 1));
} }
break; 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: case IrCmd::JUMP_EQ_TAG:
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
{ {

View file

@ -21,6 +21,7 @@ LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64)
LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64) LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64)
LUAU_FASTINTVARIABLE(LuauCodeGenLiveSlotReuseLimit, 8) LUAU_FASTINTVARIABLE(LuauCodeGenLiveSlotReuseLimit, 8)
LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks) LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks)
LUAU_FASTFLAG(LuauCodeGenDirectBtest)
namespace Luau namespace Luau
{ {
@ -607,6 +608,7 @@ static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid
case LBF_VECTOR_CLAMP: case LBF_VECTOR_CLAMP:
case LBF_VECTOR_MIN: case LBF_VECTOR_MIN:
case LBF_VECTOR_MAX: case LBF_VECTOR_MAX:
case LBF_VECTOR_LERP:
case LBF_MATH_LERP: case LBF_MATH_LERP:
break; break;
case LBF_TABLE_INSERT: case LBF_TABLE_INSERT:
@ -1326,6 +1328,9 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
case IrCmd::NOT_ANY: case IrCmd::NOT_ANY:
state.substituteOrRecord(inst, index); state.substituteOrRecord(inst, index);
break; break;
case IrCmd::CMP_INT:
CODEGEN_ASSERT(FFlag::LuauCodeGenDirectBtest);
break;
case IrCmd::CMP_ANY: case IrCmd::CMP_ANY:
state.invalidateUserCall(); state.invalidateUserCall();
break; break;

View file

@ -618,6 +618,8 @@ enum LuauBuiltinFunction
// math.lerp // math.lerp
LBF_MATH_LERP, LBF_MATH_LERP,
LBF_VECTOR_LERP,
}; };
// Capture type, used in LOP_CAPTURE // Capture type, used in LOP_CAPTURE

View file

@ -7,6 +7,8 @@
#include <array> #include <array>
LUAU_FASTFLAGVARIABLE(LuauCompileVectorLerp)
namespace Luau namespace Luau
{ {
namespace Compile namespace Compile
@ -251,6 +253,8 @@ static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& op
return LBF_VECTOR_MIN; return LBF_VECTOR_MIN;
if (builtin.method == "max") if (builtin.method == "max")
return LBF_VECTOR_MAX; return LBF_VECTOR_MAX;
if (FFlag::LuauCompileVectorLerp && builtin.method == "lerp")
return LBF_VECTOR_LERP;
} }
if (options.vectorCtor) if (options.vectorCtor)
@ -552,6 +556,8 @@ BuiltinInfo getBuiltinInfo(int bfid)
case LBF_VECTOR_MIN: case LBF_VECTOR_MIN:
case LBF_VECTOR_MAX: case LBF_VECTOR_MAX:
return {-1, 1}; // variadic return {-1, 1}; // variadic
case LBF_VECTOR_LERP:
return {3, 1, BuiltinInfo::Flag_NoneSafe};
case LBF_MATH_LERP: case LBF_MATH_LERP:
return {3, 1, BuiltinInfo::Flag_NoneSafe}; return {3, 1, BuiltinInfo::Flag_NoneSafe};

View file

@ -771,6 +771,7 @@ struct TypeMapVisitor : AstVisitor
case LBF_VECTOR_CLAMP: case LBF_VECTOR_CLAMP:
case LBF_VECTOR_MIN: case LBF_VECTOR_MIN:
case LBF_VECTOR_MAX: case LBF_VECTOR_MAX:
case LBF_VECTOR_LERP:
recordResolvedType(node, &builtinTypes.vectorType); recordResolvedType(node, &builtinTypes.vectorType);
break; break;
} }

View file

@ -25,6 +25,8 @@
#endif #endif
#endif #endif
LUAU_FASTFLAG(LuauVectorLerp)
// luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM // 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. // 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 // 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; 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<float>(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) 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)) 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_lerp,
luauF_vectorlerp,
// When adding builtins, add them above this line; what follows is 64 "dummy" entries with luauF_missing fallback. // 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. // 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. // Given the builtin addition velocity this should always provide a larger compatibility window than bytecode versions suggest.

View file

@ -58,6 +58,11 @@ inline double luai_numidiv(double a, double b)
} }
LUAU_FASTMATH_END 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)) #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 // On MSVC in 32-bit, double to unsigned cast compiles into a call to __dtoui3, so we invoke x87->int64 conversion path manually

View file

@ -6,6 +6,8 @@
#include <math.h> #include <math.h>
LUAU_FASTFLAGVARIABLE(LuauVectorLerp)
static int vector_create(lua_State* L) static int vector_create(lua_State* L)
{ {
// checking argument count to avoid accepting 'nil' as a valid value // 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); 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<float>(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[] = { static const luaL_Reg vectorlib[] = {
{"create", vector_create}, {"create", vector_create},
{"magnitude", vector_magnitude}, {"magnitude", vector_magnitude},
@ -326,6 +343,13 @@ int luaopen_vector(lua_State* L)
{ {
luaL_register(L, LUA_VECLIBNAME, vectorlib); 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 #if LUA_VECTOR_SIZE == 4
lua_pushvector(L, 0.0f, 0.0f, 0.0f, 0.0f); lua_pushvector(L, 0.0f, 0.0f, 0.0f, 0.0f);
lua_setfield(L, -2, "zero"); lua_setfield(L, -2, "zero");

View file

@ -21,7 +21,7 @@ LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauImplicitTableIndexerKeys3) LUAU_FASTFLAG(LuauImplicitTableIndexerKeys3)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauIncludeBreakContinueStatements)
using namespace Luau; using namespace Luau;
@ -4685,10 +4685,7 @@ TEST_CASE_FIXTURE(ACFixture, "bidirectional_autocomplete_in_function_call")
TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_via_bidirectional_self") TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_via_bidirectional_self")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sff{FFlag::LuauSolverV2, true};
{FFlag::LuauSolverV2, true},
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
};
check(R"( check(R"(
type IAccount = { type IAccount = {
@ -4722,4 +4719,163 @@ TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_via_bidirectional_self")
CHECK_EQ(ac.entryMap.count("balance"), 1); 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(); TEST_SUITE_END();

View file

@ -36,9 +36,13 @@ void luau_callhook(lua_State* L, lua_Hook hook, void* userdata);
LUAU_FASTFLAG(LuauHeapDumpStringSizeOverhead) LUAU_FASTFLAG(LuauHeapDumpStringSizeOverhead)
LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTFLAG(LuauCodeGenDirectBtest)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_DYNAMIC_FASTFLAG(LuauErrorYield) LUAU_DYNAMIC_FASTFLAG(LuauErrorYield)
LUAU_DYNAMIC_FASTFLAG(LuauSafeStackCheck) LUAU_DYNAMIC_FASTFLAG(LuauSafeStackCheck)
LUAU_FASTFLAG(LuauVectorLerp)
LUAU_FASTFLAG(LuauCompileVectorLerp)
LUAU_FASTFLAG(LuauTypeCheckerVectorLerp)
static lua_CompileOptions defaultOptions() static lua_CompileOptions defaultOptions()
{ {
@ -1130,6 +1134,8 @@ TEST_CASE("Vector")
TEST_CASE("VectorLibrary") TEST_CASE("VectorLibrary")
{ {
ScopedFastFlag _[]{{FFlag::LuauCompileVectorLerp, true}, {FFlag::LuauTypeCheckerVectorLerp, true}, {FFlag::LuauVectorLerp, true}};
lua_CompileOptions copts = defaultOptions(); lua_CompileOptions copts = defaultOptions();
SUBCASE("O0") SUBCASE("O0")
@ -3112,6 +3118,8 @@ TEST_CASE("SafeEnv")
TEST_CASE("Native") TEST_CASE("Native")
{ {
ScopedFastFlag luauCodeGenDirectBtest{FFlag::LuauCodeGenDirectBtest, true};
// This tests requires code to run natively, otherwise all 'is_native' checks will fail // This tests requires code to run natively, otherwise all 'is_native' checks will fail
if (!codegen || !luau_codegen_supported()) if (!codegen || !luau_codegen_supported())
return; return;

View file

@ -29,7 +29,6 @@
LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests) LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests)
LUAU_FASTFLAG(LuauUpdateSetMetatableTypeSignature)
LUAU_FASTFLAG(LuauTidyTypeUtils) LUAU_FASTFLAG(LuauTidyTypeUtils)
LUAU_FASTFLAG(DebugLuauAlwaysShowConstraintSolvingIncomplete); LUAU_FASTFLAG(DebugLuauAlwaysShowConstraintSolvingIncomplete);
@ -153,7 +152,6 @@ struct Fixture
// Most often those are changes related to builtin type definitions. // Most often those are changes related to builtin type definitions.
// In that case, flag can be forced to 'true' using the example below: // In that case, flag can be forced to 'true' using the example below:
// ScopedFastFlag sff_LuauExampleFlagDefinition{FFlag::LuauExampleFlagDefinition, true}; // ScopedFastFlag sff_LuauExampleFlagDefinition{FFlag::LuauExampleFlagDefinition, true};
ScopedFastFlag sff_LuauUpdateSetMetatableTypeSignature{FFlag::LuauUpdateSetMetatableTypeSignature, true};
ScopedFastFlag sff_TypeUtilTidy{FFlag::LuauTidyTypeUtils, true}; ScopedFastFlag sff_TypeUtilTidy{FFlag::LuauTidyTypeUtils, true};

View file

@ -31,7 +31,6 @@ LUAU_FASTFLAG(LuauSolverAgnosticStringification)
LUAU_FASTFLAG(LuauFragmentRequiresCanBeResolvedToAModule) LUAU_FASTFLAG(LuauFragmentRequiresCanBeResolvedToAModule)
LUAU_FASTFLAG(LuauFragmentAutocompleteTracksRValueRefinements) LUAU_FASTFLAG(LuauFragmentAutocompleteTracksRValueRefinements)
LUAU_FASTFLAG(LuauPopulateSelfTypesInFragment) LUAU_FASTFLAG(LuauPopulateSelfTypesInFragment)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauParseIncompleteInterpStringsWithLocation) LUAU_FASTFLAG(LuauParseIncompleteInterpStringsWithLocation)
LUAU_FASTFLAG(LuauForInProvidesRecommendations) LUAU_FASTFLAG(LuauForInProvidesRecommendations)
@ -3948,8 +3947,6 @@ require(script.A).
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_types_provide_rich_autocomplete") TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_types_provide_rich_autocomplete")
{ {
ScopedFastFlag sff{FFlag::LuauPushFunctionTypesInFunctionStatement, true};
const std::string source = R"( const std::string source = R"(
type Service = { type Service = {
Start: (self: Service) -> (), Start: (self: Service) -> (),
@ -3990,7 +3987,6 @@ end
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_with_fancy_metatable_setting_new_solver") TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_with_fancy_metatable_setting_new_solver")
{ {
ScopedFastFlag sff{FFlag::LuauPushFunctionTypesInFunctionStatement, true};
const std::string source = R"( const std::string source = R"(
type IAccount = { type IAccount = {
__index: IAccount, __index: IAccount,
@ -4060,8 +4056,6 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_with_fancy_metatabl
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_with_colon_good_recommendations") TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_with_colon_good_recommendations")
{ {
ScopedFastFlag sff{FFlag::LuauPushFunctionTypesInFunctionStatement, true};
const std::string source = R"( const std::string source = R"(
type Service = { type Service = {
Start: (self: Service) -> (), Start: (self: Service) -> (),

View file

@ -19,6 +19,9 @@ LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauResetConditionalContextProperly)
LUAU_FASTFLAG(DebugLuauForbidInternalTypes) LUAU_FASTFLAG(DebugLuauForbidInternalTypes)
LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches)
LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance)
TEST_SUITE_BEGIN("Generalization"); 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") 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. CheckResult result = check(R"(
LUAU_REQUIRE_NO_ERRORS(check(R"(
local func: <T>(T, (T) -> ()) -> () = nil :: any local func: <T>(T, (T) -> ()) -> () = nil :: any
local foobar: (number) -> () = nil :: any local foobar: (number) -> () = nil :: any
func({}, function(obj) func({}, function(obj)
foobar(obj) foobar(obj)
end) end)
)")); )");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const GenericBoundsMismatch* gbm = get<GenericBoundsMismatch>(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") TEST_CASE_FIXTURE(Fixture, "generic_argument_with_singleton_oss_1808")

View file

@ -17,6 +17,7 @@
#include <string_view> #include <string_view>
LUAU_FASTFLAG(LuauCodeGenSimplifyImport2) LUAU_FASTFLAG(LuauCodeGenSimplifyImport2)
LUAU_FASTFLAG(LuauCodeGenDirectBtest)
static void luauLibraryConstantLookup(const char* library, const char* member, Luau::CompileConstant* constant) 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(); TEST_SUITE_END();

View file

@ -24,7 +24,6 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauIceLess) LUAU_FASTFLAG(LuauIceLess)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauSimplifyAnyAndUnion) LUAU_FASTFLAG(LuauSimplifyAnyAndUnion)
LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3)
LUAU_FASTFLAG(LuauDontDynamicallyCreateRedundantSubtypeConstraints) LUAU_FASTFLAG(LuauDontDynamicallyCreateRedundantSubtypeConstraints)
@ -302,7 +301,6 @@ TEST_CASE_FIXTURE(LimitFixture, "Signal_exerpt" * doctest::timeout(0.5))
{FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauEagerGeneralization4, true},
{FFlag::LuauTrackFreeInteriorTypePacks, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true},
{FFlag::LuauResetConditionalContextProperly, true}, {FFlag::LuauResetConditionalContextProperly, true},
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
// And this flag is the one that fixes it. // And this flag is the one that fixes it.
{FFlag::LuauSimplifyAnyAndUnion, true}, {FFlag::LuauSimplifyAnyAndUnion, true},
@ -387,7 +385,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "limit_number_of_dynamically_created_constrai
{FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauEagerGeneralization4, true},
{FFlag::LuauTrackFreeInteriorTypePacks, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true},
{FFlag::LuauResetConditionalContextProperly, true}, {FFlag::LuauResetConditionalContextProperly, true},
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
{FFlag::LuauDontDynamicallyCreateRedundantSubtypeConstraints, true}, {FFlag::LuauDontDynamicallyCreateRedundantSubtypeConstraints, true},
}; };

View file

@ -21,6 +21,7 @@ LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauResetConditionalContextProperly)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance)
LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing)
using namespace Luau; using namespace Luau;
@ -1762,4 +1763,19 @@ TEST_CASE_FIXTURE(SubtypeFixture, "free_types_might_be_subtypes")
REQUIRE(1 == result.assumedConstraints.size()); 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(); TEST_SUITE_END();

View file

@ -15,6 +15,8 @@ using namespace Luau;
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction) LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSolverAgnosticStringification)
LUAU_FASTFLAG(LuauExplicitSkipBoundTypes)
LUAU_FASTFLAG(LuauReduceSetTypeStackPressure)
TEST_SUITE_BEGIN("ToString"); 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") TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_cyclic_function_type_in_intersection")
{ {
ScopedFastFlag sffs[] = {
{FFlag::LuauExplicitSkipBoundTypes, true},
{FFlag::LuauReduceSetTypeStackPressure, true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f() return f end function f() return f end
local a: ((number) -> ()) & typeof(f) local a: ((number) -> ()) & typeof(f)
@ -700,9 +707,6 @@ TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_cyclic_function_type_in_inters
LUAU_REQUIRE_NO_ERRORS(result); 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")));
} }

View file

@ -12,6 +12,7 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauResetConditionalContextProperly)
LUAU_FASTFLAG(LuauTypeFunNoScopeMapRef)
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
@ -2451,4 +2452,35 @@ end
CHECK(toString(result.errors[0]) == R"(Redefinition of type 't0', previously defined at line 2)"); 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<A...>() end
export type function t0<A>() end
else
type function t1(...) end
type function t66<A...>(...) 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(); TEST_SUITE_END();

View file

@ -13,7 +13,6 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll)
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads) LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads)
TEST_SUITE_BEGIN("BuiltinTests"); 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") TEST_CASE_FIXTURE(Fixture, "write_only_table_assertion")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sff{FFlag::LuauSolverV2, true};
{FFlag::LuauSolverV2, true},
{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true},
};
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
local function accept(t: { write foo: number }) local function accept(t: { write foo: number })

View file

@ -15,7 +15,6 @@ using namespace Luau;
using std::nullopt; using std::nullopt;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAG(LuauScopeMethodsAreSolverAgnostic) LUAU_FASTFLAG(LuauScopeMethodsAreSolverAgnostic)
TEST_SUITE_BEGIN("TypeInferExternTypes"); 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") TEST_CASE_FIXTURE(ExternTypeFixture, "detailed_class_unification_error")
{ {
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function foo(v) local function foo(v)
return v.X :: number + string.len(v.Y) return v.X :: number + string.len(v.Y)

View file

@ -27,11 +27,11 @@ LUAU_FASTFLAG(LuauCollapseShouldNotCrash)
LUAU_FASTFLAG(LuauFormatUseLastPosition) LUAU_FASTFLAG(LuauFormatUseLastPosition)
LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSolverAgnosticStringification)
LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads) LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauResetConditionalContextProperly)
LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance)
LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions)
LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches)
TEST_SUITE_BEGIN("TypeInferFunctions"); TEST_SUITE_BEGIN("TypeInferFunctions");
@ -1449,6 +1449,13 @@ local a = {{x=4}, {x=7}, {x=1}}
table.sort(a, function(x, y) return x.x < y.x end) table.sort(a, function(x, y) return x.x < y.x end)
)"); )");
if (FFlag::LuauSubtypingReportGenericBoundMismatches)
{
// FIXME CLI-161355
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK(get<GenericBoundsMismatch>(result.errors[0]));
}
else
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
@ -3184,10 +3191,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_pack_variadic")
TEST_CASE_FIXTURE(Fixture, "table_annotated_explicit_self") TEST_CASE_FIXTURE(Fixture, "table_annotated_explicit_self")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sff{FFlag::LuauSolverV2, true};
{FFlag::LuauSolverV2, true},
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
};
CheckResult results = check(R"( CheckResult results = check(R"(
type MyObject = { type MyObject = {
@ -3210,11 +3214,7 @@ TEST_CASE_FIXTURE(Fixture, "table_annotated_explicit_self")
TEST_CASE_FIXTURE(Fixture, "oss_1871") TEST_CASE_FIXTURE(Fixture, "oss_1871")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sff{FFlag::LuauSolverV2, true};
{FFlag::LuauSolverV2, true},
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
};
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
export type Test = { export type Test = {
@ -3233,10 +3233,7 @@ TEST_CASE_FIXTURE(Fixture, "oss_1871")
TEST_CASE_FIXTURE(BuiltinsFixture, "io_manager_oop_ish") TEST_CASE_FIXTURE(BuiltinsFixture, "io_manager_oop_ish")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sff{FFlag::LuauSolverV2, true};
{FFlag::LuauSolverV2, true},
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
};
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
type IIOManager = { type IIOManager = {
@ -3268,10 +3265,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "io_manager_oop_ish")
TEST_CASE_FIXTURE(BuiltinsFixture, "generic_function_statement") TEST_CASE_FIXTURE(BuiltinsFixture, "generic_function_statement")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sff{FFlag::LuauSolverV2, true};
{FFlag::LuauSolverV2, true},
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
};
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
type Object = { type Object = {

View file

@ -13,11 +13,11 @@ LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
LUAU_FASTFLAG(LuauIntersectNotNil) LUAU_FASTFLAG(LuauIntersectNotNil)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauContainsAnyGenericFollowBeforeChecking) LUAU_FASTFLAG(LuauContainsAnyGenericFollowBeforeChecking)
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauResetConditionalContextProperly)
LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance)
LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches)
using namespace Luau; using namespace Luau;
@ -65,7 +65,7 @@ TEST_CASE_FIXTURE(Fixture, "check_generic_local_function2")
CHECK_EQ(getBuiltins()->numberType, requireType("y")); CHECK_EQ(getBuiltins()->numberType, requireType("y"));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "unions_and_generics") TEST_CASE_FIXTURE(Fixture, "unions_and_generics")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
type foo = <T>(T | {T}) -> T type foo = <T>(T | {T}) -> T
@ -1220,12 +1220,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_table_method")
TEST_CASE_FIXTURE(Fixture, "correctly_instantiate_polymorphic_member_functions") TEST_CASE_FIXTURE(Fixture, "correctly_instantiate_polymorphic_member_functions")
{ {
// Prior to `LuauPushFunctionTypesInFunctionStatement`, we _always_ forced ScopedFastFlag sff{FFlag::DebugLuauAssertOnForcedConstraint, true};
// a constraint when solving this block.
ScopedFastFlag sffs[] = {
{FFlag::DebugLuauAssertOnForcedConstraint, true},
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local T = {} local T = {}
@ -1490,6 +1485,9 @@ 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) g12({x=1}, {x=2}, function(x, y) return {x=x.x + y.x} end)
)"); )");
if (FFlag::LuauSubtypingReportGenericBoundMismatches)
LUAU_REQUIRE_ERROR_COUNT(2, result); // FIXME CLI-161355
else
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
@ -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, (T) -> ()) -> () = nil :: any
local foobar: (number) -> () = nil :: any
func({}, foobar)
)");
LUAU_REQUIRE_ERROR_COUNT(1, res);
if (FFlag::LuauSolverV2)
CHECK(get<GenericBoundsMismatch>(res.errors[0]));
else
CHECK(get<TypeMismatch>(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<T>(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<GenericBoundsMismatch>(res.errors[0]));
else
CHECK(get<TypeMismatch>(res.errors[0]));
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -11,7 +11,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
TEST_SUITE_BEGIN("IntersectionTypes"); 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") TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect")
{ {
ScopedFastFlag sff{FFlag::LuauPushFunctionTypesInFunctionStatement, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type X = { x: (number) -> number } type X = { x: (number) -> number }
type Y = { y: (string) -> string } type Y = { y: (string) -> string }

View file

@ -21,6 +21,7 @@ LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauResetConditionalContextProperly)
LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions)
LUAU_FASTFLAG(LuauNewNonStrictNoErrorsPassingNever) LUAU_FASTFLAG(LuauNewNonStrictNoErrorsPassingNever)
LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches)
using namespace Luau; using namespace Luau;
@ -2350,6 +2351,13 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "mutate_prop_of_some_refined_symb
end end
)"); )");
if (FFlag::LuauSubtypingReportGenericBoundMismatches)
{
// FIXME CLI-161355
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK(get<GenericBoundsMismatch>(result.errors[0]));
}
else
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }

View file

@ -24,17 +24,16 @@ LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit) LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit)
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSolverAgnosticStringification)
LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(LuauInferActualIfElseExprType2)
LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex) LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll)
LUAU_FASTFLAG(LuauNormalizationLimitTyvarUnionSize) LUAU_FASTFLAG(LuauNormalizationLimitTyvarUnionSize)
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauResetConditionalContextProperly)
LUAU_FASTFLAG(LuauExtendSealedTableUpperBounds) LUAU_FASTFLAG(LuauExtendSealedTableUpperBounds)
LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance)
LUAU_FASTFLAG(LuauAllowMixedTables)
TEST_SUITE_BEGIN("TableTests"); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "table_param_width_subtyping_2")
{ {
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
function foo(o) 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") TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_tables_in_call_is_unsound")
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff{FFlag::LuauInstantiateInSubtyping, true};
{FFlag::LuauInstantiateInSubtyping, true},
{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
@ -4436,8 +4430,6 @@ TEST_CASE_FIXTURE(Fixture, "new_solver_supports_read_write_properties")
TEST_CASE_FIXTURE(Fixture, "table_subtyping_error_suppression") TEST_CASE_FIXTURE(Fixture, "table_subtyping_error_suppression")
{ {
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true};
CheckResult result = check(R"( CheckResult result = check(R"(
function one(tbl: {x: any}) end function one(tbl: {x: any}) end
function two(tbl: {x: string}) one(tbl) end -- ok, string <: any and any <: string 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") TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_call")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sff{FFlag::LuauSolverV2, true};
{FFlag::LuauSolverV2, true},
{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true},
};
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
local function take(_: { foo: string? }) end 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") TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_call_incorrect")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sff{FFlag::LuauSolverV2, true};
{FFlag::LuauSolverV2, true},
{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true},
};
CheckResult results = check(R"( CheckResult results = check(R"(
local function take(_: { foo: string?, bing: number }) end 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") TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_call_singleton")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sff{FFlag::LuauSolverV2, true};
{FFlag::LuauSolverV2, true},
{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true},
};
CheckResult results = check(R"( CheckResult results = check(R"(
local function take(_: { foo: "foo" }) end local function take(_: { foo: "foo" }) end
@ -5888,7 +5871,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1450")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true},
{FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauEagerGeneralization4, true},
{FFlag::LuauTrackFreeInteriorTypePacks, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true},
{FFlag::LuauResetConditionalContextProperly, 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"))); 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(); TEST_SUITE_END();

View file

@ -28,11 +28,11 @@ LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(LuauInferActualIfElseExprType2)
LUAU_FASTFLAG(LuauForceSimplifyConstraint2) LUAU_FASTFLAG(LuauForceSimplifyConstraint2)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
LUAU_FASTFLAG(LuauMissingFollowMappedGenericPacks) LUAU_FASTFLAG(LuauMissingFollowMappedGenericPacks)
LUAU_FASTFLAG(LuauOccursCheckInCommit)
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauResetConditionalContextProperly)
@ -2513,36 +2513,6 @@ TEST_CASE_FIXTURE(Fixture, "if_then_else_two_errors")
CHECK_EQ("number", toString(err2->givenType)); 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") TEST_CASE_FIXTURE(Fixture, "standalone_constraint_solving_incomplete_is_hidden")
{ {
ScopedFastFlag sffs[] = { 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(); TEST_SUITE_END();

View file

@ -366,7 +366,7 @@ struct VisitCountTracker final : TypeOnceVisitor
std::unordered_map<TypePackId, unsigned> tpVisits; std::unordered_map<TypePackId, unsigned> tpVisits;
VisitCountTracker() VisitCountTracker()
: TypeOnceVisitor("VisitCountTracker") : TypeOnceVisitor("VisitCountTracker", /* skipBoundTypes */ true)
{ {
} }

View file

@ -237,6 +237,17 @@ end
assert(pcall(fuzzfail23) == false) 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 function arraySizeInv1()
local t = {1, 2, nil, nil, nil, nil, nil, nil, nil, true} local t = {1, 2, nil, nil, nil, nil, nil, nil, nil, true}

View file

@ -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(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) 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 -- validate component access
assert(vector.create(1, 2, 3).x == 1) assert(vector.create(1, 2, 3).x == 1)
assert(vector.create(1, 2, 3).X == 1) assert(vector.create(1, 2, 3).X == 1)

View file

@ -29,6 +29,8 @@
#include <sys/sysctl.h> #include <sys/sysctl.h>
#endif #endif
#include <fstream>
#include <iostream>
#include <optional> #include <optional>
#include <stdio.h> #include <stdio.h>
@ -426,6 +428,11 @@ int main(int argc, char** argv)
doctest::String filter; doctest::String filter;
if (doctest::parseOption(argc, argv, "--run_test", &filter) && filter[0] == '=') 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* f = filter.c_str() + 1;
const char* s = strchr(f, '/'); 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<char>(filter_stream)), std::istreambuf_iterator<char>());
context.addFilter("test-case", case_list.c_str());
}
// These callbacks register unit tests that need runtime support to be // These callbacks register unit tests that need runtime support to be
// correctly set up. Running them here means that all command line flags // 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 // have been parsed, fast flags have been set, and we've potentially already