From f3f3bf8f729967bd42d518a97773fb800d0191f9 Mon Sep 17 00:00:00 2001 From: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Date: Fri, 1 Aug 2025 14:44:50 -0700 Subject: [PATCH] Sync to upstream/release/685 (#1940) Another week, another release! ## Analysis - Do not warn on unknown `require`s in non-strict mode. - Improve `Luau::dump`'s output for `DenseHashMap`. - Raise type checking errors when we would otherwise be leaking internal error types from modules. - Fix a crash that would sometimes occur when calling a function with an incomplete type. - Replace uses of `FFlag::LuauSolverV2` in `ClonePublicInterface` with a `solverMode` field. - Limit the number of constraints that can be dynamically created to fail more gracefully in complex cases. - Fix #1932. --------- Co-authored-by: Alexander Youngblood Co-authored-by: Andy Friesen Co-authored-by: Ariel Weiss Co-authored-by: Hunter Goldstein Co-authored-by: Sora Kanosue Co-authored-by: Talha Pathan Co-authored-by: Vighnesh Vijay Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/ConstraintSolver.h | 5 + Analysis/include/Luau/Frontend.h | 3 +- Analysis/include/Luau/TypeUtils.h | 3 + Analysis/include/Luau/UnifierSharedState.h | 2 +- Analysis/src/AstJsonEncoder.cpp | 6 + Analysis/src/Constraint.cpp | 6 +- Analysis/src/ConstraintGenerator.cpp | 26 ++- Analysis/src/ConstraintSolver.cpp | 50 +++- Analysis/src/DataFlowGraph.cpp | 252 ++++++--------------- Analysis/src/Frontend.cpp | 74 ++++-- Analysis/src/Module.cpp | 87 +++++-- Analysis/src/NonStrictTypeChecker.cpp | 17 ++ Analysis/src/Subtyping.cpp | 11 +- Analysis/src/ToString.cpp | 4 +- Analysis/src/TypeChecker2.cpp | 57 ++++- tests/AstJsonEncoder.test.cpp | 14 ++ tests/DataFlowGraph.test.cpp | 11 - tests/NonStrictTypeChecker.test.cpp | 47 ++++ tests/RuntimeLimits.test.cpp | 36 +++ tests/TypeInfer.anyerror.test.cpp | 3 - tests/TypeInfer.functions.test.cpp | 39 ++++ tests/TypeInfer.loops.test.cpp | 33 +-- tests/TypeInfer.modules.test.cpp | 20 ++ tests/TypeInfer.provisional.test.cpp | 7 +- tests/TypeInfer.refinements.test.cpp | 33 ++- tests/TypeInfer.test.cpp | 51 ++++- tests/TypeInfer.typestates.test.cpp | 3 +- tests/TypeInfer.unknownnever.test.cpp | 4 +- 28 files changed, 594 insertions(+), 310 deletions(-) diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index cbf59a6e..e7781e88 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -102,6 +102,11 @@ struct ConstraintSolver // scope tree. std::vector> solverConstraints; + // Ticks downward toward zero each time a new constraint is pushed into + // solverConstraints. When this counter reaches zero, the type inference + // engine reports a CodeTooComplex error and aborts. + size_t solverConstraintLimit = 0; + // This includes every constraint that has not been fully solved. // A constraint can be both blocked and unsolved, for instance. std::vector> unsolvedConstraints; diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index 983b41d9..915e4311 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -113,7 +113,8 @@ struct FrontendOptions bool applyInternalLimitScaling = false; // An optional callback which is called for every *dirty* module was checked - // Is multi-threaded typechecking is used, this callback might be called from multiple threads and has to be thread-safe + // If multi-threaded typechecking is used, this callback might be called + // from multiple threads and has to be thread-safe std::function customModuleCheck; bool collectTypeAllocationStats = false; diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h index a65d1568..26efef1a 100644 --- a/Analysis/include/Luau/TypeUtils.h +++ b/Analysis/include/Luau/TypeUtils.h @@ -359,6 +359,9 @@ inline constexpr char kLuauPrint[] = "_luau_print"; // a constraint solving incomplete error to test semantics around that specific // error. inline constexpr char kLuauForceConstraintSolvingIncomplete[] = "_luau_force_constraint_solving_incomplete"; +// `_luau_blocked_type` will cause us to always mint a blocked type that does +// not get emplaced by constraint solving. +inline constexpr char kLuauBlockedType[] = "_luau_blocked_type"; } // namespace Luau diff --git a/Analysis/include/Luau/UnifierSharedState.h b/Analysis/include/Luau/UnifierSharedState.h index bc2acbf1..e5bb2da2 100644 --- a/Analysis/include/Luau/UnifierSharedState.h +++ b/Analysis/include/Luau/UnifierSharedState.h @@ -34,7 +34,7 @@ struct UnifierCounters struct UnifierSharedState { - UnifierSharedState(InternalErrorReporter* iceHandler) + explicit UnifierSharedState(InternalErrorReporter* iceHandler) : iceHandler(iceHandler) { } diff --git a/Analysis/src/AstJsonEncoder.cpp b/Analysis/src/AstJsonEncoder.cpp index 65b3018e..64f932d6 100644 --- a/Analysis/src/AstJsonEncoder.cpp +++ b/Analysis/src/AstJsonEncoder.cpp @@ -1465,6 +1465,12 @@ struct AstJsonEncoder : public AstVisitor return false; } + bool visit(class AstTypeOptional* node) override + { + write(node); + return false; + } + bool visit(class AstTypeUnion* node) override { write(node); diff --git a/Analysis/src/Constraint.cpp b/Analysis/src/Constraint.cpp index 0211083d..d75f5f76 100644 --- a/Analysis/src/Constraint.cpp +++ b/Analysis/src/Constraint.cpp @@ -6,7 +6,7 @@ LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) -LUAU_FASTFLAG(LuauForceSimplifyConstraint) +LUAU_FASTFLAG(LuauForceSimplifyConstraint2) namespace Luau { @@ -66,7 +66,7 @@ struct ReferenceCountInitializer_DEPRECATED : TypeOnceVisitor bool visit(TypeId, const TypeFunctionInstanceType& tfit) override { - if (FFlag::LuauForceSimplifyConstraint) + if (FFlag::LuauForceSimplifyConstraint2) return tfit.function->canReduceGenerics; else return FFlag::LuauEagerGeneralization4 && traverseIntoTypeFunctions; @@ -121,7 +121,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor bool visit(TypeId, const TypeFunctionInstanceType& tfit) override { - if (FFlag::LuauForceSimplifyConstraint) + if (FFlag::LuauForceSimplifyConstraint2) return tfit.function->canReduceGenerics; else return FFlag::LuauEagerGeneralization4 && traverseIntoTypeFunctions; diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 21910e38..17c93999 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -41,7 +41,6 @@ LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties) LUAU_FASTFLAGVARIABLE(LuauSimplifyOutOfLine2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) -LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAGVARIABLE(LuauDisablePrimitiveInferenceInLargeTables) LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500) LUAU_FASTFLAGVARIABLE(LuauSkipLvalueForCompoundAssignment) @@ -51,6 +50,7 @@ LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTracksRValueRefinements) LUAU_FASTFLAGVARIABLE(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAGVARIABLE(LuauInferActualIfElseExprType) LUAU_FASTFLAGVARIABLE(LuauDoNotPrototypeTableIndex) +LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving) namespace Luau { @@ -1292,8 +1292,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFor* for_) visit(forScope, for_->body); - if (FFlag::LuauDfgAllowUpdatesInLoops) - scope->inheritAssignments(forScope); + scope->inheritAssignments(forScope); return ControlFlow::None; } @@ -1353,8 +1352,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI visit(loopScope, forIn->body); Checkpoint end = checkpoint(this); - if (FFlag::LuauDfgAllowUpdatesInLoops) - scope->inheritAssignments(loopScope); + scope->inheritAssignments(loopScope); // This iter constraint must dispatch first. forEachConstraint( @@ -1379,8 +1377,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatWhile* whil visit(whileScope, while_->body); - if (FFlag::LuauDfgAllowUpdatesInLoops) - scope->inheritAssignments(whileScope); + scope->inheritAssignments(whileScope); return ControlFlow::None; } @@ -1393,8 +1390,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatRepeat* rep check(repeatScope, repeat->condition); - if (FFlag::LuauDfgAllowUpdatesInLoops) - scope->inheritAssignments(repeatScope); + scope->inheritAssignments(repeatScope); return ControlFlow::None; } @@ -1997,6 +1993,14 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareExte } ); + if (FFlag::LuauLimitDynamicConstraintSolving) + { + // If we don't emplace an error type here, then later we'll be + // exposing a blocked type in this file's type interface. This + // is _normally_ harmless. + emplaceType(asMutable(bindingIt->second.type), builtinTypes->errorType); + } + return ControlFlow::None; } } @@ -3771,6 +3775,10 @@ TypeId ConstraintGenerator::resolveReferenceType( else return resolveType_(scope, ref->parameters.data[0].type, inTypeArguments); } + else if (FFlag::LuauLimitDynamicConstraintSolving && ref->name == "_luau_blocked_type") + { + return arena->addType(BlockedType{}); + } } std::optional alias; diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index ea81acf1..fd589bc6 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -26,11 +26,13 @@ #include #include +LUAU_FASTINTVARIABLE(LuauSolverConstraintLimit, 1000) +LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500) + LUAU_FASTFLAGVARIABLE(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies) LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings) -LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) @@ -41,8 +43,10 @@ LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAGVARIABLE(LuauUseOrderedTypeSetsInConstraints) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying) -LUAU_FASTFLAGVARIABLE(LuauForceSimplifyConstraint) +LUAU_FASTFLAGVARIABLE(LuauForceSimplifyConstraint2) +LUAU_FASTFLAGVARIABLE(LuauCollapseShouldNotCrash) LUAU_FASTFLAGVARIABLE(LuauContainsAnyGenericFollowBeforeChecking) +LUAU_FASTFLAGVARIABLE(LuauLimitDynamicConstraintSolving) namespace Luau { @@ -333,6 +337,7 @@ ConstraintSolver::ConstraintSolver( , rootScope(constraintSet.rootScope) , currentModuleName(std::move(moduleName)) , dfg(dfg) + , solverConstraintLimit(FInt::LuauSolverConstraintLimit) , moduleResolver(moduleResolver) , requireCycles(std::move(requireCycles)) , logger(logger) @@ -367,6 +372,7 @@ ConstraintSolver::ConstraintSolver( , rootScope(rootScope) , currentModuleName(std::move(moduleName)) , dfg(dfg) + , solverConstraintLimit(FInt::LuauSolverConstraintLimit) , moduleResolver(moduleResolver) , requireCycles(std::move(requireCycles)) , logger(logger) @@ -461,6 +467,11 @@ void ConstraintSolver::run() if (limits.cancellationToken && limits.cancellationToken->requested()) throwUserCancelError(); + // If we were _given_ a limit, and the current limit has hit zero, ] + // then early exit from constraint solving. + if (FFlag::LuauLimitDynamicConstraintSolving && FInt::LuauSolverConstraintLimit > 0 && solverConstraintLimit == 0) + break; + std::string saveMe = FFlag::DebugLuauLogSolver ? toString(*c, opts) : std::string{}; StepSnapshot snapshot; @@ -1528,7 +1539,9 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNullscope, constraint->location, result, ty); } + if (FFlag::LuauForceSimplifyConstraint2) + { + // If we forced, then there _may_ be blocked types, and we should + // include those in the union as well. + for (TypeId ty : finder.blockedTys) + { + ty = follow(ty); + if (ty == target) + continue; + result = simplifyUnion(constraint->scope, constraint->location, result, ty); + } + } emplaceType(asMutable(target), result); return true; } @@ -3832,6 +3857,17 @@ NotNull ConstraintSolver::pushConstraint(NotNull scope, const solverConstraints.push_back(std::move(c)); unsolvedConstraints.emplace_back(borrow); + if (FFlag::LuauLimitDynamicConstraintSolving) + { + if (solverConstraintLimit > 0) + { + --solverConstraintLimit; + + if (solverConstraintLimit == 0) + reportError(CodeTooComplex{}, location); + } + } + return borrow; } @@ -3899,7 +3935,7 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target) if (!isReferenceCountedType(target)) return; - if (FFlag::LuauForceSimplifyConstraint) + if (FFlag::LuauForceSimplifyConstraint2) { // This can happen in the _very_ specific case of: // diff --git a/Analysis/src/DataFlowGraph.cpp b/Analysis/src/DataFlowGraph.cpp index 6ffe3353..5d821ef9 100644 --- a/Analysis/src/DataFlowGraph.cpp +++ b/Analysis/src/DataFlowGraph.cpp @@ -14,7 +14,6 @@ LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull) -LUAU_FASTFLAGVARIABLE(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauFragmentAutocompleteTracksRValueRefinements) LUAU_FASTFLAGVARIABLE(LuauDfgForwardNilFromAndOr) @@ -157,33 +156,13 @@ void DfgScope::inherit(const DfgScope* childScope) bool DfgScope::canUpdateDefinition(Symbol symbol) const { - if (FFlag::LuauDfgAllowUpdatesInLoops) - return true; - - for (const DfgScope* current = this; current; current = current->parent) - { - if (current->bindings.find(symbol)) - return true; - else if (current->scopeType == DfgScope::Loop) - return false; - } - + // NOTE: Vestigial as of clipping LuauDfgAllowUpdatesInLoops return true; } bool DfgScope::canUpdateDefinition(DefId def, const std::string& key) const { - if (FFlag::LuauDfgAllowUpdatesInLoops) - return true; - - for (const DfgScope* current = this; current; current = current->parent) - { - if (auto props = current->props.find(def)) - return true; - else if (current->scopeType == DfgScope::Loop) - return false; - } - + // NOTE: Vestigial as of clipping LuauDfgAllowUpdatesInLoops return true; } @@ -541,39 +520,20 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w) // allow a string to flow into a position that expects. DfgScope* whileScope = makeChildScope(DfgScope::Loop); - if (FFlag::LuauDfgAllowUpdatesInLoops) + ControlFlow cf; { - - ControlFlow cf; - { - PushScope ps{scopeStack, whileScope}; - visitExpr(w->condition); - cf = visit(w->body); - } - - auto scope = currentScope(); - // If the inner loop unconditioanlly returns or throws we shouldn't - // consume any type state from the loop body. - if (!matches(cf, ControlFlow::Returns | ControlFlow::Throws)) - join(scope, scope, whileScope); - - return ControlFlow::None; + PushScope ps{scopeStack, whileScope}; + visitExpr(w->condition); + cf = visit(w->body); } - else - { - { - PushScope ps{scopeStack, whileScope}; - visitExpr(w->condition); - visit(w->body); - } - if (FFlag::LuauDfgScopeStackNotNull) - currentScope()->inherit(whileScope); - else - currentScope_DEPRECATED()->inherit(whileScope); + auto scope = currentScope(); + // If the inner loop unconditioanlly returns or throws we shouldn't + // consume any type state from the loop body. + if (!matches(cf, ControlFlow::Returns | ControlFlow::Throws)) + join(scope, scope, whileScope); - return ControlFlow::None; - } + return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r) @@ -582,41 +542,23 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r) // does not consider the _second_ loop iteration. DfgScope* repeatScope = makeChildScope(DfgScope::Loop); - if (FFlag::LuauDfgAllowUpdatesInLoops) + ControlFlow cf; + { - ControlFlow cf; - - { - PushScope ps{scopeStack, repeatScope}; - cf = visitBlockWithoutChildScope(r->body); - visitExpr(r->condition); - } - - // Ultimately: the options for a repeat-until loop are more - // straightforward. - currentScope()->inherit(repeatScope); - - // `repeat` loops will unconditionally fire: if the internal control - // flow is unconditionally a break or continue, then we have linear - // control flow, but if it's throws or returns, then we need to - // return _that_ to the parent. - return matches(cf, ControlFlow::Breaks | ControlFlow::Continues) ? ControlFlow::None : cf; + PushScope ps{scopeStack, repeatScope}; + cf = visitBlockWithoutChildScope(r->body); + visitExpr(r->condition); } - else - { - { - PushScope ps{scopeStack, repeatScope}; - visitBlockWithoutChildScope(r->body); - visitExpr(r->condition); - } - if (FFlag::LuauDfgScopeStackNotNull) - currentScope()->inherit(repeatScope); - else - currentScope_DEPRECATED()->inherit(repeatScope); + // Ultimately: the options for a repeat-until loop are more + // straightforward. + currentScope()->inherit(repeatScope); - return ControlFlow::None; - } + // `repeat` loops will unconditionally fire: if the internal control + // flow is unconditionally a break or continue, then we have linear + // control flow, but if it's throws or returns, then we need to + // return _that_ to the parent. + return matches(cf, ControlFlow::Breaks | ControlFlow::Continues) ? ControlFlow::None : cf; } ControlFlow DataFlowGraphBuilder::visit(AstStatBreak* b) @@ -694,135 +636,67 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f) if (f->step) visitExpr(f->step); - if (FFlag::LuauDfgAllowUpdatesInLoops) + ControlFlow cf; { + PushScope ps{scopeStack, forScope}; - ControlFlow cf; - { - PushScope ps{scopeStack, forScope}; + if (f->var->annotation) + visitType(f->var->annotation); - if (f->var->annotation) - visitType(f->var->annotation); + DefId def = defArena->freshCell(f->var, f->var->location); + graph.localDefs[f->var] = def; + currentScope()->bindings[f->var] = def; + captures[f->var].allVersions.push_back(def); - DefId def = defArena->freshCell(f->var, f->var->location); - graph.localDefs[f->var] = def; - currentScope()->bindings[f->var] = def; - captures[f->var].allVersions.push_back(def); - - cf = visit(f->body); - } - - auto scope = currentScope(); - // If the inner loop unconditioanlly returns or throws we shouldn't - // consume any type state from the loop body. - if (!matches(cf, ControlFlow::Returns | ControlFlow::Throws)) - join(scope, scope, forScope); - - return ControlFlow::None; + cf = visit(f->body); } - else - { - { - PushScope ps{scopeStack, forScope}; - if (f->var->annotation) - visitType(f->var->annotation); + auto scope = currentScope(); + // If the inner loop unconditioanlly returns or throws we shouldn't + // consume any type state from the loop body. + if (!matches(cf, ControlFlow::Returns | ControlFlow::Throws)) + join(scope, scope, forScope); - DefId def = defArena->freshCell(f->var, f->var->location); - graph.localDefs[f->var] = def; - if (FFlag::LuauDfgScopeStackNotNull) - currentScope()->bindings[f->var] = def; - else - currentScope_DEPRECATED()->bindings[f->var] = def; - captures[f->var].allVersions.push_back(def); - - // TODO(controlflow): entry point has a back edge from exit point - visit(f->body); - } - - if (FFlag::LuauDfgScopeStackNotNull) - currentScope()->inherit(forScope); - else - currentScope_DEPRECATED()->inherit(forScope); - - return ControlFlow::None; - } + return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f) { DfgScope* forScope = makeChildScope(DfgScope::Loop); - if (FFlag::LuauDfgAllowUpdatesInLoops) + ControlFlow cf; { + PushScope ps{scopeStack, forScope}; - ControlFlow cf; + for (AstLocal* local : f->vars) { - PushScope ps{scopeStack, forScope}; + if (local->annotation) + visitType(local->annotation); - for (AstLocal* local : f->vars) - { - if (local->annotation) - visitType(local->annotation); - - DefId def = defArena->freshCell(local, local->location); - graph.localDefs[local] = def; - if (FFlag::LuauDfgScopeStackNotNull) - currentScope()->bindings[local] = def; - else - currentScope_DEPRECATED()->bindings[local] = def; - captures[local].allVersions.push_back(def); - } - - // TODO(controlflow): entry point has a back edge from exit point - // We're gonna need a `visitExprList` and `visitVariadicExpr` (function calls and `...`) - for (AstExpr* e : f->values) - visitExpr(e); - - cf = visit(f->body); + DefId def = defArena->freshCell(local, local->location); + graph.localDefs[local] = def; + if (FFlag::LuauDfgScopeStackNotNull) + currentScope()->bindings[local] = def; + else + currentScope_DEPRECATED()->bindings[local] = def; + captures[local].allVersions.push_back(def); } - auto scope = currentScope(); - // If the inner loop unconditioanlly returns or throws we shouldn't - // consume any type state from the loop body. - if (!matches(cf, ControlFlow::Returns | ControlFlow::Throws)) - join(scope, scope, forScope); + // TODO(controlflow): entry point has a back edge from exit point + // We're gonna need a `visitExprList` and `visitVariadicExpr` (function calls and `...`) + for (AstExpr* e : f->values) + visitExpr(e); - return ControlFlow::None; + cf = visit(f->body); } - else - { - { - PushScope ps{scopeStack, forScope}; - for (AstLocal* local : f->vars) - { - if (local->annotation) - visitType(local->annotation); + auto scope = currentScope(); + // If the inner loop unconditioanlly returns or throws we shouldn't + // consume any type state from the loop body. + if (!matches(cf, ControlFlow::Returns | ControlFlow::Throws)) + join(scope, scope, forScope); - DefId def = defArena->freshCell(local, local->location); - graph.localDefs[local] = def; - if (FFlag::LuauDfgScopeStackNotNull) - currentScope()->bindings[local] = def; - else - currentScope_DEPRECATED()->bindings[local] = def; - captures[local].allVersions.push_back(def); - } - - // TODO(controlflow): entry point has a back edge from exit point - // We're gonna need a `visitExprList` and `visitVariadicExpr` (function calls and `...`) - for (AstExpr* e : f->values) - visitExpr(e); - - visit(f->body); - } - if (FFlag::LuauDfgScopeStackNotNull) - currentScope()->inherit(forScope); - else - currentScope_DEPRECATED()->inherit(forScope); - - return ControlFlow::None; - } + return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(AstStatAssign* a) diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index ed920643..5dfcdf5a 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -49,6 +49,7 @@ LUAU_FASTFLAGVARIABLE(LuauTrackTypeAllocations) LUAU_FASTFLAGVARIABLE(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) LUAU_FASTFLAGVARIABLE(DebugLuauAlwaysShowConstraintSolvingIncomplete) +LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving) namespace Luau { @@ -1653,38 +1654,77 @@ ModulePtr check( sourceModule.root->visit(&etv); } + // NOTE: This used to be done prior to cloning the public interface, but + // we now replace "internal" types with `*error-type*`. + if (FFlag::LuauLimitDynamicConstraintSolving) + { + if (FFlag::DebugLuauForbidInternalTypes) + { + InternalTypeFinder finder; + + // `result->returnType` is not filled in yet, so we + // traverse the return type of the root module. + finder.traverse(result->getModuleScope()->returnType); + + for (const auto& [_, binding] : result->exportedTypeBindings) + finder.traverse(binding.type); + + for (const auto& [_, ty] : result->astTypes) + finder.traverse(ty); + + for (const auto& [_, ty] : result->astExpectedTypes) + finder.traverse(ty); + + for (const auto& [_, tp] : result->astTypePacks) + finder.traverse(tp); + + for (const auto& [_, ty] : result->astResolvedTypes) + finder.traverse(ty); + + for (const auto& [_, ty] : result->astOverloadResolvedTypes) + finder.traverse(ty); + + for (const auto& [_, tp] : result->astResolvedTypePacks) + finder.traverse(tp); + } + } + + unfreeze(result->interfaceTypes); if (FFlag::LuauUseWorkspacePropToChooseSolver) result->clonePublicInterface(builtinTypes, *iceHandler, SolverMode::New); else result->clonePublicInterface_DEPRECATED(builtinTypes, *iceHandler); - if (FFlag::DebugLuauForbidInternalTypes) + if (!FFlag::LuauLimitDynamicConstraintSolving) { - InternalTypeFinder finder; + if (FFlag::DebugLuauForbidInternalTypes) + { + InternalTypeFinder finder; - finder.traverse(result->returnType); + finder.traverse(result->returnType); - for (const auto& [_, binding] : result->exportedTypeBindings) - finder.traverse(binding.type); + for (const auto& [_, binding] : result->exportedTypeBindings) + finder.traverse(binding.type); - for (const auto& [_, ty] : result->astTypes) - finder.traverse(ty); + for (const auto& [_, ty] : result->astTypes) + finder.traverse(ty); - for (const auto& [_, ty] : result->astExpectedTypes) - finder.traverse(ty); + for (const auto& [_, ty] : result->astExpectedTypes) + finder.traverse(ty); - for (const auto& [_, tp] : result->astTypePacks) - finder.traverse(tp); + for (const auto& [_, tp] : result->astTypePacks) + finder.traverse(tp); - for (const auto& [_, ty] : result->astResolvedTypes) - finder.traverse(ty); + for (const auto& [_, ty] : result->astResolvedTypes) + finder.traverse(ty); - for (const auto& [_, ty] : result->astOverloadResolvedTypes) - finder.traverse(ty); + for (const auto& [_, ty] : result->astOverloadResolvedTypes) + finder.traverse(ty); - for (const auto& [_, tp] : result->astResolvedTypePacks) - finder.traverse(tp); + for (const auto& [_, tp] : result->astResolvedTypePacks) + finder.traverse(tp); + } } // It would be nice if we could freeze the arenas before doing type diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index cbe10f37..12c538e7 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -15,6 +15,8 @@ #include LUAU_FASTFLAG(LuauSolverV2); +LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) +LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving) namespace Luau { @@ -95,6 +97,9 @@ struct ClonePublicInterface : Substitution { NotNull builtinTypes; NotNull module; + // NOTE: This can be made non-optional after + // LuauUseWorkspacePropToChooseSolver is clipped. + std::optional solverMode{std::nullopt}; ClonePublicInterface(const TxnLog* log, NotNull builtinTypes, Module* module) : Substitution(log, &module->interfaceTypes) @@ -104,6 +109,20 @@ struct ClonePublicInterface : Substitution LUAU_ASSERT(module); } + ClonePublicInterface(const TxnLog* log, NotNull builtinTypes, Module* module, SolverMode solverMode) + : Substitution(log, &module->interfaceTypes) + , builtinTypes(builtinTypes) + , module(module) + , solverMode(solverMode) + { + LUAU_ASSERT(module); + } + + bool isNewSolver() const + { + return FFlag::LuauSolverV2 || (FFlag::LuauUseWorkspacePropToChooseSolver && solverMode == SolverMode::New); + } + bool isDirty(TypeId ty) override { if (ty->owningArena == &module->internalTypes) @@ -157,25 +176,45 @@ struct ClonePublicInterface : Substitution else if (TableType* ttv = getMutable(result)) { ttv->level = TypeLevel{0, 0}; - if (FFlag::LuauSolverV2) + if (isNewSolver()) ttv->scope = nullptr; } - if (FFlag::LuauSolverV2) + if (isNewSolver()) { - if (auto freety = getMutable(result)) + if (FFlag::LuauLimitDynamicConstraintSolving) { - module->errors.emplace_back( - freety->scope->location, - module->name, - InternalError{"Free type is escaping its module; please report this bug at " - "https://github.com/luau-lang/luau/issues"} - ); - result = builtinTypes->errorType; + if (is(ty)) + { + module->errors.emplace_back( + Location{}, // Not amazing but the best we can do. + module->name, + InternalError{"An internal type is escaping this module; please report this bug at " + "https://github.com/luau-lang/luau/issues"} + ); + result = builtinTypes->errorType; + } + else if (auto genericty = getMutable(result)) + { + genericty->scope = nullptr; + } } - else if (auto genericty = getMutable(result)) + else { - genericty->scope = nullptr; + if (auto freety = getMutable(result)) + { + module->errors.emplace_back( + freety->scope->location, + module->name, + InternalError{"Free type is escaping its module; please report this bug at " + "https://github.com/luau-lang/luau/issues"} + ); + result = builtinTypes->errorType; + } + else if (auto genericty = getMutable(result)) + { + genericty->scope = nullptr; + } } } @@ -184,8 +223,27 @@ struct ClonePublicInterface : Substitution TypePackId clean(TypePackId tp) override { - if (FFlag::LuauSolverV2) + if (isNewSolver()) { + if (FFlag::LuauLimitDynamicConstraintSolving) + { + if (is(tp)) + { + module->errors.emplace_back( + Location{}, + module->name, + InternalError{"An internal type pack is escaping this module; please report this bug at " + "https://github.com/luau-lang/luau/issues"} + ); + return builtinTypes->errorTypePack; + } + + auto clonedTp = clone(tp); + if (auto gtp = getMutable(clonedTp)) + gtp->scope = nullptr; + return clonedTp; + } + auto clonedTp = clone(tp); if (auto ftp = getMutable(clonedTp)) { @@ -200,6 +258,7 @@ struct ClonePublicInterface : Substitution else if (auto gtp = getMutable(clonedTp)) gtp->scope = nullptr; return clonedTp; + } else { @@ -325,7 +384,7 @@ void Module::clonePublicInterface(NotNull builtinTypes, InternalEr std::optional varargPack = mode == SolverMode::New ? std::nullopt : moduleScope->varargPack; TxnLog log; - ClonePublicInterface clonePublicInterface{&log, builtinTypes, this}; + ClonePublicInterface clonePublicInterface{&log, builtinTypes, this, mode}; returnType = clonePublicInterface.cloneTypePack(returnType); diff --git a/Analysis/src/NonStrictTypeChecker.cpp b/Analysis/src/NonStrictTypeChecker.cpp index 302c4d69..eae1dcfa 100644 --- a/Analysis/src/NonStrictTypeChecker.cpp +++ b/Analysis/src/NonStrictTypeChecker.cpp @@ -25,6 +25,7 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictFixGenericTypePacks) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictMoreUnknownSymbols) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictNoErrorsPassingNever) +LUAU_FASTFLAGVARIABLE(LuauNewNonStrictSuppressesDynamicRequireErrors) namespace Luau { @@ -1272,6 +1273,22 @@ void checkNonStrict( typeChecker.visit(sourceModule.root); unfreeze(module->interfaceTypes); copyErrors(module->errors, module->interfaceTypes, builtinTypes); + + if (FFlag::LuauNewNonStrictSuppressesDynamicRequireErrors) + { + module->errors.erase( + std::remove_if( + module->errors.begin(), + module->errors.end(), + [](auto err) + { + return get(err) != nullptr; + } + ), + module->errors.end() + ); + } + freeze(module->interfaceTypes); } diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index 241253f5..9dd214ad 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -23,6 +23,7 @@ LUAU_FASTFLAGVARIABLE(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAGVARIABLE(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAGVARIABLE(LuauMissingFollowMappedGenericPacks) namespace Luau { @@ -975,7 +976,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) { const TypePack* tp = get(*other); - if (const VariadicTypePack* vtp = tp ? get(tp->tail) : nullptr; vtp && vtp->hidden) + if (const VariadicTypePack* vtp = tp + ? get( + FFlag::LuauMissingFollowMappedGenericPacks ? follow(tp->tail) : tp->tail) + : nullptr; vtp && vtp->hidden) { TypePackId taillessTp = arena->addTypePack(tp->head); results.push_back(isCovariantWith(env, taillessTp, superTailPack, scope) @@ -1066,7 +1070,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) { const TypePack* tp = get(*other); - if (const VariadicTypePack* vtp = tp ? get(tp->tail) : nullptr; vtp && vtp->hidden) + if (const VariadicTypePack* vtp = tp + ? get( + FFlag::LuauMissingFollowMappedGenericPacks ? follow(tp->tail) : tp->tail) + : nullptr; vtp && vtp->hidden) { TypePackId taillessTp = arena->addTypePack(tp->head); results.push_back(isCovariantWith(env, subTailPack, taillessTp, scope) diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 8729d3ab..de024749 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -1916,7 +1916,7 @@ std::string dump(DenseHashMap& types) ToStringOptions& opts = dumpOptions(); for (const auto& [key, value] : types) { - if (s.length() == 1) + if (s.length() > 1) s += ", "; s += toString(key, opts) + " : " + toString(value, opts); } @@ -1930,7 +1930,7 @@ std::string dump(DenseHashMap& types) ToStringOptions& opts = dumpOptions(); for (const auto& [key, value] : types) { - if (s.length() == 1) + if (s.length() > 1) s += ", "; s += toString(key, opts) + " : " + toString(value, opts); } diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index bfd81696..49b109ff 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -41,6 +41,8 @@ LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauInferActualIfElseExprType) LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) +LUAU_FASTFLAGVARIABLE(LuauIceLess) + namespace Luau { @@ -1551,7 +1553,15 @@ void TypeChecker2::visitCall(AstExprCall* call) { AstExprIndexName* indexExpr = call->func->as(); if (!indexExpr) - ice->ice("method call expression has no 'self'"); + { + if (FFlag::LuauIceLess) + { + reportError(InternalError{"method call expression has no 'self'"}, call->location); + return; + } + else + ice->ice("method call expression has no 'self'"); + } args.head.push_back(lookupType(indexExpr->expr)); argExprs.push_back(indexExpr->expr); @@ -1950,12 +1960,26 @@ void TypeChecker2::visit(AstExprFunction* fn) } else if (!normalizedFnTy->hasFunctions()) { - ice->ice("Internal error: Lambda has non-function type " + toString(inferredFnTy), fn->location); + if (FFlag::LuauIceLess) + { + reportError(InternalError{"Internal error: Lambda has non-function type " + toString(inferredFnTy)}, fn->location); + return; + } + else + ice->ice("Internal error: Lambda has non-function type " + toString(inferredFnTy), fn->location); } else { if (1 != normalizedFnTy->functions.parts.size()) - ice->ice("Unexpected: Lambda has unexpected type " + toString(inferredFnTy), fn->location); + { + if (FFlag::LuauIceLess) + { + reportError(InternalError{"Unexpected: Lambda has unexpected type " + toString(inferredFnTy)}, fn->location); + return; + } + else + ice->ice("Unexpected: Lambda has unexpected type " + toString(inferredFnTy), fn->location); + } const FunctionType* inferredFtv = get(normalizedFnTy->functions.parts.front()); LUAU_ASSERT(inferredFtv); @@ -2591,6 +2615,11 @@ TypeId TypeChecker2::flattenPack(TypePackId pack) return builtinTypes->errorType; else if (finite(pack) && size(pack) == 0) return builtinTypes->nilType; // `(f())` where `f()` returns no values is coerced into `nil` + else if (FFlag::LuauIceLess) + { + reportError(InternalError{"flattenPack got a weird pack!"}, Location{}); + return builtinTypes->errorType; // todo test this + } else ice->ice("flattenPack got a weird pack!"); } @@ -2648,7 +2677,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 == kLuauPrint || ty->name == kLuauForceConstraintSolvingIncomplete)) + if (FFlag::DebugLuauMagicTypes && (ty->name == kLuauPrint || ty->name == kLuauForceConstraintSolvingIncomplete || ty->name == kLuauBlockedType)) return; for (const AstTypeOrPack& param : ty->parameters) @@ -2938,7 +2967,15 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc : traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes); if (!optSubLeaf || !optSuperLeaf) - ice->ice("Subtyping test returned a reasoning with an invalid path", location); + { + if (FFlag::LuauIceLess) + { + reportError(InternalError{"Subtyping test returned a reasoning with an invalid path"}, location); + return {}; // TOOD test this + } + else + ice->ice("Subtyping test returned a reasoning with an invalid path", location); + } const TypeOrPack& subLeaf = *optSubLeaf; const TypeOrPack& superLeaf = *optSuperLeaf; @@ -2950,7 +2987,15 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc auto superLeafTp = get(superLeaf); if (!subLeafTy && !superLeafTy && !subLeafTp && !superLeafTp) - ice->ice("Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack.", location); + { + if (FFlag::LuauIceLess) + { + reportError(InternalError{"Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack."}, location); + return {}; // TODO test this? + } + else + ice->ice("Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack.", location); + } std::string relation = "a subtype of"; if (reasoning.variance == SubtypingVariance::Invariant) diff --git a/tests/AstJsonEncoder.test.cpp b/tests/AstJsonEncoder.test.cpp index 4c0de902..ac0ef4b7 100644 --- a/tests/AstJsonEncoder.test.cpp +++ b/tests/AstJsonEncoder.test.cpp @@ -592,4 +592,18 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericTypePackWithDefault") CHECK(toJson(root->body.data[0]) == expected); } +TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypeOptional") +{ + AstStatBlock* root = expectParse(R"( + type Foo = string? + )"); + + CHECK(1 == root->body.size); + + std::string_view expected = + R"({"type":"AstStatTypeAlias","location":"1,12 - 1,30","name":"Foo","generics":[],"genericPacks":[],"value":{"type":"AstTypeUnion","location":"1,23 - 1,30","types":[{"type":"AstTypeReference","location":"1,23 - 1,29","name":"string","nameLocation":"1,23 - 1,29","parameters":[]},{"type":"AstTypeOptional","location":"1,29 - 1,30"}]},"exported":false})"; + + CHECK(toJson(root->body.data[0]) == expected); +} + TEST_SUITE_END(); diff --git a/tests/DataFlowGraph.test.cpp b/tests/DataFlowGraph.test.cpp index 2bf5d563..a9765f6a 100644 --- a/tests/DataFlowGraph.test.cpp +++ b/tests/DataFlowGraph.test.cpp @@ -13,7 +13,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); -LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) struct DataFlowGraphFixture { @@ -129,8 +128,6 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "phi") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_while") { - ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; - dfg(R"( local x @@ -170,8 +167,6 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_while") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_repeat") { - ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; - dfg(R"( local x @@ -210,8 +205,6 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_repeat") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_for") { - ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; - dfg(R"( local x @@ -251,8 +244,6 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_for") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_for_in") { - ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; - dfg(R"( local x @@ -292,8 +283,6 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_for_in") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_preexisting_property_not_owned_by_while") { - ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; - dfg(R"( local t = {} t.x = 5 diff --git a/tests/NonStrictTypeChecker.test.cpp b/tests/NonStrictTypeChecker.test.cpp index 190366ad..9b2e2a9e 100644 --- a/tests/NonStrictTypeChecker.test.cpp +++ b/tests/NonStrictTypeChecker.test.cpp @@ -18,6 +18,7 @@ LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks) LUAU_FASTFLAG(LuauNewNonStrictMoreUnknownSymbols) LUAU_FASTFLAG(LuauNewNonStrictNoErrorsPassingNever) +LUAU_FASTFLAG(LuauNewNonStrictSuppressesDynamicRequireErrors) using namespace Luau; @@ -814,5 +815,51 @@ TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_one_sided_conditionals") CHECK_EQ(err->context, UnknownSymbol::Context::Binding); } +TEST_CASE_FIXTURE(BuiltinsFixture, "new_non_strict_should_suppress_dynamic_require_errors") +{ + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauNewNonStrictSuppressesDynamicRequireErrors, true}}; + // Avoid warning about dynamic requires in new nonstrict mode + CheckResult result = check(Mode::Nonstrict, R"( +function passThrough(module) + require(module) +end + )"); + + LUAU_REQUIRE_ERROR_COUNT(0, result); + // We should still warn about dynamic requires in strict mode + result = check(Mode::Strict, R"( +function passThrough(module) + require(module) +end +)"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + const UnknownRequire* req = get(result.errors[0]); + CHECK(req != nullptr); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "new_non_strict_should_suppress_unknown_require_errors") +{ + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauNewNonStrictSuppressesDynamicRequireErrors, true}}; + + // Avoid warning about dynamic requires in new nonstrict mode + CheckResult result = check(Mode::Nonstrict, R"( +require(script.NonExistent) +require("@self/NonExistent") + )"); + + LUAU_REQUIRE_ERROR_COUNT(0, result); + // We should still warn about dynamic requires in strict mode + result = check(Mode::Strict, R"( +require(script.NonExistent) +require("@self/NonExistent") +)"); + + LUAU_REQUIRE_ERROR_COUNT(2, result); + const UnknownRequire* req1 = get(result.errors[0]); + CHECK(req1 != nullptr); + const UnknownRequire* req2 = get(result.errors[1]); + CHECK(req2 != nullptr); +} TEST_SUITE_END(); diff --git a/tests/RuntimeLimits.test.cpp b/tests/RuntimeLimits.test.cpp index e51310d6..ed3553ec 100644 --- a/tests/RuntimeLimits.test.cpp +++ b/tests/RuntimeLimits.test.cpp @@ -17,11 +17,15 @@ using namespace Luau; +LUAU_FASTINT(LuauSolverConstraintLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) + LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) +LUAU_FASTFLAG(LuauIceLess) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauSimplifyAnyAndUnion) +LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving) struct LimitFixture : BuiltinsFixture { @@ -333,4 +337,36 @@ TEST_CASE_FIXTURE(LimitFixture, "Signal_exerpt" * doctest::timeout(0.5)) (void)result; } +TEST_CASE_FIXTURE(Fixture, "limit_number_of_dynamically_created_constraints") +{ + ScopedFastFlag sff[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauLimitDynamicConstraintSolving, true}, + }; + + constexpr const char* src = R"( + type Array = {T} + + type Hello = Array>>>>>>>>> + )"; + + { + ScopedFastInt sfi{FInt::LuauSolverConstraintLimit, 1}; + CheckResult result = check(src); + LUAU_CHECK_ERROR(result, CodeTooComplex); + } + + { + ScopedFastInt sfi{FInt::LuauSolverConstraintLimit, 1000}; + CheckResult result = check(src); + LUAU_CHECK_NO_ERRORS(result); + } + + { + ScopedFastInt sfi{FInt::LuauSolverConstraintLimit, 0}; + CheckResult result = check(src); + LUAU_CHECK_NO_ERRORS(result); + } +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.anyerror.test.cpp b/tests/TypeInfer.anyerror.test.cpp index db202f65..4388cdc7 100644 --- a/tests/TypeInfer.anyerror.test.cpp +++ b/tests/TypeInfer.anyerror.test.cpp @@ -14,7 +14,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) TEST_SUITE_BEGIN("TypeInferAnyError"); @@ -305,8 +304,6 @@ TEST_CASE_FIXTURE(Fixture, "chain_calling_error_type_yields_error") TEST_CASE_FIXTURE(BuiltinsFixture, "replace_every_free_type_when_unifying_a_complex_function_with_any") { - ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; - CheckResult result = check(R"( local a: any local b diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 5818e0f8..132adf6a 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -23,6 +23,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauEagerGeneralization4) +LUAU_FASTFLAG(LuauCollapseShouldNotCrash) LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) LUAU_FASTFLAG(LuauFormatUseLastPosition) LUAU_FASTFLAG(LuauSimplifyOutOfLine2) @@ -3306,4 +3307,42 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "generic_function_statement") CHECK_EQ("a", toString(requireTypeAtPosition({9, 21}))); } +TEST_CASE_FIXTURE(BuiltinsFixture, "function_calls_should_not_crash") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + // crash only happens right now with eager generalization off + {FFlag::LuauEagerGeneralization4, false}, + {FFlag::LuauCollapseShouldNotCrash, true}, + }; + + CheckResult result = check(R"( + return { + StartAPI = function() + local pointers = {} + local API = {} + local function getRealEnvResult(PointerOrPath) + if pointers[PointerOrPath] then + return pointers[PointerOrPath] + end + end + API.OnInvoke = function() + local realEnvResult, isResultPointer = getRealEnvResult(FunctionInEnvToRunPath) + return realEnvResult(table.unpack(args, 2, args.n)) + if TableInEnvPath and type(TableInEnvPath) == 'string' then + local realEnvResult, isResultPointer = getRealEnvResult(TableInEnvPath) + return getmetatable(realEnvResult) + end + local realEnvResult, isResultPointer = getRealEnvResult(TableInEnvPath) + local metaTableInEnv = getmetatable(realEnvResult) + local result = metaTableInEnv[FuncToRun](realEnvResult,table.unpack(args, 3, args.n)) + end + end + } + )"); + + // no expected behavior here beyond not crashing +} + + TEST_SUITE_END(); diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index bd606338..11a29c63 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -16,7 +16,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSimplifyOutOfLine2) -LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) TEST_SUITE_BEGIN("TypeInferLoops"); @@ -183,10 +182,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_next") } TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_next_and_multiple_elements") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSimplifyOutOfLine2, true}, - {FFlag::LuauDfgAllowUpdatesInLoops, true}, - }; + ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true}; CheckResult result = check(R"( local n @@ -1106,8 +1102,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dcr_iteration_on_never_gives_never") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; - CheckResult result = check(R"( local iter: never local ans @@ -1329,8 +1323,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_require") TEST_CASE_FIXTURE(Fixture, "oss_1480") { - ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; - LUAU_REQUIRE_NO_ERRORS(check(R"( type Part = { Parent: Part? } type Instance = Part @@ -1346,8 +1338,6 @@ TEST_CASE_FIXTURE(Fixture, "oss_1480") TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1413") { - ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; - LUAU_REQUIRE_NO_ERRORS(check(R"( local function KahanSum(values: {number}): number local sum: number = 0 @@ -1401,10 +1391,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "while_loop_error_in_body") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sffs[] = { - {FFlag::LuauDfgAllowUpdatesInLoops, true}, - }; - LUAU_REQUIRE_NO_ERRORS(check(R"( local function foo() local x = "" @@ -1421,10 +1407,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "while_loop_error_in_body") TEST_CASE_FIXTURE(BuiltinsFixture, "while_loop_assign_different_type") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauDfgAllowUpdatesInLoops, true}, - }; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( local function takesString(_: string) end @@ -1445,8 +1428,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "while_loop_assign_different_type") TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_loop_assignment") { - ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; - LUAU_REQUIRE_NO_ERRORS(check(R"( local x = nil repeat @@ -1460,8 +1441,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_loop_assignment") TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_loop_assignment_with_break") { - ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; - LUAU_REQUIRE_NO_ERRORS(check(R"( local x = nil repeat @@ -1475,8 +1454,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_loop_assignment_with_break") TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_unconditionally_fires_error") { - ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; - LUAU_REQUIRE_NO_ERRORS(check(R"( local x = nil repeat @@ -1495,8 +1472,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_is_linearish") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; - LUAU_REQUIRE_NO_ERRORS(check(R"( local x = nil if math.random () > 0.5 then @@ -1515,10 +1490,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_is_linearish") TEST_CASE_FIXTURE(Fixture, "ensure_local_in_loop_does_not_escape") { - ScopedFastFlag sffs[] = { - {FFlag::LuauDfgAllowUpdatesInLoops, true}, - }; - LUAU_REQUIRE_NO_ERRORS(check(R"( local x = 42 repeat diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index 34338005..a5b5c391 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -14,6 +14,8 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(DebugLuauMagicTypes) +LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving) using namespace Luau; @@ -847,4 +849,22 @@ return wrapper(test2, 1, "") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "internal_types_are_scrubbed_from_module") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::DebugLuauMagicTypes, true}, + {FFlag::LuauLimitDynamicConstraintSolving, true} + }; + + fileResolver.source["game/A"] = R"( +return function(): _luau_blocked_type return nil :: any end + )"; + + CheckResult result = getFrontend().check("game/A"); + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); + CHECK("(...any) -> *error-type*" == toString(getFrontend().moduleResolver.getModule("game/A")->returnType)); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index 8e20fc00..7a01222a 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -18,7 +18,6 @@ LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) -LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauSolverAgnosticStringification) TEST_SUITE_BEGIN("ProvisionalTests"); @@ -1340,10 +1339,8 @@ TEST_CASE_FIXTURE(Fixture, "we_cannot_infer_functions_that_return_inconsistently TEST_CASE_FIXTURE(Fixture, "loop_unsoundness") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauDfgAllowUpdatesInLoops, true}, - }; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; + // This is a tactical unsoundness we're introducing to resolve issues around // cyclic types. You can see that if this loop were to run more than once, // we'd error as we'd try to call a number. diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index d40da80a..80227834 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -20,7 +20,9 @@ LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect) LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauRefineNoRefineAlways) LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex) -LUAU_FASTFLAG(LuauForceSimplifyConstraint) +LUAU_FASTFLAG(LuauForceSimplifyConstraint2) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) +LUAU_FASTFLAG(LuauSimplifyOutOfLine2) using namespace Luau; @@ -2251,7 +2253,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction" {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauStuckTypeFunctionsStillDispatch, true}, - {FFlag::LuauForceSimplifyConstraint, true}, + {FFlag::LuauForceSimplifyConstraint2, true}, }; CheckResult result = check(R"( @@ -2900,4 +2902,31 @@ TEST_CASE_FIXTURE(Fixture, "cli_120460_table_access_on_phi_node") )")); } +TEST_CASE_FIXTURE(Fixture, "force_simplify_constraint_doesnt_drop_blocked_type") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck2, true}, + {FFlag::LuauForceSimplifyConstraint2, true}, + {FFlag::LuauSimplifyOutOfLine2, true}, + }; + + CheckResult results = check(R"( + local function track(instance): boolean + local isBasePart = instance:IsA("BasePart") + local isCharacter = false + if not isBasePart then + isCharacter = instance:FindFirstChildOfClass("Humanoid") and instance:FindFirstChild("HumanoidRootPart") + end + -- A verison of `SimplifyConstraint` mucked up the fact that this + -- is `boolean | and`, and claimed it was only + -- `boolean`. + return isCharacter + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, results); + REQUIRE(get(results.errors[0])); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index f7867536..f3be42bd 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -35,10 +35,12 @@ LUAU_FASTFLAG(LuauInferPolarityOfReadWriteProperties) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauInferActualIfElseExprType) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) -LUAU_FASTFLAG(LuauForceSimplifyConstraint) +LUAU_FASTFLAG(LuauForceSimplifyConstraint2) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauMissingFollowMappedGenericPacks) using namespace Luau; @@ -1902,7 +1904,6 @@ end TEST_CASE_FIXTURE(Fixture, "fuzzer_derived_unsound_loops") { - ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( for _ in ... do repeat @@ -2529,7 +2530,7 @@ TEST_CASE_FIXTURE(Fixture, "simplify_constraint_can_force") { ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauForceSimplifyConstraint, true}, + {FFlag::LuauForceSimplifyConstraint2, true}, {FFlag::LuauSimplifyOutOfLine2, true}, // NOTE: Feel free to clip this test when this flag is clipped. {FFlag::LuauPushFunctionTypesInFunctionStatement, false}, @@ -2589,4 +2590,48 @@ TEST_CASE_FIXTURE(Fixture, "non_standalone_constraint_solving_incomplete_is_hidd CHECK(get(results.errors[1])); } +TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_missing_type_pack_follow") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}, + {FFlag::LuauMissingFollowMappedGenericPacks, true}, + }; + + LUAU_REQUIRE_ERRORS(check(R"( +local _ = {[0]=_,} +while _ do +do +local l2 = require(module0) +end +end +do end +function _(l0:typeof(_),l0,l0) +local l0 = require(module0) +_()(l0(),_,_(_())((_))) +do end +end +_()(_(if nil then _))("",_,_(_,(_))) +do end + )")); + + LUAU_REQUIRE_ERRORS(check(R"( +local _ = {_,} +while _ do +do +do end +end +end +_ = nil +function _(l0,l0,l0) +local l0 = require(module0) +_()(_(),_,_(_())(_,true)(_,_),l0) +do end +end +_()(_())("",_.n0,_,_(_,true,(_))) +do end + )")); + +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.typestates.test.cpp b/tests/TypeInfer.typestates.test.cpp index 602443ed..dc486c22 100644 --- a/tests/TypeInfer.typestates.test.cpp +++ b/tests/TypeInfer.typestates.test.cpp @@ -6,7 +6,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) -LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) using namespace Luau; @@ -710,7 +709,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring") TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring_in_loop") { - ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauDfgAllowUpdatesInLoops, true}}; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; CheckResult result = check(R"( --!strict diff --git a/tests/TypeInfer.unknownnever.test.cpp b/tests/TypeInfer.unknownnever.test.cpp index f8a58c42..19d6a388 100644 --- a/tests/TypeInfer.unknownnever.test.cpp +++ b/tests/TypeInfer.unknownnever.test.cpp @@ -9,7 +9,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauEagerGeneralization4); LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch); -LUAU_FASTFLAG(LuauForceSimplifyConstraint) +LUAU_FASTFLAG(LuauForceSimplifyConstraint2) TEST_SUITE_BEGIN("TypeInferUnknownNever"); @@ -333,7 +333,7 @@ TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_i ScopedFastFlag sffs[] = { {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauStuckTypeFunctionsStillDispatch, true}, - {FFlag::LuauForceSimplifyConstraint, true}, + {FFlag::LuauForceSimplifyConstraint2, true}, }; CheckResult result = check(R"(