diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index 61253732..2b0fbeb7 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -109,6 +109,21 @@ struct FunctionCheckConstraint NotNull> astExpectedTypes; }; +// table_check expectedType exprType +// +// If `expectedType` is a table type and `exprType` is _also_ a table type, +// propogate the member types of `expectedType` into the types of `exprType`. +// This is used to implement bidirectional inference on table assignment. +// Also see: FunctionCheckConstraint. +struct TableCheckConstraint +{ + TypeId expectedType; + TypeId exprType; + AstExprTable* table = nullptr; + NotNull> astTypes; + NotNull> astExpectedTypes; +}; + // prim FreeType ExpectedType PrimitiveType // // FreeType is bounded below by the singleton type and above by PrimitiveType @@ -273,7 +288,8 @@ using ConstraintV = Variant< UnpackConstraint, ReduceConstraint, ReducePackConstraint, - EqualityConstraint>; + EqualityConstraint, + TableCheckConstraint>; struct Constraint { diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index b8eaac56..0cff0ae4 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -118,6 +118,8 @@ struct ConstraintSolver // A mapping from free types to the number of unresolved constraints that mention them. DenseHashMap unresolvedConstraints{{}}; + std::unordered_map, DenseHashSet> maybeMutatedFreeTypes; + // Irreducible/uninhabited type functions or type pack functions. DenseHashSet uninhabitedTypeFunctions{{}}; @@ -201,6 +203,7 @@ public: bool tryDispatch(const NameConstraint& c, NotNull constraint); bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull constraint); bool tryDispatch(const FunctionCallConstraint& c, NotNull constraint); + bool tryDispatch(const TableCheckConstraint& c, NotNull constraint); bool tryDispatch(const FunctionCheckConstraint& c, NotNull constraint); bool tryDispatch(const PrimitiveTypeConstraint& c, NotNull constraint); bool tryDispatch(const HasPropConstraint& c, NotNull constraint); @@ -421,10 +424,7 @@ public: ToStringOptions opts; - void fillInDiscriminantTypes( - NotNull constraint, - const std::vector>& discriminantTypes - ); + void fillInDiscriminantTypes(NotNull constraint, const std::vector>& discriminantTypes); }; void dump(NotNull rootScope, struct ToStringOptions& opts); diff --git a/Analysis/include/Luau/EqSatSimplificationImpl.h b/Analysis/include/Luau/EqSatSimplificationImpl.h index e021baa8..73019621 100644 --- a/Analysis/include/Luau/EqSatSimplificationImpl.h +++ b/Analysis/include/Luau/EqSatSimplificationImpl.h @@ -105,7 +105,7 @@ private: std::vector storage; }; -template +template using Node = EqSat::Node; using EType = EqSat::Language< diff --git a/Analysis/include/Luau/RequireTracer.h b/Analysis/include/Luau/RequireTracer.h index 718a6cc1..beffaa2e 100644 --- a/Analysis/include/Luau/RequireTracer.h +++ b/Analysis/include/Luau/RequireTracer.h @@ -11,14 +11,12 @@ namespace Luau { -class AstStat; -class AstExpr; +class AstNode; class AstStatBlock; -struct AstLocal; struct RequireTraceResult { - DenseHashMap exprs{nullptr}; + DenseHashMap exprs{nullptr}; std::vector> requireList; }; diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index 5c268f67..890c7078 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -310,7 +310,8 @@ struct MagicFunctionTypeCheckContext struct MagicFunction { - virtual std::optional> handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) = 0; + virtual std::optional> + handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) = 0; // Callback to allow custom typechecking of builtin function calls whose argument types // will only be resolved after constraint solving. For example, the arguments to string.format diff --git a/Analysis/include/Luau/TypeFunctionRuntime.h b/Analysis/include/Luau/TypeFunctionRuntime.h index d715ccd3..5759268d 100644 --- a/Analysis/include/Luau/TypeFunctionRuntime.h +++ b/Analysis/include/Luau/TypeFunctionRuntime.h @@ -3,6 +3,7 @@ #include "Luau/Common.h" #include "Luau/Variant.h" +#include "Luau/TypeFwd.h" #include #include @@ -217,7 +218,9 @@ struct TypeFunctionClassType std::optional parent; - std::string name; + TypeId classTy; + + std::string name_DEPRECATED; }; struct TypeFunctionGenericType diff --git a/Analysis/include/Luau/TypeFunctionRuntimeBuilder.h b/Analysis/include/Luau/TypeFunctionRuntimeBuilder.h index c9e1152f..040a3092 100644 --- a/Analysis/include/Luau/TypeFunctionRuntimeBuilder.h +++ b/Analysis/include/Luau/TypeFunctionRuntimeBuilder.h @@ -32,7 +32,7 @@ struct TypeFunctionRuntimeBuilderState // Invariant: users can not create a new class types -> any class types that get deserialized must have been an argument to the type function // Using this invariant, whenever a ClassType is serialized, we can put it into this map // whenever a ClassType is deserialized, we can use this map to return the corresponding value - DenseHashMap classesSerialized{{}}; + DenseHashMap classesSerialized_DEPRECATED{{}}; // List of errors that occur during serialization/deserialization // At every iteration of serialization/deserialzation, if this list.size() != 0, we halt the process @@ -40,8 +40,6 @@ struct TypeFunctionRuntimeBuilderState TypeFunctionRuntimeBuilderState(NotNull ctx) : ctx(ctx) - , classesSerialized({}) - , errors({}) { } }; diff --git a/Analysis/include/Luau/UnifierSharedState.h b/Analysis/include/Luau/UnifierSharedState.h index eb16b2fa..bc2acbf1 100644 --- a/Analysis/include/Luau/UnifierSharedState.h +++ b/Analysis/include/Luau/UnifierSharedState.h @@ -51,7 +51,6 @@ struct UnifierSharedState UnifierCounters counters; bool reentrantTypeReduction = false; - }; struct TypeReductionRentrancyGuard final diff --git a/Analysis/src/AstJsonEncoder.cpp b/Analysis/src/AstJsonEncoder.cpp index dbc1b5d8..c0a6c254 100644 --- a/Analysis/src/AstJsonEncoder.cpp +++ b/Analysis/src/AstJsonEncoder.cpp @@ -1168,7 +1168,7 @@ struct AstJsonEncoder : public AstVisitor "AstTypeGroup", [&]() { - write("type", node->type); + write("inner", node->type); } ); return false; diff --git a/Analysis/src/AutocompleteCore.cpp b/Analysis/src/AutocompleteCore.cpp index f7f19826..c007ad50 100644 --- a/Analysis/src/AutocompleteCore.cpp +++ b/Analysis/src/AutocompleteCore.cpp @@ -20,7 +20,6 @@ #include LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAGVARIABLE(AutocompleteRequirePathSuggestions2) LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) @@ -1521,12 +1520,9 @@ static std::optional autocompleteStringParams( { for (const std::string& tag : funcType->tags) { - if (FFlag::AutocompleteRequirePathSuggestions2) + if (tag == kRequireTagName && fileResolver) { - if (tag == kRequireTagName && fileResolver) - { - return convertRequireSuggestionsToAutocompleteEntryMap(fileResolver->getRequireSuggestions(module->name, candidateString)); - } + return convertRequireSuggestionsToAutocompleteEntryMap(fileResolver->getRequireSuggestions(module->name, candidateString)); } if (std::optional ret = callback(tag, getMethodContainingClass(module, candidate->func), candidateString)) { diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 756451a7..2a93195f 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -30,7 +30,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression) -LUAU_FASTFLAG(AutocompleteRequirePathSuggestions2) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAGVARIABLE(LuauFreezeIgnorePersistent) @@ -41,68 +40,79 @@ namespace Luau struct MagicSelect final : MagicFunction { - std::optional> handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> + handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicSetMetatable final : MagicFunction { - std::optional> handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> + handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicAssert final : MagicFunction { - std::optional> handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> + handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicPack final : MagicFunction { - std::optional> handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> + handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicRequire final : MagicFunction { - std::optional> handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> + handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicClone final : MagicFunction { - std::optional> handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> + handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicFreeze final : MagicFunction { - std::optional> handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> + handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicFormat final : MagicFunction { - std::optional> handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> + handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; bool infer(const MagicFunctionCallContext& ctx) override; bool typeCheck(const MagicFunctionTypeCheckContext& ctx) override; }; struct MagicMatch final : MagicFunction { - std::optional> handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> + handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicGmatch final : MagicFunction { - std::optional> handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> + handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicFind final : MagicFunction { - std::optional> handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> + handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; bool infer(const MagicFunctionCallContext& ctx) override; }; @@ -454,16 +464,9 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC attachMagicFunction(ttv->props["freeze"].type(), std::make_shared()); } - if (FFlag::AutocompleteRequirePathSuggestions2) - { - TypeId requireTy = getGlobalBinding(globals, "require"); - attachTag(requireTy, kRequireTagName); - attachMagicFunction(requireTy, std::make_shared()); - } - else - { - attachMagicFunction(getGlobalBinding(globals, "require"), std::make_shared()); - } + TypeId requireTy = getGlobalBinding(globals, "require"); + attachTag(requireTy, kRequireTagName); + attachMagicFunction(requireTy, std::make_shared()); } static std::vector parseFormatString(NotNull builtinTypes, const char* data, size_t size) @@ -637,15 +640,15 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context) { switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy)) { - case ErrorSuppression::Suppress: - break; - case ErrorSuppression::NormalizationFailed: - break; - case ErrorSuppression::DoNotSuppress: - Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result); + case ErrorSuppression::Suppress: + break; + case ErrorSuppression::NormalizationFailed: + break; + case ErrorSuppression::DoNotSuppress: + Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result); - if (!reasonings.suppressed) - context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); + if (!reasonings.suppressed) + context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); } } else @@ -1503,7 +1506,8 @@ static std::optional freezeTable(TypeId inputType, const MagicFunctionCa return std::nullopt; } -std::optional> MagicFreeze::handleOldSolver(struct TypeChecker &, const std::shared_ptr &, const class AstExprCall &, WithPredicate) +std::optional> MagicFreeze:: + handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) { return std::nullopt; } diff --git a/Analysis/src/Constraint.cpp b/Analysis/src/Constraint.cpp index b0f7c432..0fe97abc 100644 --- a/Analysis/src/Constraint.cpp +++ b/Analysis/src/Constraint.cpp @@ -146,6 +146,10 @@ DenseHashSet Constraint::getMaybeMutatedFreeTypes() const { rci.traverse(rpc->tp); } + else if (auto tcc = get(*this)) + { + rci.traverse(tcc->exprType); + } return types; } diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index d830fac3..369b1170 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -36,6 +36,7 @@ LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAGVARIABLE(LuauNewSolverPrePopulateClasses) LUAU_FASTFLAGVARIABLE(LuauNewSolverPopulateTableLocations) LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope) +LUAU_FASTFLAGVARIABLE(LuauDeferBidirectionalInferenceForTableAssignment) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments) @@ -2998,30 +2999,46 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, if (expectedType) { - Unifier2 unifier{arena, builtinTypes, NotNull{scope.get()}, ice}; - std::vector toBlock; - // This logic is incomplete as we want to re-run this - // _after_ blocked types have resolved, but this - // allows us to do some bidirectional inference. - toBlock = findBlockedTypesIn(expr, NotNull{&module->astTypes}); - - if (toBlock.empty()) + if (FFlag::LuauDeferBidirectionalInferenceForTableAssignment) { - matchLiteralType( - NotNull{&module->astTypes}, - NotNull{&module->astExpectedTypes}, - builtinTypes, - arena, - NotNull{&unifier}, - *expectedType, - ty, - expr, - toBlock + addConstraint( + scope, + expr->location, + TableCheckConstraint{ + *expectedType, + ty, + expr, + NotNull{&module->astTypes}, + NotNull{&module->astExpectedTypes}, + } ); - // The visitor we ran prior should ensure that there are no - // blocked types that we would encounter while matching on - // this expression. - LUAU_ASSERT(toBlock.empty()); + } + else + { + Unifier2 unifier{arena, builtinTypes, NotNull{scope.get()}, ice}; + std::vector toBlock; + // This logic is incomplete as we want to re-run this + // _after_ blocked types have resolved, but this + // allows us to do some bidirectional inference. + toBlock = findBlockedTypesIn(expr, NotNull{&module->astTypes}); + if (toBlock.empty()) + { + matchLiteralType( + NotNull{&module->astTypes}, + NotNull{&module->astExpectedTypes}, + builtinTypes, + arena, + NotNull{&unifier}, + *expectedType, + ty, + expr, + toBlock + ); + // The visitor we ran prior should ensure that there are no + // blocked types that we would encounter while matching on + // this expression. + LUAU_ASSERT(toBlock.empty()); + } } } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 6f7bd132..b3dc904d 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -37,7 +37,7 @@ LUAU_FASTFLAGVARIABLE(LuauAllowNilAssignmentToIndexer) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAGVARIABLE(LuauAlwaysFillInFunctionCallDiscriminantTypes) LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope) -LUAU_FASTFLAGVARIABLE(LuauPrecalculateMutatedFreeTypes) +LUAU_FASTFLAGVARIABLE(LuauPrecalculateMutatedFreeTypes2) namespace Luau { @@ -355,13 +355,27 @@ ConstraintSolver::ConstraintSolver( { unsolvedConstraints.emplace_back(c); - // initialize the reference counts for the free types in this constraint. - for (auto ty : c->getMaybeMutatedFreeTypes()) + if (FFlag::LuauPrecalculateMutatedFreeTypes2) { - // increment the reference count for `ty` - auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); - refCount += 1; + auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes(); + for (auto ty : maybeMutatedTypesPerConstraint) + { + auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); + refCount += 1; + } + maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint); } + else + { + // initialize the reference counts for the free types in this constraint. + for (auto ty : c->getMaybeMutatedFreeTypes()) + { + // increment the reference count for `ty` + auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); + refCount += 1; + } + } + for (NotNull dep : c->dependencies) { @@ -439,10 +453,6 @@ void ConstraintSolver::run() snapshot = logger->prepareStepSnapshot(rootScope, c, force, unsolvedConstraints); } - std::optional> mutatedFreeTypes = std::nullopt; - if (FFlag::LuauPrecalculateMutatedFreeTypes) - mutatedFreeTypes = c->getMaybeMutatedFreeTypes(); - bool success = tryDispatch(c, force); progress |= success; @@ -452,23 +462,29 @@ void ConstraintSolver::run() unblock(c); unsolvedConstraints.erase(unsolvedConstraints.begin() + ptrdiff_t(i)); - if (FFlag::LuauPrecalculateMutatedFreeTypes) + if (FFlag::LuauPrecalculateMutatedFreeTypes2) { - for (auto ty : c->getMaybeMutatedFreeTypes()) - mutatedFreeTypes->insert(ty); - for (auto ty : *mutatedFreeTypes) + const auto maybeMutated = maybeMutatedFreeTypes.find(c); + if (maybeMutated != maybeMutatedFreeTypes.end()) { - size_t& refCount = unresolvedConstraints[ty]; - if (refCount > 0) - refCount -= 1; + for (auto ty : maybeMutated->second) + { + // There is a high chance that this type has been rebound + // across blocked types, rebound free types, pending + // expansion types, etc, so we need to follow it. + ty = follow(ty); + size_t& refCount = unresolvedConstraints[ty]; + if (refCount > 0) + refCount -= 1; - // We have two constraints that are designed to wait for the - // refCount on a free type to be equal to 1: the - // PrimitiveTypeConstraint and ReduceConstraint. We - // therefore wake any constraint waiting for a free type's - // refcount to be 1 or 0. - if (refCount <= 1) - unblock(ty, Location{}); + // We have two constraints that are designed to wait for the + // refCount on a free type to be equal to 1: the + // PrimitiveTypeConstraint and ReduceConstraint. We + // therefore wake any constraint waiting for a free type's + // refcount to be 1 or 0. + if (refCount <= 1) + unblock(ty, Location{}); + } } } else @@ -668,6 +684,8 @@ bool ConstraintSolver::tryDispatch(NotNull constraint, bool fo success = tryDispatch(*fcc, constraint); else if (auto fcc = get(*constraint)) success = tryDispatch(*fcc, constraint); + else if (auto tcc = get(*constraint)) + success = tryDispatch(*tcc, constraint); else if (auto fcc = get(*constraint)) success = tryDispatch(*fcc, constraint); else if (auto hpc = get(*constraint)) @@ -1170,10 +1188,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul return true; } -void ConstraintSolver::fillInDiscriminantTypes( - NotNull constraint, - const std::vector>& discriminantTypes -) +void ConstraintSolver::fillInDiscriminantTypes(NotNull constraint, const std::vector>& discriminantTypes) { for (std::optional ty : discriminantTypes) { @@ -1521,6 +1536,28 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull constraint) +{ + // This is expensive as we need to traverse a (potentially large) + // literal up front in order to determine if there are any blocked + // types, otherwise we may run `matchTypeLiteral` multiple times, + // which right now may fail due to being non-idempotent (it + // destructively updates the underlying literal type). + auto blockedTypes = findBlockedTypesIn(c.table, c.astTypes); + for (const auto ty : blockedTypes) + { + block(ty, constraint); + } + if (!blockedTypes.empty()) + return false; + + Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}}; + std::vector toBlock; + (void)matchLiteralType(c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, c.expectedType, c.exprType, c.table, toBlock); + LUAU_ASSERT(toBlock.empty()); + return true; +} + bool ConstraintSolver::tryDispatch(const PrimitiveTypeConstraint& c, NotNull constraint) { std::optional expectedType = c.expectedType ? std::make_optional(follow(*c.expectedType)) : std::nullopt; diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index 595e4905..bbcf4841 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -3,6 +3,7 @@ LUAU_FASTFLAG(LuauBufferBitMethods2) LUAU_FASTFLAG(LuauVector2Constructor) +LUAU_FASTFLAGVARIABLE(LuauDebugInfoDefn) namespace Luau { @@ -209,6 +210,15 @@ declare table: { static const std::string kBuiltinDefinitionDebugSrc = R"BUILTIN_SRC( +declare debug: { + info: ((thread: thread, level: number, options: string) -> ...any) & ((level: number, options: string) -> ...any) & ((func: (A...) -> R1..., options: string) -> ...any), + traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string), +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionDebugSrc_DEPRECATED = R"BUILTIN_SRC( + declare debug: { info: ((thread: thread, level: number, options: string) -> R...) & ((level: number, options: string) -> R...) & ((func: (A...) -> R1..., options: string) -> R2...), traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string), @@ -362,7 +372,7 @@ std::string getBuiltinDefinitionSource() result += kBuiltinDefinitionOsSrc; result += kBuiltinDefinitionCoroutineSrc; result += kBuiltinDefinitionTableSrc; - result += kBuiltinDefinitionDebugSrc; + result += FFlag::LuauDebugInfoDefn ? kBuiltinDefinitionDebugSrc : kBuiltinDefinitionDebugSrc_DEPRECATED; result += kBuiltinDefinitionUtf8Src; result += FFlag::LuauBufferBitMethods2 ? kBuiltinDefinitionBufferSrc : kBuiltinDefinitionBufferSrc_DEPRECATED; diff --git a/Analysis/src/EqSatSimplification.cpp b/Analysis/src/EqSatSimplification.cpp index 5927c773..edcc42fb 100644 --- a/Analysis/src/EqSatSimplification.cpp +++ b/Analysis/src/EqSatSimplification.cpp @@ -396,7 +396,8 @@ Id toId( { LUAU_ASSERT(tfun->packArguments.empty()); - if (tfun->userFuncName) { + if (tfun->userFuncName) + { // TODO: User defined type functions are pseudo-effectful: error // reporting is done via the `print` statement, so running a // UDTF multiple times may end up double erroring. egraphs diff --git a/Analysis/src/FragmentAutocomplete.cpp b/Analysis/src/FragmentAutocomplete.cpp index c864b836..90ecc15c 100644 --- a/Analysis/src/FragmentAutocomplete.cpp +++ b/Analysis/src/FragmentAutocomplete.cpp @@ -30,7 +30,6 @@ LUAU_FASTFLAG(LuauAllowFragmentParsing); LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete) LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteBugfixes) -LUAU_FASTFLAG(LuauReferenceAllocatorInNewSolver) LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf) LUAU_FASTFLAG(LuauBetterReverseDependencyTracking) LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule) @@ -596,7 +595,7 @@ std::pair typecheckFragment( return {}; } - if (FFlag::LuauIncrementalAutocompleteBugfixes && FFlag::LuauReferenceAllocatorInNewSolver) + if (FFlag::LuauIncrementalAutocompleteBugfixes) { if (sourceModule->allocator.get() != module->allocator.get()) { diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 0292726b..19740804 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -52,7 +52,6 @@ LUAU_FASTFLAGVARIABLE(LuauBetterReverseDependencyTracking) LUAU_FASTFLAG(StudioReportLuauAny2) LUAU_FASTFLAGVARIABLE(LuauStoreSolverTypeOnModule) -LUAU_FASTFLAGVARIABLE(LuauReferenceAllocatorInNewSolver) LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena) namespace Luau @@ -1430,11 +1429,8 @@ ModulePtr check( result->mode = mode; result->internalTypes.owningModule = result.get(); result->interfaceTypes.owningModule = result.get(); - if (FFlag::LuauReferenceAllocatorInNewSolver) - { - result->allocator = sourceModule.allocator; - result->names = sourceModule.names; - } + result->allocator = sourceModule.allocator; + result->names = sourceModule.names; iceHandler->moduleName = sourceModule.name; @@ -1751,7 +1747,7 @@ std::pair Frontend::getSourceNode(const ModuleName& if (FFlag::LuauBetterReverseDependencyTracking) { - // clear all prior dependents. we will re-add them after parsing the rest of the graph + // clear all prior dependents. we will re-add them after parsing the rest of the graph for (const auto& [moduleName, _] : sourceNode->requireLocations) { if (auto depIt = sourceNodes.find(moduleName); depIt != sourceNodes.end()) diff --git a/Analysis/src/Generalization.cpp b/Analysis/src/Generalization.cpp index ceffc307..054ad509 100644 --- a/Analysis/src/Generalization.cpp +++ b/Analysis/src/Generalization.cpp @@ -2,6 +2,8 @@ #include "Luau/Generalization.h" +#include "Luau/Common.h" +#include "Luau/DenseHash.h" #include "Luau/Scope.h" #include "Luau/Type.h" #include "Luau/ToString.h" @@ -10,7 +12,7 @@ #include "Luau/VisitType.h" LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete) -LUAU_FASTFLAGVARIABLE(LuauGeneralizationRemoveRecursiveUpperBound) +LUAU_FASTFLAGVARIABLE(LuauGeneralizationRemoveRecursiveUpperBound2) namespace Luau { @@ -50,7 +52,7 @@ struct MutatingGeneralizer : TypeOnceVisitor { } - static void replace(DenseHashSet& seen, TypeId haystack, TypeId needle, TypeId replacement) + void replace(DenseHashSet& seen, TypeId haystack, TypeId needle, TypeId replacement) { haystack = follow(haystack); @@ -97,6 +99,10 @@ struct MutatingGeneralizer : TypeOnceVisitor LUAU_ASSERT(onlyType != haystack); emplaceType(asMutable(haystack), onlyType); } + else if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound2 && ut->options.empty()) + { + emplaceType(asMutable(haystack), builtinTypes->neverType); + } return; } @@ -139,6 +145,10 @@ struct MutatingGeneralizer : TypeOnceVisitor TypeId onlyType = it->parts[0]; LUAU_ASSERT(onlyType != needle); emplaceType(asMutable(needle), onlyType); + } + else if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound2 && it->parts.empty()) + { + emplaceType(asMutable(needle), builtinTypes->unknownType); } return; @@ -233,53 +243,6 @@ struct MutatingGeneralizer : TypeOnceVisitor else { TypeId ub = follow(ft->upperBound); - if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound) - { - - // If the upper bound is a union type or an intersection type, - // and one of it's members is the free type we're - // generalizing, don't include it in the upper bound. For a - // free type such as: - // - // t1 where t1 = D <: 'a <: (A | B | C | t1) - // - // Naively replacing it with it's upper bound creates: - // - // t1 where t1 = A | B | C | t1 - // - // It makes sense to just optimize this and exclude the - // recursive component by semantic subtyping rules. - - if (auto itv = get(ub)) - { - std::vector newIds; - newIds.reserve(itv->parts.size()); - for (auto part : itv) - { - if (part != ty) - newIds.push_back(part); - } - if (newIds.size() == 1) - ub = newIds[0]; - else if (newIds.size() > 0) - ub = arena->addType(IntersectionType{std::move(newIds)}); - } - else if (auto utv = get(ub)) - { - std::vector newIds; - newIds.reserve(utv->options.size()); - for (auto part : utv) - { - if (part != ty) - newIds.push_back(part); - } - if (newIds.size() == 1) - ub = newIds[0]; - else if (newIds.size() > 0) - ub = arena->addType(UnionType{std::move(newIds)}); - } - } - if (FreeType* upperFree = getMutable(ub); upperFree && upperFree->lowerBound == ty) upperFree->lowerBound = builtinTypes->neverType; else diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 9aa6fb97..864c12a8 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -2296,7 +2296,7 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th // // Dog & ~Animal // - // Clearly this intersects to never, so we mark this class as + // Clearly this intersects to never, so we mark this class as // being removed from the normalized class type. emptyIntersectWithNegation = true; break; diff --git a/Analysis/src/RequireTracer.cpp b/Analysis/src/RequireTracer.cpp index c036a7a5..95c1a344 100644 --- a/Analysis/src/RequireTracer.cpp +++ b/Analysis/src/RequireTracer.cpp @@ -4,6 +4,8 @@ #include "Luau/Ast.h" #include "Luau/Module.h" +LUAU_FASTFLAGVARIABLE(LuauExtendedSimpleRequire) + namespace Luau { @@ -65,7 +67,7 @@ struct RequireTracer : AstVisitor return true; } - AstExpr* getDependent(AstExpr* node) + AstExpr* getDependent_DEPRECATED(AstExpr* node) { if (AstExprLocal* expr = node->as()) return locals[expr->local]; @@ -78,50 +80,122 @@ struct RequireTracer : AstVisitor else return nullptr; } + AstNode* getDependent(AstNode* node) + { + if (AstExprLocal* expr = node->as()) + return locals[expr->local]; + else if (AstExprIndexName* expr = node->as()) + return expr->expr; + else if (AstExprIndexExpr* expr = node->as()) + return expr->expr; + else if (AstExprCall* expr = node->as(); expr && expr->self) + return expr->func->as()->expr; + else if (AstExprGroup* expr = node->as()) + return expr->expr; + else if (AstExprTypeAssertion* expr = node->as()) + return expr->annotation; + else if (AstTypeGroup* expr = node->as()) + return expr->type; + else if (AstTypeTypeof* expr = node->as()) + return expr->expr; + else + return nullptr; + } void process() { ModuleInfo moduleContext{currentModuleName}; - // seed worklist with require arguments - work.reserve(requireCalls.size()); - - for (AstExprCall* require : requireCalls) - work.push_back(require->args.data[0]); - - // push all dependent expressions to the work stack; note that the vector is modified during traversal - for (size_t i = 0; i < work.size(); ++i) - if (AstExpr* dep = getDependent(work[i])) - work.push_back(dep); - - // resolve all expressions to a module info - for (size_t i = work.size(); i > 0; --i) + if (FFlag::LuauExtendedSimpleRequire) { - AstExpr* expr = work[i - 1]; + // seed worklist with require arguments + work.reserve(requireCalls.size()); - // when multiple expressions depend on the same one we push it to work queue multiple times - if (result.exprs.contains(expr)) - continue; + for (AstExprCall* require : requireCalls) + work.push_back(require->args.data[0]); - std::optional info; - - if (AstExpr* dep = getDependent(expr)) + // push all dependent expressions to the work stack; note that the vector is modified during traversal + for (size_t i = 0; i < work.size(); ++i) { - const ModuleInfo* context = result.exprs.find(dep); + if (AstNode* dep = getDependent(work[i])) + work.push_back(dep); + } - // locals just inherit their dependent context, no resolution required - if (expr->is()) - info = context ? std::optional(*context) : std::nullopt; + // resolve all expressions to a module info + for (size_t i = work.size(); i > 0; --i) + { + AstNode* expr = work[i - 1]; + + // when multiple expressions depend on the same one we push it to work queue multiple times + if (result.exprs.contains(expr)) + continue; + + std::optional info; + + if (AstNode* dep = getDependent(expr)) + { + const ModuleInfo* context = result.exprs.find(dep); + + if (context && expr->is()) + info = *context; // locals just inherit their dependent context, no resolution required + else if (context && (expr->is() || expr->is())) + info = *context; // simple group nodes propagate their value + else if (context && (expr->is() || expr->is())) + info = *context; // typeof type annotations will resolve to the typeof content + else if (AstExpr* asExpr = expr->asExpr()) + info = fileResolver->resolveModule(context, asExpr); + } + else if (AstExpr* asExpr = expr->asExpr()) + { + info = fileResolver->resolveModule(&moduleContext, asExpr); + } + + if (info) + result.exprs[expr] = std::move(*info); + } + } + else + { + // seed worklist with require arguments + work_DEPRECATED.reserve(requireCalls.size()); + + for (AstExprCall* require : requireCalls) + work_DEPRECATED.push_back(require->args.data[0]); + + // push all dependent expressions to the work stack; note that the vector is modified during traversal + for (size_t i = 0; i < work_DEPRECATED.size(); ++i) + if (AstExpr* dep = getDependent_DEPRECATED(work_DEPRECATED[i])) + work_DEPRECATED.push_back(dep); + + // resolve all expressions to a module info + for (size_t i = work_DEPRECATED.size(); i > 0; --i) + { + AstExpr* expr = work_DEPRECATED[i - 1]; + + // when multiple expressions depend on the same one we push it to work queue multiple times + if (result.exprs.contains(expr)) + continue; + + std::optional info; + + if (AstExpr* dep = getDependent_DEPRECATED(expr)) + { + const ModuleInfo* context = result.exprs.find(dep); + + // locals just inherit their dependent context, no resolution required + if (expr->is()) + info = context ? std::optional(*context) : std::nullopt; + else + info = fileResolver->resolveModule(context, expr); + } else - info = fileResolver->resolveModule(context, expr); - } - else - { - info = fileResolver->resolveModule(&moduleContext, expr); - } + { + info = fileResolver->resolveModule(&moduleContext, expr); + } - if (info) - result.exprs[expr] = std::move(*info); + if (info) + result.exprs[expr] = std::move(*info); + } } // resolve all requires according to their argument @@ -150,7 +224,8 @@ struct RequireTracer : AstVisitor ModuleName currentModuleName; DenseHashMap locals; - std::vector work; + std::vector work_DEPRECATED; + std::vector work; std::vector requireCalls; }; diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index e4985a02..a4f2ce1e 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -22,6 +22,7 @@ #include LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) +LUAU_FASTFLAGVARIABLE(LuauSubtypingFixTailPack) namespace Luau { @@ -858,7 +859,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId else return SubtypingResult{false} .withSuperComponent(TypePath::PackField::Tail) - .withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}}); + .withError({scope->location, UnexpectedTypePackInSubtyping{FFlag::LuauSubtypingFixTailPack ? *superTail : *subTail}}); } else return {false}; diff --git a/Analysis/src/TableLiteralInference.cpp b/Analysis/src/TableLiteralInference.cpp index d5a4a804..e5d8be04 100644 --- a/Analysis/src/TableLiteralInference.cpp +++ b/Analysis/src/TableLiteralInference.cpp @@ -408,7 +408,7 @@ TypeId matchLiteralType( if (FFlag::LuauDontInPlaceMutateTableType) { - for (const auto& key: keysToDelete) + for (const auto& key : keysToDelete) { const AstArray& s = key->value; std::string keyStr{s.data, s.data + s.size}; diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 96314ea1..91ec3edc 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -1865,6 +1865,8 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts) } else if constexpr (std::is_same_v) return "equality: " + tos(c.resultType) + " ~ " + tos(c.assignmentType); + else if constexpr (std::is_same_v) + return "table_check " + tos(c.expectedType) + " :> " + tos(c.exprType); else static_assert(always_false_v, "Non-exhaustive constraint switch"); }; diff --git a/Analysis/src/Transpiler.cpp b/Analysis/src/Transpiler.cpp index 4ea8a5d4..218b269a 100644 --- a/Analysis/src/Transpiler.cpp +++ b/Analysis/src/Transpiler.cpp @@ -13,6 +13,7 @@ LUAU_FASTFLAG(LuauStoreCSTData) LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon) LUAU_FASTFLAG(LuauAstTypeGroup) +LUAU_FASTFLAG(LuauFixDoBlockEndLocation) namespace { @@ -666,7 +667,8 @@ struct Printer_DEPRECATED writer.keyword("do"); for (const auto& s : block->body) visualize(*s); - writer.advance(block->location.end); + if (!FFlag::LuauFixDoBlockEndLocation) + writer.advance(block->location.end); writeEnd(program.location); } else if (const auto& a = program.as()) @@ -2036,15 +2038,23 @@ struct Printer { if (writeTypes) { + const auto* cstNode = lookupCstNode(a); + if (a->exported) writer.keyword("export"); + if (cstNode) + advance(cstNode->typeKeywordPosition); + writer.keyword("type"); + advance(a->nameLocation.begin); writer.identifier(a->name.value); if (a->generics.size > 0 || a->genericPacks.size > 0) { + if (cstNode) + advance(cstNode->genericsOpenPosition); writer.symbol("<"); - CommaSeparatorInserter comma(writer); + CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr); for (auto o : a->generics) { @@ -2055,7 +2065,15 @@ struct Printer if (o->defaultValue) { - writer.maybeSpace(o->defaultValue->location.begin, 2); + const auto* genericTypeCstNode = lookupCstNode(o); + + if (genericTypeCstNode) + { + LUAU_ASSERT(genericTypeCstNode->defaultEqualsPosition.has_value()); + advance(*genericTypeCstNode->defaultEqualsPosition); + } + else + writer.maybeSpace(o->defaultValue->location.begin, 2); writer.symbol("="); visualizeTypeAnnotation(*o->defaultValue); } @@ -2065,21 +2083,36 @@ struct Printer { comma(); + const auto* genericTypePackCstNode = lookupCstNode(o); + writer.advance(o->location.begin); writer.identifier(o->name.value); + if (genericTypePackCstNode) + advance(genericTypePackCstNode->ellipsisPosition); writer.symbol("..."); if (o->defaultValue) { - writer.maybeSpace(o->defaultValue->location.begin, 2); + if (cstNode) + { + LUAU_ASSERT(genericTypePackCstNode->defaultEqualsPosition.has_value()); + advance(*genericTypePackCstNode->defaultEqualsPosition); + } + else + writer.maybeSpace(o->defaultValue->location.begin, 2); writer.symbol("="); visualizeTypePackAnnotation(*o->defaultValue, false); } } + if (cstNode) + advance(cstNode->genericsClosePosition); writer.symbol(">"); } - writer.maybeSpace(a->type->location.begin, 2); + if (cstNode) + advance(cstNode->equalsPosition); + else + writer.maybeSpace(a->type->location.begin, 2); writer.symbol("="); visualizeTypeAnnotation(*a->type); } diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index 5a2e77a5..a258f3ab 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -489,7 +489,6 @@ static FunctionGraphReductionResult reduceFunctionsInternal( return std::move(reducer.result); } - } FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext ctx, bool force) @@ -744,11 +743,9 @@ TypeFunctionReductionResult userDefinedTypeFunction( resetTypeFunctionState(L); - // Push serialized arguments onto the stack - - // Since there aren't any new class types being created in type functions, there isn't a deserialization function - // class types. Instead, we can keep this map and return the mapping as the "deserialized value" std::unique_ptr runtimeBuilder = std::make_unique(ctx); + + // Push serialized arguments onto the stack for (auto typeParam : typeParams) { TypeId ty = follow(typeParam); @@ -2839,9 +2836,9 @@ TypeFunctionReductionResult setmetatableTypeFunction( return {std::nullopt, Reduction::Erroneous, {}, {}}; // we're trying to reject any type that has not normalized to a table or a union/intersection of tables. - if (targetNorm->hasTops() || targetNorm->hasBooleans() || targetNorm->hasErrors() || targetNorm->hasNils() || - targetNorm->hasNumbers() || targetNorm->hasStrings() || targetNorm->hasThreads() || targetNorm->hasBuffers() || - targetNorm->hasFunctions() || targetNorm->hasTyvars() || targetNorm->hasClasses()) + if (targetNorm->hasTops() || targetNorm->hasBooleans() || targetNorm->hasErrors() || targetNorm->hasNils() || targetNorm->hasNumbers() || + targetNorm->hasStrings() || targetNorm->hasThreads() || targetNorm->hasBuffers() || targetNorm->hasFunctions() || targetNorm->hasTyvars() || + targetNorm->hasClasses()) return {std::nullopt, Reduction::Erroneous, {}, {}}; // if the supposed metatable is not a table, we will fail to reduce. @@ -2899,11 +2896,7 @@ TypeFunctionReductionResult setmetatableTypeFunction( return {result, Reduction::MaybeOk, {}, {}}; } -static TypeFunctionReductionResult getmetatableHelper( - TypeId targetTy, - const Location& location, - NotNull ctx -) +static TypeFunctionReductionResult getmetatableHelper(TypeId targetTy, const Location& location, NotNull ctx) { targetTy = follow(targetTy); diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index e36a53c0..6ba5f261 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -13,7 +13,9 @@ #include #include +LUAU_FASTFLAGVARIABLE(LuauTypeFunFixHydratedClasses) LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit) +LUAU_FASTFLAGVARIABLE(LuauTypeFunSingletonEquality) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypeofReturnsType) LUAU_FASTFLAGVARIABLE(LuauTypeFunPrintFix) @@ -1566,7 +1568,7 @@ void registerTypeUserData(lua_State* L) // Create and register metatable for type userdata luaL_newmetatable(L, "type"); - + if (FFlag::LuauUserTypeFunTypeofReturnsType) { lua_pushstring(L, "type"); @@ -1708,14 +1710,14 @@ bool areEqual(SeenSet& seen, const TypeFunctionSingletonType& lhs, const TypeFun { const TypeFunctionBooleanSingleton* lp = get(&lhs); - const TypeFunctionBooleanSingleton* rp = get(&lhs); + const TypeFunctionBooleanSingleton* rp = get(FFlag::LuauTypeFunSingletonEquality ? &rhs : &lhs); if (lp && rp) return lp->value == rp->value; } { const TypeFunctionStringSingleton* lp = get(&lhs); - const TypeFunctionStringSingleton* rp = get(&lhs); + const TypeFunctionStringSingleton* rp = get(FFlag::LuauTypeFunSingletonEquality ? &rhs : &lhs); if (lp && rp) return lp->value == rp->value; } @@ -1868,7 +1870,10 @@ bool areEqual(SeenSet& seen, const TypeFunctionClassType& lhs, const TypeFunctio if (seenSetContains(seen, &lhs, &rhs)) return true; - return lhs.name == rhs.name; + if (FFlag::LuauTypeFunFixHydratedClasses) + return lhs.classTy == rhs.classTy; + else + return lhs.name_DEPRECATED == rhs.name_DEPRECATED; } bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType& rhs) diff --git a/Analysis/src/TypeFunctionRuntimeBuilder.cpp b/Analysis/src/TypeFunctionRuntimeBuilder.cpp index e9d8e41f..fa2aab9e 100644 --- a/Analysis/src/TypeFunctionRuntimeBuilder.cpp +++ b/Analysis/src/TypeFunctionRuntimeBuilder.cpp @@ -19,6 +19,7 @@ // used to control the recursion limit of any operations done by user-defined type functions // currently, controls serialization, deserialization, and `type.copy` LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000); +LUAU_FASTFLAG(LuauTypeFunFixHydratedClasses) namespace Luau { @@ -207,8 +208,19 @@ private: } else if (auto c = get(ty)) { - state->classesSerialized[c->name] = ty; - target = typeFunctionRuntime->typeArena.allocate(TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, c->name}); + if (FFlag::LuauTypeFunFixHydratedClasses) + { + // Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the + // original class + target = typeFunctionRuntime->typeArena.allocate(TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, ty}); + } + else + { + state->classesSerialized_DEPRECATED[c->name] = ty; + target = typeFunctionRuntime->typeArena.allocate( + TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, /* classTy */ nullptr, c->name} + ); + } } else if (auto g = get(ty)) { @@ -687,10 +699,17 @@ private: } else if (auto c = get(ty)) { - if (auto result = state->classesSerialized.find(c->name)) - target = *result; + if (FFlag::LuauTypeFunFixHydratedClasses) + { + target = c->classTy; + } else - state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious class type is being deserialized"); + { + if (auto result = state->classesSerialized_DEPRECATED.find(c->name_DEPRECATED)) + target = *result; + else + state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious class type is being deserialized"); + } } else if (auto g = get(ty)) { diff --git a/Ast/include/Luau/Allocator.h b/Ast/include/Luau/Allocator.h index eaabcd8a..bd7d423f 100644 --- a/Ast/include/Luau/Allocator.h +++ b/Ast/include/Luau/Allocator.h @@ -38,7 +38,7 @@ private: { Page* next; - char data[8192]; + alignas(8) char data[8192]; }; Page* root; diff --git a/Ast/include/Luau/Cst.h b/Ast/include/Luau/Cst.h index af3198cc..95211f14 100644 --- a/Ast/include/Luau/Cst.h +++ b/Ast/include/Luau/Cst.h @@ -270,6 +270,47 @@ public: Position functionKeywordPosition; }; +class CstGenericType : public CstNode +{ +public: + LUAU_CST_RTTI(CstGenericType) + + CstGenericType(std::optional defaultEqualsPosition); + + std::optional defaultEqualsPosition; +}; + +class CstGenericTypePack : public CstNode +{ +public: + LUAU_CST_RTTI(CstGenericTypePack) + + CstGenericTypePack(Position ellipsisPosition, std::optional defaultEqualsPosition); + + Position ellipsisPosition; + std::optional defaultEqualsPosition; +}; + +class CstStatTypeAlias : public CstNode +{ +public: + LUAU_CST_RTTI(CstStatTypeAlias) + + CstStatTypeAlias( + Position typeKeywordPosition, + Position genericsOpenPosition, + AstArray genericsCommaPositions, + Position genericsClosePosition, + Position equalsPosition + ); + + Position typeKeywordPosition; + Position genericsOpenPosition; + AstArray genericsCommaPositions; + Position genericsClosePosition; + Position equalsPosition; +}; + class CstTypeReference : public CstNode { public: diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index 8c7fac74..bd3cdcda 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -144,7 +144,7 @@ private: AstStat* parseReturn(); // type Name `=' Type - AstStat* parseTypeAlias(const Location& start, bool exported); + AstStat* parseTypeAlias(const Location& start, bool exported, Position typeKeywordPosition); // type function Name ... end AstStat* parseTypeFunction(const Location& start, bool exported); @@ -294,7 +294,12 @@ private: Name parseIndexName(const char* context, const Position& previous); // `<' namelist `>' - std::pair, AstArray> parseGenericTypeList(bool withDefaultValues); + std::pair, AstArray> parseGenericTypeList( + bool withDefaultValues, + Position* openPosition = nullptr, + TempVector* commaPositions = nullptr, + Position* closePosition = nullptr + ); // `<' Type[, ...] `>' AstArray parseTypeParams( diff --git a/Ast/src/Cst.cpp b/Ast/src/Cst.cpp index 9f951ef2..0d1b8352 100644 --- a/Ast/src/Cst.cpp +++ b/Ast/src/Cst.cpp @@ -111,11 +111,7 @@ CstStatForIn::CstStatForIn(AstArray varsCommaPositions, AstArray varsCommaPositions, - Position equalsPosition, - AstArray valuesCommaPositions -) +CstStatAssign::CstStatAssign(AstArray varsCommaPositions, Position equalsPosition, AstArray valuesCommaPositions) : CstNode(CstClassIndex()) , varsCommaPositions(varsCommaPositions) , equalsPosition(equalsPosition) @@ -135,6 +131,35 @@ CstStatLocalFunction::CstStatLocalFunction(Position functionKeywordPosition) { } +CstGenericType::CstGenericType(std::optional defaultEqualsPosition) + : CstNode(CstClassIndex()) + , defaultEqualsPosition(defaultEqualsPosition) +{ +} + +CstGenericTypePack::CstGenericTypePack(Position ellipsisPosition, std::optional defaultEqualsPosition) + : CstNode(CstClassIndex()) + , ellipsisPosition(ellipsisPosition) + , defaultEqualsPosition(defaultEqualsPosition) +{ +} + +CstStatTypeAlias::CstStatTypeAlias( + Position typeKeywordPosition, + Position genericsOpenPosition, + AstArray genericsCommaPositions, + Position genericsClosePosition, + Position equalsPosition +) + : CstNode(CstClassIndex()) + , typeKeywordPosition(typeKeywordPosition) + , genericsOpenPosition(genericsOpenPosition) + , genericsCommaPositions(genericsCommaPositions) + , genericsClosePosition(genericsClosePosition) + , equalsPosition(equalsPosition) +{ +} + CstTypeReference::CstTypeReference( std::optional prefixPointPosition, Position openParametersPosition, diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 1fce2216..1165a38c 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -28,6 +28,7 @@ LUAU_FASTFLAGVARIABLE(LuauStoreCSTData) LUAU_FASTFLAGVARIABLE(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup) LUAU_FASTFLAGVARIABLE(ParserNoErrorLimit) +LUAU_FASTFLAGVARIABLE(LuauFixDoBlockEndLocation) namespace Luau { @@ -374,12 +375,13 @@ AstStat* Parser::parseStat() AstName ident = getIdentifier(expr); if (ident == "type") - return parseTypeAlias(expr->location, /* exported= */ false); + return parseTypeAlias(expr->location, /* exported= */ false, expr->location.begin); if (ident == "export" && lexer.current().type == Lexeme::Name && AstName(lexer.current().name) == "type") { + Position typeKeywordPosition = lexer.current().location.begin; nextLexeme(); - return parseTypeAlias(expr->location, /* exported= */ true); + return parseTypeAlias(expr->location, /* exported= */ true, typeKeywordPosition); } if (ident == "continue") @@ -534,11 +536,13 @@ AstStat* Parser::parseDo() body->location.begin = start.begin; - Position endPosition = lexer.current().location.begin; + Location endLocation = lexer.current().location; body->hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo); + if (FFlag::LuauFixDoBlockEndLocation && body->hasEnd) + body->location.end = endLocation.end; if (FFlag::LuauStoreCSTData && options.storeCstData) - cstNodeMap[body] = allocator.alloc(endPosition); + cstNodeMap[body] = allocator.alloc(endLocation.begin); return body; } @@ -721,7 +725,12 @@ AstExpr* Parser::parseFunctionName(Location start_DEPRECATED, bool& hasself, Ast debugname = name.name; expr = allocator.alloc( - Location(FFlag::LuauFixFunctionNameStartPosition ? expr->location : start_DEPRECATED, name.location), expr, name.name, name.location, opPosition, '.' + Location(FFlag::LuauFixFunctionNameStartPosition ? expr->location : start_DEPRECATED, name.location), + expr, + name.name, + name.location, + opPosition, + '.' ); // note: while the parser isn't recursive here, we're generating recursive structures of unbounded depth @@ -742,7 +751,12 @@ AstExpr* Parser::parseFunctionName(Location start_DEPRECATED, bool& hasself, Ast debugname = name.name; expr = allocator.alloc( - Location(FFlag::LuauFixFunctionNameStartPosition ? expr->location : start_DEPRECATED, name.location), expr, name.name, name.location, opPosition, ':' + Location(FFlag::LuauFixFunctionNameStartPosition ? expr->location : start_DEPRECATED, name.location), + expr, + name.name, + name.location, + opPosition, + ':' ); hasself = true; @@ -1007,7 +1021,7 @@ AstStat* Parser::parseReturn() } // type Name [`<' varlist `>'] `=' Type -AstStat* Parser::parseTypeAlias(const Location& start, bool exported) +AstStat* Parser::parseTypeAlias(const Location& start, bool exported, Position typeKeywordPosition) { // parsing a type function if (lexer.current().type == Lexeme::ReservedFunction) @@ -1023,13 +1037,34 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported) if (!name) name = Name(nameError, lexer.current().location); - auto [generics, genericPacks] = parseGenericTypeList(/* withDefaultValues= */ true); + Position genericsOpenPosition{0, 0}; + TempVector genericsCommaPositions(scratchPosition); + Position genericsClosePosition{0, 0}; + auto [generics, genericPacks] = FFlag::LuauStoreCSTData && options.storeCstData + ? parseGenericTypeList( + /* withDefaultValues= */ true, &genericsOpenPosition, &genericsCommaPositions, &genericsClosePosition + ) + : parseGenericTypeList(/* withDefaultValues= */ true); + Position equalsPosition = lexer.current().location.begin; expectAndConsume('=', "type alias"); AstType* type = parseType(); - return allocator.alloc(Location(start, type->location), name->name, name->location, generics, genericPacks, type, exported); + if (FFlag::LuauStoreCSTData) + { + AstStatTypeAlias* node = + allocator.alloc(Location(start, type->location), name->name, name->location, generics, genericPacks, type, exported); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc( + typeKeywordPosition, genericsOpenPosition, copy(genericsCommaPositions), genericsClosePosition, equalsPosition + ); + return node; + } + else + { + return allocator.alloc(Location(start, type->location), name->name, name->location, generics, genericPacks, type, exported); + } } // type function Name `(' arglist `)' `=' funcbody `end' @@ -1733,8 +1768,8 @@ std::pair Parser::extractString switch (lexer.current().type) { case Lexeme::QuotedString: - style = lexer.current().getQuoteStyle() == Lexeme::QuoteStyle::Double ? CstExprConstantString::QuotedDouble - : CstExprConstantString::QuotedSingle; + style = + lexer.current().getQuoteStyle() == Lexeme::QuoteStyle::Double ? CstExprConstantString::QuotedDouble : CstExprConstantString::QuotedSingle; break; case Lexeme::InterpStringSimple: style = CstExprConstantString::QuotedInterp; @@ -3026,9 +3061,7 @@ AstExpr* Parser::parseFunctionArgs(AstExpr* func, bool self) { AstExprCall* node = allocator.alloc(Location(func->location, end), func, copy(args), self, Location(argStart, argEnd)); if (options.storeCstData) - cstNodeMap[node] = allocator.alloc( - matchParen.position, lexer.previousLocation().begin, copy(commaPositions) - ); + cstNodeMap[node] = allocator.alloc(matchParen.position, lexer.previousLocation().begin, copy(commaPositions)); return node; } else @@ -3314,7 +3347,12 @@ Parser::Name Parser::parseIndexName(const char* context, const Position& previou return Name(nameError, location); } -std::pair, AstArray> Parser::parseGenericTypeList(bool withDefaultValues) +std::pair, AstArray> Parser::parseGenericTypeList( + bool withDefaultValues, + Position* openPosition, + TempVector* commaPositions, + Position* closePosition +) { TempVector names{scratchGenericTypes}; TempVector namePacks{scratchGenericTypePacks}; @@ -3322,6 +3360,8 @@ std::pair, AstArray> Parser::pars if (lexer.current().type == '<') { Lexeme begin = lexer.current(); + if (FFlag::LuauStoreCSTData && openPosition) + *openPosition = begin.location.begin; nextLexeme(); bool seenPack = false; @@ -3335,6 +3375,7 @@ std::pair, AstArray> Parser::pars { seenPack = true; + Position ellipsisPosition = lexer.current().location.begin; if (lexer.current().type != Lexeme::Dot3) report(lexer.current().location, "Generic types come before generic type packs"); else @@ -3343,13 +3384,24 @@ std::pair, AstArray> Parser::pars if (withDefaultValues && lexer.current().type == '=') { seenDefault = true; + Position equalsPosition = lexer.current().location.begin; nextLexeme(); if (shouldParseTypePack(lexer)) { AstTypePack* typePack = parseTypePack(); - namePacks.push_back(allocator.alloc(nameLocation, name, typePack)); + if (FFlag::LuauStoreCSTData) + { + AstGenericTypePack* node = allocator.alloc(nameLocation, name, typePack); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(ellipsisPosition, equalsPosition); + namePacks.push_back(node); + } + else + { + namePacks.push_back(allocator.alloc(nameLocation, name, typePack)); + } } else { @@ -3358,7 +3410,17 @@ std::pair, AstArray> Parser::pars if (type) report(type->location, "Expected type pack after '=', got type"); - namePacks.push_back(allocator.alloc(nameLocation, name, typePack)); + if (FFlag::LuauStoreCSTData) + { + AstGenericTypePack* node = allocator.alloc(nameLocation, name, typePack); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(ellipsisPosition, equalsPosition); + namePacks.push_back(node); + } + else + { + namePacks.push_back(allocator.alloc(nameLocation, name, typePack)); + } } } else @@ -3366,7 +3428,17 @@ std::pair, AstArray> Parser::pars if (seenDefault) report(lexer.current().location, "Expected default type pack after type pack name"); - namePacks.push_back(allocator.alloc(nameLocation, name, nullptr)); + if (FFlag::LuauStoreCSTData) + { + AstGenericTypePack* node = allocator.alloc(nameLocation, name, nullptr); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(ellipsisPosition, std::nullopt); + namePacks.push_back(node); + } + else + { + namePacks.push_back(allocator.alloc(nameLocation, name, nullptr)); + } } } else @@ -3374,23 +3446,46 @@ std::pair, AstArray> Parser::pars if (withDefaultValues && lexer.current().type == '=') { seenDefault = true; + Position equalsPosition = lexer.current().location.begin; nextLexeme(); AstType* defaultType = parseType(); - names.push_back(allocator.alloc(nameLocation, name, defaultType)); + if (FFlag::LuauStoreCSTData) + { + AstGenericType* node = allocator.alloc(nameLocation, name, defaultType); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(equalsPosition); + names.push_back(node); + } + else + { + names.push_back(allocator.alloc(nameLocation, name, defaultType)); + } } else { if (seenDefault) report(lexer.current().location, "Expected default type after type name"); - names.push_back(allocator.alloc(nameLocation, name, nullptr)); + if (FFlag::LuauStoreCSTData) + { + AstGenericType* node = allocator.alloc(nameLocation, name, nullptr); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(std::nullopt); + names.push_back(node); + } + else + { + names.push_back(allocator.alloc(nameLocation, name, nullptr)); + } } } if (lexer.current().type == ',') { + if (FFlag::LuauStoreCSTData && commaPositions) + commaPositions->push_back(lexer.current().location.begin); nextLexeme(); if (lexer.current().type == '>') @@ -3403,6 +3498,8 @@ std::pair, AstArray> Parser::pars break; } + if (FFlag::LuauStoreCSTData && closePosition) + *closePosition = lexer.current().location.begin; expectMatchAndConsume('>', begin); } diff --git a/CodeGen/src/CodeBlockUnwind.cpp b/CodeGen/src/CodeBlockUnwind.cpp index 3e980566..d21dd14b 100644 --- a/CodeGen/src/CodeBlockUnwind.cpp +++ b/CodeGen/src/CodeBlockUnwind.cpp @@ -22,10 +22,10 @@ // __register_frame and __deregister_frame are defined in libgcc or libc++ // (depending on how it's built). We want to declare them as weak symbols -// so that if they're provided by a shared library, we'll use them, and if +// so that if they're provided by a shared library, we'll use them, and if // not, we'll disable some c++ exception handling support. However, if they're // declared as weak and the definitions are linked in a static library -// that's not linked with whole-archive, then the symbols will technically be defined here, +// that's not linked with whole-archive, then the symbols will technically be defined here, // and the linker won't look for the strong ones in the library. #ifndef LUAU_ENABLE_REGISTER_FRAME #define REGISTER_FRAME_WEAK __attribute__((weak)) diff --git a/EqSat/include/Luau/EGraph.h b/EqSat/include/Luau/EGraph.h index 2703ad9d..c3bc5ab1 100644 --- a/EqSat/include/Luau/EGraph.h +++ b/EqSat/include/Luau/EGraph.h @@ -66,7 +66,7 @@ struct Node }; }; -template +template struct NodeIterator { private: diff --git a/tests/AnyTypeSummary.test.cpp b/tests/AnyTypeSummary.test.cpp index 12e02264..a91ce5b3 100644 --- a/tests/AnyTypeSummary.test.cpp +++ b/tests/AnyTypeSummary.test.cpp @@ -22,6 +22,7 @@ LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAG(LuauAlwaysFillInFunctionCallDiscriminantTypes) LUAU_FASTFLAG(LuauStoreCSTData) LUAU_FASTFLAG(LuauAstTypeGroup) +LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment) struct ATSFixture : BuiltinsFixture @@ -693,6 +694,7 @@ TEST_CASE_FIXTURE(ATSFixture, "mutually_recursive_generic") ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, {FFlag::StudioReportLuauAny2, true}, + {FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true} }; fileResolver.source["game/Gui/Modules/A"] = R"( @@ -705,8 +707,7 @@ TEST_CASE_FIXTURE(ATSFixture, "mutually_recursive_generic") y.g.i = y )"; - CheckResult result1 = frontend.check("game/Gui/Modules/A"); - LUAU_REQUIRE_ERROR_COUNT(2, result1); + LUAU_REQUIRE_NO_ERRORS(frontend.check("game/Gui/Modules/A")); ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); diff --git a/tests/AstJsonEncoder.test.cpp b/tests/AstJsonEncoder.test.cpp index de30be04..75b7e7bd 100644 --- a/tests/AstJsonEncoder.test.cpp +++ b/tests/AstJsonEncoder.test.cpp @@ -475,7 +475,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation") if (FFlag::LuauAstTypeGroup) { - std::string_view expected = R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeGroup","location":"0,9 - 0,36","type":{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeGroup","location":"0,22 - 0,36","type":{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}}]}}},{"type":"AstTypeGroup","location":"0,40 - 0,55","type":{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}}]},"exported":false})"; + std::string_view expected = + R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeGroup","location":"0,9 - 0,36","inner":{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeGroup","location":"0,22 - 0,36","inner":{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}}]}}},{"type":"AstTypeGroup","location":"0,40 - 0,55","inner":{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}}]},"exported":false})"; CHECK(toJson(statement) == expected); } else diff --git a/tests/ClassFixture.cpp b/tests/ClassFixture.cpp index a9bf9596..40d06c85 100644 --- a/tests/ClassFixture.cpp +++ b/tests/ClassFixture.cpp @@ -132,6 +132,15 @@ ClassFixture::ClassFixture() // IndexableNumericKeyClass has a table indexer with a key type of 'number' and a return type of 'number' addIndexableClass("IndexableNumericKeyClass", numberType, numberType); + // Add a confusing derived class which shares the same name internally, but has a unique alias + TypeId duplicateBaseClassInstanceType = arena.addType(ClassType{"BaseClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}}); + + getMutable(duplicateBaseClassInstanceType)->props = { + {"Method", {makeFunction(arena, duplicateBaseClassInstanceType, {}, {stringType})}}, + }; + + addGlobalBinding(globals, "confusingBaseClassInstance", duplicateBaseClassInstanceType, "@test"); + for (const auto& [name, tf] : globals.globalScope->exportedTypeBindings) persist(tf.type); diff --git a/tests/Fixture.h b/tests/Fixture.h index c202075b..60643839 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -180,7 +180,7 @@ std::optional linearSearchForBinding(Scope* scope, const char* name); void registerHiddenTypes(Frontend* frontend); void createSomeClasses(Frontend* frontend); -template +template const E* findError(const CheckResult& result) { for (const auto& e : result.errors) diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index 58bbc16a..8805b7e6 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -31,7 +31,6 @@ LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTFLAG(LuauCloneIncrementalModule) LUAU_FASTFLAG(LuauIncrementalAutocompleteBugfixes) -LUAU_FASTFLAG(LuauReferenceAllocatorInNewSolver) LUAU_FASTFLAG(LuauMixedModeDefFinderTraversesTypeOf) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) @@ -65,13 +64,12 @@ struct FragmentAutocompleteFixtureImpl : BaseType { static_assert(std::is_base_of_v, "BaseType must be a descendant of Fixture"); - ScopedFastFlag sffs[8] = { + ScopedFastFlag sffs[7] = { {FFlag::LuauAllowFragmentParsing, true}, {FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete, true}, {FFlag::LuauStoreSolverTypeOnModule, true}, {FFlag::LuauSymbolEquality, true}, {FFlag::LexerResumesFromPosition2, true}, - {FFlag::LuauReferenceAllocatorInNewSolver, true}, {FFlag::LuauIncrementalAutocompleteBugfixes, true}, {FFlag::LuauBetterReverseDependencyTracking, true}, }; @@ -929,6 +927,148 @@ tbl.abc. ); } +TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "multiple_functions_complex") +{ + const std::string text = R"( local function f1(a1) + local l1 = 1;" + g1 = 1;" +end + +local function f2(a2) + local l2 = 1; + g2 = 1; +end +)"; + + autocompleteFragmentInBothSolvers( + text, + text, + Position{0, 0}, + [](FragmentAutocompleteResult& fragment) + { + auto strings = fragment.acResults.entryMap; + CHECK(strings.count("f1") == 0); + CHECK(strings.count("a1") == 0); + CHECK(strings.count("l1") == 0); + CHECK(strings.count("g1") != 0); + CHECK(strings.count("f2") == 0); + CHECK(strings.count("a2") == 0); + CHECK(strings.count("l2") == 0); + CHECK(strings.count("g2") != 0); + } + ); + + autocompleteFragmentInBothSolvers( + text, + text, + Position{0, 22}, + [](FragmentAutocompleteResult& fragment) + { + auto strings = fragment.acResults.entryMap; + CHECK(strings.count("f1") != 0); + CHECK(strings.count("a1") != 0); + CHECK(strings.count("l1") == 0); + CHECK(strings.count("g1") != 0); + CHECK(strings.count("f2") == 0); + CHECK(strings.count("a2") == 0); + CHECK(strings.count("l2") == 0); + CHECK(strings.count("g2") != 0); + } + ); + + autocompleteFragmentInBothSolvers( + text, + text, + Position{1, 17}, + [](FragmentAutocompleteResult& fragment) + { + auto strings = fragment.acResults.entryMap; + CHECK(strings.count("f1") != 0); + CHECK(strings.count("a1") != 0); + CHECK(strings.count("l1") != 0); + CHECK(strings.count("g1") != 0); + CHECK(strings.count("f2") == 0); + CHECK(strings.count("a2") == 0); + CHECK(strings.count("l2") == 0); + CHECK(strings.count("g2") != 0); + } + ); + + autocompleteFragmentInBothSolvers( + text, + text, + Position{2, 11}, + [](FragmentAutocompleteResult& fragment) + { + auto strings = fragment.acResults.entryMap; + CHECK(strings.count("f1") != 0); + CHECK(strings.count("a1") != 0); + CHECK(strings.count("l1") != 0); + CHECK(strings.count("g1") != 0); + CHECK(strings.count("f2") == 0); + CHECK(strings.count("a2") == 0); + CHECK(strings.count("l2") == 0); + CHECK(strings.count("g2") != 0); + } + ); + + autocompleteFragmentInBothSolvers( + text, + text, + Position{4, 0}, + [](FragmentAutocompleteResult& fragment) + { + auto strings = fragment.acResults.entryMap; + CHECK(strings.count("f1") != 0); + // FIXME: RIDE-11123: This should be zero counts of `a1`. + CHECK(strings.count("a1") != 0); + CHECK(strings.count("l1") == 0); + CHECK(strings.count("g1") != 0); + CHECK(strings.count("f2") == 0); + CHECK(strings.count("a2") == 0); + CHECK(strings.count("l2") == 0); + CHECK(strings.count("g2") != 0); + } + ); + + autocompleteFragmentInBothSolvers( + text, + text, + Position{6, 17}, + [](FragmentAutocompleteResult& fragment) + { + auto strings = fragment.acResults.entryMap; + CHECK(strings.count("f1") != 0); + CHECK(strings.count("a1") == 0); + CHECK(strings.count("l1") == 0); + CHECK(strings.count("g1") != 0); + CHECK(strings.count("f2") != 0); + CHECK(strings.count("a2") != 0); + CHECK(strings.count("l2") != 0); + CHECK(strings.count("g2") != 0); + } + ); + + autocompleteFragmentInBothSolvers( + text, + text, + Position{8, 4}, + [](FragmentAutocompleteResult& fragment) + { + auto strings = fragment.acResults.entryMap; + CHECK(strings.count("f1") != 0); + CHECK(strings.count("a1") == 0); + CHECK(strings.count("l1") == 0); + CHECK(strings.count("g1") != 0); + CHECK(strings.count("f2") != 0); + // FIXME: RIDE-11123: This should be zero counts of `a2`. + CHECK(strings.count("a2") != 0); + CHECK(strings.count("l2") == 0); + CHECK(strings.count("g2") != 0); + } + ); +} + TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "inline_autocomplete_picks_the_right_scope") { const std::string source = R"( diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index 9491e28a..9d6cfa74 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -16,7 +16,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(DebugLuauFreezeArena); LUAU_FASTFLAG(DebugLuauMagicTypes); -LUAU_FASTFLAG(LuauReferenceAllocatorInNewSolver); LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena) LUAU_FASTFLAG(LuauBetterReverseDependencyTracking); @@ -1528,7 +1527,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "get_required_scripts_dirty") TEST_CASE_FIXTURE(FrontendFixture, "check_module_references_allocator") { - ScopedFastFlag sff{FFlag::LuauReferenceAllocatorInNewSolver, true}; fileResolver.source["game/workspace/MyScript"] = R"( print("Hello World") )"; @@ -1546,10 +1544,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "check_module_references_allocator") TEST_CASE_FIXTURE(FrontendFixture, "dfg_data_cleared_on_retain_type_graphs_unset") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauSelectivelyRetainDFGArena, true} - }; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauSelectivelyRetainDFGArena, true}}; fileResolver.source["game/A"] = R"( local a = 1 local b = 2 @@ -1760,7 +1755,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "test_invalid_dependency_tracking_per_module_ fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}"; fileResolver.source["game/Gui/Modules/B"] = "return require(game:GetService('Gui').Modules.A)"; - + FrontendOptions opts; opts.forAutocomplete = false; diff --git a/tests/NonStrictTypeChecker.test.cpp b/tests/NonStrictTypeChecker.test.cpp index b7d67d74..c21b64a3 100644 --- a/tests/NonStrictTypeChecker.test.cpp +++ b/tests/NonStrictTypeChecker.test.cpp @@ -641,10 +641,7 @@ TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "nonstrict_method_calls") TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_non_strict") { - ScopedFastFlag flags[] = { - {FFlag::LuauNonStrictVisitorImprovements, true}, - {FFlag::LuauNewNonStrictWarnOnUnknownGlobals, true} - }; + ScopedFastFlag flags[] = {{FFlag::LuauNonStrictVisitorImprovements, true}, {FFlag::LuauNewNonStrictWarnOnUnknownGlobals, true}}; CheckResult result = check(Mode::Nonstrict, R"( foo = 5 diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 2395efb6..c3478815 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -23,6 +23,7 @@ LUAU_FASTFLAG(LuauFixFunctionNameStartPosition) LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon) LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAG(LuauAstTypeGroup) +LUAU_FASTFLAG(LuauFixDoBlockEndLocation) namespace { @@ -2508,6 +2509,40 @@ TEST_CASE_FIXTURE(Fixture, "parse_return_type_ast_type_group") CHECK(funcType->returnTypes.types.data[0]->is()); } +TEST_CASE_FIXTURE(Fixture, "inner_and_outer_scope_of_functions_have_correct_end_position") +{ + + AstStatBlock* stat = parse(R"( + local function foo() + local x = 1 + end + )"); + REQUIRE(stat); + REQUIRE_EQ(1, stat->body.size); + + auto func = stat->body.data[0]->as(); + REQUIRE(func); + CHECK_EQ(func->func->body->location, Location{{1, 28}, {3, 8}}); + CHECK_EQ(func->location, Location{{1, 8}, {3, 11}}); +} + +TEST_CASE_FIXTURE(Fixture, "do_block_end_location_is_after_end_token") +{ + ScopedFastFlag _{FFlag::LuauFixDoBlockEndLocation, true}; + + AstStatBlock* stat = parse(R"( + do + local x = 1 + end + )"); + REQUIRE(stat); + REQUIRE_EQ(1, stat->body.size); + + auto block = stat->body.data[0]->as(); + REQUIRE(block); + CHECK_EQ(block->location, Location{{1, 8}, {3, 11}}); +} + TEST_SUITE_END(); TEST_SUITE_BEGIN("ParseErrorRecovery"); @@ -3786,7 +3821,7 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type") } else CHECK(unionTy->types.data[0]->is()); // () -> () - CHECK(unionTy->types.data[1]->is()); // nil + CHECK(unionTy->types.data[1]->is()); // nil } TEST_CASE_FIXTURE(Fixture, "complex_union_in_generic_ty") diff --git a/tests/RequireTracer.test.cpp b/tests/RequireTracer.test.cpp index ba03f363..eac9f96b 100644 --- a/tests/RequireTracer.test.cpp +++ b/tests/RequireTracer.test.cpp @@ -6,6 +6,8 @@ #include "doctest.h" +LUAU_FASTFLAG(LuauExtendedSimpleRequire) + using namespace Luau; namespace @@ -178,4 +180,59 @@ TEST_CASE_FIXTURE(RequireTracerFixture, "follow_string_indexexpr") CHECK_EQ("game/Test", result.exprs[local->values.data[0]].name); } +TEST_CASE_FIXTURE(RequireTracerFixture, "follow_group") +{ + ScopedFastFlag luauExtendedSimpleRequire{FFlag::LuauExtendedSimpleRequire, true}; + + AstStatBlock* block = parse(R"( + local R = (((game).Test)) + require(R) + )"); + REQUIRE_EQ(2, block->body.size); + + RequireTraceResult result = traceRequires(&fileResolver, block, "ModuleName"); + + AstStatLocal* local = block->body.data[0]->as(); + REQUIRE(local != nullptr); + + CHECK_EQ("game/Test", result.exprs[local->values.data[0]].name); +} + +TEST_CASE_FIXTURE(RequireTracerFixture, "follow_type_annotation") +{ + ScopedFastFlag luauExtendedSimpleRequire{FFlag::LuauExtendedSimpleRequire, true}; + + AstStatBlock* block = parse(R"( + local R = game.Test :: (typeof(game.Redirect)) + require(R) + )"); + REQUIRE_EQ(2, block->body.size); + + RequireTraceResult result = traceRequires(&fileResolver, block, "ModuleName"); + + AstStatLocal* local = block->body.data[0]->as(); + REQUIRE(local != nullptr); + + CHECK_EQ("game/Redirect", result.exprs[local->values.data[0]].name); +} + +TEST_CASE_FIXTURE(RequireTracerFixture, "follow_type_annotation_2") +{ + ScopedFastFlag luauExtendedSimpleRequire{FFlag::LuauExtendedSimpleRequire, true}; + + AstStatBlock* block = parse(R"( + local R = game.Test :: (typeof(game.Redirect)) + local N = R.Nested + require(N) + )"); + REQUIRE_EQ(3, block->body.size); + + RequireTraceResult result = traceRequires(&fileResolver, block, "ModuleName"); + + AstStatLocal* local = block->body.data[1]->as(); + REQUIRE(local != nullptr); + + CHECK_EQ("game/Redirect/Nested", result.exprs[local->values.data[0]].name); +} + TEST_SUITE_END(); diff --git a/tests/Transpiler.test.cpp b/tests/Transpiler.test.cpp index d726dfb2..caf1ba1c 100644 --- a/tests/Transpiler.test.cpp +++ b/tests/Transpiler.test.cpp @@ -317,6 +317,74 @@ TEST_CASE("returns_spaces_around_tokens") CHECK_EQ(three, transpile(three).code); } +TEST_CASE_FIXTURE(Fixture, "type_alias_spaces_around_tokens") +{ + ScopedFastFlag _{FFlag::LuauStoreCSTData, true}; + std::string code = R"( type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( export type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( export type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo< X, Y, Z...> = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); +} + +TEST_CASE_FIXTURE(Fixture, "type_alias_with_defaults_spaces_around_tokens") +{ + ScopedFastFlag _{FFlag::LuauStoreCSTData, true}; + std::string code = R"( type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = string )"; + CHECK_EQ(code, transpile(code, {}, true).code); +} + TEST_CASE("table_literals") { const std::string code = R"( local t={1, 2, 3, foo='bar', baz=99,[5.5]='five point five', 'end'} )"; diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index 1d54e1b7..f54330e3 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -7,8 +7,10 @@ using namespace Luau; +LUAU_FASTFLAG(LuauTypeFunFixHydratedClasses) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauEqSatSimplification) +LUAU_FASTFLAG(LuauTypeFunSingletonEquality) LUAU_FASTFLAG(LuauUserTypeFunTypeofReturnsType) LUAU_FASTFLAG(LuauTypeFunPrintFix) @@ -489,7 +491,10 @@ local function notok(idx: fail): never return idx end )"); LUAU_REQUIRE_ERROR_COUNT(4, result); - CHECK(toString(result.errors[0]) == R"('fail' type function errored at runtime: [string "fail"]:7: type.inner: cannot call inner method on non-negation type: `number` type)"); + CHECK( + toString(result.errors[0]) == + R"('fail' type function errored at runtime: [string "fail"]:7: type.inner: cannot call inner method on non-negation type: `number` type)" + ); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_serialization_works") @@ -639,6 +644,21 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works2") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauTypeFunFixHydratedClasses{FFlag::LuauTypeFunFixHydratedClasses, true}; + + CheckResult result = check(R"( + type function serialize_class(arg) + return arg + end + local function ok(idx: serialize_class): typeof(confusingBaseClassInstance) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_CASE_FIXTURE(ClassFixture, "udtf_class_methods_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1868,6 +1888,40 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_eqsat_opaque") CHECK_EQ("t0", toString(simplified->result)); // NOLINT(bugprone-unchecked-optional-access) } +TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_bool") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauTypeFunSingletonEquality{FFlag::LuauTypeFunSingletonEquality, true}; + + CheckResult result = check(R"( +type function compare(arg) + return types.singleton(types.singleton(false) == arg) +end + +local function ok(idx: compare): true return idx end +local function ok(idx: compare): false return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_string") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauTypeFunSingletonEquality{FFlag::LuauTypeFunSingletonEquality, true}; + + CheckResult result = check(R"( +type function compare(arg) + return types.singleton(types.singleton("") == arg) +end + +local function ok(idx: compare<"">): true return idx end +local function ok(idx: compare<"a">): false return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "typeof_type_userdata_returns_type") { ScopedFastFlag solverV2{FFlag::LuauSolverV2, true}; diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 942ef6a7..b8d80743 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -20,6 +20,7 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAG(DebugLuauEqSatSimplification) +LUAU_FASTFLAG(LuauSubtypingFixTailPack) TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -3027,4 +3028,17 @@ TEST_CASE_FIXTURE(Fixture, "hidden_variadics_should_not_break_subtyping") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "coroutine_wrap_result_call") +{ + ScopedFastFlag luauSubtypingFixTailPack{FFlag::LuauSubtypingFixTailPack, true}; + + CheckResult result = check(R"( + function foo(a, b) + coroutine.wrap(a)(b) + end + )"); + + // New solver still reports an error in this case, but the main goal of the test is to not crash +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index 0809a682..0a53836b 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -12,6 +12,7 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping); LUAU_FASTFLAG(LuauSolverV2); +LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment) using namespace Luau; @@ -854,6 +855,8 @@ end TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe") { + ScopedFastFlag _{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true}; + CheckResult result = check(R"( --!strict -- At one point this produced a UAF @@ -865,15 +868,19 @@ local y: T = { a = { c = nil, d = 5 }, b = 37 } y.a.c = y )"); - LUAU_REQUIRE_ERRORS(result); - if (FFlag::LuauSolverV2) - CHECK( - toString(result.errors.at(0)) == - R"(Type '{ a: { c: nil, d: number }, b: number }' could not be converted into 'T'; type { a: { c: nil, d: number }, b: number }[read "a"][read "c"] (nil) is not exactly T[read "a"][read "c"][0] (T))" - ); + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + auto mismatch = get(result.errors.at(0)); + CHECK(mismatch); + CHECK_EQ(toString(mismatch->givenType), "{ a: { c: T?, d: number }, b: number }"); + CHECK_EQ(toString(mismatch->wantedType), "T"); + std::string reason = "at [read \"a\"][read \"d\"], number is not exactly string\n\tat [read \"b\"], number is not exactly string"; + CHECK_EQ(mismatch->reason, reason); + } else { + LUAU_REQUIRE_ERROR_COUNT(2, result); const std::string expected = R"(Type 'y' could not be converted into 'T' caused by: Property 'a' is not compatible. diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index 7460434e..32338a68 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -815,11 +815,11 @@ TEST_CASE_FIXTURE(Fixture, "strict_binary_op_where_lhs_unknown") TEST_CASE_FIXTURE(BuiltinsFixture, "and_binexps_dont_unify") { - ScopedFastFlag _{FFlag::LuauDoNotGeneralizeInTypeFunctions, true}; + ScopedFastFlag _{FFlag::LuauDoNotGeneralizeInTypeFunctions, true}; // `t` will be inferred to be of type `{ { test: unknown } }` which is // reasonable, in that it's empty with no bounds on its members. Optimally - // we might emit an error here that the `print(...)` expression is + // we might emit an error here that the `print(...)` expression is // unreachable. LUAU_REQUIRE_NO_ERRORS(check(R"( --!strict diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 8f816bc2..27aeb625 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -9,7 +9,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauEqSatSimplification) -LUAU_FASTFLAG(LuauGeneralizationRemoveRecursiveUpperBound) +LUAU_FASTFLAG(LuauGeneralizationRemoveRecursiveUpperBound2) using namespace Luau; @@ -2445,7 +2445,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::DebugLuauEqSatSimplification, true}, - {FFlag::LuauGeneralizationRemoveRecursiveUpperBound, true}, + {FFlag::LuauGeneralizationRemoveRecursiveUpperBound2, true}, }; LUAU_REQUIRE_NO_ERRORS(check(R"( diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 4cf027b1..3b0380d7 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -25,7 +25,8 @@ LUAU_FASTFLAG(LuauTrackInteriorFreeTablesOnScope) LUAU_FASTFLAG(LuauDontInPlaceMutateTableType) LUAU_FASTFLAG(LuauAllowNonSharedTableTypesInLiteral) LUAU_FASTFLAG(LuauFollowTableFreeze) -LUAU_FASTFLAG(LuauPrecalculateMutatedFreeTypes) +LUAU_FASTFLAG(LuauPrecalculateMutatedFreeTypes2) +LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment) TEST_SUITE_BEGIN("TableTests"); @@ -5012,7 +5013,7 @@ TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager") // bidirectional inference is known to be broken. ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauPrecalculateMutatedFreeTypes, true}, + {FFlag::LuauPrecalculateMutatedFreeTypes2, true}, }; auto result = check(R"( @@ -5153,4 +5154,44 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_musnt_assert") )"); } +TEST_CASE_FIXTURE(Fixture, "optional_property_with_call") +{ + ScopedFastFlag _{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true}; + + LUAU_CHECK_NO_ERRORS(check(R"( + type t = { + key: boolean?, + time: number, + } + + local function num(): number + return 0 + end + + local _: t = { + time = num(), + } + )")); +} + +TEST_CASE_FIXTURE(Fixture, "empty_union_container_overflow") +{ + LUAU_REQUIRE_NO_ERRORS(check(R"( + --!strict + local CellRenderer = {} + function CellRenderer:init(props) + self._separators = { + unhighlight = function() + local cellKey, prevCellKey = self.props.cellKey, self.props.prevCellKey + self.props.onUpdateSeparators({ cellKey, prevCellKey }) + end, + updateProps = function (select, newProps) + local cellKey, prevCellKey = self.props.cellKey, self.props.prevCellKey + self.props.onUpdateSeparators({ if select == 'leading' then prevCellKey else cellKey }) + end + } + end + )")); +} + TEST_SUITE_END();