From 0ce993fe6cf9850a144eaa398f6c0e2dc2f955b1 Mon Sep 17 00:00:00 2001 From: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Date: Fri, 18 Jul 2025 13:46:08 -0700 Subject: [PATCH] Sync to upstream/release/683 (#1921) Another week, another release! ## Analysis - Hide errors in all solver modes (not just strict mode) if the only error is that type inference failed to complete. - Make various analysis components solver-agnostic (`setType`, `visit`, `Scope` methods). - Fix an issue where type inference may fail to complete when assigning a table's member to the table itself. - Fix a bug when accessing a table member on a local after the local is assigned to in an if-else block, loop, or other similar language construct. - Fixes #1914. - Fix type-checking of if-then-else expressions. - Fixes #1815. --- Co-authored-by: Hunter Goldstein Co-authored-by: Vighnesh Vijay Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/ConstraintSolver.h | 2 +- Analysis/include/Luau/TypeUtils.h | 14 ++ Analysis/include/Luau/VisitType.h | 20 ++- Analysis/src/BuiltinTypeFunctions.cpp | 1 - Analysis/src/Constraint.cpp | 16 ++- Analysis/src/ConstraintGenerator.cpp | 44 +++--- Analysis/src/ConstraintSolver.cpp | 31 ++++- Analysis/src/Frontend.cpp | 46 ++----- Analysis/src/Module.cpp | 15 +-- Analysis/src/NonStrictTypeChecker.cpp | 17 ++- Analysis/src/Scope.cpp | 22 ++- Analysis/src/Type.cpp | 4 +- Analysis/src/TypeChecker2.cpp | 49 +++++-- Analysis/src/TypeFunction.cpp | 1 - Analysis/src/UserDefinedTypeFunction.cpp | 58 ++++---- VM/src/lapi.cpp | 25 +--- VM/src/laux.cpp | 4 - VM/src/lbaselib.cpp | 24 ---- VM/src/ldo.cpp | 131 +++++++----------- VM/src/lgc.cpp | 30 +---- VM/src/lgcdebug.cpp | 54 ++------ tests/Conformance.test.cpp | 9 -- tests/NonstrictMode.test.cpp | 39 ++++++ tests/TypeFunction.user.test.cpp | 17 --- tests/TypeInfer.aliases.test.cpp | 7 +- tests/TypeInfer.classes.test.cpp | 25 ++++ tests/TypeInfer.refinements.test.cpp | 65 ++++++++- tests/TypeInfer.tables.test.cpp | 66 +++++++++ tests/TypeInfer.test.cpp | 165 +++++++++++++++++++++-- tests/TypeInfer.unknownnever.test.cpp | 16 ++- 30 files changed, 626 insertions(+), 391 deletions(-) diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index 5d281eff..cbf59a6e 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -254,7 +254,7 @@ public: bool tryDispatch(const ReducePackConstraint& c, NotNull constraint, bool force); bool tryDispatch(const EqualityConstraint& c, NotNull constraint); - bool tryDispatch(const SimplifyConstraint& c, NotNull constraint); + bool tryDispatch(const SimplifyConstraint& c, NotNull constraint, bool force); bool tryDispatch(const PushFunctionTypeConstraint& c, NotNull constraint); diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h index de4d9413..a65d1568 100644 --- a/Analysis/include/Luau/TypeUtils.h +++ b/Analysis/include/Luau/TypeUtils.h @@ -347,4 +347,18 @@ bool isApproximatelyTruthyType(TypeId ty); // Unwraps any grouping expressions iteratively. AstExpr* unwrapGroup(AstExpr* expr); +// These are magic types used in `TypeChecker2` and `NonStrictTypeChecker` +// +// `_luau_print` causes it's argument to be printed out, as in: +// +// local x: _luau_print +// +// ... will cause `number` to be printed. +inline constexpr char kLuauPrint[] = "_luau_print"; +// `_luau_force_constraint_solving_incomplete` will cause us to _always_ emit +// a constraint solving incomplete error to test semantics around that specific +// error. +inline constexpr char kLuauForceConstraintSolvingIncomplete[] = "_luau_force_constraint_solving_incomplete"; + + } // namespace Luau diff --git a/Analysis/include/Luau/VisitType.h b/Analysis/include/Luau/VisitType.h index 3621d1bb..c26e1aab 100644 --- a/Analysis/include/Luau/VisitType.h +++ b/Analysis/include/Luau/VisitType.h @@ -11,6 +11,7 @@ LUAU_FASTINT(LuauVisitRecursionLimit) LUAU_FASTFLAG(LuauSolverV2) +LUAU_FASTFLAG(LuauSolverAgnosticVisitType) namespace Luau { @@ -231,7 +232,20 @@ struct GenericTypeVisitor } else if (auto ftv = get(ty)) { - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverAgnosticVisitType) + { + if (visit(ty, *ftv)) + { + // Regardless of the choice of solver, all free types are guaranteed to have + // lower and upper bounds + LUAU_ASSERT(ftv->lowerBound); + LUAU_ASSERT(ftv->upperBound); + + traverse(ftv->lowerBound); + traverse(ftv->upperBound); + } + } + else if (FFlag::LuauSolverV2) { if (visit(ty, *ftv)) { @@ -281,7 +295,7 @@ struct GenericTypeVisitor { for (auto& [_name, prop] : ttv->props) { - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticVisitType) { if (auto ty = prop.readTy) traverse(*ty); @@ -319,7 +333,7 @@ struct GenericTypeVisitor { for (const auto& [name, prop] : etv->props) { - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticVisitType) { if (auto ty = prop.readTy) traverse(*ty); diff --git a/Analysis/src/BuiltinTypeFunctions.cpp b/Analysis/src/BuiltinTypeFunctions.cpp index d4a2bf2f..81a251ec 100644 --- a/Analysis/src/BuiltinTypeFunctions.cpp +++ b/Analysis/src/BuiltinTypeFunctions.cpp @@ -21,7 +21,6 @@ LUAU_FASTFLAG(LuauNotAllBinaryTypeFunsHaveDefaults) LUAU_FASTFLAG(LuauEmptyStringInKeyOf) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature) -LUAU_FASTFLAG(LuauUserTypeFunctionAliases) LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying) LUAU_FASTFLAG(LuauOccursCheckForRefinement) diff --git a/Analysis/src/Constraint.cpp b/Analysis/src/Constraint.cpp index ee8aa04f..66cc9443 100644 --- a/Analysis/src/Constraint.cpp +++ b/Analysis/src/Constraint.cpp @@ -1,10 +1,12 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Constraint.h" +#include "Luau/TypeFunction.h" #include "Luau/VisitType.h" LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) +LUAU_FASTFLAG(LuauForceSimplifyConstraint) namespace Luau { @@ -61,9 +63,12 @@ struct ReferenceCountInitializer_DEPRECATED : TypeOnceVisitor return false; } - bool visit(TypeId, const TypeFunctionInstanceType&) override + bool visit(TypeId, const TypeFunctionInstanceType& tfit) override { - return FFlag::LuauEagerGeneralization4 && traverseIntoTypeFunctions; + if (FFlag::LuauForceSimplifyConstraint) + return tfit.function->canReduceGenerics; + else + return FFlag::LuauEagerGeneralization4 && traverseIntoTypeFunctions; } }; @@ -112,9 +117,12 @@ struct ReferenceCountInitializer : TypeOnceVisitor return false; } - bool visit(TypeId, const TypeFunctionInstanceType&) override + bool visit(TypeId, const TypeFunctionInstanceType& tfit) override { - return FFlag::LuauEagerGeneralization4 && traverseIntoTypeFunctions; + if (FFlag::LuauForceSimplifyConstraint) + return tfit.function->canReduceGenerics; + else + return FFlag::LuauEagerGeneralization4 && traverseIntoTypeFunctions; } }; diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 6bf192d1..445de472 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -39,20 +39,18 @@ LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties) -LUAU_FASTFLAGVARIABLE(LuauAvoidDoubleNegation) LUAU_FASTFLAGVARIABLE(LuauSimplifyOutOfLine2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAGVARIABLE(LuauDisablePrimitiveInferenceInLargeTables) LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500) -LUAU_FASTFLAGVARIABLE(LuauUserTypeFunctionAliases) LUAU_FASTFLAGVARIABLE(LuauSkipLvalueForCompoundAssignment) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) -LUAU_FASTFLAGVARIABLE(LuauFollowTypeAlias) -LUAU_FASTFLAGVARIABLE(LuauFollowExistingTypeFunction) LUAU_FASTFLAGVARIABLE(LuauRefineTablesWithReadType) LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTracksRValueRefinements) LUAU_FASTFLAGVARIABLE(LuauPushFunctionTypesInFunctionStatement) +LUAU_FASTFLAGVARIABLE(LuauInferActualIfElseExprType) +LUAU_FASTFLAGVARIABLE(LuauDoNotPrototypeTableIndex) namespace Luau { @@ -584,13 +582,8 @@ void ConstraintGenerator::computeRefinement( // if we have a negative sense, then we need to negate the discriminant if (!sense) { - if (FFlag::LuauAvoidDoubleNegation) - { - if (auto nt = get(follow(discriminantTy))) - discriminantTy = nt->ty; - else - discriminantTy = arena->addType(NegationType{discriminantTy}); - } + if (auto nt = get(follow(discriminantTy))) + discriminantTy = nt->ty; else discriminantTy = arena->addType(NegationType{discriminantTy}); } @@ -958,8 +951,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc auto addToEnvironment = [this, &globalNameCollector](UserDefinedFunctionData& userFuncData, ScopePtr scope, const Name& name, TypeFun tf, size_t level) { - if (auto ty = get(FFlag::LuauFollowTypeAlias ? follow(tf.type) : tf.type); - ty && ty->userFuncData.definition) + if (auto ty = get(follow(tf.type)); ty && ty->userFuncData.definition) { if (userFuncData.environmentFunction.find(name)) return; @@ -972,8 +964,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc scope->bindings[ty->userFuncData.definition->name] = Binding{existing->typeId, ty->userFuncData.definition->location}; } } - else if (FFlag::LuauUserTypeFunctionAliases && - !get(FFlag::LuauFollowTypeAlias ? follow(tf.type) : tf.type)) + else if (!get(follow(tf.type))) { if (userFuncData.environmentAlias.find(name)) return; @@ -995,7 +986,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc }; // Go up the scopes to register type functions and alises, but without reaching into the global scope - for (Scope* curr = scope.get(); curr && (!FFlag::LuauUserTypeFunctionAliases || curr != globalScope.get()); curr = curr->parent.get()) + for (Scope* curr = scope.get(); curr && curr != globalScope.get(); curr = curr->parent.get()) { for (auto& [name, tf] : curr->privateTypeBindings) addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf, level); @@ -1931,18 +1922,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio if (!existingFunctionTy) ice->ice("checkAliases did not populate type function name", function->nameLocation); - if (FFlag::LuauFollowExistingTypeFunction) - { - TypeId unpackedTy = follow(*existingFunctionTy); + TypeId unpackedTy = follow(*existingFunctionTy); - if (auto bt = get(unpackedTy); bt && nullptr == bt->getOwner()) - emplaceType(asMutable(unpackedTy), generalizedTy); - } - else - { - if (auto bt = get(*existingFunctionTy); bt && nullptr == bt->getOwner()) - emplaceType(asMutable(*existingFunctionTy), generalizedTy); - } + if (auto bt = get(unpackedTy); bt && nullptr == bt->getOwner()) + emplaceType(asMutable(unpackedTy), generalizedTy); return ControlFlow::None; } @@ -2807,7 +2790,7 @@ Inference ConstraintGenerator::checkIndexName( if (key) { - if (auto ty = lookup(scope, indexLocation, key->def)) + if (auto ty = lookup(scope, indexLocation, key->def, !FFlag::LuauDoNotPrototypeTableIndex)) return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)}; if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) @@ -3119,7 +3102,10 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIfElse* ifEls applyRefinements(elseScope, ifElse->falseExpr->location, refinementArena.negation(refinement)); TypeId elseType = check(elseScope, ifElse->falseExpr, expectedType).ty; - return Inference{expectedType ? *expectedType : makeUnion(scope, ifElse->location, thenType, elseType)}; + if (FFlag::LuauInferActualIfElseExprType) + return Inference{makeUnion(scope, ifElse->location, thenType, elseType)}; + else + return Inference{expectedType ? *expectedType : makeUnion(scope, ifElse->location, thenType, elseType)}; } Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert) diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index f6c3ff01..7c13c70a 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -35,7 +35,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion2) -LUAU_FASTFLAGVARIABLE(LuauInsertErrorTypesIntoIndexerResult) LUAU_FASTFLAGVARIABLE(LuauAvoidGenericsLeakingDuringFunctionCallCheck) LUAU_FASTFLAGVARIABLE(LuauMissingFollowInAssignIndexConstraint) LUAU_FASTFLAGVARIABLE(LuauRemoveTypeCallsForReadWriteProps) @@ -43,6 +42,7 @@ LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAGVARIABLE(LuauUseOrderedTypeSetsInConstraints) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying) +LUAU_FASTFLAGVARIABLE(LuauForceSimplifyConstraint) namespace Luau { @@ -905,7 +905,7 @@ bool ConstraintSolver::tryDispatch(NotNull constraint, bool fo else if (auto eqc = get(*constraint)) success = tryDispatch(*eqc, constraint); else if (auto sc = get(*constraint)) - success = tryDispatch(*sc, constraint); + success = tryDispatch(*sc, constraint, force); else if (auto pftc = get(*constraint)) success = tryDispatch(*pftc, constraint); else @@ -2286,8 +2286,7 @@ bool ConstraintSolver::tryDispatchHasIndexer( continue; r = follow(r); - if (FFlag::LuauInsertErrorTypesIntoIndexerResult || !get(r)) - results.insert(r); + results.insert(r); } if (0 == results.size()) @@ -2911,7 +2910,7 @@ struct FindAllUnionMembers : TypeOnceVisitor } }; -bool ConstraintSolver::tryDispatch(const SimplifyConstraint& c, NotNull constraint) +bool ConstraintSolver::tryDispatch(const SimplifyConstraint& c, NotNull constraint, bool force) { TypeId target = follow(c.ty); @@ -2927,7 +2926,14 @@ bool ConstraintSolver::tryDispatch(const SimplifyConstraint& c, NotNullexportedTypeBindings) tf.type = builtinTypes->errorType; } - else if (FFlag::LuauNewSolverTypecheckCatchTimeouts) + else { try { @@ -1622,40 +1623,15 @@ ModulePtr check( result->cancelled = true; } } - else + + // if the only error we're producing is one about constraint solving being incomplete, we can silence it. + // this means we won't give this warning if types seem totally nonsensical, but there are no other errors. + // this is probably, on the whole, a good decision to not annoy users though. + if (FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) { - switch (mode) - { - case Mode::Nonstrict: - Luau::checkNonStrict( - builtinTypes, - NotNull{simplifier.get()}, - NotNull{&typeFunctionRuntime}, - iceHandler, - NotNull{&unifierState}, - NotNull{&dfg}, - NotNull{&limits}, - sourceModule, - result.get() - ); - break; - case Mode::Definition: - // fallthrough intentional - case Mode::Strict: - Luau::check( - builtinTypes, - NotNull{simplifier.get()}, - NotNull{&typeFunctionRuntime}, - NotNull{&unifierState}, - NotNull{&limits}, - logger.get(), - sourceModule, - result.get() - ); - break; - case Mode::NoCheck: - break; - }; + if (result->errors.size() == 1 && get(result->errors[0]) && + !FFlag::DebugLuauAlwaysShowConstraintSolvingIncomplete) + result->errors.clear(); } if (FFlag::LuauExpectedTypeVisitor) diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index e7c09bf2..cbe10f37 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -15,7 +15,6 @@ #include LUAU_FASTFLAG(LuauSolverV2); -LUAU_FASTFLAG(LuauUserTypeFunctionAliases) namespace Luau { @@ -306,12 +305,9 @@ void Module::clonePublicInterface_DEPRECATED(NotNull builtinTypes, ty = clonePublicInterface.cloneType(ty); } - if (FFlag::LuauUserTypeFunctionAliases) + for (auto& tf : typeFunctionAliases) { - for (auto& tf : typeFunctionAliases) - { - *tf = clonePublicInterface.cloneTypeFun(*tf); - } + *tf = clonePublicInterface.cloneTypeFun(*tf); } // Copy external stuff over to Module itself @@ -350,12 +346,9 @@ void Module::clonePublicInterface(NotNull builtinTypes, InternalEr ty = clonePublicInterface.cloneType(ty); } - if (FFlag::LuauUserTypeFunctionAliases) + for (auto& tf : typeFunctionAliases) { - for (auto& tf : typeFunctionAliases) - { - *tf = clonePublicInterface.cloneTypeFun(*tf); - } + *tf = clonePublicInterface.cloneTypeFun(*tf); } // Copy external stuff over to Module itself diff --git a/Analysis/src/NonStrictTypeChecker.cpp b/Analysis/src/NonStrictTypeChecker.cpp index 8334abf3..302c4d69 100644 --- a/Analysis/src/NonStrictTypeChecker.cpp +++ b/Analysis/src/NonStrictTypeChecker.cpp @@ -863,9 +863,20 @@ struct NonStrictTypeChecker void visit(AstTypeReference* ty) { - // No further validation is necessary in this case. The main logic for - // _luau_print is contained in lookupAnnotation. - if (FFlag::DebugLuauMagicTypes && ty->name == "_luau_print") + if (FFlag::DebugLuauMagicTypes) + { + // No further validation is necessary in this case. + if (ty->name == kLuauPrint) + return; + + if (ty->name == kLuauForceConstraintSolvingIncomplete) + { + reportError(ConstraintSolvingIncompleteError{}, ty->location); + return; + } + } + + if (FFlag::DebugLuauMagicTypes && (ty->name == kLuauPrint || ty->name == kLuauForceConstraintSolvingIncomplete)) return; for (const AstTypeOrPack& param : ty->parameters) diff --git a/Analysis/src/Scope.cpp b/Analysis/src/Scope.cpp index deddc506..93b23302 100644 --- a/Analysis/src/Scope.cpp +++ b/Analysis/src/Scope.cpp @@ -4,6 +4,8 @@ LUAU_FASTFLAG(LuauSolverV2); +LUAU_FASTFLAGVARIABLE(LuauScopeMethodsAreSolverAgnostic) + namespace Luau { @@ -218,17 +220,25 @@ std::optional> Scope::linearSearchForBindingPair(cons // Updates the `this` scope with the assignments from the `childScope` including ones that doesn't exist in `this`. void Scope::inheritAssignments(const ScopePtr& childScope) { - if (!FFlag::LuauSolverV2) - return; - - for (const auto& [k, a] : childScope->lvalueTypes) - lvalueTypes[k] = a; + if (FFlag::LuauScopeMethodsAreSolverAgnostic) + { + for (const auto& [k, a] : childScope->lvalueTypes) + lvalueTypes[k] = a; + } + else + { + if (!FFlag::LuauSolverV2) + return; + + for (const auto& [k, a] : childScope->lvalueTypes) + lvalueTypes[k] = a; + } } // Updates the `this` scope with the refinements from the `childScope` excluding ones that doesn't exist in `this`. void Scope::inheritRefinements(const ScopePtr& childScope) { - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2 || FFlag::LuauScopeMethodsAreSolverAgnostic) { for (const auto& [k, a] : childScope->rvalueRefinements) { diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index cf951fe7..b232665a 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -32,6 +32,8 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) +LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticVisitType) +LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticSetType) namespace Luau { @@ -722,7 +724,7 @@ TypeId Property::type_DEPRECATED() const void Property::setType(TypeId ty) { readTy = ty; - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticSetType) writeTy = ty; } diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 430f8c12..26679903 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -32,13 +32,14 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks) -LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases) LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) +LUAU_FASTFLAG(LuauInferActualIfElseExprType) +LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) namespace Luau { @@ -48,7 +49,6 @@ namespace Luau using PrintLineProc = void (*)(const std::string&); extern PrintLineProc luauPrintLine; - /* Push a scope onto the end of a stack for the lifetime of the StackPusher instance. * TypeChecker2 uses this to maintain knowledge about which scope encloses every * given AstNode. @@ -289,11 +289,11 @@ void check( typeChecker.visit(sourceModule.root); - // if the only error we're producing is one about constraint solving being incomplete, we can silence it. - // this means we won't give this warning if types seem totally nonsensical, but there are no other errors. - // this is probably, on the whole, a good decision to not annoy users though. - if (module->errors.size() == 1 && get(module->errors[0])) - module->errors.clear(); + if (!FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) + { + if (module->errors.size() == 1 && get(module->errors[0])) + module->errors.clear(); + } unfreeze(module->interfaceTypes); copyErrors(module->errors, module->interfaceTypes, builtinTypes); @@ -539,7 +539,7 @@ TypeId TypeChecker2::lookupAnnotation(AstType* annotation) { if (FFlag::DebugLuauMagicTypes) { - if (auto ref = annotation->as(); ref && ref->name == "_luau_print" && ref->parameters.size > 0) + if (auto ref = annotation->as(); ref && ref->name == kLuauPrint && ref->parameters.size > 0) { if (auto ann = ref->parameters.data[0].type) { @@ -550,6 +550,11 @@ TypeId TypeChecker2::lookupAnnotation(AstType* annotation) return follow(argTy); } } + else if (auto ref = annotation->as(); ref && ref->name == kLuauForceConstraintSolvingIncomplete) + { + reportError(ConstraintSolvingIncompleteError{}, ref->location); + return builtinTypes->anyType; + } } TypeId* ty = module->astResolvedTypes.find(annotation); @@ -1288,7 +1293,7 @@ void TypeChecker2::visit(AstStatTypeAlias* stat) // We will not visit type aliases that do not have an associated scope, // this means that (probably) this was a duplicate type alias or a // type alias with an illegal name (like `typeof`). - if (FFlag::LuauSkipMalformedTypeAliases && !module->astScopes.contains(stat)) + if (!module->astScopes.contains(stat)) return; visitGenerics(stat->generics, stat->genericPacks); @@ -2634,7 +2639,7 @@ void TypeChecker2::visit(AstTypeReference* ty) { // No further validation is necessary in this case. The main logic for // _luau_print is contained in lookupAnnotation. - if (FFlag::DebugLuauMagicTypes && ty->name == "_luau_print") + if (FFlag::DebugLuauMagicTypes && (ty->name == kLuauPrint || ty->name == kLuauForceConstraintSolvingIncomplete)) return; for (const AstTypeOrPack& param : ty->parameters) @@ -3051,6 +3056,30 @@ bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedT auto exprType = follow(lookupType(expr)); expectedType = follow(expectedType); + if (FFlag::LuauInferActualIfElseExprType) + { + if (auto group = expr->as()) + { + return testPotentialLiteralIsSubtype(group->expr, expectedType); + } + else if (auto ifElse = expr->as()) + { + bool passes = testPotentialLiteralIsSubtype(ifElse->trueExpr, expectedType); + passes &= testPotentialLiteralIsSubtype(ifElse->falseExpr, expectedType); + return passes; + } + else if (auto binExpr = expr->as(); binExpr && binExpr->op == AstExprBinary::Or) + { + // In this case: `{ ... } or { ... }` is literal _enough_ that + // we should do this covariant check. + auto relaxedExpectedLhs = module->internalTypes.addType(UnionType{{builtinTypes->falsyType, expectedType}}); + bool passes = testPotentialLiteralIsSubtype(binExpr->left, relaxedExpectedLhs); + passes &= testPotentialLiteralIsSubtype(binExpr->right, expectedType); + return passes; + } + // FIXME: We probably should do a check for `and` here. + } + auto exprTable = expr->as(); auto exprTableType = get(exprType); auto expectedTableType = get(expectedType); diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index 0f88ce5f..d2c1ed03 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -37,7 +37,6 @@ LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies) LUAU_FASTFLAGVARIABLE(LuauNotAllBinaryTypeFunsHaveDefaults) -LUAU_FASTFLAG(LuauUserTypeFunctionAliases) LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAGVARIABLE(LuauOccursCheckForRefinement) diff --git a/Analysis/src/UserDefinedTypeFunction.cpp b/Analysis/src/UserDefinedTypeFunction.cpp index b2f81e2f..a22147df 100644 --- a/Analysis/src/UserDefinedTypeFunction.cpp +++ b/Analysis/src/UserDefinedTypeFunction.cpp @@ -12,8 +12,6 @@ #include "lua.h" #include "lualib.h" -LUAU_FASTFLAG(LuauUserTypeFunctionAliases) - namespace Luau { @@ -200,14 +198,11 @@ TypeFunctionReductionResult userDefinedTypeFunction( for (auto typeParam : typeParams) check.traverse(follow(typeParam)); - if (FFlag::LuauUserTypeFunctionAliases) + // Check that our environment doesn't depend on any type aliases that are blocked + for (auto& [name, definition] : typeFunction->userFuncData.environmentAlias) { - // Check that our environment doesn't depend on any type aliases that are blocked - for (auto& [name, definition] : typeFunction->userFuncData.environmentAlias) - { - if (definition.first->typeParams.empty() && definition.first->typePackParams.empty()) - check.traverse(follow(definition.first->type)); - } + if (definition.first->typeParams.empty() && definition.first->typePackParams.empty()) + check.traverse(follow(definition.first->type)); } if (!check.blockingTypes.empty()) @@ -281,36 +276,33 @@ TypeFunctionReductionResult userDefinedTypeFunction( } } - if (FFlag::LuauUserTypeFunctionAliases) + for (auto& [name, definition] : typeFunction->userFuncData.environmentAlias) { - for (auto& [name, definition] : typeFunction->userFuncData.environmentAlias) + // Filter visibility based on original scope depth + if (definition.second >= curr.second) { - // Filter visibility based on original scope depth - if (definition.second >= curr.second) + if (definition.first->typeParams.empty() && definition.first->typePackParams.empty()) { - if (definition.first->typeParams.empty() && definition.first->typePackParams.empty()) + TypeId ty = follow(definition.first->type); + + // This is checked at the top of the function, and should still be true. + LUAU_ASSERT(!isPending(ty, ctx->solver)); + + TypeFunctionTypeId serializedTy = serialize(ty, runtimeBuilder.get()); + + // Only register aliases that are representable in type environment + if (runtimeBuilder->errors.empty()) { - TypeId ty = follow(definition.first->type); - - // This is checked at the top of the function, and should still be true. - LUAU_ASSERT(!isPending(ty, ctx->solver)); - - TypeFunctionTypeId serializedTy = serialize(ty, runtimeBuilder.get()); - - // Only register aliases that are representable in type environment - if (runtimeBuilder->errors.empty()) - { - allocTypeUserData(L, serializedTy->type); - lua_setfield(L, -2, name.c_str()); - } - } - else - { - lua_pushlightuserdata(L, definition.first); - lua_pushcclosure(L, evaluateTypeAliasCall, name.c_str(), 1); + allocTypeUserData(L, serializedTy->type); lua_setfield(L, -2, name.c_str()); } } + else + { + lua_pushlightuserdata(L, definition.first); + lua_pushcclosure(L, evaluateTypeAliasCall, name.c_str(), 1); + lua_setfield(L, -2, name.c_str()); + } } } @@ -388,4 +380,4 @@ TypeFunctionReductionResult userDefinedTypeFunction( return {retTypeId, Reduction::MaybeOk, {}, {}, std::nullopt, ctx->typeFunctionRuntime->messages}; } -} \ No newline at end of file +} // namespace Luau diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index 98c34f93..a65933b5 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -15,8 +15,6 @@ #include -LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauUnrefExisting, false) - /* * This file contains most implementations of core Lua APIs from lua.h. * @@ -1448,25 +1446,16 @@ void lua_unref(lua_State* L, int ref) global_State* g = L->global; LuaTable* reg = hvalue(registry(L)); - if (DFFlag::LuauUnrefExisting) - { - const TValue* slot = luaH_getnum(reg, ref); - api_check(L, slot != luaO_nilobject); + const TValue* slot = luaH_getnum(reg, ref); + api_check(L, slot != luaO_nilobject); - // similar to how 'luaH_setnum' makes non-nil slot value mutable - TValue* mutableSlot = (TValue*)slot; + // similar to how 'luaH_setnum' makes non-nil slot value mutable + TValue* mutableSlot = (TValue*)slot; - // NB: no barrier needed because value isn't collectable - setnvalue(mutableSlot, g->registryfree); + // NB: no barrier needed because value isn't collectable + setnvalue(mutableSlot, g->registryfree); - g->registryfree = ref; - } - else - { - TValue* slot = luaH_setnum(L, reg, ref); - setnvalue(slot, g->registryfree); // NB: no barrier needed because value isn't collectable - g->registryfree = ref; - } + g->registryfree = ref; } void lua_setuserdatatag(lua_State* L, int idx, int tag) diff --git a/VM/src/laux.cpp b/VM/src/laux.cpp index ba1281ba..e5ae693d 100644 --- a/VM/src/laux.cpp +++ b/VM/src/laux.cpp @@ -11,8 +11,6 @@ #include -LUAU_FASTFLAG(LuauYieldableContinuations) - // convert a stack index to positive #define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1) @@ -355,8 +353,6 @@ const char* luaL_typename(lua_State* L, int idx) int luaL_callyieldable(lua_State* L, int nargs, int nresults) { - LUAU_ASSERT(FFlag::LuauYieldableContinuations); - api_check(L, iscfunction(L->ci->func)); Closure* cl = clvalue(L->ci->func); api_check(L, cl->c.cont); diff --git a/VM/src/lbaselib.cpp b/VM/src/lbaselib.cpp index 18f5791c..c98c31cb 100644 --- a/VM/src/lbaselib.cpp +++ b/VM/src/lbaselib.cpp @@ -11,8 +11,6 @@ #include #include -LUAU_FASTFLAG(LuauYieldableContinuations) - static void writestring(const char* s, size_t l) { fwrite(s, 1, l, stdout); @@ -296,19 +294,8 @@ static int luaB_pcally(lua_State* L) // any errors from this point on are handled by continuation L->ci->flags |= LUA_CALLINFO_HANDLE; - if (!FFlag::LuauYieldableContinuations) - { - // maintain yieldable invariant (baseCcalls <= nCcalls) - L->baseCcalls++; - } - int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), 0); - if (!FFlag::LuauYieldableContinuations) - { - L->baseCcalls--; - } - // necessary to accomodate functions that return lots of values expandstacklimit(L, L->top); @@ -358,19 +345,8 @@ static int luaB_xpcally(lua_State* L) StkId errf = L->base; StkId func = L->base + 1; - if (!FFlag::LuauYieldableContinuations) - { - // maintain yieldable invariant (baseCcalls <= nCcalls) - L->baseCcalls++; - } - int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), savestack(L, errf)); - if (!FFlag::LuauYieldableContinuations) - { - L->baseCcalls--; - } - // necessary to accommodate functions that return lots of values expandstacklimit(L, L->top); diff --git a/VM/src/ldo.cpp b/VM/src/ldo.cpp index 4d6f3ecf..814a8cd2 100644 --- a/VM/src/ldo.cpp +++ b/VM/src/ldo.cpp @@ -17,8 +17,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauYieldableContinuations) - // keep max stack allocation request under 1GB #define MAX_STACK_SIZE (int(1024 / sizeof(TValue)) * 1024 * 1024) @@ -262,80 +260,56 @@ void luaD_call(lua_State* L, StkId func, int nresults) if (++L->nCcalls >= LUAI_MAXCCALLS) luaD_checkCstack(L); - if (FFlag::LuauYieldableContinuations) + // when called from a yieldable C function, maintain yieldable invariant (baseCcalls <= nCcalls) + bool fromyieldableccall = false; + + if (L->ci != L->base_ci) { - // when called from a yieldable C function, maintain yieldable invariant (baseCcalls <= nCcalls) - bool fromyieldableccall = false; + Closure* ccl = clvalue(L->ci->func); - if (L->ci != L->base_ci) + if (ccl->isC && ccl->c.cont) { - Closure* ccl = clvalue(L->ci->func); - - if (ccl->isC && ccl->c.cont) - { - fromyieldableccall = true; - L->baseCcalls++; - } + fromyieldableccall = true; + L->baseCcalls++; } - - ptrdiff_t funcoffset = savestack(L, func); - ptrdiff_t cioffset = saveci(L, L->ci); - - if (luau_precall(L, func, nresults) == PCRLUA) - { // is a Lua function? - L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame - - bool oldactive = L->isactive; - L->isactive = true; - luaC_threadbarrier(L); - - luau_execute(L); // call it - - if (!oldactive) - L->isactive = false; - } - - bool yielded = L->status == LUA_YIELD || L->status == LUA_BREAK; - - if (fromyieldableccall) - { - // restore original yieldable invariant - // in case of an error, this would either be restored by luaD_pcall or the thread would no longer be resumable - L->baseCcalls--; - - // on yield, we have to set the CallInfo top of the C function including slots for expected results, to restore later - if (yielded) - { - CallInfo* callerci = restoreci(L, cioffset); - callerci->top = restorestack(L, funcoffset) + (nresults != LUA_MULTRET ? nresults : 0); - } - } - - if (nresults != LUA_MULTRET && !yielded) - L->top = restorestack(L, funcoffset) + nresults; } - else + + ptrdiff_t funcoffset = savestack(L, func); + ptrdiff_t cioffset = saveci(L, L->ci); + + if (luau_precall(L, func, nresults) == PCRLUA) + { // is a Lua function? + L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame + + bool oldactive = L->isactive; + L->isactive = true; + luaC_threadbarrier(L); + + luau_execute(L); // call it + + if (!oldactive) + L->isactive = false; + } + + bool yielded = L->status == LUA_YIELD || L->status == LUA_BREAK; + + if (fromyieldableccall) { - ptrdiff_t old_func = savestack(L, func); + // restore original yieldable invariant + // in case of an error, this would either be restored by luaD_pcall or the thread would no longer be resumable + L->baseCcalls--; - if (luau_precall(L, func, nresults) == PCRLUA) - { // is a Lua function? - L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame - - bool oldactive = L->isactive; - L->isactive = true; - luaC_threadbarrier(L); - - luau_execute(L); // call it - - if (!oldactive) - L->isactive = false; + // on yield, we have to set the CallInfo top of the C function including slots for expected results, to restore later + if (yielded) + { + CallInfo* callerci = restoreci(L, cioffset); + callerci->top = restorestack(L, funcoffset) + (nresults != LUA_MULTRET ? nresults : 0); } - - if (nresults != LUA_MULTRET) - L->top = restorestack(L, old_func) + nresults; } + if (nresults != LUA_MULTRET && !yielded) + L->top = restorestack(L, funcoffset) + nresults; + L->nCcalls--; luaC_checkGC(L); } @@ -380,18 +354,9 @@ static void resume_continue(lua_State* L) // C continuation; we expect this to be followed by Lua continuations int n = cl->c.cont(L, 0); - if (FFlag::LuauYieldableContinuations) - { - // continuation can break or yield again - if (L->status == LUA_BREAK || L->status == LUA_YIELD) - break; - } - else - { - // Continuation can break again - if (L->status == LUA_BREAK) - break; - } + // continuation can break or yield again + if (L->status == LUA_BREAK || L->status == LUA_YIELD) + break; luau_poscall(L, L->top - n); } @@ -436,7 +401,7 @@ static void resume(lua_State* L, void* ud) // finish interrupted execution of `OP_CALL' luau_poscall(L, firstArg); } - else if (FFlag::LuauYieldableContinuations) + else { // restore arguments we have protected for C continuation L->base = L->ci->base; @@ -647,7 +612,7 @@ static void restore_stack_limit(lua_State* L) int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t ef) { unsigned short oldnCcalls = L->nCcalls; - unsigned short oldbaseCcalls = FFlag::LuauYieldableContinuations ? L->baseCcalls : 0; + unsigned short oldbaseCcalls = L->baseCcalls; ptrdiff_t old_ci = saveci(L, L->ci); bool oldactive = L->isactive; int status = luaD_rawrunprotected(L, func, u); @@ -681,11 +646,9 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e bool yieldable = L->nCcalls <= L->baseCcalls; // Inlined logic from 'lua_isyieldable' to avoid potential for an out of line call. - // restore nCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored. + // restore nCcalls and baseCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored. L->nCcalls = oldnCcalls; - - if (FFlag::LuauYieldableContinuations) - L->baseCcalls = oldbaseCcalls; + L->baseCcalls = oldbaseCcalls; // an error occurred, check if we have a protected error callback if (yieldable && L->global->cb.debugprotectederror) diff --git a/VM/src/lgc.cpp b/VM/src/lgc.cpp index b4983035..873877c8 100644 --- a/VM/src/lgc.cpp +++ b/VM/src/lgc.cpp @@ -14,8 +14,6 @@ #include -LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauGcAgainstOom, false) - /* * Luau uses an incremental non-generational non-moving mark&sweep garbage collector. * @@ -514,12 +512,7 @@ static size_t propagatemark(global_State* g) // we could shrink stack at any time but we opt to do it during initial mark to do that just once per cycle if (g->gcstate == GCSpropagate) - { - if (DFFlag::LuauGcAgainstOom) - shrinkstackprotected(th); - else - shrinkstack(th); - } + shrinkstackprotected(th); return sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci; } @@ -634,12 +627,7 @@ static size_t cleartable(lua_State* L, GCObject* l) { // shrink at 37.5% occupancy if (activevalues < sizenode(h) * 3 / 8) - { - if (DFFlag::LuauGcAgainstOom) - tableresizeprotected(L, h, activevalues); - else - luaH_resizehash(L, h, activevalues); - } + tableresizeprotected(L, h, activevalues); } } @@ -706,12 +694,7 @@ static void shrinkbuffers(lua_State* L) global_State* g = L->global; // check size of string hash if (g->strt.nuse < cast_to(uint32_t, g->strt.size / 4) && g->strt.size > LUA_MINSTRTABSIZE * 2) - { - if (DFFlag::LuauGcAgainstOom) - stringresizeprotected(L, g->strt.size / 2); // table is too big - else - luaS_resize(L, g->strt.size / 2); // table is too big - } + stringresizeprotected(L, g->strt.size / 2); // table is too big } static void shrinkbuffersfull(lua_State* L) @@ -722,12 +705,7 @@ static void shrinkbuffersfull(lua_State* L) while (g->strt.nuse < cast_to(uint32_t, hashsize / 4) && hashsize > LUA_MINSTRTABSIZE * 2) hashsize /= 2; if (hashsize != g->strt.size) - { - if (DFFlag::LuauGcAgainstOom) - stringresizeprotected(L, hashsize); // table is too big - else - luaS_resize(L, hashsize); // table is too big - } + stringresizeprotected(L, hashsize); // table is too big } static bool deletegco(void* context, lua_Page* page, GCObject* gco) diff --git a/VM/src/lgcdebug.cpp b/VM/src/lgcdebug.cpp index 609d3ec3..9cef3ae9 100644 --- a/VM/src/lgcdebug.cpp +++ b/VM/src/lgcdebug.cpp @@ -14,8 +14,6 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauHeapNameDetails) - static void validateobjref(global_State* g, GCObject* f, GCObject* t) { LUAU_ASSERT(!isdead(g, t)); @@ -728,20 +726,10 @@ static void enumclosure(EnumContext* ctx, Closure* cl) char buf[LUA_IDSIZE]; - if (FFlag::LuauHeapNameDetails) - { - if (p->source) - snprintf(buf, sizeof(buf), "%s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source)); - else - snprintf(buf, sizeof(buf), "%s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined); - } + if (p->source) + snprintf(buf, sizeof(buf), "%s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source)); else - { - if (p->source) - snprintf(buf, sizeof(buf), "%s:%d %s", p->debugname ? getstr(p->debugname) : "", p->linedefined, getstr(p->source)); - else - snprintf(buf, sizeof(buf), "%s:%d", p->debugname ? getstr(p->debugname) : "", p->linedefined); - } + snprintf(buf, sizeof(buf), "%s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined); enumnode(ctx, obj2gco(cl), sizeLclosure(cl->nupvalues), buf); } @@ -809,21 +797,10 @@ static void enumthread(EnumContext* ctx, lua_State* th) char buf[LUA_IDSIZE]; - if (FFlag::LuauHeapNameDetails) - { - if (p->source) - snprintf(buf, sizeof(buf), "thread at %s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source)); - else - snprintf(buf, sizeof(buf), "thread at %s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined); - } + if (p->source) + snprintf(buf, sizeof(buf), "thread at %s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source)); else - { - - if (p->source) - snprintf(buf, sizeof(buf), "%s:%d %s", p->debugname ? getstr(p->debugname) : "", p->linedefined, getstr(p->source)); - else - snprintf(buf, sizeof(buf), "%s:%d", p->debugname ? getstr(p->debugname) : "", p->linedefined); - } + snprintf(buf, sizeof(buf), "thread at %s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined); enumnode(ctx, obj2gco(th), size, buf); } @@ -856,21 +833,14 @@ static void enumproto(EnumContext* ctx, Proto* p) ctx->edge(ctx->context, enumtopointer(obj2gco(p)), p->execdata, "[native]"); } - if (FFlag::LuauHeapNameDetails) - { - char buf[LUA_IDSIZE]; + char buf[LUA_IDSIZE]; - if (p->source) - snprintf(buf, sizeof(buf), "proto %s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source)); - else - snprintf(buf, sizeof(buf), "proto %s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined); - - enumnode(ctx, obj2gco(p), size, buf); - } + if (p->source) + snprintf(buf, sizeof(buf), "proto %s:%d %s", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined, getstr(p->source)); else - { - enumnode(ctx, obj2gco(p), size, p->source ? getstr(p->source) : NULL); - } + snprintf(buf, sizeof(buf), "proto %s:%d", p->debugname ? getstr(p->debugname) : "unnamed", p->linedefined); + + enumnode(ctx, obj2gco(p), size, buf); if (p->sizek) enumedges(ctx, obj2gco(p), p->k, p->sizek, "constants"); diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index f5578a75..58c7869c 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -36,10 +36,7 @@ void luau_callhook(lua_State* L, lua_Hook hook, void* userdata); LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTINT(CodegenHeuristicsInstructionLimit) -LUAU_FASTFLAG(LuauYieldableContinuations) -LUAU_FASTFLAG(LuauHeapNameDetails) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) -LUAU_DYNAMIC_FASTFLAG(LuauGcAgainstOom) static lua_CompileOptions defaultOptions() { @@ -785,8 +782,6 @@ static void* blockableRealloc(void* ud, void* ptr, size_t osize, size_t nsize) TEST_CASE("GC") { - ScopedFastFlag luauGcAgainstOom{DFFlag::LuauGcAgainstOom, true}; - runConformance( "gc.luau", [](lua_State* L) @@ -1062,8 +1057,6 @@ int passthroughCallWithStateContinuation(lua_State* L, int status) TEST_CASE("CYield") { - ScopedFastFlag luauYieldableContinuations{FFlag::LuauYieldableContinuations, true}; - runConformance( "cyield.luau", [](lua_State* L) @@ -2322,8 +2315,6 @@ TEST_CASE("StringConversion") TEST_CASE("GCDump") { - ScopedFastFlag luauHeapNameDetails{FFlag::LuauHeapNameDetails, true}; - // internal function, declared in lgc.h - not exposed via lua.h extern void luaC_dump(lua_State * L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat)); extern void luaC_enumheap( diff --git a/tests/NonstrictMode.test.cpp b/tests/NonstrictMode.test.cpp index ed39df31..683028ba 100644 --- a/tests/NonstrictMode.test.cpp +++ b/tests/NonstrictMode.test.cpp @@ -12,6 +12,10 @@ using namespace Luau; + +LUAU_FASTFLAG(DebugLuauMagicTypes) +LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) + TEST_SUITE_BEGIN("NonstrictModeTests"); TEST_CASE_FIXTURE(Fixture, "infer_nullary_function") @@ -315,4 +319,39 @@ TEST_CASE_FIXTURE(Fixture, "returning_too_many_values") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "standalone_constraint_solving_incomplete_is_hidden_nonstrict") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::DebugLuauMagicTypes, true}, + {FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true}, + }; + + CheckResult results = check(R"( + --!nonstrict + local function _f(_x: _luau_force_constraint_solving_incomplete) end + )"); + + LUAU_REQUIRE_NO_ERRORS(results); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "non_standalone_constraint_solving_incomplete_is_hidden_nonstrict") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::DebugLuauMagicTypes, true}, + {FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true}, + }; + + CheckResult results = check(R"( + --!nonstrict + local function _f(_x: _luau_force_constraint_solving_incomplete) end + math.abs("pls") + )"); + + LUAU_REQUIRE_ERROR_COUNT(2, results); + CHECK(get(results.errors[0])); + CHECK(get(results.errors[1])); +} + TEST_SUITE_END(); diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index 4309d606..0c887117 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -11,9 +11,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) -LUAU_FASTFLAG(LuauUserTypeFunctionAliases) -LUAU_FASTFLAG(LuauFollowTypeAlias) -LUAU_FASTFLAG(LuauFollowExistingTypeFunction) LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAG(LuauTypeFunctionSerializeFollowMetatable) @@ -2257,7 +2254,6 @@ TEST_CASE_FIXTURE(Fixture, "typeof_is_not_a_valid_type_function_name") TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_call") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; CheckResult result = check(R"( type Test = T? @@ -2279,7 +2275,6 @@ local y: foo<{b: number}> = { b = 2 } TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_values") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; CheckResult result = check(R"( type Test = { a: number } @@ -2303,8 +2298,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_call_with_reduction") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; - CheckResult result = check(R"( type Test = rawget @@ -2327,8 +2320,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_implicit_export") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; - fileResolver.source["game/A"] = R"( type Test = rawget @@ -2356,7 +2347,6 @@ local y: Test.foo<{ a: string }> = "x" TEST_CASE_FIXTURE(ExternTypeFixture, "type_alias_not_too_many_globals") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; CheckResult result = check(R"( type function get() @@ -2372,7 +2362,6 @@ local function ok(idx: get<>): number return idx end TEST_CASE_FIXTURE(ExternTypeFixture, "type_alias_not_enough_arguments") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; CheckResult result = check(R"( type Test = (a: A, b: B) -> A @@ -2391,7 +2380,6 @@ local function ok(idx: get<>): number return idx end TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_can_call_packs") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; CheckResult result = check(R"( type Test = (U...) -> T @@ -2414,7 +2402,6 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "type_alias_reduction_errors") return; ScopedFastFlag sff[] = { - {FFlag::LuauUserTypeFunctionAliases, true}, {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauStuckTypeFunctionsStillDispatch, true}, }; @@ -2439,7 +2426,6 @@ local function ok(idx: get<>): number return idx end TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_unreferenced_do_not_block") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; CheckResult result = check(R"( type function foo(t) @@ -2458,8 +2444,6 @@ local x: foo TEST_CASE_FIXTURE(Fixture, "udtf_type_alias_registration_follows") { - ScopedFastFlag luauFollowTypeAlias{FFlag::LuauFollowTypeAlias, true}; - LUAU_REQUIRE_ERRORS(check(R"( export type t110 = ""type--" function _(...):(any)&(any) @@ -2490,7 +2474,6 @@ end TEST_CASE_FIXTURE(Fixture, "udtf_double_definition") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag luauFollowExistingTypeFunction{FFlag::LuauFollowExistingTypeFunction, true}; CheckResult result = check(R"( type function t0() diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index b2bb3448..c72309dd 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -11,7 +11,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauGuardAgainstMalformedTypeAliasExpansion2) -LUAU_FASTFLAG(LuauSkipMalformedTypeAliases) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) TEST_SUITE_BEGIN("TypeAliases"); @@ -1257,10 +1256,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_cursed_type_aliases") TEST_CASE_FIXTURE(Fixture, "type_alias_dont_crash_on_bad_name") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauSkipMalformedTypeAliases, true}, - }; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; CheckResult result = check(R"( type typeof = typeof(nil :: any) @@ -1276,7 +1272,6 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_dont_crash_on_duplicate_with_typeof") // // type Foo = typeof(setmetatable({} :: SomeType, {} :: SomeMetatableType)) // - ScopedFastFlag _{FFlag::LuauSkipMalformedTypeAliases, true}; CheckResult result = check(R"( type A = typeof(nil :: any) type A = typeof(nil :: any) diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index a903a037..8f5b5ecd 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -16,6 +16,7 @@ using std::nullopt; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) +LUAU_FASTFLAG(LuauScopeMethodsAreSolverAgnostic) TEST_SUITE_BEGIN("TypeInferExternTypes"); @@ -894,4 +895,28 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "cyclic_tables_are_assumed_to_be_compatible LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(ExternTypeFixture, "ice_while_checking_script_due_to_scopes_not_being_solver_agnostic") +{ + // This is intentional - if LuauSolverV2 is false, but we elect the new solver, we should still follow + // new solver code paths. + // This is necessary to repro an ice that can occur in studio + ScopedFastFlag luauSolverOff{FFlag::LuauSolverV2, false}; + getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New); + ScopedFastFlag sff{FFlag::LuauScopeMethodsAreSolverAgnostic, true}; + + auto result = check(R"( +local function ExitSeat(player, character, seat, weld) + --Find vehicle model + local model + local newParent = seat + repeat + model = newParent + newParent = model.Parent + until newParent.ClassName ~= "Model" + local part, _ = Raycast(seat.Position, dir, dist, {character, model}) +end +)"); + LUAU_REQUIRE_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 35f93128..44cfb485 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -17,9 +17,10 @@ LUAU_FASTFLAG(LuauBetterCannotCallFunctionPrimitive) LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAG(LuauNormalizationIntersectTablesPreservesExternTypes) LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect) -LUAU_FASTFLAG(LuauAvoidDoubleNegation) LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauRefineNoRefineAlways) +LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex) +LUAU_FASTFLAG(LuauForceSimplifyConstraint) using namespace Luau; @@ -1271,10 +1272,7 @@ TEST_CASE_FIXTURE(Fixture, "apply_refinements_on_astexprindexexpr_whose_subscrip TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x") { - ScopedFastFlag sffs[] = { - {FFlag::LuauAvoidDoubleNegation, true}, - {FFlag::LuauRefineTablesWithReadType, true}, - }; + ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true}; CheckResult result = check(R"( type T = {tag: "missing", x: nil} | {tag: "exists", x: string} @@ -2248,6 +2246,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "globals_can_be_narrowed_too") TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction") { + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauEagerGeneralization4, true}, + {FFlag::LuauStuckTypeFunctionsStillDispatch, true}, + {FFlag::LuauForceSimplifyConstraint, true}, + }; + CheckResult result = check(R"( local function isIndexKey(k, contiguousLength) return type(k) == "number" @@ -2257,7 +2262,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction" end )"); - LUAU_REQUIRE_NO_ERRORS(result); + LUAU_REQUIRE_ERROR_COUNT(3, result); + + // For some reason we emit three error here. + for (const auto& e : result.errors) + CHECK(get(e)); } TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_variant") @@ -2846,4 +2855,48 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_by_no_refine_should_always_reduce") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "table_name_index_without_prior_assignment_from_branch") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauDoNotPrototypeTableIndex, true}, + }; + + // The important part of this test case is: + // - `CharEntry` is represented as a phi node in the data flow graph; + // - We never _set_ `CharEntry.Player` prior to accessing it. + CheckResult results = check(R"( + local GetDictionary : (unknown, boolean) -> { Player: {} }? = nil :: any + + local CharEntry = GetDictionary(nil, false) + if not CharEntry then + CharEntry = GetDictionary(nil, true) + end + + local x = CharEntry.Player + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, results); + CHECK(get(results.errors[0])); + CHECK_EQ("{ }", toString(requireType("x"))); +} + +TEST_CASE_FIXTURE(Fixture, "cli_120460_table_access_on_phi_node") +{ + ScopedFastFlag _{FFlag::LuauDoNotPrototypeTableIndex, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + --!strict + local function foo(bar: string): string + local baz: boolean = true + if baz then + local _ = (bar:sub(1)) + else + local _ = (bar:sub(1)) + end + return bar:sub(2) -- previously this would be `...never` + end + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 1df666d4..86a23d95 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -37,6 +37,9 @@ LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck) LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauDfgForwardNilFromAndOr) +LUAU_FASTFLAG(LuauInferActualIfElseExprType) +LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex) +LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) TEST_SUITE_BEGIN("TableTests"); @@ -6108,4 +6111,67 @@ TEST_CASE_FIXTURE(Fixture, "oss_1888_and_or_subscriptable") )")); } +TEST_CASE_FIXTURE(Fixture, "cli_119126_regression") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck2, true}, + }; + + CheckResult results = check(R"( + type literals = "foo" | "bar" | "foobar" + + local exampleA: {[literals]: string} = { + foo = '1', + bar = 2, + foobar = 3, + } + )"); + + LUAU_REQUIRE_ERROR_COUNT(2, results); + for (const auto& err: results.errors) + { + auto e = get(err); + REQUIRE(e); + CHECK_EQ("string", toString(e->wantedType)); + CHECK_EQ("number", toString(e->givenType)); + } +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1914_access_after_assignment_with_assertion") +{ + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauDoNotPrototypeTableIndex, true}}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + --!strict + + type WallHolder = { + __type: "Model", + Wall: { + __type: "BasePart", + age: number, + }, + } + + local walls = { + { name = "Part1" }, + { name = "Part2" }, + { name = "Wall" }, + } + + local baseWall: WallHolder? + for _, wall in walls do + if wall.name == "Wall" then + baseWall = wall :: WallHolder + end + end + assert(baseWall, "Failed to get base wall when creating room props") + + local myAge = baseWall.Wall.age + )")); + + CHECK_EQ("number", toString(requireType("myAge"))); +} + + TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index dc7af332..5ea3441a 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -25,8 +25,6 @@ LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAG(LuauAvoidDoubleNegation) -LUAU_FASTFLAG(LuauInsertErrorTypesIntoIndexerResult) LUAU_FASTFLAG(LuauSimplifyOutOfLine2) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature) @@ -35,6 +33,12 @@ LUAU_FASTFLAG(LuauMissingFollowInAssignIndexConstraint) LUAU_FASTFLAG(LuauOccursCheckForRefinement) LUAU_FASTFLAG(LuauInferPolarityOfReadWriteProperties) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) +LUAU_FASTFLAG(LuauInferActualIfElseExprType) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) +LUAU_FASTFLAG(LuauForceSimplifyConstraint) +LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) +LUAU_FASTFLAG(DebugLuauMagicTypes) +LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) using namespace Luau; @@ -2118,10 +2122,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_missing_follow_table_freeze") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_avoid_double_negation" * doctest::timeout(0.5)) { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauAvoidDoubleNegation, true}, - }; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; + // We don't care about errors, only that we don't OOM during typechecking. LUAU_REQUIRE_ERRORS(check(R"( local _ = _ @@ -2153,8 +2155,6 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_has_indexer_can_create_cyclic_union") { - ScopedFastFlag _{FFlag::LuauInsertErrorTypesIntoIndexerResult, true}; - LUAU_REQUIRE_ERRORS(check(R"( local _ = nil repeat @@ -2442,4 +2442,153 @@ end then _._G else ... )")); } +TEST_CASE_FIXTURE(Fixture, "oss_1815_verbatim") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauInferActualIfElseExprType, true}, + // This is needed so that we don't hide the string literal free types + // behind a `union<_, _>` + {FFlag::LuauSimplifyOutOfLine2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck2, true}, + }; + + CheckResult results = check(R"( + --!strict + local item: "foo" = "bar" + item = if true then "foo" else "foo" + + local item2: "foo" = if true then "doge" else "doge2" + )"); + LUAU_REQUIRE_ERROR_COUNT(3, results); + CHECK_EQ(results.errors[0].location, Location{{2, 28}, {2, 33}}); + auto err1 = get(results.errors[0]); + REQUIRE(err1); + CHECK_EQ("\"foo\"", toString(err1->wantedType)); + CHECK_EQ("\"bar\"", toString(err1->givenType)); + CHECK_EQ(results.errors[1].location, Location{{5, 42}, {5, 48}}); + auto err2 = get(results.errors[1]); + REQUIRE(err2); + CHECK_EQ("\"foo\"", toString(err2->wantedType)); + CHECK_EQ("\"doge\"", toString(err2->givenType)); + CHECK_EQ(results.errors[2].location, Location{{5, 54}, {5, 61}}); + auto err3 = get(results.errors[2]); + REQUIRE(err3); + CHECK_EQ("\"foo\"", toString(err3->wantedType)); + CHECK_EQ("\"doge2\"", toString(err3->givenType)); + +} + +TEST_CASE_FIXTURE(Fixture, "if_then_else_bidirectional_inference") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauInferActualIfElseExprType, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck2, true}, + }; + + CheckResult results = check(R"( + type foo = { + bar: (() -> string)?, + } + local qux: foo = if false then {} else 10 + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, results); + auto err = get(results.errors[0]); + REQUIRE(err); + CHECK_EQ("number", toString(err->givenType)); + CHECK_EQ("foo", toString(err->wantedType)); +} + +TEST_CASE_FIXTURE(Fixture, "if_then_else_two_errors") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauInferActualIfElseExprType, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck2, true}, + }; + + CheckResult results = check(R"( + type foo = { + bar: () -> string, + } + local qux: foo = if false then {} else 10 + )"); + + LUAU_REQUIRE_ERROR_COUNT(2, results); + auto err1 = get(results.errors[0]); + REQUIRE(err1); + CHECK_EQ("foo", toString(err1->superType)); + CHECK_EQ("{ }", toString(err1->subType)); + auto err2 = get(results.errors[1]); + REQUIRE(err2); + CHECK_EQ("foo", toString(err2->wantedType)); + CHECK_EQ("number", toString(err2->givenType)); +} + +TEST_CASE_FIXTURE(Fixture, "simplify_constraint_can_force") +{ + ScopedFastFlag sff[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauForceSimplifyConstraint, true}, + {FFlag::LuauSimplifyOutOfLine2, true}, + // NOTE: Feel free to clip this test when this flag is clipped. + {FFlag::LuauPushFunctionTypesInFunctionStatement, false}, + }; + + CheckResult result = check(R"( + --!strict + + local foo = nil + + bar(function() + if foo then + foo.baz() + end + end) + + foo = {} + + foo.a = { + foo.b + } + )"); + + LUAU_CHECK_NO_ERROR(result, ConstraintSolvingIncompleteError); +} + +TEST_CASE_FIXTURE(Fixture, "standalone_constraint_solving_incomplete_is_hidden") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::DebugLuauMagicTypes, true}, + {FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true}, + }; + + CheckResult results = check(R"( + local function _f(_x: _luau_force_constraint_solving_incomplete) end + )"); + + LUAU_REQUIRE_NO_ERRORS(results); +} + +TEST_CASE_FIXTURE(Fixture, "non_standalone_constraint_solving_incomplete_is_hidden") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::DebugLuauMagicTypes, true}, + {FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true}, + }; + + CheckResult results = check(R"( + local function _f(_x: _luau_force_constraint_solving_incomplete) end + local x: number = true + )"); + + LUAU_REQUIRE_ERROR_COUNT(2, results); + CHECK(get(results.errors[0])); + CHECK(get(results.errors[1])); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.unknownnever.test.cpp b/tests/TypeInfer.unknownnever.test.cpp index f22d561c..f8a58c42 100644 --- a/tests/TypeInfer.unknownnever.test.cpp +++ b/tests/TypeInfer.unknownnever.test.cpp @@ -9,6 +9,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauEagerGeneralization4); LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch); +LUAU_FASTFLAG(LuauForceSimplifyConstraint) TEST_SUITE_BEGIN("TypeInferUnknownNever"); @@ -329,21 +330,30 @@ TEST_CASE_FIXTURE(Fixture, "length_of_never") TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_in_any_ordering_operators") { + ScopedFastFlag sffs[] = { + {FFlag::LuauEagerGeneralization4, true}, + {FFlag::LuauStuckTypeFunctionsStillDispatch, true}, + {FFlag::LuauForceSimplifyConstraint, true}, + }; + CheckResult result = check(R"( local function ord(x: nil, y) return x ~= nil and x > y end )"); - LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::LuauSolverV2) { - // FIXME: CLI-152325 - CHECK_EQ("(nil, nil & ~nil) -> boolean", toString(requireType("ord"))); + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); + CHECK_EQ("(nil, a) -> false | le", toString(requireType("ord"))); } else + { + LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ("(nil, a) -> boolean", toString(requireType("ord"))); + } } TEST_CASE_FIXTURE(Fixture, "math_operators_and_never")