From 3adf25898af7bf1fb7941ed57fde0a1d02d69c4c Mon Sep 17 00:00:00 2001 From: Ariel Weiss Date: Fri, 2 May 2025 12:25:17 -0700 Subject: [PATCH] Sync to upstream/release/672 --- Analysis/include/Luau/Type.h | 4 - Analysis/include/Luau/TypeArena.h | 4 - Analysis/include/Luau/TypePack.h | 2 + Analysis/src/AstJsonEncoder.cpp | 16 +- Analysis/src/BuiltinDefinitions.cpp | 4 +- Analysis/src/Constraint.cpp | 19 +- Analysis/src/ConstraintGenerator.cpp | 42 ++- Analysis/src/ConstraintSolver.cpp | 77 ++++- Analysis/src/DataFlowGraph.cpp | 31 +- Analysis/src/EmbeddedBuiltinDefinitions.cpp | 45 ++- Analysis/src/FragmentAutocomplete.cpp | 84 ++--- Analysis/src/Generalization.cpp | 114 +++++-- Analysis/src/Instantiation.cpp | 3 +- Analysis/src/NonStrictTypeChecker.cpp | 60 ++-- Analysis/src/Simplify.cpp | 119 ++++--- Analysis/src/Subtyping.cpp | 4 +- Analysis/src/Transpiler.cpp | 13 +- Analysis/src/Type.cpp | 25 -- Analysis/src/TypeArena.cpp | 27 -- Analysis/src/TypeChecker2.cpp | 9 +- Analysis/src/TypeFunction.cpp | 72 +++- Analysis/src/TypeFunctionRuntime.cpp | 34 ++ Analysis/src/TypeInfer.cpp | 7 +- Analysis/src/TypePack.cpp | 20 ++ Analysis/src/TypeUtils.cpp | 3 +- Analysis/src/Unifier.cpp | 3 +- Ast/src/Parser.cpp | 3 +- .../Navigator/include/Luau/RequireNavigator.h | 5 + Require/Navigator/src/RequireNavigator.cpp | 83 +++-- Require/Runtime/include/Luau/Require.h | 11 +- Require/Runtime/src/Navigation.cpp | 8 +- Require/Runtime/src/Navigation.h | 5 +- Require/Runtime/src/Require.cpp | 12 +- Require/Runtime/src/RequireImpl.cpp | 90 +++-- Require/Runtime/src/RequireImpl.h | 4 + tests/AstJsonEncoder.test.cpp | 59 ++++ tests/DataFlowGraph.test.cpp | 10 +- tests/Fixture.h | 5 + tests/FragmentAutocomplete.test.cpp | 12 - tests/Frontend.test.cpp | 4 +- tests/NonStrictTypeChecker.test.cpp | 15 +- tests/Parser.test.cpp | 3 - tests/RequireByString.test.cpp | 70 +++- tests/Subtyping.test.cpp | 3 +- tests/Transpiler.test.cpp | 10 +- tests/TypeFunction.test.cpp | 67 ++++ tests/TypeFunction.user.test.cpp | 38 ++- tests/TypeInfer.aliases.test.cpp | 4 +- tests/TypeInfer.builtins.test.cpp | 5 +- tests/TypeInfer.functions.test.cpp | 22 +- tests/TypeInfer.operators.test.cpp | 38 ++- tests/TypeInfer.refinements.test.cpp | 78 ++++- tests/TypeInfer.tables.test.cpp | 8 +- tests/TypeInfer.test.cpp | 2 +- tests/TypeInfer.typePacks.test.cpp | 6 +- tests/TypeInfer.typestates.test.cpp | 311 +++++++++++++++++- .../GlobalLuauLibraries/global_library.luau | 1 - .../ProjectLuauLibraries/library.luau | 1 - 58 files changed, 1389 insertions(+), 445 deletions(-) delete mode 100644 tests/require/with_config/GlobalLuauLibraries/global_library.luau delete mode 100644 tests/require/with_config/ProjectLuauLibraries/library.luau diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index 65661b46..a110268a 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -74,10 +74,6 @@ struct FreeType // This one got promoted to explicit explicit FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound, Polarity polarity = Polarity::Unknown); explicit FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId upperBound); - // Old constructors - explicit FreeType(TypeLevel level); - explicit FreeType(Scope* scope); - FreeType(Scope* scope, TypeLevel level); int index; TypeLevel level; diff --git a/Analysis/include/Luau/TypeArena.h b/Analysis/include/Luau/TypeArena.h index dde1a5be..dbc5d513 100644 --- a/Analysis/include/Luau/TypeArena.h +++ b/Analysis/include/Luau/TypeArena.h @@ -37,10 +37,6 @@ struct TypeArena TypeId freshType(NotNull builtins, Scope* scope); TypeId freshType(NotNull builtins, Scope* scope, TypeLevel level); - TypeId freshType_DEPRECATED(TypeLevel level); - TypeId freshType_DEPRECATED(Scope* scope); - TypeId freshType_DEPRECATED(Scope* scope, TypeLevel level); - TypePackId freshTypePack(Scope* scope, Polarity polarity = Polarity::Unknown); TypePackId addTypePack(std::initializer_list types); diff --git a/Analysis/include/Luau/TypePack.h b/Analysis/include/Luau/TypePack.h index 1266f27c..a93f6fca 100644 --- a/Analysis/include/Luau/TypePack.h +++ b/Analysis/include/Luau/TypePack.h @@ -185,6 +185,8 @@ TypePackIterator begin(TypePackId tp); TypePackIterator begin(TypePackId tp, const TxnLog* log); TypePackIterator end(TypePackId tp); +TypePackId getTail(TypePackId tp); + using SeenSet = std::set>; bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs); diff --git a/Analysis/src/AstJsonEncoder.cpp b/Analysis/src/AstJsonEncoder.cpp index 416a0b9c..2ae7d8c1 100644 --- a/Analysis/src/AstJsonEncoder.cpp +++ b/Analysis/src/AstJsonEncoder.cpp @@ -475,26 +475,26 @@ struct AstJsonEncoder : public AstVisitor writeRaw("}"); } - void write(const AstGenericType& genericType) + void write(class AstGenericType* genericType) { writeRaw("{"); bool c = pushComma(); writeType("AstGenericType"); - write("name", genericType.name); - if (genericType.defaultValue) - write("luauType", genericType.defaultValue); + write("name", genericType->name); + if (genericType->defaultValue) + write("luauType", genericType->defaultValue); popComma(c); writeRaw("}"); } - void write(const AstGenericTypePack& genericTypePack) + void write(class AstGenericTypePack* genericTypePack) { writeRaw("{"); bool c = pushComma(); writeType("AstGenericTypePack"); - write("name", genericTypePack.name); - if (genericTypePack.defaultValue) - write("luauType", genericTypePack.defaultValue); + write("name", genericTypePack->name); + if (genericTypePack->defaultValue) + write("luauType", genericTypePack->defaultValue); popComma(c); writeRaw("}"); } diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 6250cc0b..f328f3b3 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -32,7 +32,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) -LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked) LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition) @@ -1550,8 +1549,7 @@ bool MagicClone::infer(const MagicFunctionCallContext& context) static std::optional freezeTable(TypeId inputType, const MagicFunctionCallContext& context) { TypeArena* arena = context.solver->arena; - if (FFlag::LuauFollowTableFreeze) - inputType = follow(inputType); + inputType = follow(inputType); if (auto mt = get(inputType)) { std::optional frozenTable = freezeTable(mt->table, context); diff --git a/Analysis/src/Constraint.cpp b/Analysis/src/Constraint.cpp index 42fd690c..1e59f4f8 100644 --- a/Analysis/src/Constraint.cpp +++ b/Analysis/src/Constraint.cpp @@ -51,16 +51,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor bool visit(TypeId, const TypeFunctionInstanceType&) override { - // We do not consider reference counted types that are inside a type - // function to be part of the reachable reference counted types. - // Otherwise, code can be constructed in just the right way such - // that two type functions both claim to mutate a free type, which - // prevents either type function from trying to generalize it, so - // we potentially get stuck. - // - // The default behavior here is `true` for "visit the child types" - // of this type, hence: - return false; + return FFlag::DebugLuauGreedyGeneralization; } }; @@ -130,8 +121,10 @@ DenseHashSet Constraint::getMaybeMutatedFreeTypes() const } else if (auto hic = get(*this)) { + if (FFlag::DebugLuauGreedyGeneralization) + rci.traverse(hic->subjectType); rci.traverse(hic->resultType); - // `HasIndexerConstraint` should not mutate `subjectType` or `indexType`. + // `HasIndexerConstraint` should not mutate `indexType`. } else if (auto apc = get(*this)) { @@ -150,6 +143,10 @@ DenseHashSet Constraint::getMaybeMutatedFreeTypes() const rci.traverse(ty); // `UnpackConstraint` should not mutate `sourcePack`. } + else if (auto rpc = get(*this); FFlag::DebugLuauGreedyGeneralization && rpc) + { + rci.traverse(rpc->ty); + } else if (auto rpc = get(*this)) { rci.traverse(rpc->tp); diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index db4dd714..22ec7280 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -39,7 +39,6 @@ LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions) -LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations) @@ -52,6 +51,7 @@ LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAGVARIABLE(LuauNoTypeFunctionsNamedTypeOf) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) +LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) namespace Luau { @@ -1409,7 +1409,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location); sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->name->location}; - bool sigFullyDefined = !hasFreeType(sig.signature); + bool sigFullyDefined = FFlag::DebugLuauGreedyGeneralization ? false : !hasFreeType(sig.signature); if (sigFullyDefined) emplaceType(asMutable(functionType), sig.signature); @@ -1471,7 +1471,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f Checkpoint start = checkpoint(this); FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location); - bool sigFullyDefined = !hasFreeType(sig.signature); + bool sigFullyDefined = FFlag::DebugLuauGreedyGeneralization ? false : !hasFreeType(sig.signature); DefId def = dfg->getDef(function->name); @@ -2389,9 +2389,12 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* this, [checkConstraint, callConstraint](const ConstraintPtr& constraint) { - constraint->dependencies.emplace_back(checkConstraint); + if (!(FFlag::DebugLuauGreedyGeneralization && get(*constraint))) + { + constraint->dependencies.emplace_back(checkConstraint); - callConstraint->dependencies.emplace_back(constraint.get()); + callConstraint->dependencies.emplace_back(constraint.get()); + } } ); @@ -2496,8 +2499,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin } else { - FreeType ft = - FFlag::LuauFreeTypesMustHaveBounds ? FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType} : FreeType{scope.get()}; + FreeType ft = FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType}; ft.lowerBound = arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}}); ft.upperBound = builtinTypes->stringType; freeTy = arena->addType(ft); @@ -2524,8 +2526,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool* } else { - FreeType ft = - FFlag::LuauFreeTypesMustHaveBounds ? FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType} : FreeType{scope.get()}; + FreeType ft = FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType}; ft.lowerBound = singletonType; ft.upperBound = builtinTypes->booleanType; freeTy = arena->addType(ft); @@ -3076,7 +3077,7 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprLocal* local if (ty) { TypeIds* localDomain = localTypes.find(*ty); - if (localDomain) + if (localDomain && !(FFlag::LuauDoNotAddUpvalueTypesToLocalType && local->upvalue)) localDomain->insert(rhsType); } else @@ -3107,8 +3108,10 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprLocal* local if (annotatedTy) addConstraint(scope, local->location, SubtypeConstraint{rhsType, *annotatedTy}); - if (TypeIds* localDomain = localTypes.find(*ty)) - localDomain->insert(rhsType); + // This is vestigial. + if (!FFlag::LuauDoNotAddUpvalueTypesToLocalType) + if (TypeIds* localDomain = localTypes.find(*ty)) + localDomain->insert(rhsType); } void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprGlobal* global, TypeId rhsType) @@ -3410,13 +3413,22 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu bodyScope->varargPack = std::nullopt; } + LUAU_ASSERT(nullptr != varargPack); + if (FFlag::DebugLuauGreedyGeneralization) { - genericTypes = argTypes; + // Some of the types in argTypes will eventually be generics, and some + // will not. The ones that are not generic will be pruned when + // GeneralizationConstraint dispatches. + genericTypes.insert(genericTypes.begin(), argTypes.begin(), argTypes.end()); + varargPack = follow(varargPack); + returnType = follow(returnType); + if (varargPack == returnType) + genericTypePacks = {varargPack}; + else + genericTypePacks = {varargPack, returnType}; } - LUAU_ASSERT(nullptr != varargPack); - // If there is both an annotation and an expected type, the annotation wins. // Type checking will sort out any discrepancies later. if (FFlag::LuauStoreReturnTypesAsPackOnAst && fn->returnAnnotation) diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 2259af45..e518a56a 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -681,7 +681,6 @@ void ConstraintSolver::initFreeTypeTracking() } maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint); - for (NotNull dep : c->dependencies) { block(dep, c); @@ -2082,24 +2081,61 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull(lhsType)) { auto lhsFreeUpperBound = follow(lhsFree->upperBound); - if (get(lhsFreeUpperBound) || get(lhsFreeUpperBound)) - lhsType = lhsFreeUpperBound; + + if (FFlag::DebugLuauGreedyGeneralization) + { + const auto [blocked, maybeTy, isIndex] = lookupTableProp(constraint, lhsType, propName, ValueContext::LValue); + if (!blocked.empty()) + { + for (TypeId t : blocked) + block(t, constraint); + return false; + } + else if (maybeTy) + { + bind(constraint, c.propType, isIndex ? arena->addType(UnionType{{*maybeTy, builtinTypes->nilType}}) : *maybeTy); + unify(constraint, rhsType, *maybeTy); + return true; + } + else + { + TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope}); + + trackInteriorFreeType(constraint->scope, newUpperBound); + + TableType* upperTable = getMutable(newUpperBound); + LUAU_ASSERT(upperTable); + + upperTable->props[c.propName] = rhsType; + + // Food for thought: Could we block if simplification encounters a blocked type? + lhsFree->upperBound = simplifyIntersection(constraint->scope, constraint->location, lhsFreeUpperBound, newUpperBound); + + bind(constraint, c.propType, rhsType); + return true; + } + } else { - TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope}); + if (get(lhsFreeUpperBound) || get(lhsFreeUpperBound)) + lhsType = lhsFreeUpperBound; + else + { + TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope}); - trackInteriorFreeType(constraint->scope, newUpperBound); + trackInteriorFreeType(constraint->scope, newUpperBound); - TableType* upperTable = getMutable(newUpperBound); - LUAU_ASSERT(upperTable); + TableType* upperTable = getMutable(newUpperBound); + LUAU_ASSERT(upperTable); - upperTable->props[c.propName] = rhsType; + upperTable->props[c.propName] = rhsType; - // Food for thought: Could we block if simplification encounters a blocked type? - lhsFree->upperBound = simplifyIntersection(constraint->scope, constraint->location, lhsFreeUpperBound, newUpperBound); + // Food for thought: Could we block if simplification encounters a blocked type? + lhsFree->upperBound = simplifyIntersection(constraint->scope, constraint->location, lhsFreeUpperBound, newUpperBound); - bind(constraint, c.propType, rhsType); - return true; + bind(constraint, c.propType, rhsType); + return true; + } } } @@ -2873,8 +2909,21 @@ TablePropLookupResult ConstraintSolver::lookupTableProp( { const TypeId upperBound = follow(ft->upperBound); - if (get(upperBound) || get(upperBound)) - return lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen); + if (FFlag::DebugLuauGreedyGeneralization) + { + if (get(upperBound) || get(upperBound)) + { + TablePropLookupResult res = lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen); + // If the upper bound is a table that already has the property, we don't need to extend its bounds. + if (res.propType || get(upperBound)) + return res; + } + } + else + { + if (get(upperBound) || get(upperBound)) + return lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen); + } // TODO: The upper bound could be an intersection that contains suitable tables or extern types. diff --git a/Analysis/src/DataFlowGraph.cpp b/Analysis/src/DataFlowGraph.cpp index 467b341f..0b419fa4 100644 --- a/Analysis/src/DataFlowGraph.cpp +++ b/Analysis/src/DataFlowGraph.cpp @@ -17,7 +17,8 @@ LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument) LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset) LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) - +LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType) +LUAU_FASTFLAGVARIABLE(LuauDfgIfBlocksShouldRespectControlFlow) namespace Luau { @@ -501,12 +502,26 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i) } DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); - if (thencf != ControlFlow::None && elsecf == ControlFlow::None) - join(scope, scope, elseScope); - else if (thencf == ControlFlow::None && elsecf != ControlFlow::None) - join(scope, thenScope, scope); - else if ((thencf | elsecf) == ControlFlow::None) - join(scope, thenScope, elseScope); + if (FFlag::LuauDfgIfBlocksShouldRespectControlFlow) + { + // If the control flow from the `if` or `else` block is non-linear, + // then we should assume that the _other_ branch is the one taken. + if (thencf != ControlFlow::None && elsecf == ControlFlow::None) + scope->inherit(elseScope); + else if (thencf == ControlFlow::None && elsecf != ControlFlow::None) + scope->inherit(thenScope); + else if ((thencf | elsecf) == ControlFlow::None) + join(scope, thenScope, elseScope); + } + else + { + if (thencf != ControlFlow::None && elsecf == ControlFlow::None) + join(scope, scope, elseScope); + else if (thencf == ControlFlow::None && elsecf != ControlFlow::None) + join(scope, thenScope, scope); + else if ((thencf | elsecf) == ControlFlow::None) + join(scope, thenScope, elseScope); + } if (thencf == elsecf) return thencf; @@ -1164,7 +1179,7 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef) DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); // In order to avoid alias tracking, we need to clip the reference to the parent def. - if (scope->canUpdateDefinition(l->local)) + if (scope->canUpdateDefinition(l->local) && !(FFlag::LuauDoNotAddUpvalueTypesToLocalType && l->upvalue)) { DefId updated = defArena->freshCell(l->local, l->location, containsSubscriptedDefinition(incomingDef)); scope->bindings[l->local] = updated; diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index 778fb567..65b7ef00 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -2,6 +2,7 @@ #include "Luau/BuiltinDefinitions.h" LUAU_FASTFLAG(LuauDeclareExternType) +LUAU_FASTFLAG(LuauTypeFunOptional) namespace Luau { @@ -339,11 +340,11 @@ std::string getBuiltinDefinitionSource() } // TODO: split into separate tagged unions when the new solver can appropriately handle that. -static const std::string kBuiltinDefinitionTypesSrc = R"BUILTIN_SRC( +static const std::string kBuiltinDefinitionTypeMethodSrc = R"BUILTIN_SRC( export type type = { tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" | - "singleton" | "negation" | "union" | "intesection" | "table" | "function" | "class" | "generic", + "singleton" | "negation" | "union" | "intersection" | "table" | "function" | "class" | "generic", is: (self: type, arg: string) -> boolean, @@ -390,6 +391,10 @@ export type type = { ispack: (self: type) -> boolean, } +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC( + declare types: { unknown: type, never: type, @@ -409,12 +414,44 @@ declare types: { newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type, copy: @checked (arg: type) -> type, } - )BUILTIN_SRC"; +static const std::string kBuiltinDefinitionTypesLibWithOptionalSrc = R"BUILTIN_SRC( + +declare types: { + unknown: type, + never: type, + any: type, + boolean: type, + number: type, + string: type, + thread: type, + buffer: type, + + singleton: @checked (arg: string | boolean | nil) -> type, + optional: @checked (arg: type) -> type, + generic: @checked (name: string, ispack: boolean?) -> type, + negationof: @checked (arg: type) -> type, + unionof: @checked (...type) -> type, + intersectionof: @checked (...type) -> type, + newtable: @checked (props: {[type]: type} | {[type]: { read: type, write: type } } | nil, indexer: { index: type, readresult: type, writeresult: type }?, metatable: type?) -> type, + newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type, + copy: @checked (arg: type) -> type, +} +)BUILTIN_SRC"; + + std::string getTypeFunctionDefinitionSource() { - return kBuiltinDefinitionTypesSrc; + + std::string result = kBuiltinDefinitionTypeMethodSrc; + + if (FFlag::LuauTypeFunOptional) + result += kBuiltinDefinitionTypesLibWithOptionalSrc; + else + result += kBuiltinDefinitionTypesLibSrc; + + return result; } } // namespace Luau diff --git a/Analysis/src/FragmentAutocomplete.cpp b/Analysis/src/FragmentAutocomplete.cpp index 07be1405..56f3f684 100644 --- a/Analysis/src/FragmentAutocomplete.cpp +++ b/Analysis/src/FragmentAutocomplete.cpp @@ -28,8 +28,6 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit); LUAU_FASTINT(LuauTypeInferIterationLimit); LUAU_FASTINT(LuauTarjanChildLimit) -LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf) -LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule) LUAU_FASTFLAGVARIABLE(DebugLogFragmentsFromAutocomplete) LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection) LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes) @@ -737,7 +735,7 @@ struct MixedModeIncrementalTCDefFinder : public AstVisitor // requires that we find the local/global `m` and place it in the environment. // The default behaviour here is to return false, and have individual visitors override // the specific behaviour they need. - return FFlag::LuauMixedModeDefFinderTraversesTypeOf; + return true; } bool visit(AstStatTypeAlias* alias) override @@ -1227,31 +1225,6 @@ ModulePtr cloneModule(CloneState& cloneState, const ModulePtr& source, std::uniq return incremental; } -ModulePtr copyModule(const ModulePtr& result, std::unique_ptr alloc) -{ - ModulePtr incrementalModule = std::make_shared(); - incrementalModule->name = result->name; - incrementalModule->humanReadableName = "Incremental$" + result->humanReadableName; - incrementalModule->internalTypes.owningModule = incrementalModule.get(); - incrementalModule->interfaceTypes.owningModule = incrementalModule.get(); - incrementalModule->allocator = std::move(alloc); - // Don't need to keep this alive (it's already on the source module) - copyModuleVec(incrementalModule->scopes, result->scopes); - copyModuleMap(incrementalModule->astTypes, result->astTypes); - copyModuleMap(incrementalModule->astTypePacks, result->astTypePacks); - copyModuleMap(incrementalModule->astExpectedTypes, result->astExpectedTypes); - // Don't need to clone astOriginalCallTypes - copyModuleMap(incrementalModule->astOverloadResolvedTypes, result->astOverloadResolvedTypes); - // Don't need to clone astForInNextTypes - copyModuleMap(incrementalModule->astForInNextTypes, result->astForInNextTypes); - // Don't need to clone astResolvedTypes - // Don't need to clone astResolvedTypePacks - // Don't need to clone upperBoundContributors - copyModuleMap(incrementalModule->astScopes, result->astScopes); - // Don't need to clone declared Globals; - return incrementalModule; -} - void mixedModeCompatibility( const ScopePtr& bottomScopeStale, const ScopePtr& myFakeScope, @@ -1315,10 +1288,8 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED( ModulePtr incrementalModule = nullptr; if (FFlag::LuauAllFreeTypesHaveScopes) incrementalModule = cloneModule(cloneState, stale, std::move(astAllocator), freshChildOfNearestScope.get()); - else if (FFlag::LuauCloneIncrementalModule) - incrementalModule = cloneModule_DEPRECATED(cloneState, stale, std::move(astAllocator)); else - incrementalModule = copyModule(stale, std::move(astAllocator)); + incrementalModule = cloneModule_DEPRECATED(cloneState, stale, std::move(astAllocator)); reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneModuleEnd); incrementalModule->checkedInNewSolver = true; @@ -1372,44 +1343,27 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED( }; reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart); - if (FFlag::LuauCloneIncrementalModule) - { - incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope); - cg.rootScope = freshChildOfNearestScope.get(); + incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope); + cg.rootScope = freshChildOfNearestScope.get(); - if (FFlag::LuauAllFreeTypesHaveScopes) - cloneAndSquashScopes( - cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get() - ); - else - cloneAndSquashScopes_DEPRECATED( - cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get() - ); - - reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeEnd); - cg.visitFragmentRoot(freshChildOfNearestScope, root); - - if (FFlag::LuauPersistConstraintGenerationScopes) - { - for (auto p : cg.scopes) - incrementalModule->scopes.emplace_back(std::move(p)); - } - } + if (FFlag::LuauAllFreeTypesHaveScopes) + cloneAndSquashScopes( + cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get() + ); else + cloneAndSquashScopes_DEPRECATED( + cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get() + ); + + reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeEnd); + cg.visitFragmentRoot(freshChildOfNearestScope, root); + + if (FFlag::LuauPersistConstraintGenerationScopes) { - // Any additions to the scope must occur in a fresh scope - cg.rootScope = stale->getModuleScope().get(); - incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope); - mixedModeCompatibility(closestScope, freshChildOfNearestScope, stale, NotNull{&dfg}, root); - // closest Scope -> children = { ...., freshChildOfNearestScope} - // We need to trim nearestChild from the scope hierarchy - closestScope->children.emplace_back(freshChildOfNearestScope.get()); - cg.visitFragmentRoot(freshChildOfNearestScope, root); - // Trim nearestChild from the closestScope - Scope* back = closestScope->children.back().get(); - LUAU_ASSERT(back == freshChildOfNearestScope.get()); - closestScope->children.pop_back(); + for (auto p : cg.scopes) + incrementalModule->scopes.emplace_back(std::move(p)); } + reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverStart); if (FFlag::LuauAllFreeTypesHaveScopes) diff --git a/Analysis/src/Generalization.cpp b/Analysis/src/Generalization.cpp index ad07b0fd..5971c927 100644 --- a/Analysis/src/Generalization.cpp +++ b/Analysis/src/Generalization.cpp @@ -1403,7 +1403,10 @@ std::optional generalize( } for (TypeId unsealedTableTy : fts.unsealedTables) - sealTable(scope, unsealedTableTy); + { + if (!generalizationTarget || unsealedTableTy == *generalizationTarget) + sealTable(scope, unsealedTableTy); + } for (const auto& [freePackId, params] : fts.typePacks) { @@ -1463,29 +1466,93 @@ std::optional generalize( struct GenericCounter : TypeVisitor { + struct CounterState + { + size_t count = 0; + Polarity polarity = Polarity::None; + }; + NotNull> cachedTypes; - DenseHashMap generics{nullptr}; - DenseHashMap genericPacks{nullptr}; + DenseHashMap generics{nullptr}; + DenseHashMap genericPacks{nullptr}; + + Polarity polarity = Polarity::Positive; explicit GenericCounter(NotNull> cachedTypes) : cachedTypes(cachedTypes) { } + bool visit(TypeId ty, const FunctionType& ft) override + { + if (ty->persistent) + return false; + + polarity = invert(polarity); + traverse(ft.argTypes); + polarity = invert(polarity); + traverse(ft.retTypes); + + return false; + } + + bool visit(TypeId ty, const TableType& tt) override + { + if (ty->persistent) + return false; + + const Polarity previous = polarity; + + for (const auto& [_name, prop] : tt.props) + { + if (prop.isReadOnly()) + traverse(*prop.readTy); + else + { + LUAU_ASSERT(prop.isShared()); + + polarity = Polarity::Mixed; + traverse(prop.type()); + polarity = previous; + } + } + + if (tt.indexer) + { + polarity = Polarity::Mixed; + traverse(tt.indexer->indexType); + traverse(tt.indexer->indexResultType); + polarity = previous; + } + + return false; + } + + bool visit(TypeId ty, const ExternType&) override + { + return false; + } + bool visit(TypeId ty, const GenericType&) override { - size_t* count = generics.find(ty); - if (count) - ++*count; + auto state = generics.find(ty); + if (state) + { + ++state->count; + state->polarity |= polarity; + } return false; } bool visit(TypePackId tp, const GenericTypePack&) override { - size_t* count = genericPacks.find(tp); - if (count) - ++*count; + auto state = genericPacks.find(tp); + if (state) + { + ++state->count; + state->polarity |= polarity; + } return false; } @@ -1521,21 +1588,30 @@ void pruneUnnecessaryGenerics( generic = follow(generic); auto g = get(generic); if (g && !g->explicitName) - counter.generics[generic] = 0; + counter.generics[generic] = {}; } - for (TypePackId genericPack : functionTy->genericPacks) + + // It is sometimes the case that a pack in the generic list will become a + // pack that (transitively) has a generic tail. If it does, we need to add + // that generic tail to the generic pack list. + for (size_t i = 0; i < functionTy->genericPacks.size(); ++i) { - genericPack = follow(genericPack); - auto g = get(genericPack); - if (g && !g->explicitName) - counter.genericPacks[genericPack] = 0; + TypePackId genericPack = follow(functionTy->genericPacks[i]); + + TypePackId tail = getTail(genericPack); + + if (tail != genericPack) + functionTy->genericPacks.push_back(tail); + + if (auto g = get(tail); g && !g->explicitName) + counter.genericPacks[genericPack] = {}; } counter.traverse(ty); - for (const auto& [generic, count] : counter.generics) + for (const auto& [generic, state] : counter.generics) { - if (count == 1) + if (state.count == 1 && state.polarity != Polarity::Mixed) emplaceType(asMutable(generic), builtinTypes->unknownType); } @@ -1557,9 +1633,9 @@ void pruneUnnecessaryGenerics( functionTy->generics.erase(it, functionTy->generics.end()); - for (const auto& [genericPack, count] : counter.genericPacks) + for (const auto& [genericPack, state] : counter.genericPacks) { - if (count == 1) + if (state.count == 1) emplaceTypePack(asMutable(genericPack), builtinTypes->unknownTypePack); } diff --git a/Analysis/src/Instantiation.cpp b/Analysis/src/Instantiation.cpp index 7ab53d9c..bd6a4b95 100644 --- a/Analysis/src/Instantiation.cpp +++ b/Analysis/src/Instantiation.cpp @@ -11,7 +11,6 @@ #include LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) namespace Luau { @@ -164,7 +163,7 @@ TypeId ReplaceGenerics::clean(TypeId ty) } else { - return FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtinTypes, scope, level) : addType(FreeType{scope, level}); + return arena->freshType(builtinTypes, scope, level); } } diff --git a/Analysis/src/NonStrictTypeChecker.cpp b/Analysis/src/NonStrictTypeChecker.cpp index d404f5ad..fd2c1d0f 100644 --- a/Analysis/src/NonStrictTypeChecker.cpp +++ b/Analysis/src/NonStrictTypeChecker.cpp @@ -22,10 +22,9 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals) -LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes) +LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes2) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) namespace Luau @@ -218,7 +217,7 @@ struct NonStrictTypeChecker return *fst; else if (auto ftp = get(pack)) { - TypeId result = FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtinTypes, ftp->scope) : arena->addType(FreeType{ftp->scope}); + TypeId result = arena->freshType(builtinTypes, ftp->scope); TypePackId freeTail = arena->addTypePack(FreeTypePack{ftp->scope}); TypePack* resultPack = emplaceTypePack(asMutable(pack)); @@ -341,11 +340,8 @@ struct NonStrictTypeChecker { ctx.remove(dfg->getDef(local)); - if (FFlag::LuauNewNonStrictVisitTypes) - { - if (local->annotation) - visit(local->annotation); - } + if (FFlag::LuauNewNonStrictVisitTypes2) + visit(local->annotation); } } else @@ -431,9 +427,8 @@ struct NonStrictTypeChecker NonStrictContext visit(AstStatFor* forStatement) { - if (FFlag::LuauNewNonStrictVisitTypes) - if (forStatement->var->annotation) - visit(forStatement->var->annotation); + if (FFlag::LuauNewNonStrictVisitTypes2) + visit(forStatement->var->annotation); if (FFlag::LuauNonStrictVisitorImprovements) { @@ -454,13 +449,10 @@ struct NonStrictTypeChecker NonStrictContext visit(AstStatForIn* forInStatement) { - if (FFlag::LuauNewNonStrictVisitTypes) + if (FFlag::LuauNewNonStrictVisitTypes2) { for (auto var : forInStatement->vars) - { - if (var->annotation) - visit(var->annotation); - } + visit(var->annotation); } if (FFlag::LuauNonStrictVisitorImprovements) @@ -511,7 +503,7 @@ struct NonStrictTypeChecker NonStrictContext visit(AstStatTypeAlias* typeAlias) { - if (FFlag::LuauNewNonStrictVisitTypes) + if (FFlag::LuauNewNonStrictVisitTypes2) { visitGenerics(typeAlias->generics, typeAlias->genericPacks); visit(typeAlias->type); @@ -527,7 +519,7 @@ struct NonStrictTypeChecker NonStrictContext visit(AstStatDeclareFunction* declFn) { - if (FFlag::LuauNewNonStrictVisitTypes) + if (FFlag::LuauNewNonStrictVisitTypes2) { visitGenerics(declFn->generics, declFn->genericPacks); visit(declFn->params); @@ -539,7 +531,7 @@ struct NonStrictTypeChecker NonStrictContext visit(AstStatDeclareGlobal* declGlobal) { - if (FFlag::LuauNewNonStrictVisitTypes) + if (FFlag::LuauNewNonStrictVisitTypes2) visit(declGlobal->type); return {}; @@ -547,7 +539,7 @@ struct NonStrictTypeChecker NonStrictContext visit(AstStatDeclareExternType* declClass) { - if (FFlag::LuauNewNonStrictVisitTypes) + if (FFlag::LuauNewNonStrictVisitTypes2) { if (declClass->indexer) { @@ -823,22 +815,16 @@ struct NonStrictTypeChecker } remainder.remove(dfg->getDef(local)); - if (FFlag::LuauNewNonStrictVisitTypes) - { - if (local->annotation) - visit(local->annotation); - } + if (FFlag::LuauNewNonStrictVisitTypes2) + visit(local->annotation); } - if (FFlag::LuauNewNonStrictVisitTypes) + if (FFlag::LuauNewNonStrictVisitTypes2) { visitGenerics(exprFn->generics, exprFn->genericPacks); if (FFlag::LuauStoreReturnTypesAsPackOnAst) - { - if (exprFn->returnAnnotation) - visit(exprFn->returnAnnotation); - } + visit(exprFn->returnAnnotation); else { if (exprFn->returnAnnotation_DEPRECATED) @@ -889,7 +875,7 @@ struct NonStrictTypeChecker NonStrictContext visit(AstExprTypeAssertion* typeAssertion) { - if (FFlag::LuauNewNonStrictVisitTypes) + if (FFlag::LuauNewNonStrictVisitTypes2) visit(typeAssertion->annotation); if (FFlag::LuauNonStrictVisitorImprovements) @@ -930,7 +916,11 @@ struct NonStrictTypeChecker void visit(AstType* ty) { - LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes); + LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes2); + + // If this node is `nullptr`, early exit. + if (!ty) + return; if (auto t = ty->as()) return visit(t); @@ -1139,7 +1129,11 @@ struct NonStrictTypeChecker void visit(AstTypePack* pack) { - LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes); + LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes2); + + // If there is no pack node, early exit. + if (!pack) + return; if (auto p = pack->as()) return visit(p); diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index 67043e4f..f9d48a7d 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -6,6 +6,7 @@ #include "Luau/DenseHash.h" #include "Luau/RecursionCounter.h" #include "Luau/Set.h" +#include "Luau/Type.h" #include "Luau/TypeArena.h" #include "Luau/TypePairHash.h" #include "Luau/TypeUtils.h" @@ -17,6 +18,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8) LUAU_FASTFLAGVARIABLE(LuauSimplificationRecheckAssumption) LUAU_FASTFLAGVARIABLE(LuauOptimizeFalsyAndTruthyIntersect) +LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType) namespace Luau { @@ -316,12 +318,14 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) { if (get(right)) return Relation::Subset; - else if (get(right)) + + if (get(right)) return Relation::Coincident; - else if (get(right)) + + if (get(right)) return Relation::Disjoint; - else - return Relation::Superset; + + return Relation::Superset; } if (get(right)) @@ -331,8 +335,8 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) { if (get(right)) return Relation::Coincident; - else - return Relation::Superset; + + return Relation::Superset; } if (get(right)) @@ -364,26 +368,33 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) if (isTypeVariable(left) || isTypeVariable(right)) return Relation::Intersects; + if (FFlag::LuauSimplificationTableExternType) + { + // if either type is a type function, we cannot know if they'll be related. + if (get(left) || get(right)) + return Relation::Intersects; + } + if (get(left)) { if (get(right)) return Relation::Coincident; else if (get(right)) return Relation::Subset; - else - return Relation::Disjoint; + + return Relation::Disjoint; } - if (get(right)) + else if (get(right)) return flip(relate(right, left, seen)); if (get(left)) { if (get(right)) return Relation::Coincident; - else - return Relation::Subset; + + return Relation::Subset; } - if (get(right)) + else if (get(right)) return flip(relate(right, left, seen)); if (auto ut = get(left)) @@ -447,33 +458,34 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) { if (lp->type == rp->type) return Relation::Coincident; - else - return Relation::Disjoint; + + return Relation::Disjoint; } if (auto rs = get(right)) { if (lp->type == PrimitiveType::String && rs->variant.get_if()) return Relation::Superset; - else if (lp->type == PrimitiveType::Boolean && rs->variant.get_if()) + + if (lp->type == PrimitiveType::Boolean && rs->variant.get_if()) return Relation::Superset; - else - return Relation::Disjoint; + + return Relation::Disjoint; } if (lp->type == PrimitiveType::Function) { if (get(right)) return Relation::Superset; - else - return Relation::Disjoint; + + return Relation::Disjoint; } if (lp->type == PrimitiveType::Table) { if (get(right)) return Relation::Superset; - else - return Relation::Disjoint; + + return Relation::Disjoint; } if (get(right) || get(right) || get(right) || get(right)) @@ -487,12 +499,13 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) if (get(right)) return flip(relate(right, left, seen)); + if (auto rs = get(right)) { if (ls->variant == rs->variant) return Relation::Coincident; - else - return Relation::Disjoint; + + return Relation::Disjoint; } } @@ -502,11 +515,11 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) { if (rp->type == PrimitiveType::Function) return Relation::Subset; - else - return Relation::Disjoint; + + return Relation::Disjoint; } - else - return Relation::Intersects; + + return Relation::Intersects; } if (auto lt = get(left)) @@ -515,10 +528,11 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) { if (rp->type == PrimitiveType::Table) return Relation::Subset; - else - return Relation::Disjoint; + + return Relation::Disjoint; } - else if (auto rt = get(right)) + + if (auto rt = get(right)) { // TODO PROBABLY indexers and metatables. if (1 == rt->props.size()) @@ -538,14 +552,42 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) */ if (lt->props.size() > 1 && r == Relation::Superset) return Relation::Intersects; - else - return r; + + return r; } - else if (1 == lt->props.size()) + + if (1 == lt->props.size()) return flip(relate(right, left, seen)); - else - return Relation::Intersects; + + return Relation::Intersects; } + + if (FFlag::LuauSimplificationTableExternType) + { + if (auto re = get(right)) + { + Relation overall = Relation::Coincident; + + for (auto& [name, prop] : lt->props) + { + if (auto propInExternType = re->props.find(name); propInExternType != re->props.end()) + { + Relation propRel = relate(prop.type(), propInExternType->second.type()); + + if (propRel == Relation::Disjoint) + return Relation::Disjoint; + + if (propRel == Relation::Coincident) + continue; + + overall = Relation::Intersects; + } + } + + return overall; + } + } + // TODO metatables return Relation::Disjoint; @@ -557,10 +599,11 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) { if (isSubclass(ct, rct)) return Relation::Subset; - else if (isSubclass(rct, ct)) + + if (isSubclass(rct, ct)) return Relation::Superset; - else - return Relation::Disjoint; + + return Relation::Disjoint; } return Relation::Disjoint; diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index 0c965ca6..0dbad322 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -7,13 +7,11 @@ #include "Luau/Normalize.h" #include "Luau/RecursionCounter.h" #include "Luau/Scope.h" -#include "Luau/StringUtils.h" #include "Luau/Substitution.h" #include "Luau/ToString.h" #include "Luau/TxnLog.h" #include "Luau/Type.h" #include "Luau/TypeArena.h" -#include "Luau/TypeCheckLimits.h" #include "Luau/TypeFunction.h" #include "Luau/TypePack.h" #include "Luau/TypePath.h" @@ -33,7 +31,7 @@ struct VarianceFlipper Subtyping::Variance* variance; Subtyping::Variance oldValue; - VarianceFlipper(Subtyping::Variance* v) + explicit VarianceFlipper(Subtyping::Variance* v) : variance(v) , oldValue(*v) { diff --git a/Analysis/src/Transpiler.cpp b/Analysis/src/Transpiler.cpp index 00e69ecd..b3cf82a1 100644 --- a/Analysis/src/Transpiler.cpp +++ b/Analysis/src/Transpiler.cpp @@ -12,7 +12,6 @@ LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_FASTFLAG(LuauAstTypeGroup3) -LUAU_FASTFLAG(LuauFixDoBlockEndLocation) LUAU_FASTFLAG(LuauParseOptionalAsNode2) LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) @@ -706,8 +705,6 @@ struct Printer_DEPRECATED writer.keyword("do"); for (const auto& s : block->body) visualize(*s); - if (!FFlag::LuauFixDoBlockEndLocation) - writer.advance(block->location.end); writeEnd(program.location); } else if (const auto& a = program.as()) @@ -2323,8 +2320,6 @@ struct Printer { const auto cstNode = lookupCstNode(&func); - // TODO(CLI-139347): need to handle return type (incl. parentheses of return type) - if (func.generics.size > 0 || func.genericPacks.size > 0) { CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr); @@ -2395,12 +2390,18 @@ struct Printer if (cstNode) advance(cstNode->returnSpecifierPosition); writer.symbol(":"); - writer.space(); if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + if (!cstNode) + writer.space(); visualizeTypePackAnnotation(*func.returnAnnotation, false, false); + } else + { + writer.space(); visualizeTypeList(*func.returnAnnotation_DEPRECATED, false); + } } visualizeBlock(*func.body); diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index 3add4de9..88ab4e53 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -506,31 +506,6 @@ FreeType::FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId uppe { } -// Old constructors -FreeType::FreeType(TypeLevel level) - : index(Unifiable::freshIndex()) - , level(level) - , scope(nullptr) -{ - LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds); -} - -FreeType::FreeType(Scope* scope) - : index(Unifiable::freshIndex()) - , level{} - , scope(scope) -{ - LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds); -} - -FreeType::FreeType(Scope* scope, TypeLevel level) - : index(Unifiable::freshIndex()) - , level(level) - , scope(scope) -{ - LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds); -} - GenericType::GenericType() : index(Unifiable::freshIndex()) , name("g" + std::to_string(index)) diff --git a/Analysis/src/TypeArena.cpp b/Analysis/src/TypeArena.cpp index c5ccc0d6..5a46dcb7 100644 --- a/Analysis/src/TypeArena.cpp +++ b/Analysis/src/TypeArena.cpp @@ -50,33 +50,6 @@ TypeId TypeArena::freshType(NotNull builtins, Scope* scope, TypeLe return allocated; } -TypeId TypeArena::freshType_DEPRECATED(TypeLevel level) -{ - TypeId allocated = types.allocate(FreeType{level}); - - asMutable(allocated)->owningArena = this; - - return allocated; -} - -TypeId TypeArena::freshType_DEPRECATED(Scope* scope) -{ - TypeId allocated = types.allocate(FreeType{scope}); - - asMutable(allocated)->owningArena = this; - - return allocated; -} - -TypeId TypeArena::freshType_DEPRECATED(Scope* scope, TypeLevel level) -{ - TypeId allocated = types.allocate(FreeType{scope, level}); - - asMutable(allocated)->owningArena = this; - - return allocated; -} - TypePackId TypeArena::freshTypePack(Scope* scope, Polarity polarity) { TypePackId allocated = typePacks.allocate(FreeTypePack{scope, polarity}); diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 3550a65a..8648ec7e 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -30,7 +30,6 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats) @@ -2114,10 +2113,7 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey) } else { - expectedRets = module->internalTypes.addTypePack( - {FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, scope, TypeLevel{}) - : module->internalTypes.freshType_DEPRECATED(scope, TypeLevel{})} - ); + expectedRets = module->internalTypes.addTypePack({module->internalTypes.freshType(builtinTypes, scope, TypeLevel{})}); } TypeId expectedTy = module->internalTypes.addType(FunctionType(expectedArgs, expectedRets)); @@ -2380,8 +2376,7 @@ TypeId TypeChecker2::flattenPack(TypePackId pack) return *fst; else if (auto ftp = get(pack)) { - TypeId result = FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, ftp->scope) - : module->internalTypes.addType(FreeType{ftp->scope}); + TypeId result = module->internalTypes.freshType(builtinTypes, ftp->scope); TypePackId freeTail = module->internalTypes.addTypePack(FreeTypePack{ftp->scope}); TypePack* resultPack = emplaceTypePack(asMutable(pack)); diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index 4fae1a70..3bd05335 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -1839,10 +1839,20 @@ TypeFunctionReductionResult orTypeFunction( return {rhsTy, Reduction::MaybeOk, {}, {}}; // check to see if both operand types are resolved enough, and wait to reduce if not - if (isPending(lhsTy, ctx->solver)) - return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; - else if (isPending(rhsTy, ctx->solver)) - return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}}; + if (FFlag::DebugLuauGreedyGeneralization) + { + if (is(lhsTy)) + return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; + else if (is(rhsTy)) + return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}}; + } + else + { + if (isPending(lhsTy, ctx->solver)) + return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; + else if (isPending(rhsTy, ctx->solver)) + return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}}; + } // Or evalutes to the LHS type if the LHS is truthy, and the RHS type if LHS is falsy. SimplifyResult filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->truthyType); @@ -1876,10 +1886,20 @@ static TypeFunctionReductionResult comparisonTypeFunction( if (lhsTy == instance || rhsTy == instance) return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}}; - if (isPending(lhsTy, ctx->solver)) - return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; - else if (isPending(rhsTy, ctx->solver)) - return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}}; + if (FFlag::DebugLuauGreedyGeneralization) + { + if (is(lhsTy)) + return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; + else if (is(rhsTy)) + return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}}; + } + else + { + if (isPending(lhsTy, ctx->solver)) + return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; + else if (isPending(rhsTy, ctx->solver)) + return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}}; + } // Algebra Reduction Rules for comparison type functions // Note that comparing to never tells you nothing about the other operand @@ -2240,8 +2260,12 @@ TypeFunctionReductionResult refineTypeFunction( for (size_t i = 1; i < typeParams.size(); i++) discriminantTypes.push_back(follow(typeParams.at(i))); + const bool targetIsPending = FFlag::DebugLuauGreedyGeneralization + ? is(targetTy) + : isPending(targetTy, ctx->solver); + // check to see if both operand types are resolved enough, and wait to reduce if not - if (isPending(targetTy, ctx->solver)) + if (targetIsPending) return {std::nullopt, Reduction::MaybeOk, {targetTy}, {}}; else { @@ -2326,8 +2350,32 @@ TypeFunctionReductionResult refineTypeFunction( if (is(target) || isSimpleDiscriminant(discriminant)) { SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant); - if (!result.blockedTypes.empty()) - return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}}; + if (FFlag::DebugLuauGreedyGeneralization) + { + // Simplification considers free and generic types to be + // 'blocking', but that's not suitable for refine<>. + // + // If we are only blocked on those types, we consider + // the simplification a success and reduce. + if (std::all_of( + begin(result.blockedTypes), + end(result.blockedTypes), + [](auto&& v) + { + return is(follow(v)); + } + )) + { + return {result.result, {}}; + } + else + return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}}; + } + else + { + if (!result.blockedTypes.empty()) + return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}}; + } return {result.result, {}}; } } @@ -3527,7 +3575,7 @@ BuiltinTypeFunctions::BuiltinTypeFunctions() , ltFunc{"lt", ltTypeFunction} , leFunc{"le", leTypeFunction} , eqFunc{"eq", eqTypeFunction} - , refineFunc{"refine", refineTypeFunction} + , refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ FFlag::DebugLuauGreedyGeneralization} , singletonFunc{"singleton", singletonTypeFunction} , unionFunc{"union", unionTypeFunction} , intersectFunc{"intersect", intersectTypeFunction} diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index 15d7cfd4..65aec6d5 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -15,6 +15,7 @@ LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit) LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents) +LUAU_FASTFLAGVARIABLE(LuauTypeFunOptional) namespace Luau { @@ -315,6 +316,38 @@ static int getSingletonValue(lua_State* L) luaL_error(L, "type.value: can't call `value` method on `%s` type", getTag(L, self).c_str()); } +// Luau: `types.optional(ty: type) -> type` +// Returns the type instance representing an optional version of `ty`. +// If `ty` is a union, this adds `nil` to the components of the union. +// Otherwise, makes a union of the two things. +static int createOptional(lua_State* L) +{ + LUAU_ASSERT(FFlag::LuauTypeFunOptional); + + int argumentCount = lua_gettop(L); + if (argumentCount != 1) + luaL_error(L, "types.optional: expected 1 argument, but got %d", argumentCount); + + TypeFunctionTypeId argument = getTypeUserData(L, 1); + + std::vector components; + + if (auto unionTy = get(argument)) + { + components.reserve(unionTy->components.size() + 1); + + components.insert(components.begin(), unionTy->components.begin(), unionTy->components.end()); + } + else + components.emplace_back(argument); + + components.emplace_back(allocateTypeFunctionType(L, TypeFunctionPrimitiveType(TypeFunctionPrimitiveType::NilType))); + + allocTypeUserData(L, TypeFunctionUnionType{components}); + + return 1; +} + // Luau: `types.unionof(...: type) -> type` // Returns the type instance representing union static int createUnion(lua_State* L) @@ -1524,6 +1557,7 @@ void registerTypesLibrary(lua_State* L) {"copy", deepCopy}, {"generic", createGeneric}, + {(FFlag::LuauTypeFunOptional) ? "optional" : nullptr, (FFlag::LuauTypeFunOptional) ? createOptional : nullptr}, {nullptr, nullptr} }; diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 922ecc66..2456efd5 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -32,7 +32,6 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500) LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification) LUAU_FASTFLAG(LuauInstantiateInSubtyping) -LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) @@ -799,8 +798,7 @@ struct Demoter : Substitution { auto ftv = get(ty); LUAU_ASSERT(ftv); - return FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtins, demotedLevel(ftv->level)) - : addType(FreeType{demotedLevel(ftv->level)}); + return arena->freshType(builtins, demotedLevel(ftv->level)); } TypePackId clean(TypePackId tp) override @@ -5410,8 +5408,7 @@ TypeId TypeChecker::freshType(const ScopePtr& scope) TypeId TypeChecker::freshType(TypeLevel level) { - return FFlag::LuauFreeTypesMustHaveBounds ? currentModule->internalTypes.freshType(builtinTypes, level) - : currentModule->internalTypes.addType(Type(FreeType(level))); + return currentModule->internalTypes.freshType(builtinTypes, level); } TypeId TypeChecker::singletonType(bool value) diff --git a/Analysis/src/TypePack.cpp b/Analysis/src/TypePack.cpp index a1aa6d16..5cd59dbe 100644 --- a/Analysis/src/TypePack.cpp +++ b/Analysis/src/TypePack.cpp @@ -208,6 +208,26 @@ TypePackIterator end(TypePackId tp) return TypePackIterator{}; } +TypePackId getTail(TypePackId tp) +{ + DenseHashSet seen{nullptr}; + while (tp) + { + tp = follow(tp); + + if (seen.contains(tp)) + break; + seen.insert(tp); + + if (auto pack = get(tp); pack && pack->tail) + tp = *pack->tail; + else + break; + } + + return follow(tp); +} + bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs) { TypePackId lhsId = const_cast(&lhs); diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index 88fda762..949efde3 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -11,7 +11,6 @@ #include LUAU_FASTFLAG(LuauSolverV2); -LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode) @@ -328,7 +327,7 @@ TypePack extendTypePack( trackInteriorFreeType(ftp->scope, t); } else - t = FFlag::LuauFreeTypesMustHaveBounds ? arena.freshType(builtinTypes, ftp->scope) : arena.freshType_DEPRECATED(ftp->scope); + t = arena.freshType(builtinTypes, ftp->scope); } newPack.head.push_back(t); diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index 27356870..d84c4ca8 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -22,7 +22,6 @@ LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering) LUAU_FASTFLAGVARIABLE(LuauUnifierRecursionOnRestart) -LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) namespace Luau { @@ -1559,7 +1558,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal if (FFlag::LuauSolverV2) return freshType(NotNull{types}, builtinTypes, scope); else - return FFlag::LuauFreeTypesMustHaveBounds ? types->freshType(builtinTypes, scope, level) : types->freshType_DEPRECATED(scope, level); + return types->freshType(builtinTypes, scope, level); }; const TypePackId emptyTp = types->addTypePack(TypePack{{}, std::nullopt}); diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index de15eee8..ad0c3297 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -20,7 +20,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) LUAU_FASTFLAGVARIABLE(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauStoreCSTData2) LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3) -LUAU_FASTFLAGVARIABLE(LuauFixDoBlockEndLocation) LUAU_FASTFLAGVARIABLE(LuauParseOptionalAsNode2) LUAU_FASTFLAGVARIABLE(LuauDeclareExternType) LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer) @@ -555,7 +554,7 @@ AstStat* Parser::parseDo() Location endLocation = lexer.current().location; body->hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo); - if (FFlag::LuauFixDoBlockEndLocation && body->hasEnd) + if (body->hasEnd) body->location.end = endLocation.end; if (FFlag::LuauStoreCSTData2 && options.storeCstData) diff --git a/Require/Navigator/include/Luau/RequireNavigator.h b/Require/Navigator/include/Luau/RequireNavigator.h index 1be079b7..7e5a3dba 100644 --- a/Require/Navigator/include/Luau/RequireNavigator.h +++ b/Require/Navigator/include/Luau/RequireNavigator.h @@ -87,6 +87,11 @@ private: [[nodiscard]] Error navigateToAlias(const std::string& alias, const std::string& value); [[nodiscard]] Error navigateToAndPopulateConfig(const std::string& desiredAlias); + [[nodiscard]] Error resetToRequirer(); + [[nodiscard]] Error jumpToAlias(const std::string& aliasPath); + [[nodiscard]] Error navigateToParent(std::optional previousComponent); + [[nodiscard]] Error navigateToChild(const std::string& component); + NavigationContext& navigationContext; ErrorHandler& errorHandler; Luau::Config config; diff --git a/Require/Navigator/src/RequireNavigator.cpp b/Require/Navigator/src/RequireNavigator.cpp index f7e0c0b1..243bbaf7 100644 --- a/Require/Navigator/src/RequireNavigator.cpp +++ b/Require/Navigator/src/RequireNavigator.cpp @@ -10,24 +10,11 @@ #include #include -static constexpr char kRequireErrorAmbiguous[] = "require path could not be resolved to a unique file"; -static constexpr char kRequireErrorGeneric[] = "error requiring module"; - namespace Luau::Require { using Error = std::optional; -static Error toError(NavigationContext::NavigateResult result) -{ - if (result == NavigationContext::NavigateResult::Success) - return std::nullopt; - if (result == NavigationContext::NavigateResult::Ambiguous) - return kRequireErrorAmbiguous; - else - return kRequireErrorGeneric; -} - static std::string extractAlias(std::string_view path) { // To ignore the '@' alias prefix when processing the alias @@ -53,7 +40,7 @@ Navigator::Status Navigator::navigate(std::string path) { std::replace(path.begin(), path.end(), '\\', '/'); - if (Error error = toError(navigationContext.reset(navigationContext.getRequirerIdentifier()))) + if (Error error = resetToRequirer()) { errorHandler.reportError(*error); return Status::ErrorReported; @@ -98,7 +85,7 @@ Error Navigator::navigateImpl(std::string_view path) // If the alias is "@self", we reset to the requirer's context and // navigate directly from there. - if (Error error = toError(navigationContext.reset(navigationContext.getRequirerIdentifier()))) + if (Error error = resetToRequirer()) return error; if (Error error = navigateThroughPath(path)) return error; @@ -114,7 +101,7 @@ Error Navigator::navigateImpl(std::string_view path) if (pathType == PathType::RelativeToCurrent || pathType == PathType::RelativeToParent) { - if (Error error = toError(navigationContext.toParent())) + if (Error error = navigateToParent(std::nullopt)) return error; if (Error error = navigateThroughPath(path)) return error; @@ -133,6 +120,7 @@ Error Navigator::navigateThroughPath(std::string_view path) components = splitPath(components.second); } + std::optional previousComponent; while (!(components.first.empty() && components.second.empty())) { if (components.first == "." || components.first.empty()) @@ -142,14 +130,15 @@ Error Navigator::navigateThroughPath(std::string_view path) } else if (components.first == "..") { - if (Error error = toError(navigationContext.toParent())) + if (Error error = navigateToParent(previousComponent)) return error; } else { - if (Error error = toError(navigationContext.toChild(std::string{components.first}))) + if (Error error = navigateToChild(std::string{components.first})) return error; } + previousComponent = components.first; components = splitPath(components.second); } @@ -167,11 +156,11 @@ Error Navigator::navigateToAlias(const std::string& alias, const std::string& va } else if (pathType == PathType::Aliased) { - return "@" + alias + " cannot point to other aliases"; + return "alias \"@" + alias + "\" cannot point to an aliased path (\"" + value + "\")"; } else { - if (Error error = toError(navigationContext.jumpToAlias(value))) + if (Error error = jumpToAlias(value)) return error; } @@ -189,7 +178,7 @@ Error Navigator::navigateToAndPopulateConfig(const std::string& desiredAlias) { std::optional configContents = navigationContext.getConfig(); if (!configContents) - return "could not get configuration file contents"; + return "could not get configuration file contents to resolve alias \"" + desiredAlias + "\""; Luau::ConfigOptions opts; Luau::ConfigOptions::AliasOptions aliasOpts; @@ -205,4 +194,56 @@ Error Navigator::navigateToAndPopulateConfig(const std::string& desiredAlias) return std::nullopt; } +Error Navigator::resetToRequirer() +{ + NavigationContext::NavigateResult result = navigationContext.reset(navigationContext.getRequirerIdentifier()); + if (result == NavigationContext::NavigateResult::Success) + return std::nullopt; + + std::string errorMessage = "could not reset to requiring context"; + if (result == NavigationContext::NavigateResult::Ambiguous) + errorMessage += " (ambiguous)"; + return errorMessage; +} + +Error Navigator::jumpToAlias(const std::string& aliasPath) +{ + NavigationContext::NavigateResult result = navigationContext.jumpToAlias(aliasPath); + if (result == NavigationContext::NavigateResult::Success) + return std::nullopt; + + std::string errorMessage = "could not jump to alias \"" + aliasPath + "\""; + if (result == NavigationContext::NavigateResult::Ambiguous) + errorMessage += " (ambiguous)"; + return errorMessage; +} + +Error Navigator::navigateToParent(std::optional previousComponent) +{ + NavigationContext::NavigateResult result = navigationContext.toParent(); + if (result == NavigationContext::NavigateResult::Success) + return std::nullopt; + + std::string errorMessage; + if (previousComponent) + errorMessage = "could not get parent of component \"" + *previousComponent + "\""; + else + errorMessage = "could not get parent of requiring context"; + if (result == NavigationContext::NavigateResult::Ambiguous) + errorMessage += " (ambiguous)"; + return errorMessage; +} + +Error Navigator::navigateToChild(const std::string& component) +{ + NavigationContext::NavigateResult result = navigationContext.toChild(component); + if (result == NavigationContext::NavigateResult::Success) + return std::nullopt; + + std::string errorMessage = "could not resolve child component \"" + component + "\""; + if (result == NavigationContext::NavigateResult::Ambiguous) + errorMessage += " (ambiguous)"; + return errorMessage; +} + } // namespace Luau::Require diff --git a/Require/Runtime/include/Luau/Require.h b/Require/Runtime/include/Luau/Require.h index 45a05e7e..5df7e41e 100644 --- a/Require/Runtime/include/Luau/Require.h +++ b/Require/Runtime/include/Luau/Require.h @@ -106,7 +106,9 @@ struct luarequire_Configuration luarequire_WriteResult (*get_config)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out); // Executes the module and places the result on the stack. Returns the - // number of results placed on the stack. + // number of results placed on the stack. Returning -1 directs the requiring + // thread to yield. In this case, this thread should be resumed with the + // module result pushed onto its stack. int (*load)(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* contents); }; @@ -130,3 +132,10 @@ LUALIB_API int luarequire_pushproxyrequire(lua_State* L, luarequire_Configuratio // result will always be immediately returned when the given path is required. // Expects the path and table to be passed as arguments on the stack. LUALIB_API int luarequire_registermodule(lua_State* L); + +// Clears the entry associated with the given cache key from the require cache. +// Expects the cache key to be passed as an argument on the stack. +LUALIB_API int luarequire_clearcacheentry(lua_State* L); + +// Clears all entries from the require cache. +LUALIB_API int luarequire_clearcache(lua_State* L); diff --git a/Require/Runtime/src/Navigation.cpp b/Require/Runtime/src/Navigation.cpp index 79712062..fd54bd61 100644 --- a/Require/Runtime/src/Navigation.cpp +++ b/Require/Runtime/src/Navigation.cpp @@ -6,6 +6,8 @@ #include "lua.h" #include "lualib.h" +#include + static constexpr size_t initalFileBufferSize = 1024; static constexpr size_t initalIdentifierBufferSize = 64; @@ -111,14 +113,16 @@ std::optional RuntimeNavigationContext::getStringFromCWriter( } -RuntimeErrorHandler::RuntimeErrorHandler(lua_State* L) +RuntimeErrorHandler::RuntimeErrorHandler(lua_State* L, std::string requiredPath) : L(L) + , errorPrefix("error requiring module \"" + std::move(requiredPath) + "\": ") { } void RuntimeErrorHandler::reportError(std::string message) { - luaL_errorL(L, "%s", message.c_str()); + std::string fullError = errorPrefix + std::move(message); + luaL_errorL(L, "%s", fullError.c_str()); } } // namespace Luau::Require diff --git a/Require/Runtime/src/Navigation.h b/Require/Runtime/src/Navigation.h index 35a836b3..7b97c75e 100644 --- a/Require/Runtime/src/Navigation.h +++ b/Require/Runtime/src/Navigation.h @@ -4,6 +4,8 @@ #include "Luau/RequireNavigator.h" #include "Luau/Require.h" +#include + struct lua_State; struct luarequire_Configuration; @@ -48,11 +50,12 @@ private: class RuntimeErrorHandler : public ErrorHandler { public: - RuntimeErrorHandler(lua_State* L); + RuntimeErrorHandler(lua_State* L, std::string requiredPath); void reportError(std::string message) override; private: lua_State* L; + std::string errorPrefix; }; } // namespace Luau::Require diff --git a/Require/Runtime/src/Require.cpp b/Require/Runtime/src/Require.cpp index fa4b3feb..b08b8e05 100644 --- a/Require/Runtime/src/Require.cpp +++ b/Require/Runtime/src/Require.cpp @@ -53,7 +53,7 @@ static int pushrequireclosureinternal( lua_pushlightuserdata(L, ctx); // require-like closure captures config and ctx as upvalues - lua_pushcclosure(L, requirelikefunc, debugname, 2); + lua_pushcclosurek(L, requirelikefunc, debugname, 2, Luau::Require::lua_requirecont); return 1; } @@ -77,3 +77,13 @@ int luarequire_registermodule(lua_State* L) { return Luau::Require::registerModuleImpl(L); } + +int luarequire_clearcacheentry(lua_State* L) +{ + return Luau::Require::clearCacheEntry(L); +} + +int luarequire_clearcache(lua_State* L) +{ + return Luau::Require::clearCache(L); +} diff --git a/Require/Runtime/src/RequireImpl.cpp b/Require/Runtime/src/RequireImpl.cpp index 30575964..e6fba3e8 100644 --- a/Require/Runtime/src/RequireImpl.cpp +++ b/Require/Runtime/src/RequireImpl.cpp @@ -50,7 +50,7 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State* luaL_error(L, "require is not supported in this context"); RuntimeNavigationContext navigationContext{lrc, L, ctx, requirerChunkname}; - RuntimeErrorHandler errorHandler{L}; // Errors reported directly to lua_State. + RuntimeErrorHandler errorHandler{L, path}; // Errors reported directly to lua_State. Navigator navigator(navigationContext, errorHandler); @@ -61,7 +61,7 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State* if (!navigationContext.isModulePresent()) { - luaL_errorL(L, "no module present at resolved path"); + errorHandler.reportError("no module present at resolved path"); return ResolvedRequire{ResolvedRequire::Status::ErrorReported}; } @@ -118,24 +118,13 @@ static int checkRegisteredModules(lua_State* L, const char* path) return 1; } -int lua_requireinternal(lua_State* L, const char* requirerChunkname) +int lua_requirecont(lua_State* L, int status) { - luarequire_Configuration* lrc = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!lrc) - luaL_error(L, "unable to find require configuration"); + // Number of stack arguments present before this continuation is called. + const int numStackArgs = 2; + const int numResults = lua_gettop(L) - numStackArgs; + const char* cacheKey = luaL_checkstring(L, 2); - void* ctx = lua_tolightuserdata(L, lua_upvalueindex(2)); - - const char* path = luaL_checkstring(L, 1); - - if (checkRegisteredModules(L, path) == 1) - return 1; - - ResolvedRequire resolvedRequire = resolveRequire(lrc, L, ctx, requirerChunkname, path); - if (resolvedRequire.status == ResolvedRequire::Status::Cached) - return 1; - - int numResults = lrc->load(L, ctx, path, resolvedRequire.chunkname.c_str(), resolvedRequire.contents.c_str()); if (numResults > 1) luaL_error(L, "module must return a single value"); @@ -151,7 +140,7 @@ int lua_requireinternal(lua_State* L, const char* requirerChunkname) lua_pushvalue(L, -2); // (-3) result, (-2) cache table, (-1) result - lua_setfield(L, -2, resolvedRequire.cacheKey.c_str()); + lua_setfield(L, -2, cacheKey); // (-2) result, (-1) cache table lua_pop(L, 1); @@ -161,6 +150,43 @@ int lua_requireinternal(lua_State* L, const char* requirerChunkname) return numResults; } +int lua_requireinternal(lua_State* L, const char* requirerChunkname) +{ + // If modifying the state of the stack, please update numStackArgs in the + // lua_requirecont continuation function. + + luarequire_Configuration* lrc = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + if (!lrc) + luaL_error(L, "unable to find require configuration"); + + void* ctx = lua_tolightuserdata(L, lua_upvalueindex(2)); + + // (1) path + const char* path = luaL_checkstring(L, 1); + + if (checkRegisteredModules(L, path) == 1) + return 1; + + ResolvedRequire resolvedRequire = resolveRequire(lrc, L, ctx, requirerChunkname, path); + if (resolvedRequire.status == ResolvedRequire::Status::Cached) + return 1; + + // (1) path, (2) cacheKey + lua_pushstring(L, resolvedRequire.cacheKey.c_str()); + + int numArgsBeforeLoad = lua_gettop(L); + int numResults = lrc->load(L, ctx, path, resolvedRequire.chunkname.c_str(), resolvedRequire.contents.c_str()); + if (numResults == -1) + { + if (lua_gettop(L) != numArgsBeforeLoad) + luaL_error(L, "stack cannot be modified when require yields"); + + return lua_yield(L, 0); + } + + return lua_requirecont(L, LUA_OK); +} + int lua_proxyrequire(lua_State* L) { const char* requirerChunkname = luaL_checkstring(L, 2); @@ -170,7 +196,14 @@ int lua_proxyrequire(lua_State* L) int lua_require(lua_State* L) { lua_Debug ar; - lua_getinfo(L, 1, "s", &ar); + int level = 1; + + do + { + if (!lua_getinfo(L, level++, "s", &ar)) + luaL_error(L, "require is not supported in this context"); + } while (ar.what[0] == 'C'); + return lua_requireinternal(L, ar.source); } @@ -199,4 +232,21 @@ int registerModuleImpl(lua_State* L) return 0; } +int clearCacheEntry(lua_State* L) +{ + const char* cacheKey = luaL_checkstring(L, 1); + luaL_findtable(L, LUA_REGISTRYINDEX, requiredCacheTableKey, 1); + lua_pushnil(L); + lua_setfield(L, -2, cacheKey); + lua_pop(L, 1); + return 0; +} + +int clearCache(lua_State* L) +{ + lua_newtable(L); + lua_setfield(L, LUA_REGISTRYINDEX, requiredCacheTableKey); + return 0; +} + } // namespace Luau::Require diff --git a/Require/Runtime/src/RequireImpl.h b/Require/Runtime/src/RequireImpl.h index 5b460779..37564038 100644 --- a/Require/Runtime/src/RequireImpl.h +++ b/Require/Runtime/src/RequireImpl.h @@ -8,7 +8,11 @@ namespace Luau::Require int lua_require(lua_State* L); int lua_proxyrequire(lua_State* L); +int lua_requirecont(lua_State* L, int status); int registerModuleImpl(lua_State* L); +int clearCacheEntry(lua_State* L); +int clearCache(lua_State* L); + } // namespace Luau::Require diff --git a/tests/AstJsonEncoder.test.cpp b/tests/AstJsonEncoder.test.cpp index 5b894bea..00e9c8c3 100644 --- a/tests/AstJsonEncoder.test.cpp +++ b/tests/AstJsonEncoder.test.cpp @@ -599,4 +599,63 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypePackExplicit") CHECK(toJson(root->body.data[1]) == expected); } +TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericType") +{ + AstStatBlock* root = expectParse(R"( + a = function() + end + )"); + + CHECK(1 == root->body.size); + + std::string_view expected = + R"({"type":"AstStatAssign","location":"1,8 - 2,11","vars":[{"type":"AstExprGlobal","location":"1,8 - 1,9","global":"a"}],"values":[{"type":"AstExprFunction","location":"1,12 - 2,11","attributes":[],"generics":[{"type":"AstGenericType","name":"b"},{"type":"AstGenericType","name":"c"}],"genericPacks":[],"args":[],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"1,28 - 2,8","hasEnd":true,"body":[]},"functionDepth":1,"debugname":""}]})"; + + CHECK(toJson(root->body.data[0]) == expected); +} + +TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericTypeWithDefault") +{ + AstStatBlock* root = expectParse(R"( + type Foo = X + )"); + + CHECK(1 == root->body.size); + + std::string_view expected = + R"({"type":"AstStatTypeAlias","location":"1,8 - 1,32","name":"Foo","generics":[{"type":"AstGenericType","name":"X","luauType":{"type":"AstTypeReference","location":"1,21 - 1,27","name":"string","nameLocation":"1,21 - 1,27","parameters":[]}}],"genericPacks":[],"value":{"type":"AstTypeReference","location":"1,31 - 1,32","name":"X","nameLocation":"1,31 - 1,32","parameters":[]},"exported":false})"; + + CHECK(toJson(root->body.data[0]) == expected); +} + +TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericTypePack") +{ + AstStatBlock* root = expectParse(R"( + a = function() + end + )"); + + CHECK(1 == root->body.size); + + std::string_view expected = + R"({"type":"AstStatAssign","location":"1,8 - 2,11","vars":[{"type":"AstExprGlobal","location":"1,8 - 1,9","global":"a"}],"values":[{"type":"AstExprFunction","location":"1,12 - 2,11","attributes":[],"generics":[],"genericPacks":[{"type":"AstGenericTypePack","name":"b"},{"type":"AstGenericTypePack","name":"c"}],"args":[],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"1,34 - 2,8","hasEnd":true,"body":[]},"functionDepth":1,"debugname":""}]})"; + + CHECK(toJson(root->body.data[0]) == expected); +} + +TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericTypePackWithDefault") +{ + AstStatBlock* root = expectParse(R"( + type Foo = any + )"); + + CHECK(1 == root->body.size); + + std::string_view expected = + R"({"type":"AstStatTypeAlias","location":"1,8 - 1,40","name":"Foo","generics":[],"genericPacks":[{"type":"AstGenericTypePack","name":"X","luauType":{"type":"AstTypePackVariadic","location":"1,24 - 1,33","variadicType":{"type":"AstTypeReference","location":"1,27 - 1,33","name":"string","nameLocation":"1,27 - 1,33","parameters":[]}}}],"value":{"type":"AstTypeReference","location":"1,37 - 1,40","name":"any","nameLocation":"1,37 - 1,40","parameters":[]},"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 1b7e243c..cbdfc6df 100644 --- a/tests/DataFlowGraph.test.cpp +++ b/tests/DataFlowGraph.test.cpp @@ -13,6 +13,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); +LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) struct DataFlowGraphFixture { @@ -421,6 +422,8 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "property_lookup_on_a_phi_node_3") TEST_CASE_FIXTURE(DataFlowGraphFixture, "function_captures_are_phi_nodes_of_all_versions") { + ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true}; + dfg(R"( local x = 5 @@ -439,15 +442,14 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "function_captures_are_phi_nodes_of_all_ DefId x4 = getDef(); // x = "five" CHECK(x1 != x2); - CHECK(x2 != x3); + CHECK(x2 == x3); CHECK(x3 != x4); const Phi* phi = get(x2); REQUIRE(phi); - REQUIRE(phi->operands.size() == 3); + REQUIRE(phi->operands.size() == 2); CHECK(phi->operands.at(0) == x1); - CHECK(phi->operands.at(1) == x3); - CHECK(phi->operands.at(2) == x4); + CHECK(phi->operands.at(1) == x4); } TEST_CASE_FIXTURE(DataFlowGraphFixture, "function_captures_are_phi_nodes_of_all_versions_properties") diff --git a/tests/Fixture.h b/tests/Fixture.h index 2d0f9790..531d76f0 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -28,6 +28,8 @@ LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests) +LUAU_FASTFLAG(LuauTypeFunOptional) + #define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::LuauSolverV2, FFlag::DebugLuauForceAllNewSolverTests}; #define DOES_NOT_PASS_NEW_SOLVER_GUARD() DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(__LINE__) @@ -182,6 +184,9 @@ private: struct BuiltinsFixture : Fixture { explicit BuiltinsFixture(bool prepareAutocomplete = false); + + // For the purpose of our tests, we're always the latest version of type functions. + ScopedFastFlag sff_optionalInTypeFunctionLib{FFlag::LuauTypeFunOptional, true}; }; std::optional pathExprToModuleName(const ModuleName& currentModuleName, const std::vector& segments); diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index 04d6e48c..f59073db 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -24,10 +24,6 @@ using namespace Luau; LUAU_FASTINT(LuauParseErrorLimit) -LUAU_FASTFLAG(LuauCloneIncrementalModule) - -LUAU_FASTFLAG(LuauMixedModeDefFinderTraversesTypeOf) -LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauBetterReverseDependencyTracking) LUAU_FASTFLAG(LuauAutocompleteUsesModuleForTypeCompatibility) @@ -72,8 +68,6 @@ struct FragmentAutocompleteFixtureImpl : BaseType { static_assert(std::is_base_of_v, "BaseType must be a descendant of Fixture"); - ScopedFastFlag luauFreeTypesMustHaveBounds{FFlag::LuauFreeTypesMustHaveBounds, true}; - ScopedFastFlag luauCloneIncrementalModule{FFlag::LuauCloneIncrementalModule, true}; ScopedFastFlag luauAllFreeTypesHaveScopes{FFlag::LuauAllFreeTypesHaveScopes, true}; ScopedFastFlag luauClonedTableAndFunctionTypesMustHaveScopes{FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes, true}; ScopedFastFlag luauDisableNewSolverAssertsInMixedMode{FFlag::LuauDisableNewSolverAssertsInMixedMode, true}; @@ -1562,8 +1556,6 @@ return module)"; { ScopedFastFlag sff{FFlag::LuauSolverV2, false}; - ScopedFastFlag sff2{FFlag::LuauCloneIncrementalModule, true}; - ScopedFastFlag sff3{FFlag::LuauFreeTypesMustHaveBounds, true}; checkAndExamine(source, "module", "{ }"); fragmentACAndCheck(updated1, Position{1, 17}, "module", "{ }", "{ a: (%error-id%: unknown) -> () }"); fragmentACAndCheck(updated2, Position{1, 18}, "module", "{ }", "{ ab: (%error-id%: unknown) -> () }"); @@ -2782,7 +2774,6 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "fragment_ac_must_travers // This test ensures that we traverse typeof expressions for defs that are being referred to in the fragment // In this case, we want to ensure we populate the incremental environment with the reference to `m` // Without this, we would ice as we will refer to the local `m` before it's declaration - ScopedFastFlag sff{FFlag::LuauMixedModeDefFinderTraversesTypeOf, true}; const std::string source = R"( --!strict local m = {} @@ -2860,7 +2851,6 @@ type V = {h : number, i : U?} TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "generalization_crash_when_old_solver_freetypes_have_no_bounds_set") { - ScopedFastFlag sff{FFlag::LuauFreeTypesMustHaveBounds, true}; const std::string source = R"( local UserInputService = game:GetService("UserInputService"); @@ -2892,7 +2882,6 @@ end) TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_ensures_memory_isolation") { - ScopedFastFlag sff{FFlag::LuauCloneIncrementalModule, true}; ToStringOptions opt; opt.exhaustive = true; opt.exhaustive = true; @@ -2962,7 +2951,6 @@ return module)"; TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_shouldnt_crash_on_cross_module_mutation") { - ScopedFastFlag sff{FFlag::LuauCloneIncrementalModule, true}; const std::string source = R"(local module = {} function module. return module diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index 6f115c01..76debed8 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -17,7 +17,7 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) -LUAU_FASTFLAG(LuauNewNonStrictVisitTypes) +LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2) namespace { @@ -1015,7 +1015,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "environments") LUAU_REQUIRE_NO_ERRORS(resultA); CheckResult resultB = frontend.check("B"); - if (FFlag::LuauSolverV2 && !FFlag::LuauNewNonStrictVisitTypes) + if (FFlag::LuauSolverV2 && !FFlag::LuauNewNonStrictVisitTypes2) LUAU_REQUIRE_NO_ERRORS(resultB); else LUAU_REQUIRE_ERROR_COUNT(1, resultB); diff --git a/tests/NonStrictTypeChecker.test.cpp b/tests/NonStrictTypeChecker.test.cpp index 69d02cdc..55eacb5b 100644 --- a/tests/NonStrictTypeChecker.test.cpp +++ b/tests/NonStrictTypeChecker.test.cpp @@ -17,7 +17,7 @@ LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals) LUAU_FASTFLAG(LuauNonStrictVisitorImprovements) -LUAU_FASTFLAG(LuauNewNonStrictVisitTypes) +LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2) using namespace Luau; @@ -668,7 +668,7 @@ TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_non_strict") TEST_CASE_FIXTURE(BuiltinsFixture, "unknown_types_in_non_strict") { - ScopedFastFlag sff{FFlag::LuauNewNonStrictVisitTypes, true}; + ScopedFastFlag sff{FFlag::LuauNewNonStrictVisitTypes2, true}; CheckResult result = check(Mode::Nonstrict, R"( --!nonstrict @@ -683,7 +683,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "unknown_types_in_non_strict") TEST_CASE_FIXTURE(BuiltinsFixture, "unknown_types_in_non_strict_2") { - ScopedFastFlag sff{FFlag::LuauNewNonStrictVisitTypes, true}; + ScopedFastFlag sff{FFlag::LuauNewNonStrictVisitTypes2, true}; CheckResult result = check(Mode::Nonstrict, R"( --!nonstrict @@ -707,4 +707,13 @@ end LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "incomplete_function_annotation") +{ + CheckResult result = check(Mode::Nonstrict, R"( + local x: () -> + )"); + + LUAU_REQUIRE_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index f8c6d63f..5a20670a 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -18,7 +18,6 @@ LUAU_FASTINT(LuauTypeLengthLimit) LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauAstTypeGroup3) -LUAU_FASTFLAG(LuauFixDoBlockEndLocation) LUAU_FASTFLAG(LuauParseOptionalAsNode2) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauParseStringIndexer) @@ -2869,8 +2868,6 @@ TEST_CASE_FIXTURE(Fixture, "inner_and_outer_scope_of_functions_have_correct_end_ TEST_CASE_FIXTURE(Fixture, "do_block_end_location_is_after_end_token") { - ScopedFastFlag _{FFlag::LuauFixDoBlockEndLocation, true}; - AstStatBlock* stat = parse(R"( do local x = 1 diff --git a/tests/RequireByString.test.cpp b/tests/RequireByString.test.cpp index 44670df8..1a6c06f7 100644 --- a/tests/RequireByString.test.cpp +++ b/tests/RequireByString.test.cpp @@ -332,6 +332,13 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireSimpleRelativePath") assertOutputContainsAll({"true", "result from dependency"}); } +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireSimpleRelativePathWithinPcall") +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/dependency"; + runCode(L, "return pcall(require, \"" + path + "\")"); + assertOutputContainsAll({"true", "result from dependency"}); +} + TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireRelativeToRequiringFile") { std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module"; @@ -372,7 +379,9 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithFileAmbiguity") std::string ambiguousPath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/ambiguous_file_requirer"; runProtectedRequire(ambiguousPath); - assertOutputContainsAll({"false", "require path could not be resolved to a unique file"}); + assertOutputContainsAll( + {"false", "error requiring module \"./ambiguous/file/dependency\": could not resolve child component \"dependency\" (ambiguous)"} + ); } TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithDirectoryAmbiguity") @@ -380,7 +389,9 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithDirectoryAmbiguity") std::string ambiguousPath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/ambiguous_directory_requirer"; runProtectedRequire(ambiguousPath); - assertOutputContainsAll({"false", "require path could not be resolved to a unique file"}); + assertOutputContainsAll( + {"false", "error requiring module \"./ambiguous/directory/dependency\": could not resolve child component \"dependency\" (ambiguous)"} + ); } TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireLuau") @@ -466,6 +477,61 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCachedResult") assertOutputContainsAll({"true"}); } +TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckClearCacheEntry") +{ + std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module"; + std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/module"; + std::string cacheKey = absolutePath + ".luau"; + + luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1); + lua_getfield(L, -1, cacheKey.c_str()); + REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result"); + + runProtectedRequire(relativePath); + + assertOutputContainsAll({"true", "result from dependency", "required into module"}); + + // Check cache for the absolute path as a cache key + luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1); + lua_getfield(L, -1, cacheKey.c_str()); + REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result"); + + lua_pushcfunction(L, luarequire_clearcacheentry, nullptr); + lua_pushstring(L, cacheKey.c_str()); + lua_call(L, 1, 0); + + luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1); + lua_getfield(L, -1, cacheKey.c_str()); + REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache was not cleared"); +} + +TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckClearCache") +{ + std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module"; + std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/module"; + std::string cacheKey = absolutePath + ".luau"; + + luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1); + lua_getfield(L, -1, cacheKey.c_str()); + REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result"); + + runProtectedRequire(relativePath); + + assertOutputContainsAll({"true", "result from dependency", "required into module"}); + + // Check cache for the absolute path as a cache key + luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1); + lua_getfield(L, -1, cacheKey.c_str()); + REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result"); + + lua_pushcfunction(L, luarequire_clearcache, nullptr); + lua_call(L, 0, 0); + + luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1); + lua_getfield(L, -1, cacheKey.c_str()); + REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache was not cleared"); +} + TEST_CASE_FIXTURE(ReplWithPathFixture, "RegisterRuntimeModule") { lua_pushcfunction(L, luarequire_registermodule, nullptr); diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index fb20c82f..4b5f6876 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -1249,8 +1249,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "bill") CHECK(isSubtype(b, a).isSubtype); } -// TEST_CASE_FIXTURE(SubtypeFixture, "({[string]: number, a: string}) -> () <: ({[string]: number, a: string}) -> ()") -TEST_CASE_FIXTURE(SubtypeFixture, "fred") +TEST_CASE_FIXTURE(SubtypeFixture, "({[string]: number, a: string}) -> () <: ({[string]: number, a: string}) -> ()") { auto makeTheType = [&]() { diff --git a/tests/Transpiler.test.cpp b/tests/Transpiler.test.cpp index ae170c3d..1775336d 100644 --- a/tests/Transpiler.test.cpp +++ b/tests/Transpiler.test.cpp @@ -341,7 +341,10 @@ TEST_CASE("function_spaces_around_tokens") TEST_CASE("function_with_types_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauStoreCSTData2, true}, + {FFlag::LuauStoreReturnTypesAsPackOnAst, true} + }; std::string code = R"( function p(o: string, m: number, ...: any): string end )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -392,9 +395,8 @@ TEST_CASE("function_with_types_spaces_around_tokens") code = R"( function p(o: string, m: number, ...: any ): string end )"; CHECK_EQ(code, transpile(code, {}, true).code); - // TODO(CLI-139347): re-enable test once return type positions are supported - // code = R"( function p(o: string, m: number, ...: any) :string end )"; - // CHECK_EQ(code, transpile(code, {}, true).code); + code = R"( function p(o: string, m: number, ...: any) :string end )"; + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( function p(o: string, m: number, ...: any): string end )"; CHECK_EQ(code, transpile(code, {}, true).code); diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index 59a3509f..1a0c3273 100644 --- a/tests/TypeFunction.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -18,6 +18,7 @@ LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) LUAU_FASTFLAG(LuauIndexTypeFunctionFunctionMetamethods) LUAU_FASTFLAG(LuauMetatableTypeFunctions) LUAU_FASTFLAG(LuauMetatablesHaveLength) +LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauIndexAnyIsAny) LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2) LUAU_FASTFLAG(LuauHasPropProperBlock) @@ -1674,4 +1675,70 @@ print(test.a) CHECK("Type 'add' does not have key 'a'" == toString(result.errors[1])); } +struct TFFixture +{ + TypeArena arena_; + NotNull arena{&arena_}; + + BuiltinTypes builtinTypes_; + NotNull builtinTypes{&builtinTypes_}; + + ScopePtr globalScope = std::make_shared(builtinTypes->anyTypePack); + + InternalErrorReporter ice; + UnifierSharedState unifierState{&ice}; + SimplifierPtr simplifier = EqSatSimplification::newSimplifier(arena, builtinTypes); + Normalizer normalizer{arena, builtinTypes, NotNull{&unifierState}}; + TypeCheckLimits limits; + TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}}; + + const ScopedFastFlag sff[1] = { + {FFlag::DebugLuauGreedyGeneralization, true}, + }; + + BuiltinTypeFunctions builtinTypeFunctions; + + TypeFunctionContext tfc{ + arena, + builtinTypes, + NotNull{globalScope.get()}, + NotNull{simplifier.get()}, + NotNull{&normalizer}, + NotNull{&runtime}, + NotNull{&ice}, + NotNull{&limits} + }; +}; + +TEST_CASE_FIXTURE(TFFixture, "refine") +{ + TypeId g = arena->addType(GenericType{globalScope.get(), Polarity::Negative}); + + TypeId refineTy = arena->addType(TypeFunctionInstanceType{ + builtinTypeFunctions.refineFunc, {g, builtinTypes->truthyType} + }); + + FunctionGraphReductionResult res = reduceTypeFunctions(refineTy, Location{}, tfc); + + CHECK(res.reducedTypes.size() == 1); + + CHECK(res.errors.size() == 0); + CHECK(res.irreducibleTypes.size() == 0); + CHECK(res.blockedTypes.size() == 0); +} + +TEST_CASE_FIXTURE(TFFixture, "or<'a, 'b>") +{ + TypeId aType = arena->freshType(builtinTypes, globalScope.get()); + TypeId bType = arena->freshType(builtinTypes, globalScope.get()); + + TypeId orType = arena->addType(TypeFunctionInstanceType{ + builtinTypeFunctions.orFunc, {aType, bType} + }); + + FunctionGraphReductionResult res = reduceTypeFunctions(orType, Location{}, tfc); + + CHECK(res.reducedTypes.size() == 1); +} + TEST_SUITE_END(); diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index 5c692faf..0a10b7a7 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -15,7 +15,6 @@ LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2) LUAU_FASTFLAG(LuauNoTypeFunctionsNamedTypeOf) - TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_nil_serialization_works") @@ -370,6 +369,43 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_serialization_works") CHECK(toString(tpm->givenTp) == "boolean | number | string"); } +TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optional_works") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + + CheckResult result = check(R"( + type function numberhuh() + return types.optional(types.number) + end + -- forcing an error here to check the exact type of the union + local function ok(idx: numberhuh<>): never return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + TypePackMismatch* tpm = get(result.errors[0]); + REQUIRE(tpm); + CHECK(toString(tpm->givenTp) == "number?"); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optional_works_on_unions") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + + CheckResult result = check(R"( + type function foobar() + local ty = types.unionof(types.string, types.number, types.boolean) + return types.optional(ty) + end + -- forcing an error here to check the exact type of the union + local function ok(idx: foobar<>): never return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + TypePackMismatch* tpm = get(result.errors[0]); + REQUIRE(tpm); + CHECK(toString(tpm->givenTp) == "(boolean | number | string)?"); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index 04d7ae6a..899c2a19 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -14,7 +14,7 @@ LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes) LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) -LUAU_FASTFLAG(LuauNewNonStrictVisitTypes) +LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2) TEST_SUITE_BEGIN("TypeAliases"); @@ -1197,7 +1197,7 @@ TEST_CASE_FIXTURE(Fixture, "bound_type_in_alias_segfault") export type FieldConfigMap = Map> )"); - if (FFlag::LuauNewNonStrictVisitTypes) + if (FFlag::LuauNewNonStrictVisitTypes2) LUAU_CHECK_ERROR_COUNT(2, result); else LUAU_CHECK_NO_ERRORS(result); diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 6a96bd18..e58e8e42 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -12,6 +12,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) +LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) TEST_SUITE_BEGIN("BuiltinTests"); @@ -472,7 +473,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_reduce") )"); LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2 && FFlag::DebugLuauGreedyGeneralization) + CHECK("{ [number]: string | string | string, n: number }" == toString(requireType("t"))); + else if (FFlag::LuauSolverV2) CHECK_EQ("{ [number]: string, n: number }", toString(requireType("t"))); else CHECK_EQ("{| [number]: string, n: number |}", toString(requireType("t"))); diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index f672e43f..24d69663 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -24,11 +24,13 @@ LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauUngeneralizedTypesForRecursiveFunctions) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) +LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauReduceUnionFollowUnionType) LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) LUAU_FASTFLAG(LuauHasPropProperBlock) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauFormatUseLastPosition) +LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -90,7 +92,7 @@ TEST_CASE_FIXTURE(Fixture, "check_function_bodies") if (FFlag::LuauSolverV2) { const TypePackMismatch* tm = get(result.errors[0]); - REQUIRE(tm); + REQUIRE_MESSAGE(tm, "Expected TypeMismatch but got " << result.errors[0]); CHECK(toString(tm->wantedTp) == "number"); CHECK(toString(tm->givenTp) == "boolean"); } @@ -3032,7 +3034,11 @@ TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types") auto tm2 = get(result.errors[1]); REQUIRE(tm2); CHECK(toString(tm2->wantedTp) == "string"); - CHECK(toString(tm2->givenTp) == "~(false?)"); + + if (FFlag::DebugLuauGreedyGeneralization) + CHECK(toString(tm2->givenTp) == "unknown & ~(false?)"); + else + CHECK(toString(tm2->givenTp) == "~(false?)"); } else { @@ -3213,12 +3219,13 @@ TEST_CASE_FIXTURE(Fixture, "recursive_function_calls_should_not_use_the_generali TEST_CASE_FIXTURE(Fixture, "fuzz_unwind_mutually_recursive_union_type_func") { - ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauReduceUnionFollowUnionType, true}}; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauDoNotAddUpvalueTypesToLocalType, true}}; - // This block ends up minting a type like: + // Previously, this block minted a type like: // // t2 where t1 = union | union | union ; t2 = union // + // ... due to how upvalues contributed to the locally inferred types. CheckResult result = check(R"( local _ = ... function _() @@ -3226,12 +3233,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_unwind_mutually_recursive_union_type_func") end _[function(...) repeat until _(_[l100]) _ = _ end] += _ )"); - LUAU_REQUIRE_ERROR_COUNT(2, result); - auto err0 = get(result.errors[0]); - CHECK(err0); - CHECK_EQ(err0->name, "l100"); - auto err1 = get(result.errors[1]); - CHECK(err1); + LUAU_REQUIRE_ERRORS(result); } TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_pack") diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index b709157c..837d4218 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -17,6 +17,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) +LUAU_FASTFLAG(DebugLuauGreedyGeneralization) TEST_SUITE_BEGIN("TypeInferOperators"); @@ -27,8 +28,18 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types") local x:string|number = s )"); LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(toString(*requireType("s")), "number | string"); - CHECK_EQ(toString(*requireType("x")), "number | string"); + + if (FFlag::DebugLuauGreedyGeneralization) + { + // FIXME: Regression + CHECK("(string & ~(false?)) | number" == toString(*requireType("s"))); + CHECK("number | string" == toString(*requireType("x"))); + } + else + { + CHECK_EQ(toString(*requireType("s")), "number | string"); + CHECK_EQ(toString(*requireType("x")), "number | string"); + } } TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras") @@ -39,8 +50,18 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras") local y = x or "s" )"); LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(toString(*requireType("s")), "number | string"); - CHECK_EQ(toString(*requireType("y")), "number | string"); + + if (FFlag::DebugLuauGreedyGeneralization) + { + // FIXME: Regression. + CHECK("(string & ~(false?)) | number" == toString(*requireType("s"))); + CHECK("number | string | string" == toString(*requireType("y"))); + } + else + { + CHECK_EQ(toString(*requireType("s")), "number | string"); + CHECK_EQ(toString(*requireType("y")), "number | string"); + } } TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union") @@ -50,7 +71,14 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union") local x:string = s )"); LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(*requireType("s"), *builtinTypes->stringType); + + if (FFlag::DebugLuauGreedyGeneralization) + { + // FIXME: Regression + CHECK("(string & ~(false?)) | string" == toString(requireType("s"))); + } + else + CHECK_EQ(*requireType("s"), *builtinTypes->stringType); } TEST_CASE_FIXTURE(Fixture, "and_does_not_always_add_boolean") diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 72f36b65..7dfebb7e 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -1,6 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Normalize.h" #include "Luau/Scope.h" +#include "Luau/Type.h" #include "Luau/TypeInfer.h" #include "Fixture.h" @@ -11,10 +12,12 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauIntersectNotNil) LUAU_FASTFLAG(LuauSkipNoRefineDuringRefinement) +LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable) LUAU_FASTFLAG(LuauSimplyRefineNotNil) LUAU_FASTFLAG(LuauWeakNilRefinementType) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) +LUAU_FASTFLAG(LuauSimplificationTableExternType) using namespace Luau; @@ -113,10 +116,18 @@ struct RefinementExternTypeFixture : BuiltinsFixture {"Position", Property{vec3}}, }; + TypeId optionalPart = arena.addType(UnionType{{part, builtinTypes->nilType}}); + TypeId weldConstraint = frontend.globals.globalTypes.addType(ExternType{"WeldConstraint", {}, inst, std::nullopt, {}, nullptr, "Test", {}}); + getMutable(weldConstraint)->props = { + {"Part0", Property{optionalPart}}, + {"Part1", Property{optionalPart}}, + }; + frontend.globals.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vec3}; frontend.globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, inst}; frontend.globals.globalScope->exportedTypeBindings["Folder"] = TypeFun{{}, folder}; frontend.globals.globalScope->exportedTypeBindings["Part"] = TypeFun{{}, part}; + frontend.globals.globalScope->exportedTypeBindings["WeldConstraint"] = TypeFun{{}, weldConstraint}; for (const auto& [name, ty] : frontend.globals.globalScope->exportedTypeBindings) persist(ty.type); @@ -752,11 +763,22 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_ LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil" - CHECK_EQ("string", toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil" + if (FFlag::DebugLuauGreedyGeneralization) + { + CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil" + CHECK("string & unknown & unknown & ~nil" == toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil" - CHECK_EQ("nil", toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil" - CHECK_EQ("string", toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil" + CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil" + CHECK("string & unknown & unknown & ~nil" == toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil" + } + else + { + CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil" + CHECK_EQ("string", toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil" + + CHECK_EQ("nil", toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil" + CHECK_EQ("string", toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil" + } } TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_not_to_be_string") @@ -1578,9 +1600,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "refine_param_of_type_folder_or_p TEST_CASE_FIXTURE(RefinementExternTypeFixture, "isa_type_refinement_must_be_known_ahead_of_time") { - // CLI-115087 - The new solver does not consistently combine tables with - // class types when they appear in the upper bounds of a free type. - DOES_NOT_PASS_NEW_SOLVER_GUARD(); + ScopedFastFlag sff{FFlag::LuauSimplificationTableExternType, true}; CheckResult result = check(R"( local function f(x): Instance @@ -1596,8 +1616,41 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "isa_type_refinement_must_be_know LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("Instance", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28}))); + if (FFlag::LuauSolverV2) + { + CHECK_EQ("t1 where t1 = Instance & { read IsA: (t1, string) -> (unknown, ...unknown) }", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("t1 where t1 = Instance & { read IsA: (t1, string) -> (unknown, ...unknown) }", toString(requireTypeAtPosition({5, 28}))); + } + else + { + CHECK_EQ("Instance", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28}))); + } +} + +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "asserting_optional_properties_should_not_refine_extern_types_to_never") +{ + + CheckResult result = check(R"( + local weld: WeldConstraint = nil :: any + assert(weld.Part1) + print(weld) -- hover type incorrectly becomes `never` + assert(weld.Part1.Name == "RootPart") + local part1 = assert(weld.Part1) + local pos = part1.Position + )"); + + if (FFlag::LuauSolverV2) + { + // CLI-142467: this is a major regression that we need to address. + CHECK_EQ("never", toString(requireTypeAtPosition({3, 15}))); + CHECK_EQ("any", toString(requireTypeAtPosition({6, 29}))); + } + else + { + CHECK_EQ("WeldConstraint", toString(requireTypeAtPosition({3, 15}))); + CHECK_EQ("Vector3", toString(requireTypeAtPosition({6, 29}))); + } } TEST_CASE_FIXTURE(RefinementExternTypeFixture, "x_is_not_instance_or_else_not_part") @@ -1605,6 +1658,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "x_is_not_instance_or_else_not_pa // CLI-117135 - RefinementTests.x_is_not_instance_or_else_not_part not correctly applying refinements to a function parameter if (FFlag::LuauSolverV2) return; + CheckResult result = check(R"( local function f(x: Part | Folder | string) if typeof(x) ~= "Instance" or not x:IsA("Part") then @@ -1827,6 +1881,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "refine_a_param_that_got_resolved // CLI-117134 - Applying a refinement causes an optional value access error. if (FFlag::LuauSolverV2) return; + CheckResult result = check(R"( type Id = T @@ -2447,7 +2502,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi end )")); - CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24}))); + if (FFlag::DebugLuauGreedyGeneralization) + CHECK_EQ("nil & string & unknown", toString(requireTypeAtPosition({4, 24}))); + else + CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24}))); } TEST_CASE_FIXTURE(BuiltinsFixture, "nonnil_refinement_on_generic") diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 6c6eaef9..3d884e8c 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -22,7 +22,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) -LUAU_FASTFLAG(LuauFollowTableFreeze) LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) @@ -4723,10 +4722,10 @@ TEST_CASE_FIXTURE(Fixture, "refined_thing_can_be_an_array") end )"); - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2 && !FFlag::DebugLuauGreedyGeneralization) { - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(get(result.errors[0])); + LUAU_CHECK_ERROR_COUNT(1, result); + LUAU_CHECK_ERROR(result, NotATable); CHECK_EQ("(unknown, *error-type*) -> *error-type*", toString(requireType("foo"))); } else @@ -5335,7 +5334,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_musnt_assert") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauFollowTableFreeze, true}, }; auto result = check(R"( diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 87219cf4..47adbd96 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -2023,7 +2023,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_assert_table_freeze_constraint_solving" LUAU_REQUIRE_NO_ERROR(results, ConstraintSolvingIncompleteError); } -TEST_CASE_FIXTURE(BuiltinsFixture, "konnichiwa" * doctest::timeout(0.25)) +TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_unification_aborts_eventually" * doctest::timeout(0.25)) { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, false}, diff --git a/tests/TypeInfer.typePacks.test.cpp b/tests/TypeInfer.typePacks.test.cpp index c0c0e18a..f323c407 100644 --- a/tests/TypeInfer.typePacks.test.cpp +++ b/tests/TypeInfer.typePacks.test.cpp @@ -13,6 +13,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) +LUAU_FASTFLAG(DebugLuauGreedyGeneralization) TEST_SUITE_BEGIN("TypePackTests"); @@ -96,7 +97,10 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function") LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("((b...) -> (c...), (a) -> (b...), a) -> (c...)", toString(requireType("apply"))); + if (FFlag::DebugLuauGreedyGeneralization) + CHECK_EQ("((c...) -> (b...), (a) -> (c...), a) -> (b...)", toString(requireType("apply"))); + else + CHECK_EQ("((b...) -> (c...), (a) -> (b...), a) -> (c...)", toString(requireType("apply"))); } TEST_CASE_FIXTURE(Fixture, "return_type_should_be_empty_if_nothing_is_returned") diff --git a/tests/TypeInfer.typestates.test.cpp b/tests/TypeInfer.typestates.test.cpp index 2a1ba756..92015370 100644 --- a/tests/TypeInfer.typestates.test.cpp +++ b/tests/TypeInfer.typestates.test.cpp @@ -5,6 +5,8 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget) +LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) +LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow) using namespace Luau; @@ -263,6 +265,8 @@ TEST_CASE_FIXTURE(TypeStateFixture, "local_assigned_in_only_one_branch_that_fall TEST_CASE_FIXTURE(TypeStateFixture, "then_branch_assigns_and_else_branch_also_assigns_but_is_met_with_return") { + ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true}; + CheckResult result = check(R"( local x = nil if math.random() > 0.5 then @@ -275,11 +279,13 @@ TEST_CASE_FIXTURE(TypeStateFixture, "then_branch_assigns_and_else_branch_also_as )"); LUAU_REQUIRE_NO_ERRORS(result); - CHECK("number?" == toString(requireType("y"))); + CHECK("number" == toString(requireType("y"))); } TEST_CASE_FIXTURE(TypeStateFixture, "then_branch_assigns_but_is_met_with_return_and_else_branch_assigns") { + ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true}; + CheckResult result = check(R"( local x = nil if math.random() > 0.5 then @@ -292,7 +298,7 @@ TEST_CASE_FIXTURE(TypeStateFixture, "then_branch_assigns_but_is_met_with_return_ )"); LUAU_REQUIRE_NO_ERRORS(result); - CHECK("string?" == toString(requireType("y"))); + CHECK("string" == toString(requireType("y"))); } TEST_CASE_FIXTURE(TypeStateFixture, "invalidate_type_refinements_upon_assignments") @@ -338,8 +344,9 @@ TEST_CASE_FIXTURE(TypeStateFixture, "local_t_is_assigned_a_fresh_table_with_x_as } #endif -TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_are_unions_of_all_assignments") +TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_do_not_mutate_upvalue_type") { + ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true}; CheckResult result = check(R"( local x = nil @@ -352,12 +359,16 @@ TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_are_unions_of_all_assignmen f() )"); - LUAU_REQUIRE_NO_ERRORS(result); - CHECK("(number | string)?" == toString(requireTypeAtPosition({4, 18}))); + LUAU_REQUIRE_ERROR_COUNT(1, result); + auto err = get(result.errors[0]); + CHECK_EQ("number?", toString(err->wantedType)); + CHECK_EQ("string", toString(err->givenType)); + CHECK("number?" == toString(requireTypeAtPosition({4, 18}))); } -TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_are_unions_of_all_assignments_2") +TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_do_not_mutate_upvalue_type_2") { + ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true}; CheckResult result = check(R"( local t = {x = nil} @@ -370,9 +381,13 @@ TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_are_unions_of_all_assignmen f() )"); - LUAU_REQUIRE_NO_ERRORS(result); - CHECK("{ x: nil } | { x: number } | { x: string }" == toString(requireTypeAtPosition({4, 18}), {true})); - CHECK("(number | string)?" == toString(requireTypeAtPosition({4, 20}))); + LUAU_REQUIRE_ERROR_COUNT(1, result); + auto err = get(result.errors[0]); + CHECK_EQ("t | { x: number }", toString(err->wantedType)); + CHECK_EQ("{ x: string }", toString(err->givenType)); + + CHECK("{ x: nil } | { x: number }" == toString(requireTypeAtPosition({4, 18}), {true})); + CHECK("number?" == toString(requireTypeAtPosition({4, 20}))); } TEST_CASE_FIXTURE(TypeStateFixture, "prototyped_recursive_functions") @@ -555,4 +570,280 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_normalized_type_variables_are_bad" * )")); } -TEST_SUITE_END(); +TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1547_simple") +{ + ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local rand = 0 + + function a() + rand = (rand % 4) + 1; + end + )")); + + auto randTy = getType("rand"); + REQUIRE(randTy); + CHECK_EQ("number", toString(*randTy)); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1547") +{ + ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local rand = 0 + + function a() + rand = (rand % 4) + 1; + end + + function b() + rand = math.max(rand - 1, 0); + end + )")); + + auto randTy = getType("rand"); + REQUIRE(randTy); + CHECK_EQ("number", toString(*randTy)); +} + +TEST_CASE_FIXTURE(Fixture, "modify_captured_table_field") +{ + LUAU_REQUIRE_NO_ERRORS(check(R"( + local state = { x = 0 } + function incr() + state.x = state.x + 1 + end + )")); + + auto randTy = getType("state"); + REQUIRE(randTy); + CHECK_EQ("{ x: number }", toString(*randTy, {true})); +} + +TEST_CASE_FIXTURE(Fixture, "oss_1561") +{ + ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true}; + + loadDefinition(R"( + declare class Vector3 + X: number + Y: number + Z: number + end + + declare Vector3: { + new: (number?, number?, number?) -> Vector3 + } + )"); + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local targetVelocity: Vector3 = Vector3.new() + function set2D(X: number, Y: number) + targetVelocity = Vector3.new(X, Y, targetVelocity.Z) + end + )")); + + CHECK_EQ("(number, number) -> ()", toString(requireType("set2D"))); +} + +TEST_CASE_FIXTURE(Fixture, "oss_1575") +{ + ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local flag = true + local function Flip() + flag = not flag + end + )")); +} + +TEST_CASE_FIXTURE(Fixture, "capture_upvalue_in_returned_function") +{ + ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + function def() + local i : number = 0 + local function Counter() + i = i + 1 + return i + end + return Counter + end + )")); + CHECK_EQ("() -> () -> number", toString(requireType("def"))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "throw_in_else_branch") +{ + ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true}; + + CheckResult result = check(R"( + --!strict + local x + local coinflip : () -> boolean = (nil :: any) + + if coinflip () then + x = "I win." + else + error("You lose.") + end + + print(x) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ("string", toString(requireTypeAtPosition({11, 14}))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "throw_in_if_branch") +{ + ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true}; + + CheckResult result = check(R"( + --!strict + local x + local coinflip : () -> boolean = (nil :: any) + + if coinflip () then + error("You lose.") + else + x = "I win." + end + + print(x) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ("string", toString(requireTypeAtPosition({11, 14}))); +} + + +TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring") +{ + ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true}; + + CheckResult result = check(R"( + --!strict + type Payload = { payload: number } + + local function decode(s: string): Payload? + return (nil :: any) + end + + local function decodeEx(s: string): Payload + local p = decode(s) + if not p then + error("failed to decode payload!!!") + end + return p + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring_in_loop") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true}, + }; + + CheckResult result = check(R"( + --!strict + + local x = nil + + while math.random() > 0.5 do + x = 42 + return + end + + print(x) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + // CLI-142447: This should probably be `nil` given that the `while` loop + // unconditionally returns, but `number?` is sound, if incomplete. + CHECK_EQ("number?", toString(requireTypeAtPosition({10, 14}))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "type_refinement_in_loop") +{ + ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true}; + + CheckResult result = check(R"( + --!strict + local function onEachString(t: { string | number }) + for _, v in t do + if type(v) ~= "string" then + continue + end + print(v) + end + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ("number | string", toString(requireTypeAtPosition({4, 24}))); + CHECK_EQ("string", toString(requireTypeAtPosition({7, 22}))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "throw_in_if_branch_and_do_nothing_in_else") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true}, + }; + + CheckResult result = check(R"( + --!strict + local x + local coinflip : () -> boolean = (nil :: any) + + if coinflip () then + error("You lose.") + else + end + + print(x) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ("nil", toString(requireTypeAtPosition({10, 14}))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "assign_in_an_if_branch_without_else") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true}, + }; + + CheckResult result = check(R"( + --!strict + local x + local coinflip : () -> boolean = (nil :: any) + + if coinflip () then + x = "I win." + end + + print(x) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ("string?", toString(requireTypeAtPosition({9, 14}))); +} + +TEST_SUITE_END(); \ No newline at end of file diff --git a/tests/require/with_config/GlobalLuauLibraries/global_library.luau b/tests/require/with_config/GlobalLuauLibraries/global_library.luau deleted file mode 100644 index 0508e0bd..00000000 --- a/tests/require/with_config/GlobalLuauLibraries/global_library.luau +++ /dev/null @@ -1 +0,0 @@ -return {"result from global_library"} diff --git a/tests/require/with_config/ProjectLuauLibraries/library.luau b/tests/require/with_config/ProjectLuauLibraries/library.luau deleted file mode 100644 index 9470401b..00000000 --- a/tests/require/with_config/ProjectLuauLibraries/library.luau +++ /dev/null @@ -1 +0,0 @@ -return {"result from library"}