From 68bd1b2349e188c374f04e00b0b5de39e18aa5c3 Mon Sep 17 00:00:00 2001 From: aaron Date: Fri, 19 Apr 2024 14:48:02 -0700 Subject: [PATCH] Sync to upstream/release/622 (#1232) # What's changed? * Improved the actual message for the type errors for `cannot call non-function` when attempting to call a union of functions/callable tables. The error now correctly explains the issue is an inability to determine the return type of the call in this situation. * Resolve an issue where tables and metatables were not correctly being cloned during instantiation (fixes #1176). * Refactor `luaM_getnextgcopage` to `luaM_getnextpage` (generally removing `gco` prefix where appropriate). * Optimize `table.move` between tables for large ranges of elements. * Reformat a bunch of code automatically using `clang-format`. ### New Type Solver * Clean up minimally-used or unused constraints in the constraint solver (`InstantiationConstraint`, `SetOpConstraint`, `SingletonOrTopTypeConstraint`). * Add a builtin `singleton` type family to replace `SingletonOrTopTypeConstraint` when inferring refinements. * Fixed a crash involving type path reasoning by recording when type family reduction has taken place in the path. * Improved constraint ordering by blocking on unreduced types families that are not yet proven uninhabitable. * Improved the handling of `SetIndexerConstraints` for both better inference quality and to resolve crashes. * Fix a crash when normalizing cyclic unions of intersections. * Fix a crash when normalizing an intersection with the negation of `unknown`. * Fix a number of crashes caused by missing `follow` calls on `TypeId`s. * Changed type family reduction to correctly use a semantic notion of uninhabited types, rather than checking for `never` types specifically. * Refactor the `union` and `intersect` type families to be variadic. ### Native Code Generation * Improve translation for userdata key get/set and userdata/vector namecall. * Provide `[top level]` and `[anonymous]` as function names to `FunctionStats` as appropriate when no function name is available. * Disable unwind support on Android platforms since it is unsupported. * --- ### Internal Contributors Co-authored-by: Aaron Weiss Co-authored-by: Alexander McCord Co-authored-by: Andy Friesen Co-authored-by: Aviral Goel Co-authored-by: Vighnesh Vijay Co-authored-by: Vyacheslav Egorov --------- Co-authored-by: Alexander McCord Co-authored-by: Andy Friesen Co-authored-by: Vighnesh Co-authored-by: Aviral Goel Co-authored-by: David Cope Co-authored-by: Lily Brown Co-authored-by: Vyacheslav Egorov --- 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 | 2 +- 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, 1403 insertions(+), 752 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 34496ced..2eac525f 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ 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 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