From 67b91452686b1aef4572734ab436bbc69d324e1d Mon Sep 17 00:00:00 2001 From: Aaron Weiss Date: Fri, 19 Apr 2024 14:04:30 -0700 Subject: [PATCH] Sync to upstream/release/622 --- Analysis/include/Luau/Constraint.h | 41 +-- Analysis/include/Luau/ConstraintSolver.h | 16 +- Analysis/include/Luau/OverloadResolution.h | 17 +- Analysis/include/Luau/Subtyping.h | 3 +- Analysis/include/Luau/TableLiteralInference.h | 2 +- Analysis/include/Luau/Type.h | 21 ++ Analysis/include/Luau/TypeArena.h | 5 + Analysis/include/Luau/TypeFamily.h | 5 +- Analysis/include/Luau/TypePack.h | 2 +- Analysis/include/Luau/TypePath.h | 12 +- Analysis/include/Luau/Unifier2.h | 4 + Analysis/src/BuiltinDefinitions.cpp | 3 +- Analysis/src/ConstraintGenerator.cpp | 317 ++++++++++-------- Analysis/src/ConstraintSolver.cpp | 263 ++++++--------- Analysis/src/Error.cpp | 59 ++++ Analysis/src/Instantiation2.cpp | 4 +- Analysis/src/Normalize.cpp | 33 +- Analysis/src/OverloadResolution.cpp | 42 +-- Analysis/src/Subtyping.cpp | 26 +- Analysis/src/TableLiteralInference.cpp | 42 +-- Analysis/src/ToString.cpp | 35 +- Analysis/src/Type.cpp | 8 +- Analysis/src/TypeArena.cpp | 20 ++ Analysis/src/TypeChecker2.cpp | 17 +- Analysis/src/TypeFamily.cpp | 302 ++++++++++------- Analysis/src/TypeInfer.cpp | 4 +- Analysis/src/TypePack.cpp | 5 +- Analysis/src/TypePath.cpp | 25 +- Analysis/src/Unifier.cpp | 6 +- Analysis/src/Unifier2.cpp | 43 ++- CodeGen/src/BytecodeAnalysis.cpp | 24 +- CodeGen/src/CodeBlockUnwind.cpp | 3 + CodeGen/src/CodeGen.cpp | 2 +- CodeGen/src/CodeGenAssembly.cpp | 5 +- CodeGen/src/IrTranslation.cpp | 42 ++- Common/include/Luau/ExperimentalFlags.h | 6 +- LICENSE.txt | 5 +- VM/src/lgc.cpp | 2 +- VM/src/lmem.cpp | 54 +-- VM/src/lmem.h | 2 +- VM/src/ltablib.cpp | 65 ++++ tests/Autocomplete.test.cpp | 54 +-- tests/ClassFixture.cpp | 14 +- tests/Conformance.test.cpp | 8 +- tests/Instantiation2.test.cpp | 6 +- tests/IrLowering.test.cpp | 127 ++++++- tests/Normalize.test.cpp | 45 ++- tests/Subtyping.test.cpp | 30 +- tests/ToString.test.cpp | 14 +- tests/TypeInfer.aliases.test.cpp | 3 +- tests/TypeInfer.builtins.test.cpp | 3 - tests/TypeInfer.classes.test.cpp | 29 +- tests/TypeInfer.functions.test.cpp | 68 +++- tests/TypeInfer.loops.test.cpp | 8 +- tests/TypeInfer.refinements.test.cpp | 7 +- tests/TypeInfer.tables.test.cpp | 83 +++-- tests/TypeInfer.test.cpp | 4 +- tests/TypeInfer.typestates.test.cpp | 2 +- tests/TypeInfer.unionTypes.test.cpp | 22 +- tests/conformance/move.lua | 33 ++ tools/faillist.txt | 6 +- 61 files changed, 1405 insertions(+), 753 deletions(-) diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index 74160c33..d52ae6e0 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -52,13 +52,6 @@ struct GeneralizationConstraint std::vector interiorTypes; }; -// subType ~ inst superType -struct InstantiationConstraint -{ - TypeId subType; - TypeId superType; -}; - // variables ~ iterate iterator // Unpack the iterator, figure out what types it iterates over, and bind those types to variables. struct IterableConstraint @@ -229,17 +222,6 @@ struct SetIndexerConstraint TypeId propType; }; -// if negation: -// result ~ if isSingleton D then ~D else unknown where D = discriminantType -// if not negation: -// result ~ if isSingleton D then D else unknown where D = discriminantType -struct SingletonOrTopTypeConstraint -{ - TypeId resultType; - TypeId discriminantType; - bool negated; -}; - // resultType ~ unpack sourceTypePack // // Similar to PackSubtypeConstraint, but with one important difference: If the @@ -269,22 +251,6 @@ struct Unpack1Constraint bool resultIsLValue = false; }; -// resultType ~ T0 op T1 op ... op TN -// -// op is either union or intersection. If any of the input types are blocked, -// this constraint will block unless forced. -struct SetOpConstraint -{ - enum - { - Intersection, - Union - } mode; - - TypeId resultType; - std::vector types; -}; - // ty ~ reduce ty // // Try to reduce ty, if it is a TypeFamilyInstanceType. Otherwise, do nothing. @@ -301,10 +267,9 @@ struct ReducePackConstraint TypePackId tp; }; -using ConstraintV = Variant; +using ConstraintV = Variant; struct Constraint { diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index 9ad885a7..bb1fe2d8 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -91,6 +91,9 @@ struct ConstraintSolver // A mapping from free types to the number of unresolved constraints that mention them. DenseHashMap unresolvedConstraints{{}}; + // Irreducible/uninhabited type families or type pack families. + DenseHashSet uninhabitedTypeFamilies{{}}; + // Recorded errors that take place within the solver. ErrorVec errors; @@ -124,7 +127,6 @@ struct ConstraintSolver bool tryDispatch(const SubtypeConstraint& c, NotNull constraint, bool force); bool tryDispatch(const PackSubtypeConstraint& c, NotNull constraint, bool force); bool tryDispatch(const GeneralizationConstraint& c, NotNull constraint, bool force); - bool tryDispatch(const InstantiationConstraint& c, NotNull constraint, bool force); bool tryDispatch(const IterableConstraint& c, NotNull constraint, bool force); bool tryDispatch(const NameConstraint& c, NotNull constraint); bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull constraint); @@ -134,22 +136,18 @@ struct ConstraintSolver bool tryDispatch(const HasPropConstraint& c, NotNull constraint); bool tryDispatch(const SetPropConstraint& c, NotNull constraint); - bool tryDispatchHasIndexer(int& recursionDepth, NotNull constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set& seen); + bool tryDispatchHasIndexer( + int& recursionDepth, NotNull constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set& seen); bool tryDispatch(const HasIndexerConstraint& c, NotNull constraint); - /// (dispatched, found) where - /// - dispatched: this constraint can be considered having dispatched. - /// - found: true if adding an indexer for a particular type was allowed. - std::pair tryDispatchSetIndexer(NotNull constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds); + std::pair> tryDispatchSetIndexer( + NotNull constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds); bool tryDispatch(const SetIndexerConstraint& c, NotNull constraint, bool force); - bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull constraint); - bool tryDispatchUnpack1(NotNull constraint, TypeId resultType, TypeId sourceType, bool resultIsLValue); bool tryDispatch(const UnpackConstraint& c, NotNull constraint); bool tryDispatch(const Unpack1Constraint& c, NotNull constraint); - bool tryDispatch(const SetOpConstraint& c, NotNull constraint, bool force); bool tryDispatch(const ReduceConstraint& c, NotNull constraint, bool force); bool tryDispatch(const ReducePackConstraint& c, NotNull constraint, bool force); bool tryDispatch(const EqualityConstraint& c, NotNull constraint, bool force); diff --git a/Analysis/include/Luau/OverloadResolution.h b/Analysis/include/Luau/OverloadResolution.h index bf9a5d40..58341fca 100644 --- a/Analysis/include/Luau/OverloadResolution.h +++ b/Analysis/include/Luau/OverloadResolution.h @@ -69,7 +69,8 @@ private: struct SolveResult { - enum OverloadCallResult { + enum OverloadCallResult + { Ok, CodeTooComplex, OccursCheckFailed, @@ -87,16 +88,8 @@ struct SolveResult // Helper utility, presently used for binary operator type families. // // Given a function and a set of arguments, select a suitable overload. -SolveResult solveFunctionCall( - NotNull arena, - NotNull builtinTypes, - NotNull normalizer, - NotNull iceReporter, - NotNull limits, - NotNull scope, - const Location& location, - TypeId fn, - TypePackId argsPack -); +SolveResult solveFunctionCall(NotNull arena, NotNull builtinTypes, NotNull normalizer, + NotNull iceReporter, NotNull limits, NotNull scope, const Location& location, TypeId fn, + TypePackId argsPack); } // namespace Luau diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index 649b76b5..11e20976 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -208,7 +208,8 @@ private: SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableIndexer& subIndexer, const TableIndexer& superIndexer); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const Property& subProperty, const Property& superProperty, const std::string& name); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const std::shared_ptr& subNorm, const std::shared_ptr& superNorm); + SubtypingResult isCovariantWith( + SubtypingEnvironment& env, const std::shared_ptr& subNorm, const std::shared_ptr& superNorm); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const NormalizedClassType& superClass); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const NormalizedStringType& superString); diff --git a/Analysis/include/Luau/TableLiteralInference.h b/Analysis/include/Luau/TableLiteralInference.h index 6f541a00..40151940 100644 --- a/Analysis/include/Luau/TableLiteralInference.h +++ b/Analysis/include/Luau/TableLiteralInference.h @@ -17,4 +17,4 @@ class AstExpr; TypeId matchLiteralType(NotNull> astTypes, NotNull> astExpectedTypes, NotNull builtinTypes, NotNull arena, NotNull unifier, TypeId expectedType, TypeId exprType, const AstExpr* expr, std::vector& toBlock); -} +} // namespace Luau diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index d03e1669..25d63e1b 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -552,6 +552,27 @@ struct TypeFamilyInstanceType std::vector typeArguments; std::vector packArguments; + + TypeFamilyInstanceType(NotNull family, std::vector typeArguments, std::vector packArguments) + : family(family) + , typeArguments(typeArguments) + , packArguments(packArguments) + { + } + + TypeFamilyInstanceType(const TypeFamily& family, std::vector typeArguments) + : family{&family} + , typeArguments(typeArguments) + , packArguments{} + { + } + + TypeFamilyInstanceType(const TypeFamily& family, std::vector typeArguments, std::vector packArguments) + : family{&family} + , typeArguments(typeArguments) + , packArguments(packArguments) + { + } }; /** Represents a pending type alias instantiation. diff --git a/Analysis/include/Luau/TypeArena.h b/Analysis/include/Luau/TypeArena.h index 5f831f18..162fde96 100644 --- a/Analysis/include/Luau/TypeArena.h +++ b/Analysis/include/Luau/TypeArena.h @@ -48,6 +48,11 @@ struct TypeArena { return addTypePack(TypePackVar(std::move(tp))); } + + TypeId addTypeFamily(const TypeFamily& family, std::initializer_list types); + TypeId addTypeFamily(const TypeFamily& family, std::vector typeArguments, std::vector packArguments = {}); + TypePackId addTypePackFamily(const TypePackFamily& family, std::initializer_list types); + TypePackId addTypePackFamily(const TypePackFamily& family, std::vector typeArguments, std::vector packArguments = {}); }; void freeze(TypeArena& arena); diff --git a/Analysis/include/Luau/TypeFamily.h b/Analysis/include/Luau/TypeFamily.h index eef26e78..fa418e17 100644 --- a/Analysis/include/Luau/TypeFamily.h +++ b/Analysis/include/Luau/TypeFamily.h @@ -99,8 +99,8 @@ struct TypeFamilyReductionResult }; template -using ReducerFunction = - std::function(T, NotNull, const std::vector&, const std::vector&, NotNull)>; +using ReducerFunction = std::function( + T, NotNull, const std::vector&, const std::vector&, NotNull)>; /// Represents a type function that may be applied to map a series of types and /// type packs to a single output type. @@ -189,6 +189,7 @@ struct BuiltinTypeFamilies TypeFamily eqFamily; TypeFamily refineFamily; + TypeFamily singletonFamily; TypeFamily unionFamily; TypeFamily intersectFamily; diff --git a/Analysis/include/Luau/TypePack.h b/Analysis/include/Luau/TypePack.h index f3df9ae7..8ce8d7f5 100644 --- a/Analysis/include/Luau/TypePack.h +++ b/Analysis/include/Luau/TypePack.h @@ -92,7 +92,7 @@ struct BlockedTypePack */ struct TypeFamilyInstanceTypePack { - NotNull family; + NotNull family; std::vector typeArguments; std::vector packArguments; diff --git a/Analysis/include/Luau/TypePath.h b/Analysis/include/Luau/TypePath.h index fe7b1a25..50c75da4 100644 --- a/Analysis/include/Luau/TypePath.h +++ b/Analysis/include/Luau/TypePath.h @@ -79,9 +79,18 @@ enum class PackField Tail, }; +/// Component that represents the result of a reduction +/// `resultType` is `never` if the reduction could not proceed +struct Reduction +{ + TypeId resultType; + + bool operator==(const Reduction& other) const; +}; + /// A single component of a path, representing one inner type or type pack to /// traverse into. -using Component = Luau::Variant; +using Component = Luau::Variant; /// A path through a type or type pack accessing a particular type or type pack /// contained within. @@ -156,6 +165,7 @@ struct PathHash size_t operator()(const Index& idx) const; size_t operator()(const TypeField& field) const; size_t operator()(const PackField& field) const; + size_t operator()(const Reduction& reduction) const; size_t operator()(const Component& component) const; size_t operator()(const Path& path) const; }; diff --git a/Analysis/include/Luau/Unifier2.h b/Analysis/include/Luau/Unifier2.h index 3ea590e6..a7d64312 100644 --- a/Analysis/include/Luau/Unifier2.h +++ b/Analysis/include/Luau/Unifier2.h @@ -48,8 +48,12 @@ struct Unifier2 int recursionLimit = 0; std::vector incompleteSubtypes; + // null if not in a constraint solving context + DenseHashSet* uninhabitedTypeFamilies; Unifier2(NotNull arena, NotNull builtinTypes, NotNull scope, NotNull ice); + Unifier2(NotNull arena, NotNull builtinTypes, NotNull scope, NotNull ice, + DenseHashSet* uninhabitedTypeFamilies); /** Attempt to commit the subtype relation subTy <: superTy to the type * graph. diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 253f851b..f9ce87e0 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -24,7 +24,6 @@ */ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); -LUAU_FASTFLAGVARIABLE(LuauSetMetatableOnUnionsOfTables, false); LUAU_FASTFLAGVARIABLE(LuauMakeStringMethodsChecked, false); namespace Luau @@ -1067,7 +1066,7 @@ static std::optional> magicFunctionSetMetaTable( else if (get(target) || get(target) || isTableIntersection(target)) { } - else if (FFlag::LuauSetMetatableOnUnionsOfTables && isTableUnion(target)) + else if (isTableUnion(target)) { const UnionType* ut = get(target); LUAU_ASSERT(ut); diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 91be2a3a..0fa8b9c4 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -233,7 +233,8 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) Checkpoint end = checkpoint(this); TypeId result = arena->addType(BlockedType{}); - NotNull genConstraint = addConstraint(scope, block->location, GeneralizationConstraint{result, moduleFnTy, std::move(interiorTypes.back())}); + NotNull genConstraint = + addConstraint(scope, block->location, GeneralizationConstraint{result, moduleFnTy, std::move(interiorTypes.back())}); getMutable(result)->setOwner(genConstraint); forEachConstraint(start, end, this, [genConstraint](const ConstraintPtr& c) { genConstraint->dependencies.push_back(NotNull{c.get()}); @@ -407,13 +408,13 @@ void ConstraintGenerator::computeRefinement(const ScopePtr& scope, Location loca else if (auto proposition = get(refinement)) { TypeId discriminantTy = proposition->discriminantTy; - if (!sense && !eq) - discriminantTy = arena->addType(NegationType{proposition->discriminantTy}); - else if (eq) - { - discriminantTy = arena->addType(BlockedType{}); - constraints->push_back(SingletonOrTopTypeConstraint{discriminantTy, proposition->discriminantTy, !sense}); - } + + // if we have a negative sense, then we need to negate the discriminant + if (!sense) + discriminantTy = arena->addType(NegationType{discriminantTy}); + + if (eq) + discriminantTy = arena->addTypeFamily(kBuiltinTypeFamilies.singletonFamily, {discriminantTy}); for (const RefinementKey* key = proposition->key; key; key = key->parent) { @@ -525,11 +526,13 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat { if (mustDeferIntersection(ty) || mustDeferIntersection(dt)) { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.refineFamily}, - {ty, dt}, - {}, - }, scope, location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.refineFamily}, + {ty, dt}, + {}, + }, + scope, location); ty = resultType; } @@ -961,7 +964,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f // With or without self TypeId generalizedType = arena->addType(BlockedType{}); - Checkpoint start = checkpoint(this); FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location); bool sigFullyDefined = !hasFreeType(sig.signature); @@ -1056,7 +1058,16 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f } }); - if (auto blocked = getMutable(generalizedType)) + + // We need to check if the blocked type has no owner here because + // if a function is defined twice anywhere in the program like: + // `function f() end` and then later like `function f() end` + // Then there will be exactly one definition in the scope for it because it's a global + // (this is the same as writing f = function() end) + // Therefore, when we visit() the multiple different expression of this global variable + // They will all be aliased to the same blocked type, which means we can create multiple constraints + // for the same blocked type. + if (auto blocked = getMutable(generalizedType); blocked && !blocked->getOwner()) blocked->setOwner(addConstraint(scope, std::move(c))); } @@ -1162,7 +1173,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatCompoundAss if (typeState) { - NotNull uc = addConstraint(scope, assign->location, Unpack1Constraint{*typeState, resultTy, /*resultIsLValue=*/ true}); + NotNull uc = addConstraint(scope, assign->location, Unpack1Constraint{*typeState, resultTy, /*resultIsLValue=*/true}); if (auto blocked = getMutable(*typeState); blocked && !blocked->getOwner()) blocked->setOwner(uc); @@ -1178,7 +1189,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatCompoundAss ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatIf* ifStatement) { - RefinementId refinement = [&](){ + RefinementId refinement = [&]() { InConditionalContext flipper{&typeContext}; return check(scope, ifStatement->condition, std::nullopt).refinement; }(); @@ -1708,8 +1719,8 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* * 4. Solve the call */ - NotNull checkConstraint = - addConstraint(scope, call->func->location, FunctionCheckConstraint{fnType, argPack, call, NotNull{&module->astTypes}, NotNull{&module->astExpectedTypes}}); + NotNull checkConstraint = addConstraint(scope, call->func->location, + FunctionCheckConstraint{fnType, argPack, call, NotNull{&module->astTypes}, NotNull{&module->astExpectedTypes}}); forEachConstraint(funcBeginCheckpoint, funcEndCheckpoint, this, [checkConstraint](const ConstraintPtr& constraint) { checkConstraint->dependencies.emplace_back(constraint.get()); @@ -1901,7 +1912,8 @@ Inference ConstraintGenerator::checkIndexName(const ScopePtr& scope, const Refin scope->rvalueRefinements[key->def] = result; } - auto c = addConstraint(scope, indexee->location, HasPropConstraint{result, obj, std::move(index), ValueContext::RValue, inConditional(typeContext)}); + auto c = + addConstraint(scope, indexee->location, HasPropConstraint{result, obj, std::move(index), ValueContext::RValue, inConditional(typeContext)}); getMutable(result)->setOwner(c); if (key) @@ -1957,7 +1969,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun Checkpoint endCheckpoint = checkpoint(this); TypeId generalizedTy = arena->addType(BlockedType{}); - NotNull gc = addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature, std::move(interiorTypes.back())}); + NotNull gc = + addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature, std::move(interiorTypes.back())}); getMutable(generalizedTy)->setOwner(gc); interiorTypes.pop_back(); @@ -1992,29 +2005,35 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprUnary* unary) { case AstExprUnary::Op::Not: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.notFamily}, - {operandType}, - {}, - }, scope, unary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.notFamily}, + {operandType}, + {}, + }, + scope, unary->location); return Inference{resultType, refinementArena.negation(refinement)}; } case AstExprUnary::Op::Len: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.lenFamily}, - {operandType}, - {}, - }, scope, unary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.lenFamily}, + {operandType}, + {}, + }, + scope, unary->location); return Inference{resultType, refinementArena.negation(refinement)}; } case AstExprUnary::Op::Minus: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.unmFamily}, - {operandType}, - {}, - }, scope, unary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.unmFamily}, + {operandType}, + {}, + }, + scope, unary->location); return Inference{resultType, refinementArena.negation(refinement)}; } default: // msvc can't prove that this is exhaustive. @@ -2030,138 +2049,168 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprBinary* binar { case AstExprBinary::Op::Add: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.addFamily}, - {leftType, rightType}, - {}, - }, scope, binary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.addFamily}, + {leftType, rightType}, + {}, + }, + scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Sub: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.subFamily}, - {leftType, rightType}, - {}, - }, scope, binary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.subFamily}, + {leftType, rightType}, + {}, + }, + scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Mul: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.mulFamily}, - {leftType, rightType}, - {}, - }, scope, binary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.mulFamily}, + {leftType, rightType}, + {}, + }, + scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Div: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.divFamily}, - {leftType, rightType}, - {}, - }, scope, binary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.divFamily}, + {leftType, rightType}, + {}, + }, + scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::FloorDiv: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.idivFamily}, - {leftType, rightType}, - {}, - }, scope, binary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.idivFamily}, + {leftType, rightType}, + {}, + }, + scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Pow: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.powFamily}, - {leftType, rightType}, - {}, - }, scope, binary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.powFamily}, + {leftType, rightType}, + {}, + }, + scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Mod: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.modFamily}, - {leftType, rightType}, - {}, - }, scope, binary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.modFamily}, + {leftType, rightType}, + {}, + }, + scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Concat: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.concatFamily}, - {leftType, rightType}, - {}, - }, scope, binary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.concatFamily}, + {leftType, rightType}, + {}, + }, + scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::And: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.andFamily}, - {leftType, rightType}, - {}, - }, scope, binary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.andFamily}, + {leftType, rightType}, + {}, + }, + scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Or: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.orFamily}, - {leftType, rightType}, - {}, - }, scope, binary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.orFamily}, + {leftType, rightType}, + {}, + }, + scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareLt: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.ltFamily}, - {leftType, rightType}, - {}, - }, scope, binary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.ltFamily}, + {leftType, rightType}, + {}, + }, + scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareGe: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.ltFamily}, - {rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)` - {}, - }, scope, binary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.ltFamily}, + {rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)` + {}, + }, + scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareLe: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.leFamily}, - {leftType, rightType}, - {}, - }, scope, binary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.leFamily}, + {leftType, rightType}, + {}, + }, + scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareGt: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.leFamily}, - {rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)` - {}, - }, scope, binary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.leFamily}, + {rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)` + {}, + }, + scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareEq: case AstExprBinary::Op::CompareNe: { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.eqFamily}, - {leftType, rightType}, - {}, - }, scope, binary->location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.eqFamily}, + {leftType, rightType}, + {}, + }, + scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Op__Count: @@ -2173,7 +2222,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprBinary* binar Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional expectedType) { - RefinementId refinement = [&](){ + RefinementId refinement = [&]() { InConditionalContext flipper{&typeContext}; ScopePtr condScope = childScope(ifElse->condition, scope); return check(condScope, ifElse->condition).refinement; @@ -2612,14 +2661,12 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, LUAU_ASSERT(!indexValueLowerBound.empty()); TypeId indexKey = indexKeyLowerBound.size() == 1 - ? *indexKeyLowerBound.begin() - : arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())}) - ; + ? *indexKeyLowerBound.begin() + : arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())}); TypeId indexValue = indexValueLowerBound.size() == 1 - ? *indexValueLowerBound.begin() - : arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())}) - ; + ? *indexValueLowerBound.begin() + : arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())}); ttv->indexer = TableIndexer{indexKey, indexValue}; } @@ -3236,22 +3283,26 @@ void ConstraintGenerator::reportCodeTooComplex(Location location) TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs) { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.unionFamily}, - {lhs, rhs}, - {}, - }, scope, location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.unionFamily}, + {lhs, rhs}, + {}, + }, + scope, location); return resultType; } TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs) { - TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.intersectFamily}, - {lhs, rhs}, - {}, - }, scope, location); + TypeId resultType = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.intersectFamily}, + {lhs, rhs}, + {}, + }, + scope, location); return resultType; } @@ -3329,9 +3380,13 @@ void ConstraintGenerator::fillInInferredBindings(const ScopePtr& globalScope, As scope->bindings[symbol] = Binding{tys.front(), location}; else { - TypeId ty = arena->addType(BlockedType{}); - auto c = addConstraint(globalScope, Location{}, SetOpConstraint{SetOpConstraint::Union, ty, std::move(tys)}); - getMutable(ty)->setOwner(c); + TypeId ty = createFamilyInstance( + TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.unionFamily}, + std::move(tys), + {}, + }, + globalScope, Location{}); scope->bindings[symbol] = Binding{ty, location}; } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 948722c6..e274047e 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -346,7 +346,8 @@ void ConstraintSolver::run() if (FFlag::DebugLuauLogSolver) { - printf("Starting solver for module %s (%s)\n", moduleResolver->getHumanReadableModuleName(currentModuleName).c_str(), currentModuleName.c_str()); + printf( + "Starting solver for module %s (%s)\n", moduleResolver->getHumanReadableModuleName(currentModuleName).c_str(), currentModuleName.c_str()); dump(this, opts); printf("Bindings:\n"); dumpBindings(rootScope, opts); @@ -492,8 +493,6 @@ bool ConstraintSolver::tryDispatch(NotNull constraint, bool fo success = tryDispatch(*psc, constraint, force); else if (auto gc = get(*constraint)) success = tryDispatch(*gc, constraint, force); - else if (auto ic = get(*constraint)) - success = tryDispatch(*ic, constraint, force); else if (auto ic = get(*constraint)) success = tryDispatch(*ic, constraint, force); else if (auto nc = get(*constraint)) @@ -514,14 +513,10 @@ bool ConstraintSolver::tryDispatch(NotNull constraint, bool fo success = tryDispatch(*spc, constraint); else if (auto spc = get(*constraint)) success = tryDispatch(*spc, constraint, force); - else if (auto sottc = get(*constraint)) - success = tryDispatch(*sottc, constraint); else if (auto uc = get(*constraint)) success = tryDispatch(*uc, constraint); else if (auto uc = get(*constraint)) success = tryDispatch(*uc, constraint); - else if (auto soc = get(*constraint)) - success = tryDispatch(*soc, constraint, force); else if (auto rc = get(*constraint)) success = tryDispatch(*rc, constraint, force); else if (auto rpc = get(*constraint)) @@ -611,40 +606,6 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull constraint, bool force) -{ - if (isBlocked(c.superType)) - return block(c.superType, constraint); - - if (!blockOnPendingTypes(c.superType, constraint)) - return false; - - // TODO childLimit - std::optional instantiated = instantiate(builtinTypes, NotNull{arena}, NotNull{&limits}, constraint->scope, c.superType); - - LUAU_ASSERT(get(c.subType)); - LUAU_ASSERT(canMutate(c.subType, constraint)); - - if (!instantiated.has_value()) - { - reportError(UnificationTooComplex{}, constraint->location); - - bindBlockedType(c.subType, errorRecoveryType(), c.superType, constraint); - unblock(c.subType, constraint->location); - - return true; - } - - bindBlockedType(c.subType, *instantiated, c.superType, constraint); - - InstantiationQueuer queuer{constraint->scope, constraint->location, this}; - queuer.traverse(c.subType); - - unblock(c.subType, constraint->location); - - return true; -} - bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull constraint, bool force) { /* @@ -936,7 +897,8 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul // Type function application will happily give us the exact same type if // there are e.g. generic saturatedTypeArguments that go unused. - bool needsClone = follow(tf->type) == target; + const TableType* tfTable = getTableType(tf->type); + bool needsClone = follow(tf->type) == target || (tfTable != nullptr && tfTable == getTableType(target)); // Only tables have the properties we're trying to set. TableType* ttv = getMutableTableType(target); @@ -1462,7 +1424,8 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set& seen) +bool ConstraintSolver::tryDispatchHasIndexer( + int& recursionDepth, NotNull constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set& seen) { RecursionLimiter _rl{&recursionDepth, FInt::LuauSolverRecursionLimit}; @@ -1481,12 +1444,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(int& recursionDepth, NotNullscope, builtinTypes->neverType, builtinTypes->unknownType}; asMutable(resultType)->ty.emplace(freeResult); - TypeId upperBound = arena->addType(TableType{ - /* props */ {}, - TableIndexer{indexType, resultType}, - TypeLevel{}, - TableState::Unsealed - }); + TypeId upperBound = arena->addType(TableType{/* props */ {}, TableIndexer{indexType, resultType}, TypeLevel{}, TableState::Unsealed}); unify(constraint, subjectType, upperBound); @@ -1538,12 +1496,12 @@ bool ConstraintSolver::tryDispatchHasIndexer(int& recursionDepth, NotNull parts{nullptr}; - for (TypeId part: it) + for (TypeId part : it) parts.insert(follow(part)); Set results{nullptr}; - for (TypeId part: parts) + for (TypeId part : parts) { TypeId r = arena->addType(BlockedType{}); getMutable(r)->setOwner(const_cast(constraint.get())); @@ -1570,12 +1528,12 @@ bool ConstraintSolver::tryDispatchHasIndexer(int& recursionDepth, NotNull(subjectType)) { Set parts{nullptr}; - for (TypeId part: ut) + for (TypeId part : ut) parts.insert(follow(part)); Set results{nullptr}; - for (TypeId part: parts) + for (TypeId part : parts) { TypeId r = arena->addType(BlockedType{}); getMutable(r)->setOwner(const_cast(constraint.get())); @@ -1625,7 +1583,7 @@ struct BlockedTypeFinder : TypeOnceVisitor } }; -} +} // namespace bool ConstraintSolver::tryDispatch(const HasIndexerConstraint& c, NotNull constraint) { @@ -1651,25 +1609,24 @@ bool ConstraintSolver::tryDispatch(const HasIndexerConstraint& c, NotNull ConstraintSolver::tryDispatchSetIndexer(NotNull constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds) +std::pair> ConstraintSolver::tryDispatchSetIndexer( + NotNull constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds) { if (isBlocked(subjectType)) - return {block(subjectType, constraint), false}; + return {block(subjectType, constraint), std::nullopt}; if (auto tt = getMutable(subjectType)) { if (tt->indexer) { unify(constraint, indexType, tt->indexer->indexType); - bindBlockedType(propType, tt->indexer->indexResultType, subjectType, constraint); - - return {true, true}; + return {true, tt->indexer->indexResultType}; } else if (tt->state == TableState::Free || tt->state == TableState::Unsealed) { - bindBlockedType(propType, freshType(arena, builtinTypes, constraint->scope.get()), subjectType, constraint); - tt->indexer = TableIndexer{indexType, propType}; - return {true, true}; + TypeId resultTy = freshType(arena, builtinTypes, constraint->scope.get()); + tt->indexer = TableIndexer{indexType, resultTy}; + return {true, resultTy}; } } else if (auto ft = getMutable(subjectType); ft && expandFreeTypeBounds) @@ -1678,41 +1635,51 @@ std::pair ConstraintSolver::tryDispatchSetIndexer(NotNullupperBound, indexType, propType, /*expandFreeTypeBounds=*/ false); - if (dispatched && !found) + auto [dispatched, resultTy] = tryDispatchSetIndexer(constraint, ft->upperBound, indexType, propType, /*expandFreeTypeBounds=*/false); + if (dispatched && !resultTy) { // Despite that we haven't found a table type, adding a table type causes us to have one that we can /now/ find. - found = true; - bindBlockedType(propType, freshType(arena, builtinTypes, constraint->scope.get()), subjectType, constraint); + resultTy = freshType(arena, builtinTypes, constraint->scope.get()); TypeId tableTy = arena->addType(TableType{TableState::Sealed, TypeLevel{}, constraint->scope.get()}); TableType* tt2 = getMutable(tableTy); - tt2->indexer = TableIndexer{indexType, propType}; + tt2->indexer = TableIndexer{indexType, *resultTy}; - ft->upperBound = simplifyIntersection(builtinTypes, arena, ft->upperBound, tableTy).result; // TODO: intersect type family or a constraint. + ft->upperBound = + simplifyIntersection(builtinTypes, arena, ft->upperBound, tableTy).result; // TODO: intersect type family or a constraint. } - return {dispatched, found}; + return {dispatched, resultTy}; } else if (auto it = get(subjectType)) { - std::pair result{true, true}; + bool dispatched = true; + std::vector results; + for (TypeId part : it) { - auto [dispatched, found] = tryDispatchSetIndexer(constraint, part, indexType, propType, expandFreeTypeBounds); - result.first &= dispatched; - result.second &= found; + auto [dispatched2, found] = tryDispatchSetIndexer(constraint, part, indexType, propType, expandFreeTypeBounds); + dispatched &= dispatched2; + results.push_back(found.value_or(builtinTypes->errorRecoveryType())); + + if (!dispatched) + return {dispatched, std::nullopt}; } - return result; - } - else if (is(subjectType) && expandFreeTypeBounds) - { - bindBlockedType(propType, subjectType, subjectType, constraint); - return {true, true}; - } + TypeId resultTy = arena->addType(TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.unionFamily}, + std::move(results), + {}, + }); - return {true, false}; + pushConstraint(constraint->scope, constraint->location, ReduceConstraint{resultTy}); + + return {dispatched, resultTy}; + } + else if (is(subjectType)) + return {true, subjectType}; + + return {true, std::nullopt}; } bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull constraint, bool force) @@ -1721,54 +1688,38 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNullerrorRecoveryType(), subjectType, constraint); - + bindBlockedType(c.propType, resultTy.value_or(builtinTypes->errorRecoveryType()), subjectType, constraint); unblock(c.propType, constraint->location); } return dispatched; } -bool ConstraintSolver::tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull constraint) -{ - if (isBlocked(c.discriminantType)) - return false; - - TypeId followed = follow(c.discriminantType); - - // `nil` is a singleton type too! There's only one value of type `nil`. - if (c.negated && (get(followed) || isNil(followed))) - *asMutable(c.resultType) = NegationType{c.discriminantType}; - else if (!c.negated && get(followed)) - *asMutable(c.resultType) = BoundType{c.discriminantType}; - else - *asMutable(c.resultType) = BoundType{builtinTypes->anyType}; - - unblock(c.resultType, constraint->location); - - return true; -} - bool ConstraintSolver::tryDispatchUnpack1(NotNull constraint, TypeId resultTy, TypeId srcTy, bool resultIsLValue) { resultTy = follow(resultTy); LUAU_ASSERT(canMutate(resultTy, constraint)); - if (auto lt = getMutable(resultTy); resultIsLValue && lt) - { + auto tryExpand = [&](TypeId ty) { + LocalType* lt = getMutable(ty); + if (!lt || !resultIsLValue) + return; + lt->domain = simplifyUnion(builtinTypes, arena, lt->domain, srcTy).result; LUAU_ASSERT(lt->blockCount > 0); --lt->blockCount; - LUAU_ASSERT(0 <= lt->blockCount); - if (0 == lt->blockCount) - asMutable(resultTy)->ty.emplace(lt->domain); - } + asMutable(ty)->ty.emplace(lt->domain); + }; + + if (auto ut = get(resultTy)) + std::for_each(begin(ut), end(ut), tryExpand); + else if (get(resultTy)) + tryExpand(resultTy); else if (get(resultTy)) { if (follow(srcTy) == resultTy) @@ -1823,21 +1774,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull(resultTy)) - { - for (auto opt : ut->options) - tryDispatchUnpack1(constraint, opt, srcTy, c.resultIsLValue); - } - else - tryDispatchUnpack1(constraint, resultTy, srcTy, c.resultIsLValue); - } - else - unify(constraint, srcTy, resultTy); + tryDispatchUnpack1(constraint, resultTy, srcTy, c.resultIsLValue); ++resultIter; ++i; @@ -1877,34 +1814,6 @@ bool ConstraintSolver::tryDispatch(const Unpack1Constraint& c, NotNull constraint, bool force) -{ - bool blocked = false; - for (TypeId ty : c.types) - { - if (isBlocked(ty)) - { - blocked = true; - block(ty, constraint); - } - } - if (blocked && !force) - return false; - - LUAU_ASSERT(SetOpConstraint::Union == c.mode); - - TypeId res = builtinTypes->neverType; - - for (TypeId ty : c.types) - res = simplifyUnion(builtinTypes, arena, res, ty).result; - - bindBlockedType(c.resultType, res, c.resultType, constraint); - - asMutable(c.resultType)->ty.emplace(res); - - return true; -} - bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull constraint, bool force) { TypeId ty = follow(c.ty); @@ -1917,6 +1826,20 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNulllocation); + bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty(); + + if (force || reductionFinished) + { + // if we're completely dispatching this constraint, we want to record any uninhabited type families to unblock. + for (auto error : result.errors) + { + if (auto utf = get(error)) + uninhabitedTypeFamilies.insert(utf->ty); + else if (auto utpf = get(error)) + uninhabitedTypeFamilies.insert(utpf->tp); + } + } + if (force) return true; @@ -1926,7 +1849,7 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull constraint, bool force) @@ -1941,6 +1864,20 @@ bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNulllocation); + bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty(); + + if (force || reductionFinished) + { + // if we're completely dispatching this constraint, we want to record any uninhabited type families to unblock. + for (auto error : result.errors) + { + if (auto utf = get(error)) + uninhabitedTypeFamilies.insert(utf->ty); + else if (auto utpf = get(error)) + uninhabitedTypeFamilies.insert(utpf->tp); + } + } + if (force) return true; @@ -1950,7 +1887,7 @@ bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull constraint, bool force) @@ -2075,7 +2012,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl LUAU_ASSERT(nextFn); const TypePackId nextRetPack = nextFn->retTypes; - pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, nextRetPack, /* resultIsLValue=*/ true}); + pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, nextRetPack, /* resultIsLValue=*/true}); return true; } else @@ -2405,7 +2342,7 @@ std::pair, std::optional> ConstraintSolver::lookupTa template bool ConstraintSolver::unify(NotNull constraint, TID subTy, TID superTy) { - Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}}; + Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}, &uninhabitedTypeFamilies}; const bool ok = u2.unify(subTy, superTy); @@ -2672,12 +2609,20 @@ bool ConstraintSolver::isBlocked(TypeId ty) if (auto lt = get(ty)) return lt->blockCount > 0; + if (auto tfit = get(ty)) + return uninhabitedTypeFamilies.contains(ty) == false; + return nullptr != get(ty) || nullptr != get(ty); } bool ConstraintSolver::isBlocked(TypePackId tp) { - return nullptr != get(follow(tp)); + tp = follow(tp); + + if (auto tfitp = get(tp)) + return uninhabitedTypeFamilies.contains(tp) == false; + + return nullptr != get(tp); } bool ConstraintSolver::isBlocked(NotNull constraint) diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index dcd591b4..98b15b77 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -15,6 +15,8 @@ LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) +LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauImproveNonFunctionCallError, false) + static std::string wrongNumberOfArgsString( size_t expectedCount, std::optional maximumCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false) { @@ -335,8 +337,65 @@ struct ErrorConverter return e.message; } + std::optional findCallMetamethod(TypeId type) const + { + type = follow(type); + + std::optional metatable; + if (const MetatableType* mtType = get(type)) + metatable = mtType->metatable; + else if (const ClassType* classType = get(type)) + metatable = classType->metatable; + + if (!metatable) + return std::nullopt; + + TypeId unwrapped = follow(*metatable); + + if (get(unwrapped)) + return unwrapped; + + const TableType* mtt = getTableType(unwrapped); + if (!mtt) + return std::nullopt; + + auto it = mtt->props.find("__call"); + if (it != mtt->props.end()) + return it->second.type(); + else + return std::nullopt; + } + std::string operator()(const Luau::CannotCallNonFunction& e) const { + if (DFFlag::LuauImproveNonFunctionCallError) + { + if (auto unionTy = get(follow(e.ty))) + { + std::string err = "Cannot call a value of the union type:"; + + for (auto option : unionTy) + { + option = follow(option); + + if (get(option) || findCallMetamethod(option)) + { + err += "\n | " + toString(option); + continue; + } + + // early-exit if we find something that isn't callable in the union. + return "Cannot call a value of type " + toString(option) + " in union:\n " + toString(e.ty); + } + + err += "\nWe are unable to determine the appropriate result type for such a call."; + + return err; + } + + return "Cannot call a value of type " + toString(e.ty); + } + return "Cannot call non-function " + toString(e.ty); } std::string operator()(const Luau::ExtraInformation& e) const diff --git a/Analysis/src/Instantiation2.cpp b/Analysis/src/Instantiation2.cpp index 8b1ffc39..d951a5b0 100644 --- a/Analysis/src/Instantiation2.cpp +++ b/Analysis/src/Instantiation2.cpp @@ -31,9 +31,7 @@ TypeId Instantiation2::clean(TypeId ty) // if we didn't learn anything about the lower bound, we pick the upper bound instead. // we default to the lower bound which represents the most specific type for the free type. - TypeId res = get(ft->lowerBound) - ? ft->upperBound - : ft->lowerBound; + TypeId res = get(ft->lowerBound) ? ft->upperBound : ft->lowerBound; // Instantiation should not traverse into the type that we are substituting for. dontTraverseInto(res); diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index ce95e635..848c8684 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -18,6 +18,8 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false) LUAU_FASTFLAGVARIABLE(LuauNormalizeAwayUninhabitableTables, false) LUAU_FASTFLAGVARIABLE(LuauFixNormalizeCaching, false); +LUAU_FASTFLAGVARIABLE(LuauNormalizeNotUnknownIntersection, false); +LUAU_FASTFLAGVARIABLE(LuauFixCyclicUnionsOfIntersections, false); // This could theoretically be 2000 on amd64, but x86 requires this. LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200); @@ -29,6 +31,11 @@ static bool fixNormalizeCaching() return FFlag::LuauFixNormalizeCaching || FFlag::DebugLuauDeferredConstraintResolution; } +static bool fixCyclicUnionsOfIntersections() +{ + return FFlag::LuauFixCyclicUnionsOfIntersections || FFlag::DebugLuauDeferredConstraintResolution; +} + namespace Luau { @@ -910,13 +917,13 @@ static bool isCacheable(TypeId ty, Set& seen) if (auto tfi = get(ty)) { - for (TypeId t: tfi->typeArguments) + for (TypeId t : tfi->typeArguments) { if (!isCacheable(t, seen)) return false; } - for (TypePackId tp: tfi->packArguments) + for (TypePackId tp : tfi->packArguments) { if (!isCacheable(tp, seen)) return false; @@ -1768,14 +1775,29 @@ NormalizationResult Normalizer::unionNormalWithTy(NormalizedType& here, TypeId t } else if (const IntersectionType* itv = get(there)) { + if (fixCyclicUnionsOfIntersections()) + { + if (seenSetTypes.count(there)) + return NormalizationResult::True; + seenSetTypes.insert(there); + } + NormalizedType norm{builtinTypes}; norm.tops = builtinTypes->anyType; for (IntersectionTypeIterator it = begin(itv); it != end(itv); ++it) { NormalizationResult res = intersectNormalWithTy(norm, *it, seenSetTypes); if (res != NormalizationResult::True) + { + if (fixCyclicUnionsOfIntersections()) + seenSetTypes.erase(there); return res; + } } + + if (fixCyclicUnionsOfIntersections()) + seenSetTypes.erase(there); + return unionNormals(here, norm); } else if (get(here.tops)) @@ -3194,6 +3216,13 @@ NormalizationResult Normalizer::intersectNormalWithTy(NormalizedType& here, Type // this is a noop since an intersection with `unknown` is trivial. return NormalizationResult::True; } + else if ((FFlag::LuauNormalizeNotUnknownIntersection || FFlag::DebugLuauDeferredConstraintResolution) && get(t)) + { + // if we're intersecting with `~unknown`, this is equivalent to intersecting with `never` + // this means we should clear the type entirely. + clearNormal(here); + return NormalizationResult::True; + } else if (auto nt = get(t)) return intersectNormalWithTy(here, nt->ty, seenSetTypes); else diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index d2bfd0e1..8bbdfec4 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -365,41 +365,25 @@ void OverloadResolver::add(Analysis analysis, TypeId ty, ErrorVec&& errors) // we wrap calling the overload resolver in a separate function to reduce overall stack pressure in `solveFunctionCall`. // this limits the lifetime of `OverloadResolver`, a large type, to only as long as it is actually needed. -std::optional selectOverload( - NotNull builtinTypes, - NotNull arena, - NotNull normalizer, - NotNull scope, - NotNull iceReporter, - NotNull limits, - const Location& location, - TypeId fn, - TypePackId argsPack -) +std::optional selectOverload(NotNull builtinTypes, NotNull arena, NotNull normalizer, + NotNull scope, NotNull iceReporter, NotNull limits, const Location& location, TypeId fn, + TypePackId argsPack) { - OverloadResolver resolver{builtinTypes, arena, normalizer, scope, iceReporter, limits, location}; - auto [status, overload] = resolver.selectOverload(fn, argsPack); + OverloadResolver resolver{builtinTypes, arena, normalizer, scope, iceReporter, limits, location}; + auto [status, overload] = resolver.selectOverload(fn, argsPack); - if (status == OverloadResolver::Analysis::Ok) - return overload; + if (status == OverloadResolver::Analysis::Ok) + return overload; - if (get(fn) || get(fn)) - return fn; + if (get(fn) || get(fn)) + return fn; - return {}; + return {}; } -SolveResult solveFunctionCall( - NotNull arena, - NotNull builtinTypes, - NotNull normalizer, - NotNull iceReporter, - NotNull limits, - NotNull scope, - const Location& location, - TypeId fn, - TypePackId argsPack -) +SolveResult solveFunctionCall(NotNull arena, NotNull builtinTypes, NotNull normalizer, + NotNull iceReporter, NotNull limits, NotNull scope, const Location& location, TypeId fn, + TypePackId argsPack) { std::optional overloadToUse = selectOverload(builtinTypes, arena, normalizer, scope, iceReporter, limits, location, fn, argsPack); if (!overloadToUse) diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index ade4d2c9..f2d51b31 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -266,7 +266,8 @@ struct ApplyMappedGenerics : Substitution MappedGenericPacks& mappedGenericPacks; - ApplyMappedGenerics(NotNull builtinTypes, NotNull arena, MappedGenerics& mappedGenerics, MappedGenericPacks& mappedGenericPacks) + ApplyMappedGenerics( + NotNull builtinTypes, NotNull arena, MappedGenerics& mappedGenerics, MappedGenericPacks& mappedGenericPacks) : Substitution(TxnLog::empty(), arena) , builtinTypes(builtinTypes) , arena(arena) @@ -1244,18 +1245,18 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl { if (superProp.isShared()) results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, superProp.type()) - .withSubComponent(TypePath::TypeField::IndexResult) - .withSuperComponent(TypePath::Property::read(name))); + .withSubComponent(TypePath::TypeField::IndexResult) + .withSuperComponent(TypePath::Property::read(name))); else { if (superProp.readTy) results.push_back(isCovariantWith(env, subTable->indexer->indexResultType, *superProp.readTy) - .withSubComponent(TypePath::TypeField::IndexResult) - .withSuperComponent(TypePath::Property::read(name))); + .withSubComponent(TypePath::TypeField::IndexResult) + .withSuperComponent(TypePath::Property::read(name))); if (superProp.writeTy) results.push_back(isContravariantWith(env, subTable->indexer->indexResultType, *superProp.writeTy) - .withSubComponent(TypePath::TypeField::IndexResult) - .withSuperComponent(TypePath::Property::write(name))); + .withSubComponent(TypePath::TypeField::IndexResult) + .withSuperComponent(TypePath::Property::write(name))); } } } @@ -1310,7 +1311,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Clas return {isSubclass(subClass, superClass)}; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable) +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable) { SubtypingResult result{true}; @@ -1421,7 +1423,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prop return res; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const std::shared_ptr& subNorm, const std::shared_ptr& superNorm) +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, const std::shared_ptr& subNorm, const std::shared_ptr& superNorm) { if (!subNorm || !superNorm) return {false, true}; @@ -1584,15 +1587,16 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type { // Reduce the typefamily instance auto [ty, errors] = handleTypeFamilyReductionResult(subFamilyInstance); + // If we return optional, that means the type family was irreducible - we can reduce that to never - return isCovariantWith(env, ty, superTy).withErrors(errors); + return isCovariantWith(env, ty, superTy).withErrors(errors).withSubComponent(TypePath::Reduction{ty}); } SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFamilyInstanceType* superFamilyInstance) { // Reduce the typefamily instance auto [ty, errors] = handleTypeFamilyReductionResult(superFamilyInstance); - return isCovariantWith(env, subTy, ty).withErrors(errors); + return isCovariantWith(env, subTy, ty).withErrors(errors).withSuperComponent(TypePath::Reduction{ty}); } /* diff --git a/Analysis/src/TableLiteralInference.cpp b/Analysis/src/TableLiteralInference.cpp index b93bdfd2..9a7dce3d 100644 --- a/Analysis/src/TableLiteralInference.cpp +++ b/Analysis/src/TableLiteralInference.cpp @@ -13,14 +13,8 @@ namespace Luau static bool isLiteral(const AstExpr* expr) { - return ( - expr->is() || - expr->is() || - expr->is() || - expr->is() || - expr->is() || - expr->is() - ); + return (expr->is() || expr->is() || expr->is() || expr->is() || + expr->is() || expr->is()); } // A fast approximation of subTy <: superTy @@ -52,7 +46,7 @@ static std::optional extractMatchingTableType(std::vector& table size_t tableCount = 0; std::optional firstTable; - for (TypeId ty: tables) + for (TypeId ty : tables) { ty = follow(ty); if (auto tt = get(ty)) @@ -65,7 +59,7 @@ static std::optional extractMatchingTableType(std::vector& table firstTable = ty; ++tableCount; - for (const auto& [name, expectedProp]: tt->props) + for (const auto& [name, expectedProp] : tt->props) { if (!expectedProp.readTy) continue; @@ -91,14 +85,12 @@ static std::optional extractMatchingTableType(std::vector& table if (ft && get(ft->lowerBound)) { - if (fastIsSubtype(builtinTypes->booleanType, ft->upperBound) && - fastIsSubtype(expectedType, builtinTypes->booleanType)) + if (fastIsSubtype(builtinTypes->booleanType, ft->upperBound) && fastIsSubtype(expectedType, builtinTypes->booleanType)) { return ty; } - if (fastIsSubtype(builtinTypes->stringType, ft->upperBound) && - fastIsSubtype(expectedType, ft->lowerBound)) + if (fastIsSubtype(builtinTypes->stringType, ft->upperBound) && fastIsSubtype(expectedType, ft->lowerBound)) { return ty; } @@ -149,11 +141,8 @@ TypeId matchLiteralType(NotNull> astTypes, if (expr->is()) { auto ft = get(exprType); - if (ft && - get(ft->lowerBound) && - fastIsSubtype(builtinTypes->stringType, ft->upperBound) && - fastIsSubtype(ft->lowerBound, builtinTypes->stringType) - ) + if (ft && get(ft->lowerBound) && fastIsSubtype(builtinTypes->stringType, ft->upperBound) && + fastIsSubtype(ft->lowerBound, builtinTypes->stringType)) { // if the upper bound is a subtype of the expected type, we can push the expected type in Relation upperBoundRelation = relate(ft->upperBound, expectedType); @@ -177,11 +166,8 @@ TypeId matchLiteralType(NotNull> astTypes, else if (expr->is()) { auto ft = get(exprType); - if (ft && - get(ft->lowerBound) && - fastIsSubtype(builtinTypes->booleanType, ft->upperBound) && - fastIsSubtype(ft->lowerBound, builtinTypes->booleanType) - ) + if (ft && get(ft->lowerBound) && fastIsSubtype(builtinTypes->booleanType, ft->upperBound) && + fastIsSubtype(ft->lowerBound, builtinTypes->booleanType)) { // if the upper bound is a subtype of the expected type, we can push the expected type in Relation upperBoundRelation = relate(ft->upperBound, expectedType); @@ -247,7 +233,7 @@ TypeId matchLiteralType(NotNull> astTypes, return exprType; } - for (const AstExprTable::Item& item: exprTable->items) + for (const AstExprTable::Item& item : exprTable->items) { if (isRecord(item)) { @@ -391,7 +377,7 @@ TypeId matchLiteralType(NotNull> astTypes, for (const auto& [name, _] : expectedTableTy->props) missingKeys.insert(name); - for (const AstExprTable::Item& item: exprTable->items) + for (const AstExprTable::Item& item : exprTable->items) { if (item.key) { @@ -402,7 +388,7 @@ TypeId matchLiteralType(NotNull> astTypes, } } - for (const auto& key: missingKeys) + for (const auto& key : missingKeys) { LUAU_ASSERT(key.has_value()); @@ -427,4 +413,4 @@ TypeId matchLiteralType(NotNull> astTypes, return exprType; } -} +} // namespace Luau diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 393765e1..62d2a6f3 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -1767,12 +1767,6 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts) std::string superStr = tos(c.sourceType); return subStr + " ~ gen " + superStr; } - else if constexpr (std::is_same_v) - { - std::string subStr = tos(c.subType); - std::string superStr = tos(c.superType); - return subStr + " ~ inst " + superStr; - } else if constexpr (std::is_same_v) { std::string iteratorStr = tos(c.iterator); @@ -1822,37 +1816,10 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts) { return "setIndexer " + tos(c.subjectType) + " [ " + tos(c.indexType) + " ] " + tos(c.propType); } - else if constexpr (std::is_same_v) - { - std::string result = tos(c.resultType); - std::string discriminant = tos(c.discriminantType); - - if (c.negated) - return result + " ~ if isSingleton D then ~D else unknown where D = " + discriminant; - else - return result + " ~ if isSingleton D then D else unknown where D = " + discriminant; - } else if constexpr (std::is_same_v) return tos(c.resultPack) + " ~ ...unpack " + tos(c.sourcePack); else if constexpr (std::is_same_v) return tos(c.resultType) + " ~ unpack " + tos(c.sourceType); - else if constexpr (std::is_same_v) - { - const char* op = c.mode == SetOpConstraint::Union ? " | " : " & "; - std::string res = tos(c.resultType) + " ~ "; - bool first = true; - for (TypeId t : c.types) - { - if (first) - first = false; - else - res += op; - - res += tos(t); - } - - return res; - } else if constexpr (std::is_same_v) return "reduce " + tos(c.ty); else if constexpr (std::is_same_v) @@ -1923,7 +1890,7 @@ std::string toString(const Position& position) std::string toString(const Location& location, int offset, bool useBegin) { return "(" + std::to_string(location.begin.line + offset) + ", " + std::to_string(location.begin.column + offset) + ") - (" + - std::to_string(location.end.line + offset) + ", " + std::to_string(location.end.column + offset) + ")"; + std::to_string(location.end.line + offset) + ", " + std::to_string(location.end.column + offset) + ")"; } std::string toString(const TypeOrPack& tyOrTp, ToStringOptions& opts) diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index 7454be32..e82b3d5c 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -546,13 +546,15 @@ BlockedType::BlockedType() { } -Constraint* BlockedType::getOwner() const { +Constraint* BlockedType::getOwner() const +{ return owner; } -void BlockedType::setOwner(Constraint* newOwner) { +void BlockedType::setOwner(Constraint* newOwner) +{ LUAU_ASSERT(owner == nullptr); - + if (owner != nullptr) return; diff --git a/Analysis/src/TypeArena.cpp b/Analysis/src/TypeArena.cpp index ed51517e..03c1e99a 100644 --- a/Analysis/src/TypeArena.cpp +++ b/Analysis/src/TypeArena.cpp @@ -94,6 +94,26 @@ TypePackId TypeArena::addTypePack(TypePackVar tp) return allocated; } +TypeId TypeArena::addTypeFamily(const TypeFamily& family, std::initializer_list types) +{ + return addType(TypeFamilyInstanceType{family, std::move(types)}); +} + +TypeId TypeArena::addTypeFamily(const TypeFamily& family, std::vector typeArguments, std::vector packArguments) +{ + return addType(TypeFamilyInstanceType{family, std::move(typeArguments), std::move(packArguments)}); +} + +TypePackId TypeArena::addTypePackFamily(const TypePackFamily& family, std::initializer_list types) +{ + return addTypePack(TypeFamilyInstanceTypePack{NotNull{&family}, std::move(types)}); +} + +TypePackId TypeArena::addTypePackFamily(const TypePackFamily& family, std::vector typeArguments, std::vector packArguments) +{ + return addTypePack(TypeFamilyInstanceTypePack{NotNull{&family}, std::move(typeArguments), std::move(packArguments)}); +} + void freeze(TypeArena& arena) { if (!FFlag::DebugLuauFreezeArena) diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index cfb49f21..0442836f 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -441,8 +441,8 @@ struct TypeChecker2 return instance; seenTypeFamilyInstances.insert(instance); - ErrorVec errors = reduceFamilies( - instance, location, TypeFamilyContext{NotNull{&module->internalTypes}, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true) + ErrorVec errors = reduceFamilies(instance, location, + TypeFamilyContext{NotNull{&module->internalTypes}, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true) .errors; if (!isErrorSuppressing(location, instance)) reportErrors(std::move(errors)); @@ -2743,7 +2743,7 @@ struct TypeChecker2 fetch(module->internalTypes.addType(IntersectionType{{tyvar, ty}})); } else - fetch(tyvar); + fetch(follow(tyvar)); if (!normValid) break; @@ -2871,17 +2871,24 @@ struct TypeChecker2 for (TypeId part : utv) { PropertyType result = hasIndexTypeFromType(part, prop, context, location, seen, astIndexExprType, errors); + if (result.present != NormalizationResult::True) return {result.present, {}}; if (result.result) parts.emplace_back(*result.result); } + if (parts.size() == 0) + return {NormalizationResult::False, {}}; + + if (parts.size() == 1) + return {NormalizationResult::True, {parts[0]}}; + TypeId propTy; if (context == ValueContext::LValue) - module->internalTypes.addType(IntersectionType{parts}); + propTy = module->internalTypes.addType(IntersectionType{parts}); else - module->internalTypes.addType(UnionType{parts}); + propTy = module->internalTypes.addType(UnionType{parts}); return {NormalizationResult::True, propTy}; } diff --git a/Analysis/src/TypeFamily.cpp b/Analysis/src/TypeFamily.cpp index 59302085..2dc0ff67 100644 --- a/Analysis/src/TypeFamily.cpp +++ b/Analysis/src/TypeFamily.cpp @@ -217,7 +217,8 @@ struct FamilyReducer else if (!reduction.uninhabited && !force) { if (FFlag::DebugLuauLogSolver) - printf("%s is irreducible; blocked on %zu types, %zu packs\n", toString(subject, {true}).c_str(), reduction.blockedTypes.size(), reduction.blockedPacks.size()); + printf("%s is irreducible; blocked on %zu types, %zu packs\n", toString(subject, {true}).c_str(), reduction.blockedTypes.size(), + reduction.blockedPacks.size()); for (TypeId b : reduction.blockedTypes) result.blockedTypes.insert(b); @@ -243,7 +244,7 @@ struct FamilyReducer if (skip == SkipTestResult::Irreducible) { if (FFlag::DebugLuauLogSolver) - printf("%s is irreducible due to a dependency on %s\n" , toString(subject, {true}).c_str(), toString(p, {true}).c_str()); + printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str()); irreducible.insert(subject); return false; @@ -251,7 +252,7 @@ struct FamilyReducer else if (skip == SkipTestResult::Defer) { if (FFlag::DebugLuauLogSolver) - printf("Deferring %s until %s is solved\n" , toString(subject, {true}).c_str(), toString(p, {true}).c_str()); + printf("Deferring %s until %s is solved\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str()); if constexpr (std::is_same_v) queuedTys.push_back(subject); @@ -269,7 +270,7 @@ struct FamilyReducer if (skip == SkipTestResult::Irreducible) { if (FFlag::DebugLuauLogSolver) - printf("%s is irreducible due to a dependency on %s\n" , toString(subject, {true}).c_str(), toString(p, {true}).c_str()); + printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str()); irreducible.insert(subject); return false; @@ -277,7 +278,7 @@ struct FamilyReducer else if (skip == SkipTestResult::Defer) { if (FFlag::DebugLuauLogSolver) - printf("Deferring %s until %s is solved\n" , toString(subject, {true}).c_str(), toString(p, {true}).c_str()); + printf("Deferring %s until %s is solved\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str()); if constexpr (std::is_same_v) queuedTys.push_back(subject); @@ -346,7 +347,8 @@ struct FamilyReducer return; TypeFamilyQueue queue{NotNull{&queuedTys}, NotNull{&queuedTps}}; - TypeFamilyReductionResult result = tfit->family->reducer(subject, NotNull{&queue}, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); + TypeFamilyReductionResult result = + tfit->family->reducer(subject, NotNull{&queue}, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); handleFamilyReduction(subject, result); } } @@ -371,7 +373,8 @@ struct FamilyReducer return; TypeFamilyQueue queue{NotNull{&queuedTys}, NotNull{&queuedTps}}; - TypeFamilyReductionResult result = tfit->family->reducer(subject, NotNull{&queue}, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); + TypeFamilyReductionResult result = + tfit->family->reducer(subject, NotNull{&queue}, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); handleFamilyReduction(subject, result); } } @@ -385,8 +388,8 @@ struct FamilyReducer } }; -static FamilyGraphReductionResult reduceFamiliesInternal( - VecDeque queuedTys, VecDeque queuedTps, TypeOrTypePackIdSet shouldGuess, std::vector cyclics, Location location, TypeFamilyContext ctx, bool force) +static FamilyGraphReductionResult reduceFamiliesInternal(VecDeque queuedTys, VecDeque queuedTps, TypeOrTypePackIdSet shouldGuess, + std::vector cyclics, Location location, TypeFamilyContext ctx, bool force) { FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(shouldGuess), std::move(cyclics), location, ctx, force}; int iterationCount = 0; @@ -422,7 +425,8 @@ FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, if (collector.tys.empty() && collector.tps.empty()) return {}; - return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess), std::move(collector.cyclicInstance), location, ctx, force); + return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess), + std::move(collector.cyclicInstance), location, ctx, force); } FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, TypeFamilyContext ctx, bool force) @@ -441,7 +445,8 @@ FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location locati if (collector.tys.empty() && collector.tps.empty()) return {}; - return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess), std::move(collector.cyclicInstance), location, ctx, force); + return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess), + std::move(collector.cyclicInstance), location, ctx, force); } void TypeFamilyQueue::add(TypeId instanceTy) @@ -461,8 +466,8 @@ bool isPending(TypeId ty, ConstraintSolver* solver) return is(ty) || (solver && solver->hasUnresolvedConstraints(ty)); } -TypeFamilyReductionResult notFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult notFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 1 || !packParams.empty()) { @@ -479,8 +484,8 @@ TypeFamilyReductionResult notFamilyFn( return {ctx->builtins->booleanType, false, {}, {}}; } -TypeFamilyReductionResult lenFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult lenFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 1 || !packParams.empty()) { @@ -496,17 +501,18 @@ TypeFamilyReductionResult lenFamilyFn( return {std::nullopt, false, {operandTy}, {}}; std::shared_ptr normTy = ctx->normalizer->normalize(operandTy); + NormalizationResult inhabited = ctx->normalizer->isInhabited(normTy.get()); // if the type failed to normalize, we can't reduce, but know nothing about inhabitance. - if (!normTy) + if (!normTy || inhabited == NormalizationResult::HitLimits) return {std::nullopt, false, {}, {}}; // if the operand type is error suppressing, we can immediately reduce to `number`. if (normTy->shouldSuppressErrors()) return {ctx->builtins->numberType, false, {}, {}}; - // if we have a `never`, we can never observe that the operator didn't work. - if (is(operandTy)) + // if we have an uninhabited type (like `never`), we can never observe that the operator didn't work. + if (inhabited == NormalizationResult::False) return {ctx->builtins->neverType, false, {}, {}}; // if we're checking the length of a string, that works! @@ -555,8 +561,8 @@ TypeFamilyReductionResult lenFamilyFn( return {ctx->builtins->numberType, false, {}, {}}; } -TypeFamilyReductionResult unmFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult unmFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 1 || !packParams.empty()) { @@ -732,25 +738,18 @@ TypeFamilyReductionResult numericBinopFamilyFn(TypeId instance, NotNull< // though there exists no arm of the union that is inhabited or have a reduced type. ctx->ice->ice("`distributeFamilyApp` failed to add any types to the results vector?"); } - else if (results.size() == 1) - return {results[0], false, {}, {}}; - else if (results.size() == 2) - { - TypeId resultTy = ctx->arena->addType(TypeFamilyInstanceType{ - NotNull{&kBuiltinTypeFamilies.unionFamily}, - std::move(results), - {}, - }); - queue->add(resultTy); - return {resultTy, false, {}, {}}; - } - else - { - // TODO: We need to generalize `union<...>` type family to be variadic. - TypeId resultTy = ctx->arena->addType(UnionType{std::move(results)}); - return {resultTy, false, {}, {}}; - } + if (results.size() == 1) + return {results[0], false, {}, {}}; + + TypeId resultTy = ctx->arena->addType(TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.unionFamily}, + std::move(results), + {}, + }); + + queue->add(resultTy); + return {resultTy, false, {}, {}}; } // findMetatableEntry demands the ability to emit errors, so we must give it @@ -794,8 +793,8 @@ TypeFamilyReductionResult numericBinopFamilyFn(TypeId instance, NotNull< return {extracted.head.front(), false, {}, {}}; } -TypeFamilyReductionResult addFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult addFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -806,8 +805,8 @@ TypeFamilyReductionResult addFamilyFn( return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__add"); } -TypeFamilyReductionResult subFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult subFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -818,8 +817,8 @@ TypeFamilyReductionResult subFamilyFn( return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__sub"); } -TypeFamilyReductionResult mulFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult mulFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -830,8 +829,8 @@ TypeFamilyReductionResult mulFamilyFn( return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__mul"); } -TypeFamilyReductionResult divFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult divFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -842,8 +841,8 @@ TypeFamilyReductionResult divFamilyFn( return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__div"); } -TypeFamilyReductionResult idivFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult idivFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -854,8 +853,8 @@ TypeFamilyReductionResult idivFamilyFn( return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__idiv"); } -TypeFamilyReductionResult powFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult powFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -866,8 +865,8 @@ TypeFamilyReductionResult powFamilyFn( return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__pow"); } -TypeFamilyReductionResult modFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult modFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -878,8 +877,8 @@ TypeFamilyReductionResult modFamilyFn( return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__mod"); } -TypeFamilyReductionResult concatFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult concatFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -964,8 +963,8 @@ TypeFamilyReductionResult concatFamilyFn( return {ctx->builtins->stringType, false, {}, {}}; } -TypeFamilyReductionResult andFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult andFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -1001,8 +1000,8 @@ TypeFamilyReductionResult andFamilyFn( return {overallResult.result, false, std::move(blockedTypes), {}}; } -TypeFamilyReductionResult orFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult orFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -1093,17 +1092,19 @@ static TypeFamilyReductionResult comparisonFamilyFn(TypeId instance, Not std::shared_ptr normLhsTy = ctx->normalizer->normalize(lhsTy); std::shared_ptr normRhsTy = ctx->normalizer->normalize(rhsTy); + NormalizationResult lhsInhabited = ctx->normalizer->isInhabited(normLhsTy.get()); + NormalizationResult rhsInhabited = ctx->normalizer->isInhabited(normRhsTy.get()); // if either failed to normalize, we can't reduce, but know nothing about inhabitance. - if (!normLhsTy || !normRhsTy) + if (!normLhsTy || !normRhsTy || lhsInhabited == NormalizationResult::HitLimits || rhsInhabited == NormalizationResult::HitLimits) return {std::nullopt, false, {}, {}}; // if one of the types is error suppressing, we can just go ahead and reduce. if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors()) return {ctx->builtins->booleanType, false, {}, {}}; - // if we have a `never`, we can never observe that the comparison didn't work. - if (is(lhsTy) || is(rhsTy)) + // if we have an uninhabited type (e.g. `never`), we can never observe that the comparison didn't work. + if (lhsInhabited == NormalizationResult::False || rhsInhabited == NormalizationResult::False) return {ctx->builtins->booleanType, false, {}, {}}; // If both types are some strict subset of `string`, we can reduce now. @@ -1153,8 +1154,8 @@ static TypeFamilyReductionResult comparisonFamilyFn(TypeId instance, Not return {ctx->builtins->booleanType, false, {}, {}}; } -TypeFamilyReductionResult ltFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult ltFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -1165,8 +1166,8 @@ TypeFamilyReductionResult ltFamilyFn( return comparisonFamilyFn(instance, queue, typeParams, packParams, ctx, "__lt"); } -TypeFamilyReductionResult leFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult leFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -1177,8 +1178,8 @@ TypeFamilyReductionResult leFamilyFn( return comparisonFamilyFn(instance, queue, typeParams, packParams, ctx, "__le"); } -TypeFamilyReductionResult eqFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult eqFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -1197,9 +1198,11 @@ TypeFamilyReductionResult eqFamilyFn( std::shared_ptr normLhsTy = ctx->normalizer->normalize(lhsTy); std::shared_ptr normRhsTy = ctx->normalizer->normalize(rhsTy); + NormalizationResult lhsInhabited = ctx->normalizer->isInhabited(normLhsTy.get()); + NormalizationResult rhsInhabited = ctx->normalizer->isInhabited(normRhsTy.get()); // if either failed to normalize, we can't reduce, but know nothing about inhabitance. - if (!normLhsTy || !normRhsTy) + if (!normLhsTy || !normRhsTy || lhsInhabited == NormalizationResult::HitLimits || rhsInhabited == NormalizationResult::HitLimits) return {std::nullopt, false, {}, {}}; // if one of the types is error suppressing, we can just go ahead and reduce. @@ -1207,7 +1210,7 @@ TypeFamilyReductionResult eqFamilyFn( return {ctx->builtins->booleanType, false, {}, {}}; // if we have a `never`, we can never observe that the comparison didn't work. - if (is(lhsTy) || is(rhsTy)) + if (lhsInhabited == NormalizationResult::False || rhsInhabited == NormalizationResult::False) return {ctx->builtins->booleanType, false, {}, {}}; // findMetatableEntry demands the ability to emit errors, so we must give it @@ -1282,8 +1285,8 @@ struct FindRefinementBlockers : TypeOnceVisitor }; -TypeFamilyReductionResult refineFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult refineFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { @@ -1340,73 +1343,145 @@ TypeFamilyReductionResult refineFamilyFn( return {resultTy, false, {}, {}}; } -TypeFamilyReductionResult unionFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult singletonFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { - if (typeParams.size() != 2 || !packParams.empty()) + if (typeParams.size() != 1 || !packParams.empty()) + { + ctx->ice->ice("singleton type family: encountered a type family instance without the required argument structure"); + LUAU_ASSERT(false); + } + + TypeId type = follow(typeParams.at(0)); + + // check to see if both operand types are resolved enough, and wait to reduce if not + if (isPending(type, ctx->solver)) + return {std::nullopt, false, {type}, {}}; + + TypeId followed = type; + // we want to follow through a negation here as well. + if (auto negation = get(followed)) + followed = follow(negation->ty); + + // if we have a singleton type or `nil`, which is its own singleton type... + if (get(followed) || isNil(followed)) + return {type, false, {}, {}}; + + // otherwise, we'll return the top type, `unknown`. + return {ctx->builtins->unknownType, false, {}, {}}; +} + +TypeFamilyReductionResult unionFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) +{ + if (!packParams.empty()) { ctx->ice->ice("union type family: encountered a type family instance without the required argument structure"); LUAU_ASSERT(false); } - TypeId lhsTy = follow(typeParams.at(0)); - TypeId rhsTy = follow(typeParams.at(1)); + // if we only have one parameter, there's nothing to do. + if (typeParams.size() == 1) + return {follow(typeParams[0]), false, {}, {}}; - // check to see if both operand types are resolved enough, and wait to reduce if not - if (isPending(lhsTy, ctx->solver)) - return {std::nullopt, false, {lhsTy}, {}}; - else if (get(lhsTy)) // if the lhs is never, we don't need this family anymore - return {rhsTy, false, {}, {}}; - else if (isPending(rhsTy, ctx->solver)) - return {std::nullopt, false, {rhsTy}, {}}; - else if (get(rhsTy)) // if the rhs is never, we don't need this family anymore - return {lhsTy, false, {}, {}}; + // we need to follow all of the type parameters. + std::vector types; + types.reserve(typeParams.size()); + for (auto ty : typeParams) + types.emplace_back(follow(ty)); + // unfortunately, we need this short-circuit: if all but one type is `never`, we will return that one type. + // this also will early return if _everything_ is `never`, since we already have to check that. + std::optional lastType = std::nullopt; + for (auto ty : types) + { + // if we have a previous type and it's not `never` and the current type isn't `never`... + if (lastType && !get(lastType) && !get(ty)) + { + // we know we are not taking the short-circuited path. + lastType = std::nullopt; + break; + } - SimplifyResult result = simplifyUnion(ctx->builtins, ctx->arena, lhsTy, rhsTy); - if (!result.blockedTypes.empty()) - return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}}; + if (get(ty)) + continue; + lastType = ty; + } - return {result.result, false, {}, {}}; + // if we still have a `lastType` at the end, we're taking the short-circuit and reducing early. + if (lastType) + return {lastType, false, {}, {}}; + + // check to see if the operand types are resolved enough, and wait to reduce if not + for (auto ty : types) + if (isPending(ty, ctx->solver)) + return {std::nullopt, false, {ty}, {}}; + + // fold over the types with `simplifyUnion` + TypeId resultTy = ctx->builtins->neverType; + for (auto ty : types) + { + SimplifyResult result = simplifyUnion(ctx->builtins, ctx->arena, resultTy, ty); + if (!result.blockedTypes.empty()) + return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}}; + + resultTy = result.result; + } + + return {resultTy, false, {}, {}}; } -TypeFamilyReductionResult intersectFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult intersectFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { - if (typeParams.size() != 2 || !packParams.empty()) + if (!packParams.empty()) { ctx->ice->ice("intersect type family: encountered a type family instance without the required argument structure"); LUAU_ASSERT(false); } - TypeId lhsTy = follow(typeParams.at(0)); - TypeId rhsTy = follow(typeParams.at(1)); + // if we only have one parameter, there's nothing to do. + if (typeParams.size() == 1) + return {follow(typeParams[0]), false, {}, {}}; - // check to see if both operand types are resolved enough, and wait to reduce if not - if (isPending(lhsTy, ctx->solver)) - return {std::nullopt, false, {lhsTy}, {}}; - else if (get(lhsTy)) // if the lhs is never, we don't need this family anymore - return {ctx->builtins->neverType, false, {}, {}}; - else if (isPending(rhsTy, ctx->solver)) - return {std::nullopt, false, {rhsTy}, {}}; - else if (get(rhsTy)) // if the rhs is never, we don't need this family anymore - return {ctx->builtins->neverType, false, {}, {}}; + // we need to follow all of the type parameters. + std::vector types; + types.reserve(typeParams.size()); + for (auto ty : typeParams) + types.emplace_back(follow(ty)); - SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, rhsTy); - if (!result.blockedTypes.empty()) - return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}}; + // check to see if the operand types are resolved enough, and wait to reduce if not + // if any of them are `never`, the intersection will always be `never`, so we can reduce directly. + for (auto ty : types) + { + if (isPending(ty, ctx->solver)) + return {std::nullopt, false, {ty}, {}}; + else if (get(ty)) + return {ctx->builtins->neverType, false, {}, {}}; + } + + // fold over the types with `simplifyIntersection` + TypeId resultTy = ctx->builtins->unknownType; + for (auto ty : types) + { + SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty); + if (!result.blockedTypes.empty()) + return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}}; + + resultTy = result.result; + } // if the intersection simplifies to `never`, this gives us bad autocomplete. // we'll just produce the intersection plainly instead, but this might be revisitable // if we ever give `never` some kind of "explanation" trail. - if (get(result.result)) + if (get(resultTy)) { - TypeId intersection = ctx->arena->addType(IntersectionType{{lhsTy, rhsTy}}); + TypeId intersection = ctx->arena->addType(IntersectionType{typeParams}); return {intersection, false, {}, {}}; } - return {result.result, false, {}, {}}; + return {resultTy, false, {}, {}}; } // computes the keys of `ty` into `result` @@ -1581,8 +1656,8 @@ TypeFamilyReductionResult keyofFamilyImpl( return {ctx->arena->addType(UnionType{singletons}), false, {}, {}}; } -TypeFamilyReductionResult keyofFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult keyofFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 1 || !packParams.empty()) { @@ -1593,8 +1668,8 @@ TypeFamilyReductionResult keyofFamilyFn( return keyofFamilyImpl(typeParams, packParams, ctx, /* isRaw */ false); } -TypeFamilyReductionResult rawkeyofFamilyFn( - TypeId instance, NotNull queue, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFamilyReductionResult rawkeyofFamilyFn(TypeId instance, NotNull queue, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 1 || !packParams.empty()) { @@ -1623,6 +1698,7 @@ BuiltinTypeFamilies::BuiltinTypeFamilies() , leFamily{"le", leFamilyFn} , eqFamily{"eq", eqFamilyFn} , refineFamily{"refine", refineFamilyFn} + , singletonFamily{"singleton", singletonFamilyFn} , unionFamily{"union", unionFamilyFn} , intersectFamily{"intersect", intersectFamilyFn} , keyofFamily{"keyof", keyofFamilyFn} diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 2f8fad49..1c21ecb2 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -33,6 +33,7 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false) LUAU_FASTFLAG(LuauInstantiateInSubtyping) +LUAU_FASTFLAGVARIABLE(LuauMetatableInstantiationCloneCheck, false) LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false) LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false) LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false) @@ -5632,7 +5633,8 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, TypeId instantiated = *maybeInstantiated; TypeId target = follow(instantiated); - bool needsClone = follow(tf.type) == target; + const TableType* tfTable = FFlag::LuauMetatableInstantiationCloneCheck ? getTableType(tf.type) : nullptr; + bool needsClone = follow(tf.type) == target || (FFlag::LuauMetatableInstantiationCloneCheck && tfTable != nullptr && tfTable == getTableType(target)); bool shouldMutate = getTableType(tf.type); TableType* ttv = getMutableTableType(target); diff --git a/Analysis/src/TypePack.cpp b/Analysis/src/TypePack.cpp index 0d86bead..94d8783b 100644 --- a/Analysis/src/TypePack.cpp +++ b/Analysis/src/TypePack.cpp @@ -6,8 +6,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauFollowEmptyTypePacks, false); - LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); namespace Luau @@ -271,8 +269,7 @@ TypePackId follow(TypePackId tp, const void* context, TypePackId (*mapper)(const if (const Unifiable::Bound* btv = get>(mapped)) return btv->boundTo; - else if (const TypePack* tp = get(mapped); - (FFlag::DebugLuauDeferredConstraintResolution || FFlag::LuauFollowEmptyTypePacks) && tp && tp->head.empty()) + else if (const TypePack* tp = get(mapped); tp && tp->head.empty()) return tp->tail; else return std::nullopt; diff --git a/Analysis/src/TypePath.cpp b/Analysis/src/TypePath.cpp index 76f24421..76a47341 100644 --- a/Analysis/src/TypePath.cpp +++ b/Analysis/src/TypePath.cpp @@ -52,6 +52,11 @@ bool Index::operator==(const Index& other) const return index == other.index; } +bool Reduction::operator==(const Reduction& other) const +{ + return resultType == other.resultType; +} + Path Path::append(const Path& suffix) const { std::vector joined(components); @@ -124,6 +129,11 @@ size_t PathHash::operator()(const PackField& field) const return static_cast(field); } +size_t PathHash::operator()(const Reduction& reduction) const +{ + return std::hash()(reduction.resultType); +} + size_t PathHash::operator()(const Component& component) const { return visit(*this, component); @@ -472,6 +482,14 @@ struct TraversalState return false; } + bool traverse(TypePath::Reduction reduction) + { + if (checkInvariants()) + return false; + updateCurrent(reduction.resultType); + return true; + } + bool traverse(TypePath::PackField field) { if (checkInvariants()) @@ -584,9 +602,14 @@ std::string toString(const TypePath::Path& path, bool prefixDot) result << "tail"; break; } - result << "()"; } + else if constexpr (std::is_same_v) + { + // We need to rework the TypePath system to make subtyping failures easier to understand + // https://roblox.atlassian.net/browse/CLI-104422 + result << "~~>"; + } else { static_assert(always_false_v, "Unhandled Component variant"); diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index 67f49722..3dc274a9 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -1006,7 +1006,8 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp if (!subNorm || !superNorm) reportError(location, NormalizationTooComplex{}); else if ((failedOptionCount == 1 || foundHeuristic) && failedOption) - tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption); + tryUnifyNormalizedTypes( + subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption); else tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the union options are compatible"); } @@ -1017,7 +1018,8 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp if (!subNorm || !superNorm) reportError(location, NormalizationTooComplex{}); else if ((failedOptionCount == 1 || foundHeuristic) && failedOption) - tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption); + tryUnifyNormalizedTypes( + subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption); else tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the union options are compatible"); } diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index b8f3d225..62d051d6 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -42,9 +42,7 @@ static bool areCompatible(TypeId left, TypeId right) LUAU_ASSERT(leftProp.isReadOnly() || leftProp.isShared()); - const TypeId leftType = follow( - leftProp.isReadOnly() ? *leftProp.readTy : leftProp.type() - ); + const TypeId leftType = follow(leftProp.isReadOnly() ? *leftProp.readTy : leftProp.type()); if (isOptional(leftType) || get(leftType) || rightTable->state == TableState::Free || rightTable->indexer.has_value()) return true; @@ -52,7 +50,7 @@ static bool areCompatible(TypeId left, TypeId right) return false; }; - for (const auto& [name, leftProp]: leftTable->props) + for (const auto& [name, leftProp] : leftTable->props) { auto it = rightTable->props.find(name); if (it == rightTable->props.end()) @@ -62,7 +60,7 @@ static bool areCompatible(TypeId left, TypeId right) } } - for (const auto& [name, rightProp]: rightTable->props) + for (const auto& [name, rightProp] : rightTable->props) { auto it = leftTable->props.find(name); if (it == leftTable->props.end()) @@ -75,6 +73,18 @@ static bool areCompatible(TypeId left, TypeId right) return true; } +// returns `true` if `ty` is irressolvable and should be added to `incompleteSubtypes`. +static bool isIrresolvable(TypeId ty) +{ + return get(ty) || get(ty); +} + +// returns `true` if `tp` is irressolvable and should be added to `incompleteSubtypes`. +static bool isIrresolvable(TypePackId tp) +{ + return get(tp) || get(tp); +} + Unifier2::Unifier2(NotNull arena, NotNull builtinTypes, NotNull scope, NotNull ice) : arena(arena) , builtinTypes(builtinTypes) @@ -82,6 +92,19 @@ Unifier2::Unifier2(NotNull arena, NotNull builtinTypes, , ice(ice) , limits(TypeCheckLimits{}) // TODO: typecheck limits in unifier2 , recursionLimit(FInt::LuauTypeInferRecursionLimit) + , uninhabitedTypeFamilies(nullptr) +{ +} + +Unifier2::Unifier2(NotNull arena, NotNull builtinTypes, NotNull scope, NotNull ice, + DenseHashSet* uninhabitedTypeFamilies) + : arena(arena) + , builtinTypes(builtinTypes) + , scope(scope) + , ice(ice) + , limits(TypeCheckLimits{}) // TODO: typecheck limits in unifier2 + , recursionLimit(FInt::LuauTypeInferRecursionLimit) + , uninhabitedTypeFamilies(uninhabitedTypeFamilies) { } @@ -110,8 +133,11 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy) // But we exclude these two subtyping patterns, they are tautological: // - never <: *blocked* // - *blocked* <: unknown - if ((get(subTy) || get(superTy)) && !get(subTy) && !get(superTy)) + if ((isIrresolvable(subTy) || isIrresolvable(superTy)) && !get(subTy) && !get(superTy)) { + if (uninhabitedTypeFamilies && (uninhabitedTypeFamilies->contains(subTy) || uninhabitedTypeFamilies->contains(superTy))) + return true; + incompleteSubtypes.push_back(SubtypeConstraint{subTy, superTy}); return true; } @@ -473,8 +499,11 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp) if (subTp == superTp) return true; - if (get(subTp) || get(superTp)) + if (isIrresolvable(subTp) || isIrresolvable(superTp)) { + if (uninhabitedTypeFamilies && (uninhabitedTypeFamilies->contains(subTp) || uninhabitedTypeFamilies->contains(superTp))) + return true; + incompleteSubtypes.push_back(PackSubtypeConstraint{subTp, superTp}); return true; } diff --git a/CodeGen/src/BytecodeAnalysis.cpp b/CodeGen/src/BytecodeAnalysis.cpp index 557e2d7e..bc9bd23a 100644 --- a/CodeGen/src/BytecodeAnalysis.cpp +++ b/CodeGen/src/BytecodeAnalysis.cpp @@ -7,6 +7,8 @@ #include "lobject.h" +LUAU_FASTFLAG(LuauCodegenDirectUserdataFlow) + namespace Luau { namespace CodeGen @@ -833,6 +835,27 @@ void analyzeBytecodeTypes(IrFunction& function) bcType.result = regTags[ra]; break; } + case LOP_NAMECALL: + { + if (FFlag::LuauCodegenDirectUserdataFlow) + { + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); + uint32_t kc = pc[1]; + + bcType.a = regTags[rb]; + bcType.b = getBytecodeConstantTag(proto, kc); + + // While namecall might result in a callable table, we assume the function fast path + regTags[ra] = LBC_TYPE_FUNCTION; + + // Namecall places source register into target + 1 + regTags[ra + 1] = bcType.a; + + bcType.result = LBC_TYPE_FUNCTION; + } + break; + } case LOP_GETGLOBAL: case LOP_SETGLOBAL: case LOP_CALL: @@ -866,7 +889,6 @@ void analyzeBytecodeTypes(IrFunction& function) case LOP_COVERAGE: case LOP_GETIMPORT: case LOP_CAPTURE: - case LOP_NAMECALL: case LOP_PREPVARARGS: case LOP_GETVARARGS: case LOP_FORGPREP: diff --git a/CodeGen/src/CodeBlockUnwind.cpp b/CodeGen/src/CodeBlockUnwind.cpp index e0d4629f..ca1a489e 100644 --- a/CodeGen/src/CodeBlockUnwind.cpp +++ b/CodeGen/src/CodeBlockUnwind.cpp @@ -163,6 +163,9 @@ bool isUnwindSupported() { #if defined(_WIN32) && defined(_M_X64) return true; +#elif defined(__ANDROID__) + // Current unwind information is not compatible with Android + return false; #elif defined(__APPLE__) && defined(__aarch64__) char ver[256]; size_t verLength = sizeof(ver); diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index 9c78a784..bdabfd1d 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -121,7 +121,7 @@ ExtraExecData* getExtraExecData(Proto* proto, void* execdata) static OldNativeProto createOldNativeProto(Proto* proto, const IrBuilder& ir) { CODEGEN_ASSERT(!FFlag::LuauCodegenContext); - + int execDataSize = calculateExecDataSize(proto); CODEGEN_ASSERT(execDataSize % 4 == 0); diff --git a/CodeGen/src/CodeGenAssembly.cpp b/CodeGen/src/CodeGenAssembly.cpp index cce9eac6..5485a22c 100644 --- a/CodeGen/src/CodeGenAssembly.cpp +++ b/CodeGen/src/CodeGenAssembly.cpp @@ -122,7 +122,10 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A if (stats && (stats->functionStatsFlags & FunctionStats_Enable)) { FunctionStats functionStat; - functionStat.name = p->debugname ? getstr(p->debugname) : ""; + + // function name is empty for anonymous and pseudo top-level functions + // properly name pseudo top-level function because it will be compiled natively if it has loops + functionStat.name = p->debugname ? getstr(p->debugname) : p->bytecodeid == root->bytecodeid ? "[top level]" : "[anonymous]"; functionStat.line = p->linedefined; functionStat.bcodeCount = getInstructionCount(p->code, p->sizecode); functionStat.irCount = unsigned(ir.function.instructions.size()); diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 362c4368..92092307 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -13,6 +13,7 @@ #include "ltm.h" LUAU_FASTFLAGVARIABLE(LuauCodegenLoadTVTag, false) +LUAU_FASTFLAGVARIABLE(LuauCodegenDirectUserdataFlow, false) namespace Luau { @@ -1230,6 +1231,14 @@ void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos) return; } + if (FFlag::LuauCodegenDirectUserdataFlow && bcTypes.a == LBC_TYPE_USERDATA) + { + build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TUSERDATA), build.vmExit(pcpos)); + + build.inst(IrCmd::FALLBACK_GETTABLEKS, build.constUint(pcpos), build.vmReg(ra), build.vmReg(rb), build.vmConst(aux)); + return; + } + IrOp fallback = build.block(IrBlockKind::Fallback); build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback); @@ -1256,10 +1265,20 @@ void translateInstSetTableKS(IrBuilder& build, const Instruction* pc, int pcpos) int rb = LUAU_INSN_B(*pc); uint32_t aux = pc[1]; - IrOp fallback = build.block(IrBlockKind::Fallback); BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos); IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); + + if (FFlag::LuauCodegenDirectUserdataFlow && bcTypes.a == LBC_TYPE_USERDATA) + { + build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TUSERDATA), build.vmExit(pcpos)); + + build.inst(IrCmd::FALLBACK_SETTABLEKS, build.constUint(pcpos), build.vmReg(ra), build.vmReg(rb), build.vmConst(aux)); + return; + } + + IrOp fallback = build.block(IrBlockKind::Fallback); + build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback); IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); @@ -1370,12 +1389,31 @@ void translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos) int rb = LUAU_INSN_B(*pc); uint32_t aux = pc[1]; + BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos); + + if (FFlag::LuauCodegenDirectUserdataFlow && bcTypes.a == LBC_TYPE_VECTOR) + { + build.loadAndCheckTag(build.vmReg(rb), LUA_TVECTOR, build.vmExit(pcpos)); + + build.inst(IrCmd::FALLBACK_NAMECALL, build.constUint(pcpos), build.vmReg(ra), build.vmReg(rb), build.vmConst(aux)); + return; + } + + if (FFlag::LuauCodegenDirectUserdataFlow && bcTypes.a == LBC_TYPE_USERDATA) + { + build.loadAndCheckTag(build.vmReg(rb), LUA_TUSERDATA, build.vmExit(pcpos)); + + build.inst(IrCmd::FALLBACK_NAMECALL, build.constUint(pcpos), build.vmReg(ra), build.vmReg(rb), build.vmConst(aux)); + return; + } + IrOp next = build.blockAtInst(pcpos + getOpLength(LOP_NAMECALL)); IrOp fallback = build.block(IrBlockKind::Fallback); IrOp firstFastPathSuccess = build.block(IrBlockKind::Internal); IrOp secondFastPath = build.block(IrBlockKind::Internal); - build.loadAndCheckTag(build.vmReg(rb), LUA_TTABLE, fallback); + build.loadAndCheckTag( + build.vmReg(rb), LUA_TTABLE, FFlag::LuauCodegenDirectUserdataFlow && bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback); IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); CODEGEN_ASSERT(build.function.proto); diff --git a/Common/include/Luau/ExperimentalFlags.h b/Common/include/Luau/ExperimentalFlags.h index c3163265..b02d3777 100644 --- a/Common/include/Luau/ExperimentalFlags.h +++ b/Common/include/Luau/ExperimentalFlags.h @@ -11,9 +11,9 @@ inline bool isFlagExperimental(const char* flag) // Flags in this list are disabled by default in various command-line tools. They may have behavior that is not fully final, // or critical bugs that are found after the code has been submitted. static const char* const kList[] = { - "LuauInstantiateInSubtyping", // requires some fixes to lua-apps code - "LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins - "LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative + "LuauInstantiateInSubtyping", // requires some fixes to lua-apps code + "LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins + "LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative // makes sure we always have at least one entry nullptr, }; diff --git a/LICENSE.txt b/LICENSE.txt index cbe14a55..2eac525f 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,7 @@ MIT License -Copyright (c) 2019-2023 Roblox Corporation +Copyright (c) 2019-2024 Roblox Corporation +Copyright (c) 1994–2019 Lua.org, PUC-Rio. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -18,4 +19,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +SOFTWARE. diff --git a/VM/src/lgc.cpp b/VM/src/lgc.cpp index 4b31580a..2ddddd97 100644 --- a/VM/src/lgc.cpp +++ b/VM/src/lgc.cpp @@ -929,7 +929,7 @@ static size_t gcstep(lua_State* L, size_t limit) { while (g->sweepgcopage && cost < limit) { - lua_Page* next = luaM_getnextgcopage(g->sweepgcopage); // page sweep might destroy the page + lua_Page* next = luaM_getnextpage(g->sweepgcopage); // page sweep might destroy the page int steps = sweepgcopage(L, g->sweepgcopage); diff --git a/VM/src/lmem.cpp b/VM/src/lmem.cpp index 54b0dd32..3de18cf9 100644 --- a/VM/src/lmem.cpp +++ b/VM/src/lmem.cpp @@ -196,9 +196,9 @@ struct lua_Page lua_Page* prev; lua_Page* next; - // list of all gco pages - lua_Page* gcolistprev; - lua_Page* gcolistnext; + // list of all pages + lua_Page* listprev; + lua_Page* listnext; int pageSize; // page size in bytes, including page header int blockSize; // block size in bytes, including block header (for non-GCO) @@ -220,7 +220,7 @@ l_noret luaM_toobig(lua_State* L) luaG_runerror(L, "memory allocation error: block too big"); } -static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int blockSize, int blockCount) +static lua_Page* newpage(lua_State* L, lua_Page** pageset, int pageSize, int blockSize, int blockCount) { global_State* g = L->global; @@ -236,8 +236,8 @@ static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int page->prev = NULL; page->next = NULL; - page->gcolistprev = NULL; - page->gcolistnext = NULL; + page->listprev = NULL; + page->listnext = NULL; page->pageSize = pageSize; page->blockSize = blockSize; @@ -249,12 +249,12 @@ static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int page->freeNext = (blockCount - 1) * blockSize; page->busyBlocks = 0; - if (gcopageset) + if (pageset) { - page->gcolistnext = *gcopageset; - if (page->gcolistnext) - page->gcolistnext->gcolistprev = page; - *gcopageset = page; + page->listnext = *pageset; + if (page->listnext) + page->listnext->listprev = page; + *pageset = page; } return page; @@ -263,7 +263,7 @@ static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int // this is part of a cold path in newblock and newgcoblock // it is marked as noinline to prevent it from being inlined into those functions // if it is inlined, then the compiler may determine those functions are "too big" to be profitably inlined, which results in reduced performance -LUAU_NOINLINE static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopageset, uint8_t sizeClass, bool storeMetadata) +LUAU_NOINLINE static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset, lua_Page** pageset, uint8_t sizeClass, bool storeMetadata) { if (FFlag::LuauExtendedSizeClasses) { @@ -272,7 +272,7 @@ LUAU_NOINLINE static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset int blockSize = sizeOfClass + (storeMetadata ? kBlockHeader : 0); int blockCount = (pageSize - offsetof(lua_Page, data)) / blockSize; - lua_Page* page = newpage(L, gcopageset, pageSize, blockSize, blockCount); + lua_Page* page = newpage(L, pageset, pageSize, blockSize, blockCount); // prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!) LUAU_ASSERT(!freepageset[sizeClass]); @@ -285,7 +285,7 @@ LUAU_NOINLINE static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset int blockSize = kSizeClassConfig.sizeOfClass[sizeClass] + (storeMetadata ? kBlockHeader : 0); int blockCount = (kSmallPageSize - offsetof(lua_Page, data)) / blockSize; - lua_Page* page = newpage(L, gcopageset, kSmallPageSize, blockSize, blockCount); + lua_Page* page = newpage(L, pageset, kSmallPageSize, blockSize, blockCount); // prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!) LUAU_ASSERT(!freepageset[sizeClass]); @@ -295,27 +295,27 @@ LUAU_NOINLINE static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset } } -static void freepage(lua_State* L, lua_Page** gcopageset, lua_Page* page) +static void freepage(lua_State* L, lua_Page** pageset, lua_Page* page) { global_State* g = L->global; - if (gcopageset) + if (pageset) { // remove page from alllist - if (page->gcolistnext) - page->gcolistnext->gcolistprev = page->gcolistprev; + if (page->listnext) + page->listnext->listprev = page->listprev; - if (page->gcolistprev) - page->gcolistprev->gcolistnext = page->gcolistnext; - else if (*gcopageset == page) - *gcopageset = page->gcolistnext; + if (page->listprev) + page->listprev->listnext = page->listnext; + else if (*pageset == page) + *pageset = page->listnext; } // so long (*g->frealloc)(g->ud, page, page->pageSize, 0); } -static void freeclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopageset, lua_Page* page, uint8_t sizeClass) +static void freeclasspage(lua_State* L, lua_Page** freepageset, lua_Page** pageset, lua_Page* page, uint8_t sizeClass) { // remove page from freelist if (page->next) @@ -326,7 +326,7 @@ static void freeclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopa else if (freepageset[sizeClass] == page) freepageset[sizeClass] = page->next; - freepage(L, gcopageset, page); + freepage(L, pageset, page); } static void* newblock(lua_State* L, int sizeClass) @@ -645,9 +645,9 @@ void luaM_getpageinfo(lua_Page* page, int* pageBlocks, int* busyBlocks, int* blo *pageSize = page->pageSize; } -lua_Page* luaM_getnextgcopage(lua_Page* page) +lua_Page* luaM_getnextpage(lua_Page* page) { - return page->gcolistnext; + return page->listnext; } void luaM_visitpage(lua_Page* page, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco)) @@ -684,7 +684,7 @@ void luaM_visitgco(lua_State* L, void* context, bool (*visitor)(void* context, l for (lua_Page* curr = g->allgcopages; curr;) { - lua_Page* next = curr->gcolistnext; // block visit might destroy the page + lua_Page* next = curr->listnext; // block visit might destroy the page luaM_visitpage(curr, context, visitor); diff --git a/VM/src/lmem.h b/VM/src/lmem.h index d6508402..aff07548 100644 --- a/VM/src/lmem.h +++ b/VM/src/lmem.h @@ -27,7 +27,7 @@ LUAI_FUNC l_noret luaM_toobig(lua_State* L); LUAI_FUNC void luaM_getpagewalkinfo(lua_Page* page, char** start, char** end, int* busyBlocks, int* blockSize); LUAI_FUNC void luaM_getpageinfo(lua_Page* page, int* pageBlocks, int* busyBlocks, int* blockSize, int* pageSize); -LUAI_FUNC lua_Page* luaM_getnextgcopage(lua_Page* page); +LUAI_FUNC lua_Page* luaM_getnextpage(lua_Page* page); LUAI_FUNC void luaM_visitpage(lua_Page* page, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco)); LUAI_FUNC void luaM_visitgco(lua_State* L, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco)); diff --git a/VM/src/ltablib.cpp b/VM/src/ltablib.cpp index 0b4a6855..27c08f11 100644 --- a/VM/src/ltablib.cpp +++ b/VM/src/ltablib.cpp @@ -3,6 +3,7 @@ #include "lualib.h" #include "lapi.h" +#include "lnumutils.h" #include "lstate.h" #include "ltable.h" #include "lstring.h" @@ -10,6 +11,8 @@ #include "ldebug.h" #include "lvm.h" +LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauFastCrossTableMove, false) + static int foreachi(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); @@ -112,6 +115,68 @@ static void moveelements(lua_State* L, int srct, int dstt, int f, int e, int t) luaC_barrierfast(L, dst); } + else if (DFFlag::LuauFastCrossTableMove && dst != src) + { + // compute the array slice we have to copy over + int slicestart = f < 1 ? 0 : (f > src->sizearray ? src->sizearray : f - 1); + int sliceend = e < 1 ? 0 : (e > src->sizearray ? src->sizearray : e); + LUAU_ASSERT(slicestart <= sliceend); + + int slicecount = sliceend - slicestart; + + if (slicecount > 0) + { + // array slice starting from INT_MIN is impossible, so we don't have to worry about int overflow + int dstslicestart = f < 1 ? -f + 1 : 0; + + // copy over the slice + for (int i = 0; i < slicecount; ++i) + { + lua_rawgeti(L, srct, slicestart + i + 1); + lua_rawseti(L, dstt, dstslicestart + t + i); + } + } + + // copy the remaining elements that could be in the hash part + int hashpartsize = sizenode(src); + + // select the strategy with the least amount of steps + if (n <= hashpartsize) + { + for (int i = 0; i < n; ++i) + { + // skip array slice elements that were already copied over + if (cast_to(unsigned int, f + i - 1) < cast_to(unsigned int, src->sizearray)) + continue; + + lua_rawgeti(L, srct, f + i); + lua_rawseti(L, dstt, t + i); + } + } + else + { + // source and destination tables are different, so we can iterate over source hash part directly + int i = hashpartsize; + + while (i--) + { + LuaNode* node = gnode(src, i); + if (ttisnumber(gkey(node))) + { + double n = nvalue(gkey(node)); + + int k; + luai_num2int(k, n); + + if (luai_numeq(cast_num(k), n) && k >= f && k <= e) + { + lua_rawgeti(L, srct, k); + lua_rawseti(L, dstt, t - f + k); + } + } + } + } + } else { if (t > e || t <= f || dst != src) diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index f86b4000..d0d4e9be 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -145,36 +145,38 @@ struct ACBuiltinsFixture : ACFixtureImpl { }; -#define LUAU_CHECK_HAS_KEY(map, key) do \ - { \ - auto&& _m = (map); \ - auto&& _k = (key); \ - const size_t count = _m.count(_k); \ +#define LUAU_CHECK_HAS_KEY(map, key) \ + do \ + { \ + auto&& _m = (map); \ + auto&& _k = (key); \ + const size_t count = _m.count(_k); \ CHECK_MESSAGE(count, "Map should have key \"" << _k << "\""); \ - if (!count) \ - { \ - MESSAGE("Keys: (count " << _m.size() << ")"); \ - for (const auto& [k, v]: _m) \ - { \ - MESSAGE("\tkey: " << k); \ - } \ - } \ + if (!count) \ + { \ + MESSAGE("Keys: (count " << _m.size() << ")"); \ + for (const auto& [k, v] : _m) \ + { \ + MESSAGE("\tkey: " << k); \ + } \ + } \ } while (false) -#define LUAU_CHECK_HAS_NO_KEY(map, key) do \ - { \ - auto&& _m = (map); \ - auto&& _k = (key); \ - const size_t count = _m.count(_k); \ +#define LUAU_CHECK_HAS_NO_KEY(map, key) \ + do \ + { \ + auto&& _m = (map); \ + auto&& _k = (key); \ + const size_t count = _m.count(_k); \ CHECK_MESSAGE(!count, "Map should not have key \"" << _k << "\""); \ - if (count) \ - { \ - MESSAGE("Keys: (count " << _m.size() << ")"); \ - for (const auto& [k, v]: _m) \ - { \ - MESSAGE("\tkey: " << k); \ - } \ - } \ + if (count) \ + { \ + MESSAGE("Keys: (count " << _m.size() << ")"); \ + for (const auto& [k, v] : _m) \ + { \ + MESSAGE("\tkey: " << k); \ + } \ + } \ } while (false) TEST_SUITE_BEGIN("AutocompleteTest"); diff --git a/tests/ClassFixture.cpp b/tests/ClassFixture.cpp index 05df44e1..7e35e40a 100644 --- a/tests/ClassFixture.cpp +++ b/tests/ClassFixture.cpp @@ -29,8 +29,7 @@ ClassFixture::ClassFixture() }; getMutable(connectionType)->props = { - {"Connect", {makeFunction(arena, connectionType, {makeFunction(arena, nullopt, {baseClassInstanceType}, {})}, {})}} - }; + {"Connect", {makeFunction(arena, connectionType, {makeFunction(arena, nullopt, {baseClassInstanceType}, {})}, {})}}}; TypeId baseClassType = arena.addType(ClassType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test"}); getMutable(baseClassType)->props = { @@ -103,13 +102,10 @@ ClassFixture::ClassFixture() }; getMutable(vector2MetaType)->props = { {"__add", {makeFunction(arena, nullopt, {vector2InstanceType, vector2InstanceType}, {vector2InstanceType})}}, - {"__mul", { - arena.addType(IntersectionType{{ - makeFunction(arena, vector2InstanceType, {vector2InstanceType}, {vector2InstanceType}), - makeFunction(arena, vector2InstanceType, {builtinTypes->numberType}, {vector2InstanceType}), - }}) - }} - }; + {"__mul", {arena.addType(IntersectionType{{ + makeFunction(arena, vector2InstanceType, {vector2InstanceType}, {vector2InstanceType}), + makeFunction(arena, vector2InstanceType, {builtinTypes->numberType}, {vector2InstanceType}), + }})}}}; globals.globalScope->exportedTypeBindings["Vector2"] = TypeFun{{}, vector2InstanceType}; addGlobalBinding(globals, "Vector2", vector2Type, "@test"); diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 4bafc8fa..238ff02b 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -36,6 +36,7 @@ LUAU_FASTFLAG(LuauCompileRepeatUntilSkippedLocals) LUAU_FASTFLAG(LuauCodegenInferNumTag) LUAU_FASTFLAG(LuauCodegenDetailedCompilationResult) LUAU_FASTFLAG(LuauCodegenCheckTruthyFormB) +LUAU_DYNAMIC_FASTFLAG(LuauFastCrossTableMove) static lua_CompileOptions defaultOptions() { @@ -415,6 +416,8 @@ TEST_CASE("Sort") TEST_CASE("Move") { + ScopedFastFlag luauFastCrossTableMove{DFFlag::LuauFastCrossTableMove, true}; + runConformance("move.lua"); } @@ -1837,7 +1840,7 @@ TEST_CASE("DebugApi") lua_pushnumber(L, 10); lua_Debug ar; - CHECK(lua_getinfo(L, -1, "f", &ar) == 0); // number is not a function + CHECK(lua_getinfo(L, -1, "f", &ar) == 0); // number is not a function CHECK(lua_getinfo(L, -10, "f", &ar) == 0); // not on stack } @@ -2174,8 +2177,7 @@ TEST_CASE("HugeFunctionLoadFailure") static size_t largeAllocationToFail = 0; static size_t largeAllocationCount = 0; - const auto testAllocate = [](void* ud, void* ptr, size_t osize, size_t nsize) -> void* - { + const auto testAllocate = [](void* ud, void* ptr, size_t osize, size_t nsize) -> void* { if (nsize == 0) { free(ptr); diff --git a/tests/Instantiation2.test.cpp b/tests/Instantiation2.test.cpp index ed9d7198..0154a211 100644 --- a/tests/Instantiation2.test.cpp +++ b/tests/Instantiation2.test.cpp @@ -19,12 +19,10 @@ TEST_CASE_FIXTURE(Fixture, "weird_cyclic_instantiation") TypeId genericT = arena.addType(GenericType{"T"}); - TypeId idTy = arena.addType(FunctionType{ - /* generics */ {genericT}, + TypeId idTy = arena.addType(FunctionType{/* generics */ {genericT}, /* genericPacks */ {}, /* argTypes */ arena.addTypePack({genericT}), - /* retTypes */ arena.addTypePack({genericT}) - }); + /* retTypes */ arena.addTypePack({genericT})}); DenseHashMap genericSubstitutions{nullptr}; DenseHashMap genericPackSubstitutions{nullptr}; diff --git a/tests/IrLowering.test.cpp b/tests/IrLowering.test.cpp index fffafe4d..7a1e0bfe 100644 --- a/tests/IrLowering.test.cpp +++ b/tests/IrLowering.test.cpp @@ -14,6 +14,7 @@ LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5) LUAU_FASTFLAG(LuauCodegenLoadTVTag) +LUAU_FASTFLAG(LuauCodegenDirectUserdataFlow) static std::string getCodegenAssembly(const char* source) { @@ -402,7 +403,7 @@ local function vecrcp(a: vector) return vector(1, 2, 3) + a end )"), -R"( + R"( ; function vecrcp($arg0) line 2 bb_0: CHECK_TAG R0, tvector, exit(entry) @@ -420,4 +421,128 @@ bb_bytecode_1: )"); } +TEST_CASE("VectorNamecall") +{ + ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true}; + + CHECK_EQ("\n" + getCodegenAssembly(R"( +local function abs(a: vector) + return a:Abs() +end +)"), + R"( +; function abs($arg0) line 2 +bb_0: + CHECK_TAG R0, tvector, exit(entry) + JUMP bb_2 +bb_2: + JUMP bb_bytecode_1 +bb_bytecode_1: + FALLBACK_NAMECALL 0u, R1, R0, K0 + INTERRUPT 2u + SET_SAVEDPC 3u + CALL R1, 1i, -1i + INTERRUPT 3u + RETURN R1, -1i +)"); +} + +TEST_CASE("UserDataGetIndex") +{ + ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true}; + + CHECK_EQ("\n" + getCodegenAssembly(R"( +local function getxy(a: Point) + return a.x + a.y +end +)"), + R"( +; function getxy($arg0) line 2 +bb_0: + CHECK_TAG R0, tuserdata, exit(entry) + JUMP bb_2 +bb_2: + JUMP bb_bytecode_1 +bb_bytecode_1: + FALLBACK_GETTABLEKS 0u, R2, R0, K0 + FALLBACK_GETTABLEKS 2u, R3, R0, K1 + CHECK_TAG R2, tnumber, bb_fallback_3 + CHECK_TAG R3, tnumber, bb_fallback_3 + %14 = LOAD_DOUBLE R2 + %16 = ADD_NUM %14, R3 + STORE_DOUBLE R1, %16 + STORE_TAG R1, tnumber + JUMP bb_4 +bb_4: + INTERRUPT 5u + RETURN R1, 1i +)"); +} + +TEST_CASE("UserDataSetIndex") +{ + ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true}; + + CHECK_EQ("\n" + getCodegenAssembly(R"( +local function setxy(a: Point) + a.x = 3 + a.y = 4 +end +)"), + R"( +; function setxy($arg0) line 2 +bb_0: + CHECK_TAG R0, tuserdata, exit(entry) + JUMP bb_2 +bb_2: + JUMP bb_bytecode_1 +bb_bytecode_1: + STORE_DOUBLE R1, 3 + STORE_TAG R1, tnumber + FALLBACK_SETTABLEKS 1u, R1, R0, K0 + STORE_DOUBLE R1, 4 + FALLBACK_SETTABLEKS 4u, R1, R0, K1 + INTERRUPT 6u + RETURN R0, 0i +)"); +} + +TEST_CASE("UserDataNamecall") +{ + ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true}; + + CHECK_EQ("\n" + getCodegenAssembly(R"( +local function getxy(a: Point) + return a:GetX() + a:GetY() +end +)"), + R"( +; function getxy($arg0) line 2 +bb_0: + CHECK_TAG R0, tuserdata, exit(entry) + JUMP bb_2 +bb_2: + JUMP bb_bytecode_1 +bb_bytecode_1: + FALLBACK_NAMECALL 0u, R2, R0, K0 + INTERRUPT 2u + SET_SAVEDPC 3u + CALL R2, 1i, 1i + FALLBACK_NAMECALL 3u, R3, R0, K1 + INTERRUPT 5u + SET_SAVEDPC 6u + CALL R3, 1i, 1i + CHECK_TAG R2, tnumber, bb_fallback_3 + CHECK_TAG R3, tnumber, bb_fallback_3 + %20 = LOAD_DOUBLE R2 + %22 = ADD_NUM %20, R3 + STORE_DOUBLE R1, %22 + STORE_TAG R1, tnumber + JUMP bb_4 +bb_4: + INTERRUPT 7u + RETURN R1, 1i +)"); +} + TEST_SUITE_END(); diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index 61fa6391..1a9ffd65 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -11,7 +11,9 @@ #include "Luau/BuiltinDefinitions.h" LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) -LUAU_FASTFLAG(LuauFixNormalizeCaching); +LUAU_FASTFLAG(LuauFixNormalizeCaching) +LUAU_FASTFLAG(LuauNormalizeNotUnknownIntersection) +LUAU_FASTFLAG(LuauFixCyclicUnionsOfIntersections); using namespace Luau; @@ -797,6 +799,36 @@ TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_union") CHECK("number" == toString(normalizer.typeFromNormal(*nt))); } +TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_union_of_intersection") +{ + ScopedFastFlag sff{FFlag::LuauFixCyclicUnionsOfIntersections, true}; + + // t1 where t1 = (string & t1) | string + TypeId boundTy = arena.addType(BlockedType{}); + TypeId intersectTy = arena.addType(IntersectionType{{builtinTypes->stringType, boundTy}}); + TypeId unionTy = arena.addType(UnionType{{builtinTypes->stringType, intersectTy}}); + asMutable(boundTy)->reassign(Type{BoundType{unionTy}}); + + std::shared_ptr nt = normalizer.normalize(unionTy); + + CHECK("string" == toString(normalizer.typeFromNormal(*nt))); +} + +TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_intersection_of_unions") +{ + ScopedFastFlag sff{FFlag::LuauFixCyclicUnionsOfIntersections, true}; + + // t1 where t1 = (string & t1) | string + TypeId boundTy = arena.addType(BlockedType{}); + TypeId unionTy = arena.addType(UnionType{{builtinTypes->stringType, boundTy}}); + TypeId intersectionTy = arena.addType(IntersectionType{{builtinTypes->stringType, unionTy}}); + asMutable(boundTy)->reassign(Type{BoundType{intersectionTy}}); + + std::shared_ptr nt = normalizer.normalize(intersectionTy); + + CHECK("string" == toString(normalizer.typeFromNormal(*nt))); +} + TEST_CASE_FIXTURE(NormalizeFixture, "crazy_metatable") { CHECK("never" == toString(normal("Mt<{}, number> & Mt<{}, string>"))); @@ -919,4 +951,15 @@ TEST_CASE_FIXTURE(NormalizeFixture, "non_final_types_can_be_normalized_but_are_n CHECK(na1 != na2); } +TEST_CASE_FIXTURE(NormalizeFixture, "intersect_with_not_unknown") +{ + ScopedFastFlag sff{FFlag::LuauNormalizeNotUnknownIntersection, true}; + + TypeId notUnknown = arena.addType(NegationType{builtinTypes->unknownType}); + TypeId type = arena.addType(IntersectionType{{builtinTypes->numberType, notUnknown}}); + std::shared_ptr normalized = normalizer.normalize(type); + + CHECK("never" == toString(normalizer.typeFromNormal(*normalized.get()))); +} + TEST_SUITE_END(); diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 36ec4e8b..d8f115ae 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -225,9 +225,9 @@ struct SubtypeFixture : Fixture }); TypeId readOnlyVec2Class = cls("ReadOnlyVec2", { - {"X", Property::readonly(builtinTypes->numberType)}, - {"Y", Property::readonly(builtinTypes->numberType)}, - }); + {"X", Property::readonly(builtinTypes->numberType)}, + {"Y", Property::readonly(builtinTypes->numberType)}, + }); // "hello" | "hello" TypeId helloOrHelloType = arena.addType(UnionType{{helloType, helloType}}); @@ -1285,6 +1285,30 @@ TEST_CASE_FIXTURE(SubtypeFixture, "({ x: T }) -> T <: ({ method: ({ x: T } CHECK_IS_SUBTYPE(tableToPropType, otherType); } +TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type_family_instance") +{ + TypeId longTy = arena.addType(UnionType{{builtinTypes->booleanType, builtinTypes->bufferType, builtinTypes->classType, builtinTypes->functionType, + builtinTypes->numberType, builtinTypes->stringType, builtinTypes->tableType, builtinTypes->threadType}}); + TypeId tblTy = tbl({{"depth", builtinTypes->unknownType}}); + TypeId combined = meet(longTy, tblTy); + TypeId subTy = arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.unionFamily}, {combined, builtinTypes->neverType}, {}}); + TypeId superTy = builtinTypes->neverType; + SubtypingResult result = isSubtype(subTy, superTy); + CHECK(!result.isSubtype); + + for (const SubtypingReasoning& reasoning : result.reasoning) + { + if (reasoning.subPath.empty() && reasoning.superPath.empty()) + continue; + + std::optional optSubLeaf = traverse(subTy, reasoning.subPath, builtinTypes); + std::optional optSuperLeaf = traverse(superTy, reasoning.superPath, builtinTypes); + + if (!optSubLeaf || !optSuperLeaf) + CHECK(false); + } +} + TEST_SUITE_END(); TEST_SUITE_BEGIN("Subtyping.Subpaths"); diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index 24dc6520..425c3c6f 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -944,7 +944,8 @@ TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch") std::string expected; if (FFlag::DebugLuauDeferredConstraintResolution) - expected = R"(Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; at [0][read "c"][read "d"], string is not exactly number)"; + expected = + R"(Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; at [0][read "c"][read "d"], string is not exactly number)"; else expected = R"(Type '{ a: number, b: string, c: { d: string } }' @@ -1013,15 +1014,8 @@ TEST_CASE_FIXTURE(Fixture, "cycle_rooted_in_a_pack") TypePack* packPtr = getMutable(thePack); REQUIRE(packPtr); - const TableType::Props theProps = { - {"BaseField", Property::readonly(builtinTypes->unknownType)}, - {"BaseMethod", Property::readonly(arena.addType( - FunctionType{ - thePack, - arena.addTypePack({}) - } - ))} - }; + const TableType::Props theProps = {{"BaseField", Property::readonly(builtinTypes->unknownType)}, + {"BaseMethod", Property::readonly(arena.addType(FunctionType{thePack, arena.addTypePack({})}))}}; TypeId theTable = arena.addType(TableType{theProps, {}, TypeLevel{}, TableState::Sealed}); diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index c65d6ec5..06e698a8 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -221,7 +221,8 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type '{ t: { v: string } }' could not be converted into 'U'; at [read "t"][read "v"], string is not exactly number)"; + const std::string expected = + R"(Type '{ t: { v: string } }' could not be converted into 'U'; at [read "t"][read "v"], string is not exactly number)"; CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}}); CHECK_EQ(expected, toString(result.errors[0])); diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 11bb5eda..e7922756 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -10,7 +10,6 @@ using namespace Luau; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls); -LUAU_FASTFLAG(LuauSetMetatableOnUnionsOfTables); TEST_SUITE_BEGIN("BuiltinTests"); @@ -371,8 +370,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_unpacks_arg_types_correctly") TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_on_union_of_tables") { - ScopedFastFlag sff{FFlag::LuauSetMetatableOnUnionsOfTables, true}; - CheckResult result = check(R"( type A = {tag: "A", x: number} type B = {tag: "B", y: string} diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index f18c3d7f..6112bd02 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -648,32 +648,20 @@ TEST_CASE_FIXTURE(Fixture, "read_write_class_properties") unfreeze(arena); TypeId instanceType = arena.addType(ClassType{"Instance", {}, nullopt, nullopt, {}, {}, "Test"}); - getMutable(instanceType)->props = { - {"Parent", Property::rw(instanceType)} - }; + getMutable(instanceType)->props = {{"Parent", Property::rw(instanceType)}}; // TypeId workspaceType = arena.addType(ClassType{"Workspace", {}, nullopt, nullopt, {}, {}, "Test"}); - TypeId scriptType = arena.addType(ClassType{ - "Script", { - {"Parent", Property::rw(workspaceType, instanceType)} - }, - instanceType, nullopt, {}, {}, "Test" - }); + TypeId scriptType = + arena.addType(ClassType{"Script", {{"Parent", Property::rw(workspaceType, instanceType)}}, instanceType, nullopt, {}, {}, "Test"}); - TypeId partType = arena.addType(ClassType{ - "Part", { - {"BrickColor", Property::rw(builtinTypes->stringType)}, - {"Parent", Property::rw(workspaceType, instanceType)} - }, - instanceType, nullopt, {}, {}, "Test"}); + TypeId partType = arena.addType( + ClassType{"Part", {{"BrickColor", Property::rw(builtinTypes->stringType)}, {"Parent", Property::rw(workspaceType, instanceType)}}, + instanceType, nullopt, {}, {}, "Test"}); - getMutable(workspaceType)->props = { - {"Script", Property::readonly(scriptType)}, - {"Part", Property::readonly(partType)} - }; + getMutable(workspaceType)->props = {{"Script", Property::readonly(scriptType)}, {"Part", Property::readonly(partType)}}; frontend.globals.globalScope->bindings[frontend.globals.globalNames.names->getOrAdd("script")] = Binding{scriptType}; @@ -703,7 +691,8 @@ TEST_CASE_FIXTURE(ClassFixture, "cannot_index_a_class_with_no_indexer") LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_MESSAGE(get(result.errors[0]), "Expected DynamicPropertyLookupOnClassesUnsafe but got " << result.errors[0]); + CHECK_MESSAGE( + get(result.errors[0]), "Expected DynamicPropertyLookupOnClassesUnsafe but got " << result.errors[0]); CHECK(builtinTypes->errorType == requireType("c")); } diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 29f70b30..1b8dea75 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -20,6 +20,8 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls); LUAU_FASTINT(LuauTarjanChildLimit); +LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError) + TEST_SUITE_BEGIN("TypeInferFunctions"); TEST_CASE_FIXTURE(Fixture, "general_case_table_literal_blocks") @@ -2129,10 +2131,20 @@ TEST_CASE_FIXTURE(Fixture, "attempt_to_call_an_intersection_of_tables") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::DebugLuauDeferredConstraintResolution) - CHECK_EQ(toString(result.errors[0]), "Cannot call non-function { x: number } & { y: string }"); + if (DFFlag::LuauImproveNonFunctionCallError) + { + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type { x: number } & { y: string }"); + else + CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type {| x: number |}"); + } else - CHECK_EQ(toString(result.errors[0]), "Cannot call non-function {| x: number |}"); + { + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ(toString(result.errors[0]), "Cannot call non-function { x: number } & { y: string }"); + else + CHECK_EQ(toString(result.errors[0]), "Cannot call non-function {| x: number |}"); + } } TEST_CASE_FIXTURE(BuiltinsFixture, "attempt_to_call_an_intersection_of_tables_with_call_metamethod") @@ -2535,4 +2547,54 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_definition_in_a_do_block_with_globa LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "fuzzer_alias_global_function_doesnt_hit_nil_assert") +{ + CheckResult result = check(R"( +function _() +end +local function l0() + function _() + end +end +_ = _ +)"); + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(Fixture, "fuzzer_bug_missing_follow_causes_assertion") +{ + CheckResult result = check(R"( +local _ = ({_=function() +return _ +end,}),true,_[_()] +for l0=_[_[_[`{function(l0) +end}`]]],_[_.n6[_[_.n6]]],_[_[_.n6[_[_.n6]]]] do +_ += if _ then "" +end +return _ +)"); +} + +TEST_CASE_FIXTURE(Fixture, "cannot_call_union_of_functions") +{ + CheckResult result = check(R"( + local f: (() -> ()) | (() -> () -> ()) = nil :: any + f() + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + + if (DFFlag::LuauImproveNonFunctionCallError) + { + std::string expected = R"(Cannot call a value of the union type: + | () -> () + | () -> () -> () +We are unable to determine the appropriate result type for such a call.)"; + + CHECK(expected == toString(result.errors[0])); + } + else + CHECK("Cannot call non-function (() -> () -> ()) | (() -> ())" == toString(result.errors[0])); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index 6e9e9327..8d14b812 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -17,6 +17,8 @@ using namespace Luau; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(LuauOkWithIteratingOverTableProperties) +LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError) + TEST_SUITE_BEGIN("TypeInferLoops"); TEST_CASE_FIXTURE(Fixture, "for_loop") @@ -165,7 +167,11 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_should_fail_with_non_function_iterator") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ("Cannot call non-function string", toString(result.errors[0])); + + if (DFFlag::LuauImproveNonFunctionCallError) + CHECK_EQ("Cannot call a value of type string", toString(result.errors[0])); + else + CHECK_EQ("Cannot call non-function string", toString(result.errors[0])); } TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_just_one_iterator_is_ok") diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index a94c649d..868aa9f2 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -338,7 +338,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_test_a_prop") )"); if (FFlag::DebugLuauDeferredConstraintResolution) - LUAU_REQUIRE_NO_ERRORS(result); + LUAU_REQUIRE_NO_ERRORS(result); else { LUAU_REQUIRE_ERROR_COUNT(2, result); @@ -592,7 +592,10 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil") LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil - CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a == nil + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "nil"); // a == nil :) + else + CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a == nil } TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue") diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index e10aea39..29537b8c 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -22,6 +22,9 @@ LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls); LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering); LUAU_FASTFLAG(DebugLuauSharedSelf); LUAU_FASTFLAG(LuauReadWritePropertySyntax); +LUAU_FASTFLAG(LuauMetatableInstantiationCloneCheck); + +LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError) TEST_SUITE_BEGIN("TableTests"); @@ -2383,7 +2386,11 @@ b() )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Cannot call non-function t1 where t1 = { @metatable { __call: t1 }, { } })"); + + if (DFFlag::LuauImproveNonFunctionCallError) + CHECK_EQ(toString(result.errors[0]), R"(Cannot call a value of type t1 where t1 = { @metatable { __call: t1 }, { } })"); + else + CHECK_EQ(toString(result.errors[0]), R"(Cannot call non-function t1 where t1 = { @metatable { __call: t1 }, { } })"); } TEST_CASE_FIXTURE(Fixture, "table_subtyping_shouldn't_add_optional_properties_to_sealed_tables") @@ -3016,7 +3023,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_must_be_callable") if (FFlag::DebugLuauDeferredConstraintResolution) { - CHECK("Cannot call non-function { @metatable { __call: number }, { } }" == toString(result.errors[0])); + if (DFFlag::LuauImproveNonFunctionCallError) + CHECK("Cannot call a value of type { @metatable { __call: number }, { } }" == toString(result.errors[0])); + else + CHECK("Cannot call non-function { @metatable { __call: number }, { } }" == toString(result.errors[0])); } else { @@ -3994,9 +4004,10 @@ TEST_CASE_FIXTURE(Fixture, "identify_all_problematic_table_fields") LUAU_REQUIRE_ERROR_COUNT(1, result); - std::string expected = "Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; at [read \"a\"], string is not exactly number" - "\n\tat [read \"b\"], boolean is not exactly string" - "\n\tat [read \"c\"], number is not exactly boolean"; + std::string expected = + "Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; at [read \"a\"], string is not exactly number" + "\n\tat [read \"b\"], boolean is not exactly string" + "\n\tat [read \"c\"], number is not exactly boolean"; CHECK(toString(result.errors[0]) == expected); } @@ -4144,10 +4155,7 @@ TEST_CASE_FIXTURE(Fixture, "write_annotations_are_unsupported_even_with_the_new_ TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported") { - ScopedFastFlag sff[] = { - {FFlag::LuauReadWritePropertySyntax, true}, - {FFlag::DebugLuauDeferredConstraintResolution, false} - }; + ScopedFastFlag sff[] = {{FFlag::LuauReadWritePropertySyntax, true}, {FFlag::DebugLuauDeferredConstraintResolution, false}}; CheckResult result = check(R"( type W = {read x: number} @@ -4171,10 +4179,7 @@ TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported TEST_CASE_FIXTURE(Fixture, "read_ond_write_only_indexers_are_unsupported") { - ScopedFastFlag sff[] = { - {FFlag::LuauReadWritePropertySyntax, true}, - {FFlag::DebugLuauDeferredConstraintResolution, false} - }; + ScopedFastFlag sff[] = {{FFlag::LuauReadWritePropertySyntax, true}, {FFlag::DebugLuauDeferredConstraintResolution, false}}; CheckResult result = check(R"( type T = {read [string]: number} @@ -4191,10 +4196,7 @@ TEST_CASE_FIXTURE(Fixture, "read_ond_write_only_indexers_are_unsupported") TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties") { - ScopedFastFlag sff[] = { - {FFlag::LuauReadWritePropertySyntax, true}, - {FFlag::DebugLuauDeferredConstraintResolution, true} - }; + ScopedFastFlag sff[] = {{FFlag::LuauReadWritePropertySyntax, true}, {FFlag::DebugLuauDeferredConstraintResolution, true}}; CheckResult result = check(R"( function oc(player, speaker) @@ -4206,8 +4208,8 @@ TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties") LUAU_REQUIRE_NO_ERRORS(result); CHECK("({{ read Character: t1 }}, { Character: t1 }) -> () " - "where " - "t1 = { read FindFirstChild: (t1, string) -> (a, b...) }" == toString(requireType("oc"))); + "where " + "t1 = { read FindFirstChild: (t1, string) -> (a, b...) }" == toString(requireType("oc"))); } TEST_CASE_FIXTURE(BuiltinsFixture, "tables_can_have_both_metatables_and_indexers") @@ -4359,4 +4361,47 @@ TEST_CASE_FIXTURE(Fixture, "setindexer_always_transmute") CHECK_EQ("(*error-type*) -> ()", toString(requireType("f"))); } +TEST_CASE_FIXTURE(BuiltinsFixture, "instantiated_metatable_frozen_table_clone_mutation") +{ + ScopedFastFlag luauMetatableInstantiationCloneCheck{FFlag::LuauMetatableInstantiationCloneCheck, true}; + + fileResolver.source["game/worker"] = R"( +type WorkerImpl = { + destroy: (self: Worker) -> boolean, +} + +type WorkerProps = { id: number } + +export type Worker = typeof(setmetatable({} :: WorkerProps, {} :: WorkerImpl)) + +return {} + )"; + + fileResolver.source["game/library"] = R"( +local Worker = require(game.worker) + +export type Worker = Worker.Worker + +return {} + )"; + + CheckResult result = frontend.check("game/library"); + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(Fixture, "setindexer_multiple_tables_intersection") +{ + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true}; + + CheckResult result = check(R"( + local function f(t: { [string]: number } & { [thread]: boolean }, x) + local k = "a" + t[k] = x + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + CHECK("({ [string]: number } & { [thread]: boolean }, boolean | number) -> ()" == toString(requireType("f"))); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index fc804265..bc986efc 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -986,7 +986,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_found_this") */ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_found_this_2") { - (void) check(R"( + (void)check(R"( local _ if _ then _ = _ @@ -999,7 +999,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_found_this_2") TEST_CASE_FIXTURE(Fixture, "indexing_a_cyclic_intersection_does_not_crash") { - (void) check(R"( + (void)check(R"( local _ if _ then while nil do diff --git a/tests/TypeInfer.typestates.test.cpp b/tests/TypeInfer.typestates.test.cpp index 0f6ddfa0..dbb9815d 100644 --- a/tests/TypeInfer.typestates.test.cpp +++ b/tests/TypeInfer.typestates.test.cpp @@ -200,7 +200,7 @@ TEST_CASE_FIXTURE(TypeStateFixture, "assignment_swap") CHECK("number" == toString(requireType("b"))); } -TEST_CASE_FIXTURE(TypeStateFixture, "parameter_x_was_constrained_by_two_types") +TEST_CASE_FIXTURE(TypeStateFixture, "parameter_x_was_constrained_by_two_types_2") { CheckResult result = check(R"( local function f(x): number? diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 4b2f029d..5f4d2a0e 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -12,6 +12,25 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); TEST_SUITE_BEGIN("UnionTypes"); +TEST_CASE_FIXTURE(Fixture, "fuzzer_union_with_one_part_assertion") +{ + CheckResult result = check(R"( +local _ = {},nil +repeat + +_,_ = if _.number == "" or _.number or _._ then + _ + elseif _.__index == _._G then + tostring + elseif _ then + _ + else + ``,_._G + +until _._ + )"); +} + TEST_CASE_FIXTURE(Fixture, "return_types_can_be_disjoint") { CheckResult result = check(R"( @@ -572,7 +591,8 @@ TEST_CASE_FIXTURE(Fixture, "indexing_into_a_cyclic_union_doesnt_crash") UnionType u; u.options.push_back(badCyclicUnionTy); - u.options.push_back(arena.addType(TableType{{}, TableIndexer{builtinTypes->numberType, builtinTypes->numberType}, TypeLevel{}, frontend.globals.globalScope.get(), TableState::Sealed})); + u.options.push_back(arena.addType(TableType{ + {}, TableIndexer{builtinTypes->numberType, builtinTypes->numberType}, TypeLevel{}, frontend.globals.globalScope.get(), TableState::Sealed})); asMutable(badCyclicUnionTy)->ty.emplace(std::move(u)); diff --git a/tests/conformance/move.lua b/tests/conformance/move.lua index 27a96ffc..9518219f 100644 --- a/tests/conformance/move.lua +++ b/tests/conformance/move.lua @@ -64,6 +64,39 @@ do a = table.move({[minI] = 100}, minI, minI, maxI) eqT(a, {[minI] = 100, [maxI] = 100}) + + -- moving small amount of elements (array/hash) using a wide range + a = {} + table.move({1, 2, 3, 4, 5}, -100000000, 100000000, -100000000, a) + eqT(a, {1, 2, 3, 4, 5}) + + a = {} + table.move({1, 2}, -100000000, 100000000, 0, a) + eqT(a, {[100000001] = 1, [100000002] = 2}) + + -- hash part copy + a = {} + table.move({[-1000000] = 1, [-100] = 2, [100] = 3, [100000] = 4}, -100000000, 100000000, 0, a) + eqT(a, {[99000000] = 1, [99999900] = 2, [100000100] = 3, [100100000] = 4}) + + -- precise hash part bounds + a = {} + table.move({[-100000000 - 1] = -1, [-100000000] = 1, [-100] = 2, [100] = 3, [100000000] = 4, [100000000 + 1] = -1}, -100000000, 100000000, 0, a) + eqT(a, {[0] = 1, [99999900] = 2, [100000100] = 3, [200000000] = 4}) + + -- no integer undeflow in corner hash part case + a = {} + table.move({[minI] = 100, [-100] = 2}, minI, minI + 100000000, minI, a) + eqT(a, {[minI] = 100}) + + -- hash part skips array slice + a = {} + table.move({[-1] = 1, [0] = 2, [1] = 3, [2] = 4}, -1, 3, 1, a) + eqT(a, {[1] = 1, [2] = 2, [3] = 3, [4] = 4}) + + a = {} + table.move({[-1] = 1, [0] = 2, [1] = 3, [2] = 4, [10] = 5, [100] = 6, [1000] = 7}, -1, 3, 1, a) + eqT(a, {[1] = 1, [2] = 2, [3] = 3, [4] = 4}) end checkerror("too many", table.move, {}, 0, maxI, 1) diff --git a/tools/faillist.txt b/tools/faillist.txt index ea609649..254c3115 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -49,8 +49,6 @@ GenericsTests.bound_tables_do_not_clone_original_fields GenericsTests.correctly_instantiate_polymorphic_member_functions GenericsTests.do_not_always_instantiate_generic_intersection_types GenericsTests.do_not_infer_generic_functions -GenericsTests.dont_leak_generic_types -GenericsTests.dont_leak_inferred_generic_types GenericsTests.dont_substitute_bound_types GenericsTests.error_detailed_function_mismatch_generic_pack GenericsTests.error_detailed_function_mismatch_generic_types @@ -155,7 +153,6 @@ RefinementTest.x_is_not_instance_or_else_not_part TableTests.a_free_shape_can_turn_into_a_scalar_directly TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible -TableTests.accidentally_checked_prop_in_opposite_branch TableTests.any_when_indexing_into_an_unsealed_table_with_no_indexer_in_nonstrict_mode TableTests.array_factory_function TableTests.casting_tables_with_props_into_table_with_indexer2 @@ -395,6 +392,8 @@ TypeInferOperators.UnknownGlobalCompoundAssign TypeInferPrimitives.CheckMethodsOfNumber TypeInferPrimitives.string_index TypeInferUnknownNever.assign_to_local_which_is_never +TypeInferUnknownNever.compare_never +TypeInferUnknownNever.dont_unify_operands_if_one_of_the_operand_is_never_in_any_ordering_operators TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never TypeInferUnknownNever.length_of_never @@ -412,7 +411,6 @@ TypeSingletons.overloaded_function_call_with_singletons_mismatch TypeSingletons.return_type_of_f_is_not_widened TypeSingletons.table_properties_type_error_escapes TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton -TypeStatesTest.prototyped_recursive_functions_but_has_future_assignments TypeStatesTest.typestates_preserve_error_suppression_properties UnionTypes.error_detailed_optional UnionTypes.error_detailed_union_all