From d518d14b922ff80e7e17d1912d637225472117a1 Mon Sep 17 00:00:00 2001 From: vegorov-rbx <75688451+vegorov-rbx@users.noreply.github.com> Date: Fri, 23 Aug 2024 09:35:30 -0700 Subject: [PATCH] Sync to upstream/release/640 (#1374) ### What's new * Fixed many of the false positive errors in indexing of table unions and table intersections * It is now possible to run custom checks over Luau AST during typechecking by setting `customModuleCheck` in `FrontendOptions` * Fixed codegen issue on arm, where number->vector cast could corrupt that number value for the next time it's read ### New Solver * `error` type now behaves as the bottom type during subtyping checks * Fixed the scope that is used in subtyping with generic types * Fixed `astOriginalCallTypes` table often used by LSP to match the old solver --- ### Internal Contributors Co-authored-by: Aaron Weiss Co-authored-by: Andy Friesen Co-authored-by: Vighnesh Vijay Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/Frontend.h | 4 + Analysis/include/Luau/Subtyping.h | 136 ++++-- Analysis/src/AnyTypeSummary.cpp | 17 +- Analysis/src/Autocomplete.cpp | 4 +- Analysis/src/ConstraintGenerator.cpp | 2 +- Analysis/src/Frontend.cpp | 12 +- Analysis/src/NonStrictTypeChecker.cpp | 14 +- Analysis/src/Normalize.cpp | 8 +- Analysis/src/OverloadResolution.cpp | 12 +- Analysis/src/Subtyping.cpp | 374 +++++++++------ Analysis/src/TypeChecker2.cpp | 51 ++- Analysis/src/TypeFunction.cpp | 20 +- Analysis/src/TypeInfer.cpp | 228 ++++++++-- Analysis/src/TypeUtils.cpp | 2 + CodeGen/include/Luau/IrVisitUseDef.h | 47 +- CodeGen/src/BytecodeAnalysis.cpp | 4 - CodeGen/src/CodeGenAssembly.cpp | 5 +- CodeGen/src/EmitBuiltinsX64.cpp | 36 -- CodeGen/src/IrBuilder.cpp | 4 - CodeGen/src/IrLoweringA64.cpp | 120 ++--- CodeGen/src/IrLoweringX64.cpp | 18 +- CodeGen/src/IrTranslateBuiltins.cpp | 88 +--- CodeGen/src/IrTranslation.cpp | 21 +- CodeGen/src/IrValueLocationTracking.cpp | 6 +- CodeGen/src/OptimizeConstProp.cpp | 12 +- Common/include/Luau/ExperimentalFlags.h | 2 +- EqSat/include/Luau/EGraph.h | 5 +- EqSat/include/Luau/Language.h | 7 - EqSat/include/Luau/LanguageHash.h | 1 + EqSat/src/UnionFind.cpp | 3 + Makefile | 3 +- tests/AnyTypeSummary.test.cpp | 505 ++++++++++++--------- tests/Autocomplete.test.cpp | 20 +- tests/Conformance.test.cpp | 3 + tests/IrBuilder.test.cpp | 10 - tests/IrLowering.test.cpp | 11 +- tests/Module.test.cpp | 3 + tests/Normalize.test.cpp | 14 +- tests/Subtyping.test.cpp | 28 +- tests/TypeInfer.functions.test.cpp | 38 +- tests/TypeInfer.generics.test.cpp | 74 ++- tests/TypeInfer.intersectionTypes.test.cpp | 196 +++++--- tests/TypeInfer.negations.test.cpp | 3 + tests/TypeInfer.refinements.test.cpp | 9 + tests/TypeInfer.tables.test.cpp | 44 +- tests/TypeInfer.typestates.test.cpp | 4 +- tests/TypeInfer.unionTypes.test.cpp | 10 +- tests/TypeInfer.unknownnever.test.cpp | 16 +- tests/VisitType.test.cpp | 25 +- tests/conformance/vector.lua | 19 + tests/main.cpp | 2 +- tools/faillist.txt | 45 -- 52 files changed, 1377 insertions(+), 968 deletions(-) diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index f476b582..d8a40d24 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -108,6 +108,10 @@ struct FrontendOptions // When true, some internal complexity limits will be scaled down for modules that miss the limit set by moduleTimeLimitSec bool applyInternalLimitScaling = false; + + // An optional callback which is called for every *dirty* module was checked + // Is multi-threaded typechecking is used, this callback might be called from multiple threads and has to be thread-safe + std::function customModuleCheck; }; struct CheckResult diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index 01f1a7ab..18217a6b 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -125,7 +125,6 @@ struct Subtyping NotNull normalizer; NotNull iceReporter; - NotNull scope; TypeCheckLimits limits; enum class Variance @@ -144,8 +143,7 @@ struct Subtyping NotNull builtinTypes, NotNull typeArena, NotNull normalizer, - NotNull iceReporter, - NotNull scope + NotNull iceReporter ); Subtyping(const Subtyping&) = delete; @@ -164,75 +162,125 @@ struct Subtyping // TODO cyclic types // TODO recursion limits - SubtypingResult isSubtype(TypeId subTy, TypeId superTy); - SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy); + SubtypingResult isSubtype(TypeId subTy, TypeId superTy, NotNull scope); + SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy, NotNull scope); private: DenseHashMap, SubtypingResult, TypePairHash> resultCache{{}}; SubtypingResult cache(SubtypingEnvironment& env, SubtypingResult res, TypeId subTy, TypeId superTy); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypePackId subTy, TypePackId superTy); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy, NotNull scope); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp, NotNull scope); template - SubtypingResult isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy); + SubtypingResult isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy, NotNull scope); template - SubtypingResult isInvariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy); + SubtypingResult isInvariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy, NotNull scope); template - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TryPair& pair); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TryPair& pair, NotNull scope); template - SubtypingResult isContravariantWith(SubtypingEnvironment& env, const TryPair& pair); + SubtypingResult isContravariantWith(SubtypingEnvironment& env, const TryPair& pair, NotNull); template - SubtypingResult isInvariantWith(SubtypingEnvironment& env, const TryPair& pair); + SubtypingResult isInvariantWith(SubtypingEnvironment& env, const TryPair& pair, NotNull); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const UnionType* superUnion); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const UnionType* subUnion, TypeId superTy); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const IntersectionType* superIntersection); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const IntersectionType* subIntersection, TypeId superTy); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const UnionType* superUnion, NotNull scope); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const UnionType* subUnion, TypeId superTy, NotNull scope); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const IntersectionType* superIntersection, NotNull scope); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const IntersectionType* subIntersection, TypeId superTy, NotNull scope); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NegationType* subNegation, TypeId superTy); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const NegationType* superNegation); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NegationType* subNegation, TypeId superTy, NotNull scope); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const NegationType* superNegation, NotNull scope); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const PrimitiveType* superPrim); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const PrimitiveType* superPrim); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const SingletonType* superSingleton); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const FunctionType* subFunction, const FunctionType* superFunction); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const PrimitiveType* superPrim); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const TableType* superTable); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const TableType* superTable); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const PrimitiveType* superPrim, NotNull scope); + SubtypingResult isCovariantWith( + SubtypingEnvironment& env, + const SingletonType* subSingleton, + const PrimitiveType* superPrim, + NotNull scope + ); + SubtypingResult isCovariantWith( + SubtypingEnvironment& env, + const SingletonType* subSingleton, + const SingletonType* superSingleton, + NotNull scope + ); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, NotNull scope); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull scope); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull scope); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull scope); + SubtypingResult + isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable, NotNull); + SubtypingResult isCovariantWith( + SubtypingEnvironment& env, + const FunctionType* subFunction, + const FunctionType* superFunction, + NotNull scope + ); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const PrimitiveType* superPrim, NotNull scope); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const TableType* superTable, NotNull scope); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const TableType* superTable, NotNull scope); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableIndexer& subIndexer, const TableIndexer& superIndexer); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const Property& subProperty, const Property& superProperty, const std::string& name); + SubtypingResult isCovariantWith( + SubtypingEnvironment& env, + const TableIndexer& subIndexer, + const TableIndexer& superIndexer, + NotNull scope + ); + SubtypingResult + isCovariantWith(SubtypingEnvironment& env, const Property& subProperty, const Property& superProperty, const std::string& name, NotNull); SubtypingResult isCovariantWith( SubtypingEnvironment& env, const std::shared_ptr& subNorm, - const std::shared_ptr& superNorm + const std::shared_ptr& superNorm, + NotNull scope ); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const NormalizedClassType& superClass); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const NormalizedStringType& superString); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const TypeIds& superTables); SubtypingResult isCovariantWith( SubtypingEnvironment& env, - const NormalizedFunctionType& subFunction, - const NormalizedFunctionType& superFunction + const NormalizedClassType& subClass, + const NormalizedClassType& superClass, + NotNull scope ); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables, NotNull scope); + SubtypingResult isCovariantWith( + SubtypingEnvironment& env, + const NormalizedStringType& subString, + const NormalizedStringType& superString, + NotNull scope + ); + SubtypingResult isCovariantWith( + SubtypingEnvironment& env, + const NormalizedStringType& subString, + const TypeIds& superTables, + NotNull scope + ); + SubtypingResult + isCovariantWith(SubtypingEnvironment& env, const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction, NotNull); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes, NotNull scope); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeFunctionInstanceType* subFunctionInstance, const TypeId superTy); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFunctionInstanceType* superFunctionInstance); + SubtypingResult isCovariantWith( + SubtypingEnvironment& env, + const VariadicTypePack* subVariadic, + const VariadicTypePack* superVariadic, + NotNull scope + ); + SubtypingResult isCovariantWith( + SubtypingEnvironment& env, + const TypeFunctionInstanceType* subFunctionInstance, + const TypeId superTy, + NotNull scope + ); + SubtypingResult isCovariantWith( + SubtypingEnvironment& env, + const TypeId subTy, + const TypeFunctionInstanceType* superFunctionInstance, + NotNull scope + ); bool bindGeneric(SubtypingEnvironment& env, TypeId subTp, TypeId superTp); bool bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp); @@ -240,7 +288,7 @@ private: template TypeId makeAggregateType(const Container& container, TypeId orElse); - std::pair handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance); + std::pair handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance, NotNull scope); [[noreturn]] void unexpected(TypeId ty); [[noreturn]] void unexpected(TypePackId tp); diff --git a/Analysis/src/AnyTypeSummary.cpp b/Analysis/src/AnyTypeSummary.cpp index 4ff2051b..ca59814c 100644 --- a/Analysis/src/AnyTypeSummary.cpp +++ b/Analysis/src/AnyTypeSummary.cpp @@ -38,7 +38,7 @@ #include -LUAU_FASTFLAGVARIABLE(StudioReportLuauAny, false); +LUAU_FASTFLAGVARIABLE(StudioReportLuauAny2, false); LUAU_FASTINTVARIABLE(LuauAnySummaryRecursionLimit, 300); LUAU_FASTFLAG(DebugLuauMagicTypes); @@ -211,14 +211,17 @@ void AnyTypeSummary::visit(const Scope* scope, AstStatLocal* local, const Module if (!maybeRequire) continue; - if (isAnyCast(scope, local->values.data[posn], module, builtinTypes)) + if (std::min(local->values.size - 1, posn) < head.size()) { - TelemetryTypePair types; + if (isAnyCast(scope, local->values.data[posn], module, builtinTypes)) + { + TelemetryTypePair types; - types.inferredType = toString(head[std::min(local->values.size - 1, posn)]); + types.inferredType = toString(head[std::min(local->values.size - 1, posn)]); - TypeInfo ti{Pattern::Casts, toString(ctxNode), types}; - typeInfo.push_back(ti); + TypeInfo ti{Pattern::Casts, toString(ctxNode), types}; + typeInfo.push_back(ti); + } } } else @@ -292,7 +295,7 @@ void AnyTypeSummary::visit(const Scope* scope, AstStatAssign* assign, const Modu types.annotatedType = toString(tp); auto loc = std::min(assign->vars.size - 1, posn); - if (head.size() >= assign->vars.size) + if (head.size() >= assign->vars.size && posn < head.size()) { types.inferredType = toString(head[posn]); } diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index a4acfb85..03c9a956 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -144,9 +144,9 @@ static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull scope, T if (FFlag::DebugLuauDeferredConstraintResolution) { - Subtyping subtyping{builtinTypes, NotNull{typeArena}, NotNull{&normalizer}, NotNull{&iceReporter}, scope}; + Subtyping subtyping{builtinTypes, NotNull{typeArena}, NotNull{&normalizer}, NotNull{&iceReporter}}; - return subtyping.isSubtype(subTy, superTy).isSubtype; + return subtyping.isSubtype(subTy, superTy, scope).isSubtype; } else { diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index ba2d1beb..face6825 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -1793,7 +1793,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* std::vector> expectedTypesForCall = getExpectedCallTypesForFunctionOverloads(fnType); - module->astOriginalCallTypes[call] = fnType; + module->astOriginalCallTypes[call->func] = fnType; Checkpoint argBeginCheckpoint = checkpoint(this); diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index f40b3e0e..b64dce8e 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -45,8 +45,9 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes, false) LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode, false) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode, false) LUAU_FASTFLAGVARIABLE(LuauSourceModuleUpdatedWithSelectedMode, false) +LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false) -LUAU_FASTFLAG(StudioReportLuauAny) +LUAU_FASTFLAG(StudioReportLuauAny2) namespace Luau { @@ -511,7 +512,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optionallintResult; - if (FFlag::StudioReportLuauAny && item.options.retainFullTypeGraphs) + if (FFlag::StudioReportLuauAny2 && item.options.retainFullTypeGraphs) { if (item.module) { @@ -1040,6 +1041,9 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item) item.stats.timeCheck += duration; item.stats.filesStrict += 1; + if (DFFlag::LuauRunCustomModuleChecks && item.options.customModuleCheck) + item.options.customModuleCheck(sourceModule, *moduleForAutocomplete); + item.module = moduleForAutocomplete; return; } @@ -1057,8 +1061,8 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item) item.stats.filesStrict += mode == Mode::Strict; item.stats.filesNonstrict += mode == Mode::Nonstrict; - if (module == nullptr) - throw InternalCompilerError("Frontend::check produced a nullptr module for " + item.name, item.name); + if (DFFlag::LuauRunCustomModuleChecks && item.options.customModuleCheck) + item.options.customModuleCheck(sourceModule, *module); if (FFlag::DebugLuauDeferredConstraintResolution && mode == Mode::NoCheck) module->errors.clear(); diff --git a/Analysis/src/NonStrictTypeChecker.cpp b/Analysis/src/NonStrictTypeChecker.cpp index 16225e96..116cf5cb 100644 --- a/Analysis/src/NonStrictTypeChecker.cpp +++ b/Analysis/src/NonStrictTypeChecker.cpp @@ -182,7 +182,7 @@ struct NonStrictTypeChecker , arena(arena) , module(module) , normalizer{arena, builtinTypes, unifierState, /* cache inhabitance */ true} - , subtyping{builtinTypes, arena, NotNull(&normalizer), ice, NotNull{module->getModuleScope().get()}} + , subtyping{builtinTypes, arena, NotNull(&normalizer), ice} , dfg(dfg) , limits(limits) { @@ -531,7 +531,7 @@ struct NonStrictTypeChecker NonStrictContext visit(AstExprCall* call) { NonStrictContext fresh{}; - TypeId* originalCallTy = module->astOriginalCallTypes.find(call); + TypeId* originalCallTy = module->astOriginalCallTypes.find(call->func); if (!originalCallTy) return fresh; @@ -699,6 +699,7 @@ struct NonStrictTypeChecker // If this fragment of the ast will run time error, return the type that causes this std::optional willRunTimeError(AstExpr* fragment, const NonStrictContext& context) { + NotNull scope{Luau::findScopeAtPosition(*module, fragment->location.end).get()}; DefId def = dfg->getDef(fragment); std::vector defs; collectOperands(def, &defs); @@ -708,7 +709,7 @@ struct NonStrictTypeChecker { TypeId actualType = lookupType(fragment); - SubtypingResult r = subtyping.isSubtype(actualType, *contextTy); + SubtypingResult r = subtyping.isSubtype(actualType, *contextTy, scope); if (r.normalizationTooComplex) reportError(NormalizationTooComplex{}, fragment->location); if (r.isSubtype) @@ -721,6 +722,7 @@ struct NonStrictTypeChecker std::optional willRunTimeErrorFunctionDefinition(AstLocal* fragment, const NonStrictContext& context) { + NotNull scope{Luau::findScopeAtPosition(*module, fragment->location.end).get()}; DefId def = dfg->getDef(fragment); std::vector defs; collectOperands(def, &defs); @@ -728,8 +730,8 @@ struct NonStrictTypeChecker { if (std::optional contextTy = context.find(def)) { - SubtypingResult r1 = subtyping.isSubtype(builtinTypes->unknownType, *contextTy); - SubtypingResult r2 = subtyping.isSubtype(*contextTy, builtinTypes->unknownType); + SubtypingResult r1 = subtyping.isSubtype(builtinTypes->unknownType, *contextTy, scope); + SubtypingResult r2 = subtyping.isSubtype(*contextTy, builtinTypes->unknownType, scope); if (r1.normalizationTooComplex || r2.normalizationTooComplex) reportError(NormalizationTooComplex{}, fragment->location); bool isUnknown = r1.isSubtype && r2.isSubtype; @@ -747,7 +749,7 @@ private: if (!cachedResult) cachedResult = arena->addType(NegationType{baseType}); return cachedResult; - }; + } }; void checkNonStrict( diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 8935a476..408d8cba 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -3429,9 +3429,9 @@ bool isSubtype(TypeId subTy, TypeId superTy, NotNull scope, NotNull scope, N // Subtyping under DCR is not implemented using unification! if (FFlag::DebugLuauDeferredConstraintResolution) { - Subtyping subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&ice}, scope}; + Subtyping subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&ice}}; - return subtyping.isSubtype(subPack, superPack).isSubtype; + return subtyping.isSubtype(subPack, superPack, scope).isSubtype; } else { diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index 612f4ad2..987a2708 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -28,7 +28,7 @@ OverloadResolver::OverloadResolver( , scope(scope) , ice(reporter) , limits(limits) - , subtyping({builtinTypes, arena, normalizer, ice, scope}) + , subtyping({builtinTypes, arena, normalizer, ice}) , callLoc(callLocation) { } @@ -41,7 +41,7 @@ std::pair OverloadResolver::selectOverload(T { Subtyping::Variance variance = subtyping.variance; subtyping.variance = Subtyping::Variance::Contravariant; - SubtypingResult r = subtyping.isSubtype(argsPack, ftv->argTypes); + SubtypingResult r = subtyping.isSubtype(argsPack, ftv->argTypes, scope); subtyping.variance = variance; if (r.isSubtype) @@ -92,7 +92,7 @@ void OverloadResolver::resolve(TypeId fnTy, const TypePack* args, AstExpr* selfE std::optional OverloadResolver::testIsSubtype(const Location& location, TypeId subTy, TypeId superTy) { - auto r = subtyping.isSubtype(subTy, superTy); + auto r = subtyping.isSubtype(subTy, superTy, scope); ErrorVec errors; if (r.normalizationTooComplex) @@ -121,7 +121,7 @@ std::optional OverloadResolver::testIsSubtype(const Location& location std::optional OverloadResolver::testIsSubtype(const Location& location, TypePackId subTy, TypePackId superTy) { - auto r = subtyping.isSubtype(subTy, superTy); + auto r = subtyping.isSubtype(subTy, superTy, scope); ErrorVec errors; if (r.normalizationTooComplex) @@ -206,7 +206,7 @@ std::pair OverloadResolver::checkOverload_ TypePackId typ = arena->addTypePack(*args); TypeId prospectiveFunction = arena->addType(FunctionType{typ, builtinTypes->anyTypePack}); - SubtypingResult sr = subtyping.isSubtype(fnTy, prospectiveFunction); + SubtypingResult sr = subtyping.isSubtype(fnTy, prospectiveFunction, scope); if (sr.isSubtype) return {Analysis::Ok, {}}; @@ -250,7 +250,7 @@ std::pair OverloadResolver::checkOverload_ // nil, then this overload does not match. for (size_t i = firstUnsatisfiedArgument; i < requiredHead.size(); ++i) { - if (!subtyping.isSubtype(builtinTypes->nilType, requiredHead[i]).isSubtype) + if (!subtyping.isSubtype(builtinTypes->nilType, requiredHead[i], scope).isSubtype) { auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes); TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, isVariadic}}; diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index 4bbe2ecd..ee199b66 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -333,28 +333,25 @@ Subtyping::Subtyping( NotNull builtinTypes, NotNull typeArena, NotNull normalizer, - NotNull iceReporter, - NotNull scope + NotNull iceReporter ) : builtinTypes(builtinTypes) , arena(typeArena) , normalizer(normalizer) , iceReporter(iceReporter) - , scope(scope) { } -SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy) +SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull scope) { SubtypingEnvironment env; - SubtypingResult result = isCovariantWith(env, subTy, superTy); + SubtypingResult result = isCovariantWith(env, subTy, superTy, scope); for (const auto& [subTy, bounds] : env.mappedGenerics) { const auto& lb = bounds.lowerBound; const auto& ub = bounds.upperBound; - TypeId lowerBound = makeAggregateType(lb, builtinTypes->neverType); TypeId upperBound = makeAggregateType(ub, builtinTypes->unknownType); @@ -382,7 +379,7 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy) result.isSubtype = false; } - SubtypingResult boundsResult = isCovariantWith(env, lowerBound, upperBound); + SubtypingResult boundsResult = isCovariantWith(env, lowerBound, upperBound, scope); boundsResult.reasoning.clear(); result.andAlso(boundsResult); @@ -406,10 +403,10 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy) return result; } -SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp) +SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp, NotNull scope) { SubtypingEnvironment env; - return isCovariantWith(env, subTp, superTp); + return isCovariantWith(env, subTp, superTp, scope); } SubtypingResult Subtyping::cache(SubtypingEnvironment& env, SubtypingResult result, TypeId subTy, TypeId superTy) @@ -443,7 +440,7 @@ struct SeenSetPopper }; } // namespace -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy, NotNull scope) { subTy = follow(subTy); superTy = follow(superTy); @@ -501,20 +498,20 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub // tested as though it were its upper bounds. We do not yet support bounded // generics, so the upper bound is always unknown. if (auto subGeneric = get(subTy); subGeneric && subsumes(subGeneric->scope, scope)) - return isCovariantWith(env, builtinTypes->neverType, superTy); + return isCovariantWith(env, builtinTypes->neverType, superTy, scope); if (auto superGeneric = get(superTy); superGeneric && subsumes(superGeneric->scope, scope)) - return isCovariantWith(env, subTy, builtinTypes->unknownType); + return isCovariantWith(env, subTy, builtinTypes->unknownType, scope); SubtypingResult result; if (auto subUnion = get(subTy)) - result = isCovariantWith(env, subUnion, superTy); + result = isCovariantWith(env, subUnion, superTy, scope); else if (auto superUnion = get(superTy)) { - result = isCovariantWith(env, subTy, superUnion); + result = isCovariantWith(env, subTy, superUnion, scope); if (!result.isSubtype && !result.normalizationTooComplex) { - SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy)); + SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); if (semantic.isSubtype) { semantic.reasoning.clear(); @@ -523,13 +520,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub } } else if (auto superIntersection = get(superTy)) - result = isCovariantWith(env, subTy, superIntersection); + result = isCovariantWith(env, subTy, superIntersection, scope); else if (auto subIntersection = get(subTy)) { - result = isCovariantWith(env, subIntersection, superTy); + result = isCovariantWith(env, subIntersection, superTy, scope); if (!result.isSubtype && !result.normalizationTooComplex) { - SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy)); + SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); if (semantic.isSubtype) { // Clear the semantic reasoning, as any reasonings within @@ -550,7 +547,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub { // any = unknown | error, so we rewrite this to match. // As per TAPL: A | B <: T iff A <: T && B <: T - result = isCovariantWith(env, builtinTypes->unknownType, superTy).andAlso(isCovariantWith(env, builtinTypes->errorType, superTy)); + result = + isCovariantWith(env, builtinTypes->unknownType, superTy, scope).andAlso(isCovariantWith(env, builtinTypes->errorType, superTy, scope)); } else if (get(superTy)) { @@ -566,15 +564,15 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub else if (get(superTy)) result = {false}; else if (get(subTy)) - result = {false}; + result = {true}; else if (auto p = get2(subTy, superTy)) - result = isCovariantWith(env, p.first->ty, p.second->ty).withBothComponent(TypePath::TypeField::Negated); + result = isCovariantWith(env, p.first->ty, p.second->ty, scope).withBothComponent(TypePath::TypeField::Negated); else if (auto subNegation = get(subTy)) { - result = isCovariantWith(env, subNegation, superTy); + result = isCovariantWith(env, subNegation, superTy, scope); if (!result.isSubtype && !result.normalizationTooComplex) { - SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy)); + SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); if (semantic.isSubtype) { semantic.reasoning.clear(); @@ -584,10 +582,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub } else if (auto superNegation = get(superTy)) { - result = isCovariantWith(env, subTy, superNegation); + result = isCovariantWith(env, subTy, superNegation, scope); if (!result.isSubtype && !result.normalizationTooComplex) { - SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy)); + SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); if (semantic.isSubtype) { semantic.reasoning.clear(); @@ -600,14 +598,14 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub if (auto substSubTy = env.applyMappedGenerics(builtinTypes, arena, subTy)) subTypeFunctionInstance = get(*substSubTy); - result = isCovariantWith(env, subTypeFunctionInstance, superTy); + result = isCovariantWith(env, subTypeFunctionInstance, superTy, scope); } else if (auto superTypeFunctionInstance = get(superTy)) { if (auto substSuperTy = env.applyMappedGenerics(builtinTypes, arena, superTy)) superTypeFunctionInstance = get(*substSuperTy); - result = isCovariantWith(env, subTy, superTypeFunctionInstance); + result = isCovariantWith(env, subTy, superTypeFunctionInstance, scope); } else if (auto subGeneric = get(subTy); subGeneric && variance == Variance::Covariant) { @@ -622,41 +620,41 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub result.isCacheable = false; } else if (auto p = get2(subTy, superTy)) - result = isCovariantWith(env, p); + result = isCovariantWith(env, p, scope); else if (auto p = get2(subTy, superTy)) - result = isCovariantWith(env, p); + result = isCovariantWith(env, p, scope); else if (auto p = get2(subTy, superTy)) - result = isCovariantWith(env, p); + result = isCovariantWith(env, p, scope); else if (auto p = get2(subTy, superTy)) { auto [subFunction, superPrimitive] = p; result.isSubtype = superPrimitive->type == PrimitiveType::Function; } else if (auto p = get2(subTy, superTy)) - result = isCovariantWith(env, p); + result = isCovariantWith(env, p, scope); else if (auto p = get2(subTy, superTy)) - result = isCovariantWith(env, p); + result = isCovariantWith(env, p, scope); else if (auto p = get2(subTy, superTy)) - result = isCovariantWith(env, p); + result = isCovariantWith(env, p, scope); else if (auto p = get2(subTy, superTy)) - result = isCovariantWith(env, p); + result = isCovariantWith(env, p, scope); else if (auto p = get2(subTy, superTy)) - result = isCovariantWith(env, p); + result = isCovariantWith(env, p, scope); else if (auto p = get2(subTy, superTy)) - result = isCovariantWith(env, subTy, p.first, superTy, p.second); + result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope); else if (auto p = get2(subTy, superTy)) - result = isCovariantWith(env, p); + result = isCovariantWith(env, p, scope); else if (auto p = get2(subTy, superTy)) - result = isCovariantWith(env, p); + result = isCovariantWith(env, p, scope); else if (auto p = get2(subTy, superTy)) - result = isCovariantWith(env, p); + result = isCovariantWith(env, p, scope); assertReasoningValid(subTy, superTy, result, builtinTypes); return cache(env, result, subTy, superTy); } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp, NotNull scope) { subTp = follow(subTp); superTp = follow(superTp); @@ -675,7 +673,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId // Match head types pairwise for (size_t i = 0; i < headSize; ++i) - results.push_back(isCovariantWith(env, subHead[i], superHead[i]).withBothComponent(TypePath::Index{i})); + results.push_back(isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i})); // Handle mismatched head sizes @@ -686,7 +684,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId if (auto vt = get(*subTail)) { for (size_t i = headSize; i < superHead.size(); ++i) - results.push_back(isCovariantWith(env, vt->ty, superHead[i]) + results.push_back(isCovariantWith(env, vt->ty, superHead[i], scope) .withSubPath(TypePath::PathBuilder().tail().variadic().build()) .withSuperComponent(TypePath::Index{i})); } @@ -704,7 +702,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId if (TypePackId* other = env.mappedGenericPacks.find(*subTail)) // TODO: TypePath can't express "slice of a pack + its tail". - results.push_back(isCovariantWith(env, *other, superTailPack).withSubComponent(TypePath::PackField::Tail)); + results.push_back(isCovariantWith(env, *other, superTailPack, scope).withSubComponent(TypePath::PackField::Tail)); else env.mappedGenericPacks.try_insert(*subTail, superTailPack); @@ -741,7 +739,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId if (auto vt = get(*superTail)) { for (size_t i = headSize; i < subHead.size(); ++i) - results.push_back(isCovariantWith(env, subHead[i], vt->ty) + results.push_back(isCovariantWith(env, subHead[i], vt->ty, scope) .withSubComponent(TypePath::Index{i}) .withSuperPath(TypePath::PathBuilder().tail().variadic().build())); } @@ -759,7 +757,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId if (TypePackId* other = env.mappedGenericPacks.find(*superTail)) // TODO: TypePath can't express "slice of a pack + its tail". - results.push_back(isContravariantWith(env, subTailPack, *other).withSuperComponent(TypePath::PackField::Tail)); + results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail)); else env.mappedGenericPacks.try_insert(*superTail, subTailPack); @@ -794,7 +792,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId { // Variadic component is added by the isCovariantWith // implementation; no need to add it here. - results.push_back(isCovariantWith(env, p).withBothComponent(TypePath::PackField::Tail)); + results.push_back(isCovariantWith(env, p, scope).withBothComponent(TypePath::PackField::Tail)); } else if (auto p = get2(*subTail, *superTail)) { @@ -899,11 +897,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId } template -SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy) +SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy, NotNull scope) { VarianceFlipper vf{&variance}; - SubtypingResult result = isCovariantWith(env, superTy, subTy); + SubtypingResult result = isCovariantWith(env, superTy, subTy, scope); if (result.reasoning.empty()) result.reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Contravariant}); else @@ -931,9 +929,9 @@ SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy& } template -SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy) +SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy, NotNull scope) { - SubtypingResult result = isCovariantWith(env, subTy, superTy).andAlso(isContravariantWith(env, subTy, superTy)); + SubtypingResult result = isCovariantWith(env, subTy, superTy, scope).andAlso(isContravariantWith(env, subTy, superTy, scope)); if (result.reasoning.empty()) result.reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invariant}); @@ -948,19 +946,19 @@ SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& su } template -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TryPair& pair) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TryPair& pair, NotNull scope) { - return isCovariantWith(env, pair.first, pair.second); + return isCovariantWith(env, pair.first, pair.second, scope); } template -SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, const TryPair& pair) +SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, const TryPair& pair, NotNull scope) { - return isContravariantWith(env, pair.first, pair.second); + return isContravariantWith(env, pair.first, pair.second, scope); } template -SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, const TryPair& pair) +SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, const TryPair& pair, NotNull scope) { return isInvariantWith(env, pair.first, pair.second); } @@ -996,13 +994,13 @@ SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, const TryP * other just asks for boolean ~ 'b. We can dispatch this and only commit * boolean ~ 'b. This constraint does not teach us anything about 'a. */ -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const UnionType* superUnion) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const UnionType* superUnion, NotNull scope) { // As per TAPL: T <: A | B iff T <: A || T <: B for (TypeId ty : superUnion) { - SubtypingResult next = isCovariantWith(env, subTy, ty); + SubtypingResult next = isCovariantWith(env, subTy, ty, scope); if (next.isSubtype) return SubtypingResult{true}; } @@ -1015,37 +1013,37 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub return SubtypingResult{false}; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const UnionType* subUnion, TypeId superTy) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const UnionType* subUnion, TypeId superTy, NotNull scope) { // As per TAPL: A | B <: T iff A <: T && B <: T std::vector subtypings; size_t i = 0; for (TypeId ty : subUnion) - subtypings.push_back(isCovariantWith(env, ty, superTy).withSubComponent(TypePath::Index{i++})); + subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++})); return SubtypingResult::all(subtypings); } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const IntersectionType* superIntersection) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const IntersectionType* superIntersection, NotNull scope) { // As per TAPL: T <: A & B iff T <: A && T <: B std::vector subtypings; size_t i = 0; for (TypeId ty : superIntersection) - subtypings.push_back(isCovariantWith(env, subTy, ty).withSuperComponent(TypePath::Index{i++})); + subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++})); return SubtypingResult::all(subtypings); } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const IntersectionType* subIntersection, TypeId superTy) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const IntersectionType* subIntersection, TypeId superTy, NotNull scope) { // As per TAPL: A & B <: T iff A <: T || B <: T std::vector subtypings; size_t i = 0; for (TypeId ty : subIntersection) - subtypings.push_back(isCovariantWith(env, ty, superTy).withSubComponent(TypePath::Index{i++})); + subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++})); return SubtypingResult::any(subtypings); } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NegationType* subNegation, TypeId superTy) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NegationType* subNegation, TypeId superTy, NotNull scope) { TypeId negatedTy = follow(subNegation->ty); @@ -1057,17 +1055,17 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Nega if (is(negatedTy)) { // ¬never ~ unknown - result = isCovariantWith(env, builtinTypes->unknownType, superTy).withSubComponent(TypePath::TypeField::Negated); + result = isCovariantWith(env, builtinTypes->unknownType, superTy, scope).withSubComponent(TypePath::TypeField::Negated); } else if (is(negatedTy)) { // ¬unknown ~ never - result = isCovariantWith(env, builtinTypes->neverType, superTy).withSubComponent(TypePath::TypeField::Negated); + result = isCovariantWith(env, builtinTypes->neverType, superTy, scope).withSubComponent(TypePath::TypeField::Negated); } else if (is(negatedTy)) { // ¬any ~ any - result = isCovariantWith(env, negatedTy, superTy).withSubComponent(TypePath::TypeField::Negated); + result = isCovariantWith(env, negatedTy, superTy, scope).withSubComponent(TypePath::TypeField::Negated); } else if (auto u = get(negatedTy)) { @@ -1078,11 +1076,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Nega for (TypeId ty : u) { if (auto negatedPart = get(follow(ty))) - subtypings.push_back(isCovariantWith(env, negatedPart->ty, superTy).withSubComponent(TypePath::TypeField::Negated)); + subtypings.push_back(isCovariantWith(env, negatedPart->ty, superTy, scope).withSubComponent(TypePath::TypeField::Negated)); else { NegationType negatedTmp{ty}; - subtypings.push_back(isCovariantWith(env, &negatedTmp, superTy)); + subtypings.push_back(isCovariantWith(env, &negatedTmp, superTy, scope)); } } @@ -1097,11 +1095,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Nega for (TypeId ty : i) { if (auto negatedPart = get(follow(ty))) - subtypings.push_back(isCovariantWith(env, negatedPart->ty, superTy).withSubComponent(TypePath::TypeField::Negated)); + subtypings.push_back(isCovariantWith(env, negatedPart->ty, superTy, scope).withSubComponent(TypePath::TypeField::Negated)); else { NegationType negatedTmp{ty}; - subtypings.push_back(isCovariantWith(env, &negatedTmp, superTy)); + subtypings.push_back(isCovariantWith(env, &negatedTmp, superTy, scope)); } } @@ -1121,7 +1119,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Nega return result; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const NegationType* superNegation) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const NegationType* superNegation, NotNull scope) { TypeId negatedTy = follow(superNegation->ty); @@ -1130,17 +1128,17 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type if (is(negatedTy)) { // ¬never ~ unknown - result = isCovariantWith(env, subTy, builtinTypes->unknownType); + result = isCovariantWith(env, subTy, builtinTypes->unknownType, scope); } else if (is(negatedTy)) { // ¬unknown ~ never - result = isCovariantWith(env, subTy, builtinTypes->neverType); + result = isCovariantWith(env, subTy, builtinTypes->neverType, scope); } else if (is(negatedTy)) { // ¬any ~ any - result = isSubtype(subTy, negatedTy); + result = isSubtype(subTy, negatedTy, scope); } else if (auto u = get(negatedTy)) { @@ -1151,11 +1149,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type for (TypeId ty : u) { if (auto negatedPart = get(follow(ty))) - subtypings.push_back(isCovariantWith(env, subTy, negatedPart->ty)); + subtypings.push_back(isCovariantWith(env, subTy, negatedPart->ty, scope)); else { NegationType negatedTmp{ty}; - subtypings.push_back(isCovariantWith(env, subTy, &negatedTmp)); + subtypings.push_back(isCovariantWith(env, subTy, &negatedTmp, scope)); } } @@ -1170,11 +1168,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type for (TypeId ty : i) { if (auto negatedPart = get(follow(ty))) - subtypings.push_back(isCovariantWith(env, subTy, negatedPart->ty)); + subtypings.push_back(isCovariantWith(env, subTy, negatedPart->ty, scope)); else { NegationType negatedTmp{ty}; - subtypings.push_back(isCovariantWith(env, subTy, &negatedTmp)); + subtypings.push_back(isCovariantWith(env, subTy, &negatedTmp, scope)); } } @@ -1218,7 +1216,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type else if (auto p = get2(subTy, negatedTy)) result = {*p.first != *p.second}; else if (auto p = get2(subTy, negatedTy)) - result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second)); + result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second, scope)); else if (get2(subTy, negatedTy)) result = {true}; else if (is(negatedTy)) @@ -1229,12 +1227,22 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type return result.withSuperComponent(TypePath::TypeField::Negated); } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const PrimitiveType* superPrim) +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, + const PrimitiveType* subPrim, + const PrimitiveType* superPrim, + NotNull scope +) { return {subPrim->type == superPrim->type}; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const PrimitiveType* superPrim) +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, + const SingletonType* subSingleton, + const PrimitiveType* superPrim, + NotNull scope +) { if (get(subSingleton) && superPrim->type == PrimitiveType::String) return {true}; @@ -1244,12 +1252,17 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Sing return {false}; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const SingletonType* superSingleton) +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, + const SingletonType* subSingleton, + const SingletonType* superSingleton, + NotNull scope +) { return {*subSingleton == *superSingleton}; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, NotNull scope) { SubtypingResult result{true}; @@ -1260,23 +1273,23 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl { std::vector results; if (auto subIter = subTable->props.find(name); subIter != subTable->props.end()) - results.push_back(isCovariantWith(env, subIter->second, superProp, name)); + results.push_back(isCovariantWith(env, subIter->second, superProp, name, scope)); else if (subTable->indexer) { - if (isCovariantWith(env, builtinTypes->stringType, subTable->indexer->indexType).isSubtype) + if (isCovariantWith(env, builtinTypes->stringType, subTable->indexer->indexType, scope).isSubtype) { if (superProp.isShared()) - results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, superProp.type()) + results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, superProp.type(), scope) .withSubComponent(TypePath::TypeField::IndexResult) .withSuperComponent(TypePath::Property::read(name))); else { if (superProp.readTy) - results.push_back(isCovariantWith(env, subTable->indexer->indexResultType, *superProp.readTy) + results.push_back(isCovariantWith(env, subTable->indexer->indexResultType, *superProp.readTy, scope) .withSubComponent(TypePath::TypeField::IndexResult) .withSuperComponent(TypePath::Property::read(name))); if (superProp.writeTy) - results.push_back(isContravariantWith(env, subTable->indexer->indexResultType, *superProp.writeTy) + results.push_back(isContravariantWith(env, subTable->indexer->indexResultType, *superProp.writeTy, scope) .withSubComponent(TypePath::TypeField::IndexResult) .withSuperComponent(TypePath::Property::write(name))); } @@ -1292,7 +1305,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl if (superTable->indexer) { if (subTable->indexer) - result.andAlso(isInvariantWith(env, *subTable->indexer, *superTable->indexer)); + result.andAlso(isInvariantWith(env, *subTable->indexer, *superTable->indexer, scope)); else return {false}; } @@ -1300,13 +1313,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl return result; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull scope) { - return isCovariantWith(env, subMt->table, superMt->table) - .andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable).withBothComponent(TypePath::TypeField::Metatable)); + return isCovariantWith(env, subMt->table, superMt->table, scope) + .andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable)); } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull scope) { if (auto subTable = get(follow(subMt->table))) { @@ -1319,7 +1332,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta // that the metatable isn't a subtype of the table, even though they have // compatible properties/shapes. We'll revisit this later when we have a // better understanding of how important this is. - return isCovariantWith(env, subTable, superTable); + return isCovariantWith(env, subTable, superTable, scope); } else { @@ -1328,7 +1341,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta } } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull scope) { return {isSubclass(subClass, superClass)}; } @@ -1338,7 +1351,8 @@ SubtypingResult Subtyping::isCovariantWith( TypeId subTy, const ClassType* subClass, TypeId superTy, - const TableType* superTable + const TableType* superTable, + NotNull scope ) { SubtypingResult result{true}; @@ -1349,7 +1363,7 @@ SubtypingResult Subtyping::isCovariantWith( { if (auto classProp = lookupClassProp(subClass, name)) { - result.andAlso(isCovariantWith(env, *classProp, prop, name)); + result.andAlso(isCovariantWith(env, *classProp, prop, name, scope)); } else { @@ -1363,19 +1377,26 @@ SubtypingResult Subtyping::isCovariantWith( return result; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const FunctionType* subFunction, const FunctionType* superFunction) +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, + const FunctionType* subFunction, + const FunctionType* superFunction, + NotNull scope +) { SubtypingResult result; { - result.orElse(isContravariantWith(env, subFunction->argTypes, superFunction->argTypes).withBothComponent(TypePath::PackField::Arguments)); + result.orElse( + isContravariantWith(env, subFunction->argTypes, superFunction->argTypes, scope).withBothComponent(TypePath::PackField::Arguments) + ); } - result.andAlso(isCovariantWith(env, subFunction->retTypes, superFunction->retTypes).withBothComponent(TypePath::PackField::Returns)); + result.andAlso(isCovariantWith(env, subFunction->retTypes, superFunction->retTypes, scope).withBothComponent(TypePath::PackField::Returns)); return result; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const PrimitiveType* superPrim) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const PrimitiveType* superPrim, NotNull scope) { SubtypingResult result{false}; if (superPrim->type == PrimitiveType::Table) @@ -1384,7 +1405,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl return result; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const TableType* superTable) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const TableType* superTable, NotNull scope) { SubtypingResult result{false}; if (subPrim->type == PrimitiveType::String) @@ -1397,7 +1418,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prim { if (auto stringTable = get(it->second.type())) result.orElse( - isCovariantWith(env, stringTable, superTable).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()) + isCovariantWith(env, stringTable, superTable, scope).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()) ); } } @@ -1412,7 +1433,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prim return result; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const TableType* superTable) +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, + const SingletonType* subSingleton, + const TableType* superTable, + NotNull scope +) { SubtypingResult result{false}; if (auto stringleton = get(subSingleton)) @@ -1425,7 +1451,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Sing { if (auto stringTable = get(it->second.type())) result.orElse( - isCovariantWith(env, stringTable, superTable).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()) + isCovariantWith(env, stringTable, superTable, scope).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()) ); } } @@ -1434,25 +1460,38 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Sing return result; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TableIndexer& subIndexer, const TableIndexer& superIndexer) +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, + const TableIndexer& subIndexer, + const TableIndexer& superIndexer, + NotNull scope +) { - return isInvariantWith(env, subIndexer.indexType, superIndexer.indexType) + return isInvariantWith(env, subIndexer.indexType, superIndexer.indexType, scope) .withBothComponent(TypePath::TypeField::IndexLookup) - .andAlso(isInvariantWith(env, subIndexer.indexResultType, superIndexer.indexResultType).withBothComponent(TypePath::TypeField::IndexResult)); + .andAlso( + isInvariantWith(env, subIndexer.indexResultType, superIndexer.indexResultType, scope).withBothComponent(TypePath::TypeField::IndexResult) + ); } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Property& subProp, const Property& superProp, const std::string& name) +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, + const Property& subProp, + const Property& superProp, + const std::string& name, + NotNull scope +) { SubtypingResult res{true}; if (superProp.isShared() && subProp.isShared()) - res.andAlso(isInvariantWith(env, subProp.type(), superProp.type()).withBothComponent(TypePath::Property::read(name))); + res.andAlso(isInvariantWith(env, subProp.type(), superProp.type(), scope).withBothComponent(TypePath::Property::read(name))); else { if (superProp.readTy.has_value() && subProp.readTy.has_value()) - res.andAlso(isCovariantWith(env, *subProp.readTy, *superProp.readTy).withBothComponent(TypePath::Property::read(name))); + res.andAlso(isCovariantWith(env, *subProp.readTy, *superProp.readTy, scope).withBothComponent(TypePath::Property::read(name))); if (superProp.writeTy.has_value() && subProp.writeTy.has_value()) - res.andAlso(isContravariantWith(env, *subProp.writeTy, *superProp.writeTy).withBothComponent(TypePath::Property::write(name))); + res.andAlso(isContravariantWith(env, *subProp.writeTy, *superProp.writeTy, scope).withBothComponent(TypePath::Property::write(name))); if (superProp.isReadWrite()) { @@ -1469,29 +1508,37 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prop SubtypingResult Subtyping::isCovariantWith( SubtypingEnvironment& env, const std::shared_ptr& subNorm, - const std::shared_ptr& superNorm + const std::shared_ptr& superNorm, + NotNull scope ) { if (!subNorm || !superNorm) return {false, true}; - SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops); - result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans)); - result.andAlso(isCovariantWith(env, subNorm->classes, superNorm->classes).orElse(isCovariantWith(env, subNorm->classes, superNorm->tables))); - result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors)); - result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils)); - result.andAlso(isCovariantWith(env, subNorm->numbers, superNorm->numbers)); - result.andAlso(isCovariantWith(env, subNorm->strings, superNorm->strings)); - result.andAlso(isCovariantWith(env, subNorm->strings, superNorm->tables)); - result.andAlso(isCovariantWith(env, subNorm->threads, superNorm->threads)); - result.andAlso(isCovariantWith(env, subNorm->buffers, superNorm->buffers)); - result.andAlso(isCovariantWith(env, subNorm->tables, superNorm->tables)); - result.andAlso(isCovariantWith(env, subNorm->functions, superNorm->functions)); + SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope); + result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope)); + result.andAlso( + isCovariantWith(env, subNorm->classes, superNorm->classes, scope).orElse(isCovariantWith(env, subNorm->classes, superNorm->tables, scope)) + ); + result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope)); + result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope)); + result.andAlso(isCovariantWith(env, subNorm->numbers, superNorm->numbers, scope)); + result.andAlso(isCovariantWith(env, subNorm->strings, superNorm->strings, scope)); + result.andAlso(isCovariantWith(env, subNorm->strings, superNorm->tables, scope)); + result.andAlso(isCovariantWith(env, subNorm->threads, superNorm->threads, scope)); + result.andAlso(isCovariantWith(env, subNorm->buffers, superNorm->buffers, scope)); + result.andAlso(isCovariantWith(env, subNorm->tables, superNorm->tables, scope)); + result.andAlso(isCovariantWith(env, subNorm->functions, superNorm->functions, scope)); // isCovariantWith(subNorm->tyvars, superNorm->tyvars); return result; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const NormalizedClassType& superClass) +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, + const NormalizedClassType& subClass, + const NormalizedClassType& superClass, + NotNull scope +) { for (const auto& [subClassTy, _] : subClass.classes) { @@ -1499,13 +1546,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Norm for (const auto& [superClassTy, superNegations] : superClass.classes) { - result.orElse(isCovariantWith(env, subClassTy, superClassTy)); + result.orElse(isCovariantWith(env, subClassTy, superClassTy, scope)); if (!result.isSubtype) continue; for (TypeId negation : superNegations) { - result.andAlso(SubtypingResult::negate(isCovariantWith(env, subClassTy, negation))); + result.andAlso(SubtypingResult::negate(isCovariantWith(env, subClassTy, negation, scope))); if (result.isSubtype) break; } @@ -1518,14 +1565,19 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Norm return {true}; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables) +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, + const NormalizedClassType& subClass, + const TypeIds& superTables, + NotNull scope +) { for (const auto& [subClassTy, _] : subClass.classes) { SubtypingResult result; for (TypeId superTableTy : superTables) - result.orElse(isCovariantWith(env, subClassTy, superTableTy)); + result.orElse(isCovariantWith(env, subClassTy, superTableTy, scope)); if (!result.isSubtype) return result; @@ -1534,13 +1586,23 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Norm return {true}; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const NormalizedStringType& superString) +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, + const NormalizedStringType& subString, + const NormalizedStringType& superString, + NotNull scope +) { bool isSubtype = Luau::isSubtype(subString, superString); return {isSubtype}; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const TypeIds& superTables) +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, + const NormalizedStringType& subString, + const TypeIds& superTables, + NotNull scope +) { if (subString.isNever()) return {true}; @@ -1550,7 +1612,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Norm SubtypingResult result; for (const auto& superTable : superTables) { - result.orElse(isCovariantWith(env, builtinTypes->stringType, superTable)); + result.orElse(isCovariantWith(env, builtinTypes->stringType, superTable, scope)); if (result.isSubtype) return result; } @@ -1566,7 +1628,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Norm SubtypingResult result{true}; for (const auto& [_, subString] : subString.singletons) { - result.andAlso(isCovariantWith(env, subString, superTable)); + result.andAlso(isCovariantWith(env, subString, superTable, scope)); if (!result.isSubtype) break; } @@ -1583,7 +1645,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Norm SubtypingResult Subtyping::isCovariantWith( SubtypingEnvironment& env, const NormalizedFunctionType& subFunction, - const NormalizedFunctionType& superFunction + const NormalizedFunctionType& superFunction, + NotNull scope ) { if (subFunction.isNever()) @@ -1591,10 +1654,10 @@ SubtypingResult Subtyping::isCovariantWith( else if (superFunction.isTop) return {true}; else - return isCovariantWith(env, subFunction.parts, superFunction.parts); + return isCovariantWith(env, subFunction.parts, superFunction.parts, scope); } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes, NotNull scope) { std::vector results; @@ -1602,15 +1665,20 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type { results.emplace_back(); for (TypeId superTy : superTypes) - results.back().orElse(isCovariantWith(env, subTy, superTy)); + results.back().orElse(isCovariantWith(env, subTy, superTy, scope)); } return SubtypingResult::all(results); } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic) +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, + const VariadicTypePack* subVariadic, + const VariadicTypePack* superVariadic, + NotNull scope +) { - return isCovariantWith(env, subVariadic->ty, superVariadic->ty).withBothComponent(TypePath::TypeField::Variadic); + return isCovariantWith(env, subVariadic->ty, superVariadic->ty, scope).withBothComponent(TypePath::TypeField::Variadic); } bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId superTy) @@ -1633,20 +1701,30 @@ bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId supe return true; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeFunctionInstanceType* subFunctionInstance, const TypeId superTy) +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, + const TypeFunctionInstanceType* subFunctionInstance, + const TypeId superTy, + NotNull scope +) { // Reduce the type function instance - auto [ty, errors] = handleTypeFunctionReductionResult(subFunctionInstance); + auto [ty, errors] = handleTypeFunctionReductionResult(subFunctionInstance, scope); // If we return optional, that means the type function was irreducible - we can reduce that to never - return isCovariantWith(env, ty, superTy).withErrors(errors).withSubComponent(TypePath::Reduction{ty}); + return isCovariantWith(env, ty, superTy, scope).withErrors(errors).withSubComponent(TypePath::Reduction{ty}); } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFunctionInstanceType* superFunctionInstance) +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, + const TypeId subTy, + const TypeFunctionInstanceType* superFunctionInstance, + NotNull scope +) { // Reduce the type function instance - auto [ty, errors] = handleTypeFunctionReductionResult(superFunctionInstance); - return isCovariantWith(env, subTy, ty).withErrors(errors).withSuperComponent(TypePath::Reduction{ty}); + auto [ty, errors] = handleTypeFunctionReductionResult(superFunctionInstance, scope); + return isCovariantWith(env, subTy, ty, scope).withErrors(errors).withSuperComponent(TypePath::Reduction{ty}); } /* @@ -1681,7 +1759,7 @@ TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse) return arena->addType(T{std::vector(begin(container), end(container))}); } -std::pair Subtyping::handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance) +std::pair Subtyping::handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance, NotNull scope) { TypeFunctionContext context{arena, builtinTypes, scope, normalizer, iceReporter, NotNull{&limits}}; TypeId function = arena->addType(*functionInstance); diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 5020adc6..beb8e650 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -252,8 +252,14 @@ struct TypeChecker2 Subtyping _subtyping; NotNull subtyping; - TypeChecker2(NotNull builtinTypes, NotNull unifierState, NotNull limits, DcrLogger* logger, - const SourceModule* sourceModule, Module* module) + TypeChecker2( + NotNull builtinTypes, + NotNull unifierState, + NotNull limits, + DcrLogger* logger, + const SourceModule* sourceModule, + Module* module + ) : builtinTypes(builtinTypes) , logger(logger) , limits(limits) @@ -261,8 +267,7 @@ struct TypeChecker2 , sourceModule(sourceModule) , module(module) , normalizer{&module->internalTypes, builtinTypes, unifierState, /* cacheInhabitance */ true} - , _subtyping{builtinTypes, NotNull{&module->internalTypes}, NotNull{&normalizer}, NotNull{unifierState->iceHandler}, - NotNull{module->getModuleScope().get()}} + , _subtyping{builtinTypes, NotNull{&module->internalTypes}, NotNull{&normalizer}, NotNull{unifierState->iceHandler}} , subtyping(&_subtyping) { } @@ -1255,8 +1260,9 @@ struct TypeChecker2 #if defined(LUAU_ENABLE_ASSERT) TypeId actualType = lookupType(expr); TypeId expectedType = builtinTypes->nilType; + NotNull scope{findInnermostScope(expr->location)}; - SubtypingResult r = subtyping->isSubtype(actualType, expectedType); + SubtypingResult r = subtyping->isSubtype(actualType, expectedType, scope); LUAU_ASSERT(r.isSubtype || isErrorSuppressing(expr->location, actualType)); #endif } @@ -1267,8 +1273,9 @@ struct TypeChecker2 const TypeId bestType = expr->value ? builtinTypes->trueType : builtinTypes->falseType; const TypeId inferredType = lookupType(expr); + NotNull scope{findInnermostScope(expr->location)}; - const SubtypingResult r = subtyping->isSubtype(bestType, inferredType); + const SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope); if (!r.isSubtype && !isErrorSuppressing(expr->location, inferredType)) reportError(TypeMismatch{inferredType, bestType}, expr->location); } @@ -1278,8 +1285,9 @@ struct TypeChecker2 #if defined(LUAU_ENABLE_ASSERT) const TypeId bestType = builtinTypes->numberType; const TypeId inferredType = lookupType(expr); + NotNull scope{findInnermostScope(expr->location)}; - const SubtypingResult r = subtyping->isSubtype(bestType, inferredType); + const SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope); LUAU_ASSERT(r.isSubtype || isErrorSuppressing(expr->location, inferredType)); #endif } @@ -1290,8 +1298,9 @@ struct TypeChecker2 const TypeId bestType = module->internalTypes.addType(SingletonType{StringSingleton{std::string{expr->value.data, expr->value.size}}}); const TypeId inferredType = lookupType(expr); + NotNull scope{findInnermostScope(expr->location)}; - const SubtypingResult r = subtyping->isSubtype(bestType, inferredType); + const SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope); if (!r.isSubtype && !isErrorSuppressing(expr->location, inferredType)) reportError(TypeMismatch{inferredType, bestType}, expr->location); } @@ -1320,7 +1329,7 @@ struct TypeChecker2 std::vector argExprs; argExprs.reserve(call->args.size + 1); - TypeId* originalCallTy = module->astOriginalCallTypes.find(call); + TypeId* originalCallTy = module->astOriginalCallTypes.find(call->func); TypeId* selectedOverloadTy = module->astOverloadResolvedTypes.find(call); if (!originalCallTy) return; @@ -1347,7 +1356,8 @@ struct TypeChecker2 if (selectedOverloadTy) { - SubtypingResult result = subtyping->isSubtype(*originalCallTy, *selectedOverloadTy); + NotNull scope{findInnermostScope(call->location)}; + SubtypingResult result = subtyping->isSubtype(*originalCallTy, *selectedOverloadTy, scope); if (result.isSubtype) fnTy = follow(*selectedOverloadTy); @@ -1622,12 +1632,17 @@ struct TypeChecker2 reportError(OptionalValueAccess{exprType}, indexExpr->location); } } - else if (auto exprIntersection = get(exprType)) + else if (auto ut = get(exprType)) { - for (TypeId part : exprIntersection) - { - (void)part; - } + // if all of the types are a table type, the union must be a table, and so we shouldn't error. + if (!std::all_of(begin(ut), end(ut), getTableType)) + reportError(NotATable{exprType}, indexExpr->location); + } + else if (auto it = get(exprType)) + { + // if any of the types are a table type, the intersection must be a table, and so we shouldn't error. + if (!std::any_of(begin(it), end(it), getTableType)) + reportError(NotATable{exprType}, indexExpr->location); } else if (get(exprType) || isErrorSuppressing(indexExpr->location, exprType)) { @@ -2726,7 +2741,8 @@ struct TypeChecker2 bool testIsSubtype(TypeId subTy, TypeId superTy, Location location) { - SubtypingResult r = subtyping->isSubtype(subTy, superTy); + NotNull scope{findInnermostScope(location)}; + SubtypingResult r = subtyping->isSubtype(subTy, superTy, scope); if (r.normalizationTooComplex) reportError(NormalizationTooComplex{}, location); @@ -2739,7 +2755,8 @@ struct TypeChecker2 bool testIsSubtype(TypePackId subTy, TypePackId superTy, Location location) { - SubtypingResult r = subtyping->isSubtype(subTy, superTy); + NotNull scope{findInnermostScope(location)}; + SubtypingResult r = subtyping->isSubtype(subTy, superTy, scope); if (r.normalizationTooComplex) reportError(NormalizationTooComplex{}, location); diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index e8d3764e..cd508368 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -701,8 +701,8 @@ TypeFunctionReductionResult lenTypeFunction( if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes)) return {std::nullopt, true, {}, {}}; // occurs check failed - Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope}; - if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance? + Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice}; + if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance? return {std::nullopt, true, {}, {}}; // `len` must return a `number`. @@ -790,8 +790,8 @@ TypeFunctionReductionResult unmTypeFunction( if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes)) return {std::nullopt, true, {}, {}}; // occurs check failed - Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope}; - if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance? + Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice}; + if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance? return {std::nullopt, true, {}, {}}; if (std::optional ret = first(instantiatedMmFtv->retTypes)) @@ -1138,8 +1138,8 @@ TypeFunctionReductionResult concatTypeFunction( if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes)) return {std::nullopt, true, {}, {}}; // occurs check failed - Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope}; - if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance? + Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice}; + if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance? return {std::nullopt, true, {}, {}}; return {ctx->builtins->stringType, false, {}, {}}; @@ -1392,8 +1392,8 @@ static TypeFunctionReductionResult comparisonTypeFunction( if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes)) return {std::nullopt, true, {}, {}}; // occurs check failed - Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope}; - if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance? + Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice}; + if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance? return {std::nullopt, true, {}, {}}; return {ctx->builtins->booleanType, false, {}, {}}; @@ -1536,8 +1536,8 @@ TypeFunctionReductionResult eqTypeFunction( if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes)) return {std::nullopt, true, {}, {}}; // occurs check failed - Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope}; - if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance? + Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice}; + if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance? return {std::nullopt, true, {}, {}}; return {ctx->builtins->booleanType, false, {}, {}}; diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index c0a43df8..5463c231 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -34,6 +34,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false) LUAU_FASTFLAGVARIABLE(LuauOkWithIteratingOverTableProperties, false) +LUAU_FASTFLAGVARIABLE(LuauAcceptIndexingTableUnionsIntersections, false) namespace Luau { @@ -3506,54 +3507,207 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex } } - TableType* exprTable = getMutableTableType(exprType); - - if (!exprTable) + if (FFlag::LuauAcceptIndexingTableUnionsIntersections) { - reportError(TypeError{expr.expr->location, NotATable{exprType}}); - return errorRecoveryType(scope); - } + // We're going to have a whole vector. + std::vector tableTypes{}; + bool isUnion = true; - if (value) - { - const auto& it = exprTable->props.find(value->value.data); - if (it != exprTable->props.end()) + // We'd like for normalization eventually to deal with this sort of thing, but as a tactical affordance, we will + // attempt to deal with _one_ level of unions or intersections. + if (auto exprUnion = get(exprType)) { - return it->second.type(); + tableTypes.reserve(exprUnion->options.size()); + + for (auto option : exprUnion) + { + TableType* optionTable = getMutableTableType(option); + + if (!optionTable) + { + // TODO: we could do better here and report `option` is not a table as reasoning for the error + reportError(TypeError{expr.expr->location, NotATable{exprType}}); + return errorRecoveryType(scope); + } + + tableTypes.push_back(optionTable); + } } - else if ((ctx == ValueContext::LValue && exprTable->state == TableState::Unsealed) || exprTable->state == TableState::Free) + else if (auto exprIntersection = get(exprType)) { - TypeId resultType = freshType(scope); - Property& property = exprTable->props[value->value.data]; - property.setType(resultType); - property.location = expr.index->location; - return resultType; + tableTypes.reserve(exprIntersection->parts.size()); + isUnion = false; + + for (auto part : exprIntersection) + { + TableType* partTable = getMutableTableType(part); + + if (!partTable) + { + // TODO: we could do better here and report `part` is not a table as reasoning for the error + reportError(TypeError{expr.expr->location, NotATable{exprType}}); + return errorRecoveryType(scope); + } + + tableTypes.push_back(partTable); + } + } + else if (auto exprTable = getMutableTableType(exprType)) + { + tableTypes.push_back(exprTable); + } + else + { + reportError(TypeError{expr.expr->location, NotATable{exprType}}); + return errorRecoveryType(scope); } - } - if (exprTable->indexer) - { - const TableIndexer& indexer = *exprTable->indexer; - unify(indexType, indexer.indexType, scope, expr.index->location); - return indexer.indexResultType; - } - else if ((ctx == ValueContext::LValue && exprTable->state == TableState::Unsealed) || exprTable->state == TableState::Free) - { - TypeId indexerType = freshType(exprTable->level); - unify(indexType, indexerType, scope, expr.location); - TypeId indexResultType = freshType(exprTable->level); + if (value) + { + DenseHashSet propTypes{{}}; - exprTable->indexer = TableIndexer{anyIfNonstrict(indexerType), anyIfNonstrict(indexResultType)}; - return indexResultType; + for (auto table : tableTypes) + { + const auto& it = table->props.find(value->value.data); + if (it != table->props.end()) + { + propTypes.insert(it->second.type()); + } + else if ((ctx == ValueContext::LValue && table->state == TableState::Unsealed) || table->state == TableState::Free) + { + TypeId resultType = freshType(scope); + Property& property = table->props[value->value.data]; + property.setType(resultType); + property.location = expr.index->location; + propTypes.insert(resultType); + } + } + + if (propTypes.size() == 1) + return *propTypes.begin(); + + if (!propTypes.empty()) + { + if (isUnion) + { + std::vector options = reduceUnion({propTypes.begin(), propTypes.end()}); + + if (options.empty()) + return neverType; + + if (options.size() == 1) + return options[0]; + + return addType(UnionType{options}); + } + + return addType(IntersectionType{{propTypes.begin(), propTypes.end()}}); + } + } + + DenseHashSet resultTypes{{}}; + + for (auto table : tableTypes) + { + if (table->indexer) + { + const TableIndexer& indexer = *table->indexer; + unify(indexType, indexer.indexType, scope, expr.index->location); + resultTypes.insert(indexer.indexResultType); + } + else if ((ctx == ValueContext::LValue && table->state == TableState::Unsealed) || table->state == TableState::Free) + { + TypeId indexerType = freshType(table->level); + unify(indexType, indexerType, scope, expr.location); + TypeId indexResultType = freshType(table->level); + + table->indexer = TableIndexer{anyIfNonstrict(indexerType), anyIfNonstrict(indexResultType)}; + resultTypes.insert(indexResultType); + } + else + { + /* + * If we use [] indexing to fetch a property from a sealed table that + * has no indexer, we have no idea if it will work so we just return any + * and hope for the best. + */ + + // if this is a union, it's going to be equivalent to `any` no matter what at this point, so we'll just call it done. + if (isUnion) + return anyType; + + resultTypes.insert(anyType); + } + } + + if (resultTypes.size() == 1) + return *resultTypes.begin(); + + if (isUnion) + { + std::vector options = reduceUnion({resultTypes.begin(), resultTypes.end()}); + + if (options.empty()) + return neverType; + + if (options.size() == 1) + return options[0]; + + return addType(UnionType{options}); + } + + return addType(IntersectionType{{resultTypes.begin(), resultTypes.end()}}); } else { - /* - * If we use [] indexing to fetch a property from a sealed table that - * has no indexer, we have no idea if it will work so we just return any - * and hope for the best. - */ - return anyType; + TableType* exprTable = getMutableTableType(exprType); + if (!exprTable) + { + reportError(TypeError{expr.expr->location, NotATable{exprType}}); + return errorRecoveryType(scope); + } + + if (value) + { + const auto& it = exprTable->props.find(value->value.data); + if (it != exprTable->props.end()) + { + return it->second.type(); + } + else if ((ctx == ValueContext::LValue && exprTable->state == TableState::Unsealed) || exprTable->state == TableState::Free) + { + TypeId resultType = freshType(scope); + Property& property = exprTable->props[value->value.data]; + property.setType(resultType); + property.location = expr.index->location; + return resultType; + } + } + + if (exprTable->indexer) + { + const TableIndexer& indexer = *exprTable->indexer; + unify(indexType, indexer.indexType, scope, expr.index->location); + return indexer.indexResultType; + } + else if ((ctx == ValueContext::LValue && exprTable->state == TableState::Unsealed) || exprTable->state == TableState::Free) + { + TypeId indexerType = freshType(exprTable->level); + unify(indexType, indexerType, scope, expr.location); + TypeId indexResultType = freshType(exprTable->level); + + exprTable->indexer = TableIndexer{anyIfNonstrict(indexerType), anyIfNonstrict(indexResultType)}; + return indexResultType; + } + else + { + /* + * If we use [] indexing to fetch a property from a sealed table that + * has no indexer, we have no idea if it will work so we just return any + * and hope for the best. + */ + return anyType; + } } } diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index b40805e9..ac265cc6 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -301,6 +301,8 @@ TypePack extendTypePack( TypePack newPack; newPack.tail = arena.freshTypePack(ftp->scope); + if (FFlag::DebugLuauDeferredConstraintResolution) + result.tail = newPack.tail; size_t overridesIndex = 0; while (result.head.size() < length) { diff --git a/CodeGen/include/Luau/IrVisitUseDef.h b/CodeGen/include/Luau/IrVisitUseDef.h index 6744bd65..39dd6cf8 100644 --- a/CodeGen/include/Luau/IrVisitUseDef.h +++ b/CodeGen/include/Luau/IrVisitUseDef.h @@ -4,8 +4,6 @@ #include "Luau/Common.h" #include "Luau/IrData.h" -LUAU_FASTFLAG(LuauCodegenFastcall3) - namespace Luau { namespace CodeGen @@ -113,47 +111,16 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i break; case IrCmd::FASTCALL: - if (FFlag::LuauCodegenFastcall3) - { - visitor.use(inst.c); + visitor.use(inst.c); - if (int nresults = function.intOp(inst.d); nresults != -1) - visitor.defRange(vmRegOp(inst.b), nresults); - } - else - { - if (int count = function.intOp(inst.e); count != -1) - { - if (count >= 3) - { - CODEGEN_ASSERT(inst.d.kind == IrOpKind::VmReg && vmRegOp(inst.d) == vmRegOp(inst.c) + 1); - - visitor.useRange(vmRegOp(inst.c), count); - } - else - { - if (count >= 1) - visitor.use(inst.c); - - if (count >= 2) - visitor.maybeUse(inst.d); // Argument can also be a VmConst - } - } - else - { - visitor.useVarargs(vmRegOp(inst.c)); - } - - // Multiple return sequences (count == -1) are defined by ADJUST_STACK_TO_REG - if (int count = function.intOp(inst.f); count != -1) - visitor.defRange(vmRegOp(inst.b), count); - } + if (int nresults = function.intOp(inst.d); nresults != -1) + visitor.defRange(vmRegOp(inst.b), nresults); break; case IrCmd::INVOKE_FASTCALL: - if (int count = function.intOp(FFlag::LuauCodegenFastcall3 ? inst.f : inst.e); count != -1) + if (int count = function.intOp(inst.f); count != -1) { // Only LOP_FASTCALL3 lowering is allowed to have third optional argument - if (count >= 3 && (!FFlag::LuauCodegenFastcall3 || inst.e.kind == IrOpKind::Undef)) + if (count >= 3 && inst.e.kind == IrOpKind::Undef) { CODEGEN_ASSERT(inst.d.kind == IrOpKind::VmReg && vmRegOp(inst.d) == vmRegOp(inst.c) + 1); @@ -167,7 +134,7 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i if (count >= 2) visitor.maybeUse(inst.d); // Argument can also be a VmConst - if (FFlag::LuauCodegenFastcall3 && count >= 3) + if (count >= 3) visitor.maybeUse(inst.e); // Argument can also be a VmConst } } @@ -177,7 +144,7 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i } // Multiple return sequences (count == -1) are defined by ADJUST_STACK_TO_REG - if (int count = function.intOp(FFlag::LuauCodegenFastcall3 ? inst.g : inst.f); count != -1) + if (int count = function.intOp(inst.g); count != -1) visitor.defRange(vmRegOp(inst.b), count); break; case IrCmd::FORGLOOP: diff --git a/CodeGen/src/BytecodeAnalysis.cpp b/CodeGen/src/BytecodeAnalysis.cpp index 01adea34..8d2efebe 100644 --- a/CodeGen/src/BytecodeAnalysis.cpp +++ b/CodeGen/src/BytecodeAnalysis.cpp @@ -11,8 +11,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauCodegenFastcall3, false) - namespace Luau { namespace CodeGen @@ -1101,8 +1099,6 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) } case LOP_FASTCALL3: { - CODEGEN_ASSERT(FFlag::LuauCodegenFastcall3); - int bfid = LUAU_INSN_A(*pc); int skip = LUAU_INSN_C(*pc); int aux = pc[1]; diff --git a/CodeGen/src/CodeGenAssembly.cpp b/CodeGen/src/CodeGenAssembly.cpp index 295e1cb8..c423a1ce 100644 --- a/CodeGen/src/CodeGenAssembly.cpp +++ b/CodeGen/src/CodeGenAssembly.cpp @@ -149,7 +149,10 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A Proto* root = clvalue(func)->l.p; if ((options.compilationOptions.flags & CodeGen_OnlyNativeModules) != 0 && (root->flags & LPF_NATIVE_MODULE) == 0) + { + build.finalize(); return std::string(); + } std::vector protos; if (FFlag::LuauNativeAttribute) @@ -174,7 +177,7 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A if (protos.empty()) { - build.finalize(); // to avoid assertion in AssemblyBuilder dtor + build.finalize(); return std::string(); } diff --git a/CodeGen/src/EmitBuiltinsX64.cpp b/CodeGen/src/EmitBuiltinsX64.cpp index 15aab4b6..bee59908 100644 --- a/CodeGen/src/EmitBuiltinsX64.cpp +++ b/CodeGen/src/EmitBuiltinsX64.cpp @@ -12,8 +12,6 @@ #include "lstate.h" -LUAU_FASTFLAG(LuauCodegenMathSign) - namespace Luau { namespace CodeGen @@ -57,36 +55,6 @@ static void emitBuiltinMathModf(IrRegAllocX64& regs, AssemblyBuilderX64& build, } } -static void emitBuiltinMathSign(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int arg) -{ - CODEGEN_ASSERT(!FFlag::LuauCodegenMathSign); - - ScopedRegX64 tmp0{regs, SizeX64::xmmword}; - ScopedRegX64 tmp1{regs, SizeX64::xmmword}; - ScopedRegX64 tmp2{regs, SizeX64::xmmword}; - ScopedRegX64 tmp3{regs, SizeX64::xmmword}; - - build.vmovsd(tmp0.reg, luauRegValue(arg)); - build.vxorpd(tmp1.reg, tmp1.reg, tmp1.reg); - - // Set tmp2 to -1 if arg < 0, else 0 - build.vcmpltsd(tmp2.reg, tmp0.reg, tmp1.reg); - build.vmovsd(tmp3.reg, build.f64(-1)); - build.vandpd(tmp2.reg, tmp2.reg, tmp3.reg); - - // Set mask bit to 1 if 0 < arg, else 0 - build.vcmpltsd(tmp0.reg, tmp1.reg, tmp0.reg); - - // Result = (mask-bit == 1) ? 1.0 : tmp2 - // If arg < 0 then tmp2 is -1 and mask-bit is 0, result is -1 - // If arg == 0 then tmp2 is 0 and mask-bit is 0, result is 0 - // If arg > 0 then tmp2 is 0 and mask-bit is 1, result is 1 - build.vblendvpd(tmp0.reg, tmp2.reg, build.f64x2(1, 1), tmp0.reg); - - build.vmovsd(luauRegValue(ra), tmp0.reg); - build.mov(luauRegTag(ra), LUA_TNUMBER); -} - void emitBuiltin(IrRegAllocX64& regs, AssemblyBuilderX64& build, int bfid, int ra, int arg, int nresults) { switch (bfid) @@ -97,10 +65,6 @@ void emitBuiltin(IrRegAllocX64& regs, AssemblyBuilderX64& build, int bfid, int r case LBF_MATH_MODF: CODEGEN_ASSERT(nresults == 1 || nresults == 2); return emitBuiltinMathModf(regs, build, ra, arg, nresults); - case LBF_MATH_SIGN: - CODEGEN_ASSERT(!FFlag::LuauCodegenMathSign); - CODEGEN_ASSERT(nresults == 1); - return emitBuiltinMathSign(regs, build, ra, arg); default: CODEGEN_ASSERT(!"Missing x64 lowering"); } diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index 53def728..3e6f85d8 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -13,8 +13,6 @@ #include -LUAU_FASTFLAG(LuauCodegenFastcall3) - namespace Luau { namespace CodeGen @@ -458,8 +456,6 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) handleFastcallFallback(translateFastCallN(*this, pc, i, true, 2, vmConst(pc[1]), undef()), pc, i); break; case LOP_FASTCALL3: - CODEGEN_ASSERT(FFlag::LuauCodegenFastcall3); - handleFastcallFallback(translateFastCallN(*this, pc, i, true, 3, vmReg(pc[1] & 0xff), vmReg((pc[1] >> 8) & 0xff)), pc, i); break; case LOP_FORNPREP: diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index f40faa2d..cd73bcbb 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -11,8 +11,7 @@ #include "lstate.h" #include "lgc.h" -LUAU_FASTFLAG(LuauCodegenFastcall3) -LUAU_FASTFLAG(LuauCodegenMathSign) +LUAU_FASTFLAGVARIABLE(LuauCodegenArmNumToVecFix, false) namespace Luau { @@ -235,25 +234,6 @@ static bool emitBuiltin(AssemblyBuilderA64& build, IrFunction& function, IrRegAl } return true; } - case LBF_MATH_SIGN: - { - CODEGEN_ASSERT(!FFlag::LuauCodegenMathSign); - CODEGEN_ASSERT(nresults == 1); - build.ldr(d0, mem(rBase, arg * sizeof(TValue) + offsetof(TValue, value.n))); - build.fcmpz(d0); - build.fmov(d0, 0.0); - build.fmov(d1, 1.0); - build.fcsel(d0, d1, d0, getConditionFP(IrCondition::Greater)); - build.fmov(d1, -1.0); - build.fcsel(d0, d1, d0, getConditionFP(IrCondition::Less)); - build.str(d0, mem(rBase, res * sizeof(TValue) + offsetof(TValue, value.n))); - - RegisterA64 temp = regs.allocTemp(KindA64::w); - build.mov(temp, LUA_TNUMBER); - build.str(temp, mem(rBase, res * sizeof(TValue) + offsetof(TValue, tt))); - - return true; - } default: CODEGEN_ASSERT(!"Missing A64 lowering"); @@ -701,8 +681,6 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } case IrCmd::SIGN_NUM: { - CODEGEN_ASSERT(FFlag::LuauCodegenMathSign); - inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a}); RegisterA64 temp = tempDouble(inst.a); @@ -1143,7 +1121,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) else { RegisterA64 tempd = tempDouble(inst.a); - RegisterA64 temps = castReg(KindA64::s, tempd); + RegisterA64 temps = FFlag::LuauCodegenArmNumToVecFix ? regs.allocTemp(KindA64::s) : castReg(KindA64::s, tempd); build.fcvt(temps, tempd); build.dup_4s(inst.regA64, castReg(KindA64::q, temps), 0); @@ -1194,88 +1172,54 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) case IrCmd::FASTCALL: regs.spill(build, index); - if (FFlag::LuauCodegenFastcall3) - error |= !emitBuiltin(build, function, regs, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d)); - else - error |= !emitBuiltin(build, function, regs, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.f)); - + error |= !emitBuiltin(build, function, regs, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d)); break; case IrCmd::INVOKE_FASTCALL: { - if (FFlag::LuauCodegenFastcall3) + // We might need a temporary and we have to preserve it over the spill + RegisterA64 temp = regs.allocTemp(KindA64::q); + regs.spill(build, index, {temp}); + + build.mov(x0, rState); + build.add(x1, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue))); + build.add(x2, rBase, uint16_t(vmRegOp(inst.c) * sizeof(TValue))); + build.mov(w3, intOp(inst.g)); // nresults + + // 'E' argument can only be produced by LOP_FASTCALL3 lowering + if (inst.e.kind != IrOpKind::Undef) { - // We might need a temporary and we have to preserve it over the spill - RegisterA64 temp = regs.allocTemp(KindA64::q); - regs.spill(build, index, {temp}); + CODEGEN_ASSERT(intOp(inst.f) == 3); - build.mov(x0, rState); - build.add(x1, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue))); - build.add(x2, rBase, uint16_t(vmRegOp(inst.c) * sizeof(TValue))); - build.mov(w3, intOp(inst.g)); // nresults + build.ldr(x4, mem(rState, offsetof(lua_State, top))); - // 'E' argument can only be produced by LOP_FASTCALL3 lowering - if (inst.e.kind != IrOpKind::Undef) - { - CODEGEN_ASSERT(intOp(inst.f) == 3); + build.ldr(temp, mem(rBase, vmRegOp(inst.d) * sizeof(TValue))); + build.str(temp, mem(x4, 0)); - build.ldr(x4, mem(rState, offsetof(lua_State, top))); - - build.ldr(temp, mem(rBase, vmRegOp(inst.d) * sizeof(TValue))); - build.str(temp, mem(x4, 0)); - - build.ldr(temp, mem(rBase, vmRegOp(inst.e) * sizeof(TValue))); - build.str(temp, mem(x4, sizeof(TValue))); - } - else - { - if (inst.d.kind == IrOpKind::VmReg) - build.add(x4, rBase, uint16_t(vmRegOp(inst.d) * sizeof(TValue))); - else if (inst.d.kind == IrOpKind::VmConst) - emitAddOffset(build, x4, rConstants, vmConstOp(inst.d) * sizeof(TValue)); - else - CODEGEN_ASSERT(inst.d.kind == IrOpKind::Undef); - } - - // nparams - if (intOp(inst.f) == LUA_MULTRET) - { - // L->top - (ra + 1) - build.ldr(x5, mem(rState, offsetof(lua_State, top))); - build.sub(x5, x5, rBase); - build.sub(x5, x5, uint16_t((vmRegOp(inst.b) + 1) * sizeof(TValue))); - build.lsr(x5, x5, kTValueSizeLog2); - } - else - build.mov(w5, intOp(inst.f)); + build.ldr(temp, mem(rBase, vmRegOp(inst.e) * sizeof(TValue))); + build.str(temp, mem(x4, sizeof(TValue))); } else { - regs.spill(build, index); - build.mov(x0, rState); - build.add(x1, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue))); - build.add(x2, rBase, uint16_t(vmRegOp(inst.c) * sizeof(TValue))); - build.mov(w3, intOp(inst.f)); // nresults - if (inst.d.kind == IrOpKind::VmReg) build.add(x4, rBase, uint16_t(vmRegOp(inst.d) * sizeof(TValue))); else if (inst.d.kind == IrOpKind::VmConst) emitAddOffset(build, x4, rConstants, vmConstOp(inst.d) * sizeof(TValue)); else CODEGEN_ASSERT(inst.d.kind == IrOpKind::Undef); - - // nparams - if (intOp(inst.e) == LUA_MULTRET) - { - // L->top - (ra + 1) - build.ldr(x5, mem(rState, offsetof(lua_State, top))); - build.sub(x5, x5, rBase); - build.sub(x5, x5, uint16_t((vmRegOp(inst.b) + 1) * sizeof(TValue))); - build.lsr(x5, x5, kTValueSizeLog2); - } - else - build.mov(w5, intOp(inst.e)); } + // nparams + if (intOp(inst.f) == LUA_MULTRET) + { + // L->top - (ra + 1) + build.ldr(x5, mem(rState, offsetof(lua_State, top))); + build.sub(x5, x5, rBase); + build.sub(x5, x5, uint16_t((vmRegOp(inst.b) + 1) * sizeof(TValue))); + build.lsr(x5, x5, kTValueSizeLog2); + } + else + build.mov(w5, intOp(inst.f)); + build.ldr(x6, mem(rNativeContext, offsetof(NativeContext, luauF_table) + uintOp(inst.a) * sizeof(luau_FastFunction))); build.blr(x6); diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 76d88955..d06cef13 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -15,9 +15,6 @@ #include "lstate.h" #include "lgc.h" -LUAU_FASTFLAG(LuauCodegenFastcall3) -LUAU_FASTFLAG(LuauCodegenMathSign) - namespace Luau { namespace CodeGen @@ -596,8 +593,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) break; case IrCmd::SIGN_NUM: { - CODEGEN_ASSERT(FFlag::LuauCodegenMathSign); - inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a}); ScopedRegX64 tmp0{regs, SizeX64::xmmword}; @@ -1038,10 +1033,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) case IrCmd::FASTCALL: { - if (FFlag::LuauCodegenFastcall3) - emitBuiltin(regs, build, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d)); - else - emitBuiltin(regs, build, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.f)); + emitBuiltin(regs, build, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d)); break; } case IrCmd::INVOKE_FASTCALL: @@ -1052,7 +1044,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) ScopedRegX64 argsAlt{regs}; // 'E' argument can only be produced by LOP_FASTCALL3 - if (FFlag::LuauCodegenFastcall3 && inst.e.kind != IrOpKind::Undef) + if (inst.e.kind != IrOpKind::Undef) { CODEGEN_ASSERT(intOp(inst.f) == 3); @@ -1079,8 +1071,8 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) int ra = vmRegOp(inst.b); int arg = vmRegOp(inst.c); - int nparams = intOp(FFlag::LuauCodegenFastcall3 ? inst.f : inst.e); - int nresults = intOp(FFlag::LuauCodegenFastcall3 ? inst.g : inst.f); + int nparams = intOp(inst.f); + int nresults = intOp(inst.g); IrCallWrapperX64 callWrap(regs, build, index); callWrap.addArgument(SizeX64::qword, rState); @@ -1088,7 +1080,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) callWrap.addArgument(SizeX64::qword, luauRegAddress(arg)); callWrap.addArgument(SizeX64::dword, nresults); - if (FFlag::LuauCodegenFastcall3 && inst.e.kind != IrOpKind::Undef) + if (inst.e.kind != IrOpKind::Undef) callWrap.addArgument(SizeX64::qword, argsAlt); else callWrap.addArgument(SizeX64::qword, args); diff --git a/CodeGen/src/IrTranslateBuiltins.cpp b/CodeGen/src/IrTranslateBuiltins.cpp index 7abab9f0..52efaef1 100644 --- a/CodeGen/src/IrTranslateBuiltins.cpp +++ b/CodeGen/src/IrTranslateBuiltins.cpp @@ -8,9 +8,6 @@ #include -LUAU_FASTFLAG(LuauCodegenFastcall3) -LUAU_FASTFLAGVARIABLE(LuauCodegenMathSign, false) - // TODO: when nresults is less than our actual result count, we can skip computing/writing unused results static const int kMinMaxUnrolledParams = 5; @@ -39,33 +36,6 @@ static IrOp builtinLoadDouble(IrBuilder& build, IrOp arg) // Wrapper code for all builtins with a fixed signature and manual assembly lowering of the body -// (number, ...) -> number -static BuiltinImplResult translateBuiltinNumberToNumber( - IrBuilder& build, - LuauBuiltinFunction bfid, - int nparams, - int ra, - int arg, - IrOp args, - int nresults, - int pcpos -) -{ - CODEGEN_ASSERT(!FFlag::LuauCodegenMathSign); - - if (nparams < 1 || nresults > 1) - return {BuiltinImplType::None, -1}; - - builtinCheckDouble(build, build.vmReg(arg), pcpos); - - if (FFlag::LuauCodegenFastcall3) - build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), build.constInt(1)); - else - build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), args, build.constInt(1), build.constInt(1)); - - return {BuiltinImplType::Full, 1}; -} - static BuiltinImplResult translateBuiltinNumberToNumberLibm( IrBuilder& build, LuauBuiltinFunction bfid, @@ -142,18 +112,7 @@ static BuiltinImplResult translateBuiltinNumberTo2Number( builtinCheckDouble(build, build.vmReg(arg), pcpos); - if (FFlag::LuauCodegenFastcall3) - build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), build.constInt(nresults == 1 ? 1 : 2)); - else - build.inst( - IrCmd::FASTCALL, - build.constUint(bfid), - build.vmReg(ra), - build.vmReg(arg), - build.undef(), - build.constInt(1), - build.constInt(nresults == 1 ? 1 : 2) - ); + build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), build.constInt(nresults == 1 ? 1 : 2)); return {BuiltinImplType::Full, 2}; } @@ -250,10 +209,10 @@ static BuiltinImplResult translateBuiltinMathMinMax( builtinCheckDouble(build, build.vmReg(arg), pcpos); builtinCheckDouble(build, args, pcpos); - if (FFlag::LuauCodegenFastcall3 && nparams >= 3) + if (nparams >= 3) builtinCheckDouble(build, arg3, pcpos); - for (int i = (FFlag::LuauCodegenFastcall3 ? 4 : 3); i <= nparams; ++i) + for (int i = 4; i <= nparams; ++i) builtinCheckDouble(build, build.vmReg(vmRegOp(args) + (i - 2)), pcpos); IrOp varg1 = builtinLoadDouble(build, build.vmReg(arg)); @@ -261,13 +220,13 @@ static BuiltinImplResult translateBuiltinMathMinMax( IrOp res = build.inst(cmd, varg2, varg1); // Swapped arguments are required for consistency with VM builtins - if (FFlag::LuauCodegenFastcall3 && nparams >= 3) + if (nparams >= 3) { IrOp arg = builtinLoadDouble(build, arg3); res = build.inst(cmd, arg, res); } - for (int i = (FFlag::LuauCodegenFastcall3 ? 4 : 3); i <= nparams; ++i) + for (int i = 4; i <= nparams; ++i) { IrOp arg = builtinLoadDouble(build, build.vmReg(vmRegOp(args) + (i - 2))); res = build.inst(cmd, arg, res); @@ -302,10 +261,10 @@ static BuiltinImplResult translateBuiltinMathClamp( builtinCheckDouble(build, build.vmReg(arg), pcpos); builtinCheckDouble(build, args, pcpos); - builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1), pcpos); + builtinCheckDouble(build, arg3, pcpos); IrOp min = builtinLoadDouble(build, args); - IrOp max = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1)); + IrOp max = builtinLoadDouble(build, arg3); build.inst(IrCmd::JUMP_CMP_NUM, min, max, build.cond(IrCondition::NotLessEqual), fallback, block); build.beginBlock(block); @@ -386,10 +345,10 @@ static BuiltinImplResult translateBuiltinBit32BinaryOp( builtinCheckDouble(build, build.vmReg(arg), pcpos); builtinCheckDouble(build, args, pcpos); - if (FFlag::LuauCodegenFastcall3 && nparams >= 3) + if (nparams >= 3) builtinCheckDouble(build, arg3, pcpos); - for (int i = (FFlag::LuauCodegenFastcall3 ? 4 : 3); i <= nparams; ++i) + for (int i = 4; i <= nparams; ++i) builtinCheckDouble(build, build.vmReg(vmRegOp(args) + (i - 2)), pcpos); IrOp va = builtinLoadDouble(build, build.vmReg(arg)); @@ -400,7 +359,7 @@ static BuiltinImplResult translateBuiltinBit32BinaryOp( IrOp res = build.inst(cmd, vaui, vbui); - if (FFlag::LuauCodegenFastcall3 && nparams >= 3) + if (nparams >= 3) { IrOp vc = builtinLoadDouble(build, arg3); IrOp arg = build.inst(IrCmd::NUM_TO_UINT, vc); @@ -408,7 +367,7 @@ static BuiltinImplResult translateBuiltinBit32BinaryOp( res = build.inst(cmd, res, arg); } - for (int i = (FFlag::LuauCodegenFastcall3 ? 4 : 3); i <= nparams; ++i) + for (int i = 4; i <= nparams; ++i) { IrOp vc = builtinLoadDouble(build, build.vmReg(vmRegOp(args) + (i - 2))); IrOp arg = build.inst(IrCmd::NUM_TO_UINT, vc); @@ -599,8 +558,8 @@ static BuiltinImplResult translateBuiltinBit32Extract( { IrOp f = build.inst(IrCmd::NUM_TO_INT, vb); - builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(args.index + 1), pcpos); - IrOp vc = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(args.index + 1)); + builtinCheckDouble(build, arg3, pcpos); + IrOp vc = builtinLoadDouble(build, arg3); IrOp w = build.inst(IrCmd::NUM_TO_INT, vc); IrOp block1 = build.block(IrBlockKind::Internal); @@ -705,11 +664,11 @@ static BuiltinImplResult translateBuiltinBit32Replace( builtinCheckDouble(build, build.vmReg(arg), pcpos); builtinCheckDouble(build, args, pcpos); - builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(args.index + 1), pcpos); + builtinCheckDouble(build, arg3, pcpos); IrOp va = builtinLoadDouble(build, build.vmReg(arg)); IrOp vb = builtinLoadDouble(build, args); - IrOp vc = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(args.index + 1)); + IrOp vc = builtinLoadDouble(build, arg3); IrOp n = build.inst(IrCmd::NUM_TO_UINT, va); IrOp v = build.inst(IrCmd::NUM_TO_UINT, vb); @@ -734,8 +693,8 @@ static BuiltinImplResult translateBuiltinBit32Replace( } else { - builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? build.vmReg(vmRegOp(args) + 2) : build.vmReg(args.index + 2), pcpos); - IrOp vd = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? build.vmReg(vmRegOp(args) + 2) : build.vmReg(args.index + 2)); + builtinCheckDouble(build, build.vmReg(vmRegOp(args) + 2), pcpos); + IrOp vd = builtinLoadDouble(build, build.vmReg(vmRegOp(args) + 2)); IrOp w = build.inst(IrCmd::NUM_TO_INT, vd); IrOp block1 = build.block(IrBlockKind::Internal); @@ -781,11 +740,11 @@ static BuiltinImplResult translateBuiltinVector(IrBuilder& build, int nparams, i builtinCheckDouble(build, build.vmReg(arg), pcpos); builtinCheckDouble(build, args, pcpos); - builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1), pcpos); + builtinCheckDouble(build, arg3, pcpos); IrOp x = builtinLoadDouble(build, build.vmReg(arg)); IrOp y = builtinLoadDouble(build, args); - IrOp z = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1)); + IrOp z = builtinLoadDouble(build, arg3); build.inst(IrCmd::STORE_VECTOR, build.vmReg(ra), x, y, z); build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TVECTOR)); @@ -863,7 +822,7 @@ static void translateBufferArgsAndCheckBounds( builtinCheckDouble(build, args, pcpos); if (nparams == 3) - builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1), pcpos); + builtinCheckDouble(build, arg3, pcpos); buf = build.inst(IrCmd::LOAD_POINTER, build.vmReg(arg)); @@ -920,7 +879,7 @@ static BuiltinImplResult translateBuiltinBufferWrite( IrOp buf, intIndex; translateBufferArgsAndCheckBounds(build, nparams, arg, args, arg3, size, pcpos, buf, intIndex); - IrOp numValue = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1)); + IrOp numValue = builtinLoadDouble(build, arg3); build.inst(writeCmd, buf, intIndex, convCmd == IrCmd::NOP ? numValue : build.inst(convCmd, numValue)); return {BuiltinImplType::Full, 0}; @@ -982,10 +941,7 @@ BuiltinImplResult translateBuiltin( case LBF_MATH_LOG10: return translateBuiltinNumberToNumberLibm(build, LuauBuiltinFunction(bfid), nparams, ra, arg, nresults, pcpos); case LBF_MATH_SIGN: - if (FFlag::LuauCodegenMathSign) - return translateBuiltinMathUnary(build, IrCmd::SIGN_NUM, nparams, ra, arg, nresults, pcpos); - else - return translateBuiltinNumberToNumber(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos); + return translateBuiltinMathUnary(build, IrCmd::SIGN_NUM, nparams, ra, arg, nresults, pcpos); case LBF_MATH_POW: case LBF_MATH_FMOD: case LBF_MATH_ATAN2: diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 5d6aa95a..62829766 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -13,8 +13,6 @@ #include "lstate.h" #include "ltm.h" -LUAU_FASTFLAG(LuauCodegenFastcall3) - namespace Luau { namespace CodeGen @@ -767,7 +765,7 @@ IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool builtinArgs = build.constDouble(protok.value.n); } - IrOp builtinArg3 = FFlag::LuauCodegenFastcall3 ? (customParams ? customArg3 : build.vmReg(ra + 3)) : IrOp{}; + IrOp builtinArg3 = customParams ? customArg3 : build.vmReg(ra + 3); IrOp fallback = build.block(IrBlockKind::Fallback); @@ -793,7 +791,7 @@ IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool return build.undef(); } } - else if (FFlag::LuauCodegenFastcall3) + else { IrOp arg3 = customParams ? customArg3 : build.undef(); @@ -817,21 +815,6 @@ IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool else if (nparams == LUA_MULTRET) build.inst(IrCmd::ADJUST_STACK_TO_TOP); } - else - { - // TODO: we can skip saving pc for some well-behaved builtins which we didn't inline - build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + getOpLength(opcode))); - - IrOp res = build.inst( - IrCmd::INVOKE_FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), args, build.constInt(nparams), build.constInt(nresults) - ); - build.inst(IrCmd::CHECK_FASTCALL_RES, res, fallback); - - if (nresults == LUA_MULTRET) - build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(ra), res); - else if (nparams == LUA_MULTRET) - build.inst(IrCmd::ADJUST_STACK_TO_TOP); - } return fallback; } diff --git a/CodeGen/src/IrValueLocationTracking.cpp b/CodeGen/src/IrValueLocationTracking.cpp index 0224b49b..050b951e 100644 --- a/CodeGen/src/IrValueLocationTracking.cpp +++ b/CodeGen/src/IrValueLocationTracking.cpp @@ -3,8 +3,6 @@ #include "Luau/IrUtils.h" -LUAU_FASTFLAG(LuauCodegenFastcall3) - namespace Luau { namespace CodeGen @@ -46,11 +44,11 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst) invalidateRestoreVmRegs(vmRegOp(inst.a), -1); break; case IrCmd::FASTCALL: - invalidateRestoreVmRegs(vmRegOp(inst.b), function.intOp(FFlag::LuauCodegenFastcall3 ? inst.d : inst.f)); + invalidateRestoreVmRegs(vmRegOp(inst.b), function.intOp(inst.d)); break; case IrCmd::INVOKE_FASTCALL: // Multiple return sequences (count == -1) are defined by ADJUST_STACK_TO_REG - if (int count = function.intOp(FFlag::LuauCodegenFastcall3 ? inst.g : inst.f); count != -1) + if (int count = function.intOp(inst.g); count != -1) invalidateRestoreVmRegs(vmRegOp(inst.b), count); break; case IrCmd::DO_ARITH: diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index e7207731..f3271d3f 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -18,8 +18,6 @@ LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3) LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64) LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64) LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks, false) -LUAU_FASTFLAG(LuauCodegenFastcall3) -LUAU_FASTFLAG(LuauCodegenMathSign) namespace Luau { @@ -1119,7 +1117,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& { LuauBuiltinFunction bfid = LuauBuiltinFunction(function.uintOp(inst.a)); int firstReturnReg = vmRegOp(inst.b); - int nresults = function.intOp(FFlag::LuauCodegenFastcall3 ? inst.d : inst.f); + int nresults = function.intOp(inst.d); // TODO: FASTCALL is more restrictive than INVOKE_FASTCALL; we should either determine the exact semantics, or rework it handleBuiltinEffects(state, bfid, firstReturnReg, nresults); @@ -1133,19 +1131,13 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& if (nresults > 1) state.updateTag(IrOp{IrOpKind::VmReg, uint8_t(firstReturnReg + 1)}, LUA_TNUMBER); break; - case LBF_MATH_SIGN: - CODEGEN_ASSERT(!FFlag::LuauCodegenMathSign); - state.updateTag(IrOp{IrOpKind::VmReg, uint8_t(firstReturnReg)}, LUA_TNUMBER); - break; default: break; } break; } case IrCmd::INVOKE_FASTCALL: - handleBuiltinEffects( - state, LuauBuiltinFunction(function.uintOp(inst.a)), vmRegOp(inst.b), function.intOp(FFlag::LuauCodegenFastcall3 ? inst.g : inst.f) - ); + handleBuiltinEffects(state, LuauBuiltinFunction(function.uintOp(inst.a)), vmRegOp(inst.b), function.intOp(inst.g)); break; // These instructions don't have an effect on register/memory state we are tracking diff --git a/Common/include/Luau/ExperimentalFlags.h b/Common/include/Luau/ExperimentalFlags.h index f83de70b..2541a965 100644 --- a/Common/include/Luau/ExperimentalFlags.h +++ b/Common/include/Luau/ExperimentalFlags.h @@ -13,7 +13,7 @@ inline bool isFlagExperimental(const char* flag) static const char* const kList[] = { "LuauInstantiateInSubtyping", // requires some fixes to lua-apps code "LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative - "StudioReportLuauAny", // takes telemetry data for usage of any types + "StudioReportLuauAny2", // takes telemetry data for usage of any types // makes sure we always have at least one entry nullptr, }; diff --git a/EqSat/include/Luau/EGraph.h b/EqSat/include/Luau/EGraph.h index 8344b0e5..480aa07d 100644 --- a/EqSat/include/Luau/EGraph.h +++ b/EqSat/include/Luau/EGraph.h @@ -5,7 +5,6 @@ #include "Luau/Id.h" #include "Luau/Language.h" #include "Luau/UnionFind.h" -#include "Luau/VecDeque.h" #include #include @@ -145,7 +144,7 @@ private: /// The hashcons 𝐻 is a map from e-nodes to e-class ids. std::unordered_map hashcons; - VecDeque> worklist; + std::vector> worklist; private: void canonicalize(L& enode) @@ -183,7 +182,7 @@ private: for (Id operand : enode.operands()) get(operand).parents.push_back({enode, id}); - worklist.push_back({enode, id}); + worklist.emplace_back(enode, id); hashcons.insert_or_assign(enode, id); return id; diff --git a/EqSat/include/Luau/Language.h b/EqSat/include/Luau/Language.h index 4aeb6c32..8855d851 100644 --- a/EqSat/include/Luau/Language.h +++ b/EqSat/include/Luau/Language.h @@ -7,7 +7,6 @@ #include "Luau/Variant.h" #include -#include #include #include @@ -233,12 +232,6 @@ struct Language final { } - Language(const Language&) noexcept = default; - Language& operator=(const Language&) noexcept = default; - - Language(Language&&) noexcept = default; - Language& operator=(Language&&) noexcept = default; - int index() const noexcept { return v.index(); diff --git a/EqSat/include/Luau/LanguageHash.h b/EqSat/include/Luau/LanguageHash.h index 7226132c..506f352b 100644 --- a/EqSat/include/Luau/LanguageHash.h +++ b/EqSat/include/Luau/LanguageHash.h @@ -3,6 +3,7 @@ #include #include +#include namespace Luau::EqSat { diff --git a/EqSat/src/UnionFind.cpp b/EqSat/src/UnionFind.cpp index 3c4825be..619c3f47 100644 --- a/EqSat/src/UnionFind.cpp +++ b/EqSat/src/UnionFind.cpp @@ -11,6 +11,7 @@ Id UnionFind::makeSet() Id id{parents.size()}; parents.push_back(id); ranks.push_back(0); + return id; } @@ -32,6 +33,7 @@ Id UnionFind::find(Id id) parents[size_t(id)] = set; id = parent; } + return set; } @@ -47,6 +49,7 @@ void UnionFind::merge(Id a, Id b) std::swap(aSet, bSet); parents[size_t(bSet)] = aSet; + if (ranks[size_t(aSet)] == ranks[size_t(bSet)]) ranks[size_t(aSet)]++; } diff --git a/Makefile b/Makefile index b86ec2a6..3e6b85ad 100644 --- a/Makefile +++ b/Makefile @@ -181,8 +181,7 @@ coverage: $(TESTS_TARGET) $(COMPILE_CLI_TARGET) mv default.profraw tests.profraw $(TESTS_TARGET) --fflags=true mv default.profraw tests-flags.profraw - # new solver is expected to fail tests at the moment, remove '!' once tests are fixed and this starts to fail - ! $(TESTS_TARGET) --fflags=true,DebugLuauDeferredConstraintResolution=true + $(TESTS_TARGET) --fflags=true,DebugLuauDeferredConstraintResolution=true mv default.profraw tests-dcr.profraw $(TESTS_TARGET) -ts=Conformance --codegen mv default.profraw codegen.profraw diff --git a/tests/AnyTypeSummary.test.cpp b/tests/AnyTypeSummary.test.cpp index aed7b7e5..24004df0 100644 --- a/tests/AnyTypeSummary.test.cpp +++ b/tests/AnyTypeSummary.test.cpp @@ -5,6 +5,7 @@ #include "Fixture.h" +#include "ScopedFlags.h" #include "doctest.h" #include @@ -14,14 +15,13 @@ using namespace Luau; using Pattern = AnyTypeSummary::Pattern; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) -LUAU_FASTFLAG(DebugLuauFreezeArena); -LUAU_FASTFLAG(DebugLuauMagicTypes); +LUAU_FASTFLAG(DebugLuauFreezeArena) +LUAU_FASTFLAG(DebugLuauMagicTypes) +LUAU_FASTFLAG(StudioReportLuauAny2) -LUAU_FASTFLAG(StudioReportLuauAny); struct ATSFixture : BuiltinsFixture { - ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true}; ATSFixture() { @@ -34,6 +34,11 @@ TEST_SUITE_BEGIN("AnyTypeSummaryTest"); TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( type A = (number, string) -> ...any )"; @@ -43,16 +48,18 @@ type A = (number, string) -> ...any ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); - LUAU_ASSERT(module->ats.typeInfo[0].node == "type A = (number, string)->( ...any)"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 1); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); + LUAU_ASSERT(module->ats.typeInfo[0].node == "type A = (number, string)->( ...any)"); } TEST_CASE_FIXTURE(ATSFixture, "export_alias") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( export type t8 = t0 &((true | any)->('')) )"; @@ -62,16 +69,18 @@ export type t8 = t0 &((true | any)->('')) ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); - LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8 = t0 &((true | any)->(''))"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 1); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); + LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8 = t0 &((true | any)->(''))"); } TEST_CASE_FIXTURE(ATSFixture, "typepacks") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( local function fallible(t: number): ...any if t > 0 then @@ -86,16 +95,18 @@ end ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 3); - LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::TypePk); - LUAU_ASSERT(module->ats.typeInfo[0].node == "local function fallible(t: number): ...any\n if t > 0 then\n return true, t\n end\n return false, 'must be positive'\nend"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 3); + LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::TypePk); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local function fallible(t: number): ...any\n if t > 0 then\n return true, t\n end\n return false, 'must be positive'\nend"); } TEST_CASE_FIXTURE(ATSFixture, "typepacks_no_ret") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( -- TODO: if partially typed, we'd want to know too local function fallible(t: number) @@ -111,14 +122,16 @@ end ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 0); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 0); } TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any_gen_table") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( type Pair = {first: T, second: any} )"; @@ -128,16 +141,18 @@ type Pair = {first: T, second: any} ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); - LUAU_ASSERT(module->ats.typeInfo[0].node == "type Pair = {first: T, second: any}"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 1); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); + LUAU_ASSERT(module->ats.typeInfo[0].node == "type Pair = {first: T, second: any}"); } TEST_CASE_FIXTURE(ATSFixture, "assign_uneq") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/B"] = R"( local function greetings(name: string) return "Hello, " .. name, nil @@ -152,14 +167,16 @@ local x, y, z = greetings("Dibri") -- mismatch LUAU_REQUIRE_ERROR_COUNT(1, result1); ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/B"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 0); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 0); } TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any_gen") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( -- type Pair = (boolean, string, ...any) -> {T} -- type aliases with generics/pack do not seem to be processed? type Pair = (boolean, T) -> ...any @@ -170,16 +187,18 @@ type Pair = (boolean, T) -> ...any ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); - LUAU_ASSERT(module->ats.typeInfo[0].node == "type Pair = (boolean, T)->( ...any)"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 1); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); + LUAU_ASSERT(module->ats.typeInfo[0].node == "type Pair = (boolean, T)->( ...any)"); } TEST_CASE_FIXTURE(ATSFixture, "typeof_any_in_func") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( local function f() local a: any = 1 @@ -192,16 +211,18 @@ TEST_CASE_FIXTURE(ATSFixture, "typeof_any_in_func") ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 2); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot); - LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f()\n local a: any = 1\n local b: typeof(a) = 1\n end"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 2); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f()\n local a: any = 1\n local b: typeof(a) = 1\n end"); } TEST_CASE_FIXTURE(ATSFixture, "generic_types") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( local function foo(a: (...A) -> any, ...: A) return a(...) @@ -220,16 +241,18 @@ foo(addNumbers) ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 3); - LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::FuncApp); - LUAU_ASSERT(module->ats.typeInfo[0].node == "local function foo(a: (...A)->( any),...: A)\n return a(...)\nend"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 3); + LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::FuncApp); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local function foo(a: (...A)->( any),...: A)\n return a(...)\nend"); } TEST_CASE_FIXTURE(ATSFixture, "no_annot") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( local character = script.Parent )"; @@ -239,14 +262,16 @@ local character = script.Parent ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 0); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 0); } TEST_CASE_FIXTURE(ATSFixture, "if_any") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( function f(x: any) if not x then @@ -266,22 +291,24 @@ end ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); - LUAU_ASSERT( - module->ats.typeInfo[0].node == "function f(x: any)\nif not x then\nx = {\n y = math.random(0, 2^31-1),\n left = nil,\n right = " - "nil\n}\nelse\n local expected = x * 5\nend\nend" - ); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 1); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); + LUAU_ASSERT( + module->ats.typeInfo[0].node == "function f(x: any)\nif not x then\nx = {\n y = math.random(0, 2^31-1),\n left = nil,\n right = " + "nil\n}\nelse\n local expected = x * 5\nend\nend" + ); } TEST_CASE_FIXTURE(ATSFixture, "variadic_any") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( local function f(): (number, ...any) - return 1, 5 + return 1, 5 --catching this end local x, y, z = f() -- not catching this any because no annot @@ -292,16 +319,18 @@ TEST_CASE_FIXTURE(ATSFixture, "variadic_any") ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncRet); - LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(): (number, ...any)\n return 1, 5\n end"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 2); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncRet); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(): (number, ...any)\n return 1, 5\n end"); } TEST_CASE_FIXTURE(ATSFixture, "type_alias_intersection") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( type XCoord = {x: number} type YCoord = {y: any} @@ -314,16 +343,18 @@ TEST_CASE_FIXTURE(ATSFixture, "type_alias_intersection") ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 3); - LUAU_ASSERT(module->ats.typeInfo[2].code == Pattern::VarAnnot); - LUAU_ASSERT(module->ats.typeInfo[2].node == "local vec2: Vector2 = {x = 1, y = 2}"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 3); + LUAU_ASSERT(module->ats.typeInfo[2].code == Pattern::VarAnnot); + LUAU_ASSERT(module->ats.typeInfo[2].node == "local vec2: Vector2 = {x = 1, y = 2}"); } TEST_CASE_FIXTURE(ATSFixture, "var_func_arg") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( local function f(...: any) end @@ -340,16 +371,18 @@ TEST_CASE_FIXTURE(ATSFixture, "var_func_arg") ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 4); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAny); - LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(...: any)\n end"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 4); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAny); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(...: any)\n end"); } TEST_CASE_FIXTURE(ATSFixture, "var_func_apps") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( local function f(...: any) end @@ -362,17 +395,19 @@ TEST_CASE_FIXTURE(ATSFixture, "var_func_apps") ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 3); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAny); - LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(...: any)\n end"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 3); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAny); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(...: any)\n end"); } TEST_CASE_FIXTURE(ATSFixture, "CannotExtendTable") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( local CAR_COLLISION_GROUP = "Car" @@ -390,14 +425,16 @@ end ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 0); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 0); } TEST_CASE_FIXTURE(ATSFixture, "unknown_symbol") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( local function manageRace(raceContainer: Model) RaceManager.new(raceContainer) @@ -410,16 +447,18 @@ end ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 2); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); - LUAU_ASSERT(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 2); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend"); } TEST_CASE_FIXTURE(ATSFixture, "racing_3_short") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( local CollectionService = game:GetService("CollectionService") @@ -449,16 +488,18 @@ initialize() ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 5); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); - LUAU_ASSERT(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 5); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend"); } TEST_CASE_FIXTURE(ATSFixture, "racing_collision_2") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( local PhysicsService = game:GetService("PhysicsService") local ReplicatedStorage = game:GetService("ReplicatedStorage") @@ -524,22 +565,24 @@ initialize() ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 11); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); - LUAU_ASSERT( - module->ats.typeInfo[0].node == - "local function onCharacterAdded(character: Model)\n\n character.DescendantAdded:Connect(function(descendant)\n if " - "descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n end)\n\n\n for _, descendant in " - "character:GetDescendants()do\n if descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n " - "end\nend" - ); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 11); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); + LUAU_ASSERT( + module->ats.typeInfo[0].node == + "local function onCharacterAdded(character: Model)\n\n character.DescendantAdded:Connect(function(descendant)\n if " + "descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n end)\n\n\n for _, descendant in " + "character:GetDescendants()do\n if descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n " + "end\nend" + ); } TEST_CASE_FIXTURE(ATSFixture, "racing_spawning_1") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( local CollectionService = game:GetService("CollectionService") local Players = game:GetService("Players") @@ -593,23 +636,25 @@ initialize() ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 7); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); - LUAU_ASSERT( - module->ats.typeInfo[0].node == - "local function setupKiosk(kiosk: Model)\n local spawnLocation = kiosk:FindFirstChild('SpawnLocation')\n assert(spawnLocation, " - "`{kiosk:GetFullName()} has no SpawnLocation part`)\n local promptPart = kiosk:FindFirstChild('Prompt')\n assert(promptPart, " - "`{kiosk:GetFullName()} has no Prompt part`)\n\n\n spawnLocation.Transparency = 1\n\n\n local spawnPrompt = " - "spawnPromptTemplate:Clone()\n spawnPrompt.Parent = promptPart\n\n spawnPrompt.Triggered:Connect(function(player: Player)\n\n " - "destroyPlayerCars(player)\n\n spawnCar(spawnLocation.CFrame, player)\n end)\nend" - ); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 7); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); + LUAU_ASSERT( + module->ats.typeInfo[0].node == + "local function setupKiosk(kiosk: Model)\n local spawnLocation = kiosk:FindFirstChild('SpawnLocation')\n assert(spawnLocation, " + "`{kiosk:GetFullName()} has no SpawnLocation part`)\n local promptPart = kiosk:FindFirstChild('Prompt')\n assert(promptPart, " + "`{kiosk:GetFullName()} has no Prompt part`)\n\n\n spawnLocation.Transparency = 1\n\n\n local spawnPrompt = " + "spawnPromptTemplate:Clone()\n spawnPrompt.Parent = promptPart\n\n spawnPrompt.Triggered:Connect(function(player: Player)\n\n " + "destroyPlayerCars(player)\n\n spawnCar(spawnLocation.CFrame, player)\n end)\nend" + ); } TEST_CASE_FIXTURE(ATSFixture, "mutually_recursive_generic") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( --!strict type T = { f: a, g: U } @@ -625,14 +670,16 @@ TEST_CASE_FIXTURE(ATSFixture, "mutually_recursive_generic") ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 0); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 0); } TEST_CASE_FIXTURE(ATSFixture, "explicit_pack") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( type Foo = (T...) -> () -- also want to see how these are used. type Bar = Foo<(number, any)> @@ -643,16 +690,39 @@ type Bar = Foo<(number, any)> ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); - LUAU_ASSERT(module->ats.typeInfo[0].node == "type Bar = Foo<(number, any)>"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 1); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); + LUAU_ASSERT(module->ats.typeInfo[0].node == "type Bar = Foo<(number, any)>"); +} + +TEST_CASE_FIXTURE(ATSFixture, "local_val") +{ + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + + fileResolver.source["game/Gui/Modules/A"] = R"( +local a, b, c = 1 :: any +)"; + + CheckResult result1 = frontend.check("game/Gui/Modules/A"); + LUAU_REQUIRE_NO_ERRORS(result1); + + ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); + + LUAU_ASSERT(module->ats.typeInfo.size() == 1); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Casts); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local a, b, c = 1 :: any"); } TEST_CASE_FIXTURE(ATSFixture, "var_any_local") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( local x = 2 local x: any = 2, 3 @@ -665,16 +735,18 @@ local x: number, y: any, z, h: nil = 1, nil ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 3); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot); - LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: any = 2, 3"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 3); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: any = 2, 3"); } TEST_CASE_FIXTURE(ATSFixture, "table_uses_any") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( local x: any = 0 local y: number @@ -686,16 +758,18 @@ TEST_CASE_FIXTURE(ATSFixture, "table_uses_any") ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot); - LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: any = 0"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 1); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: any = 0"); } TEST_CASE_FIXTURE(ATSFixture, "typeof_any") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( local x: any = 0 function some1(x: typeof(x)) @@ -707,16 +781,18 @@ TEST_CASE_FIXTURE(ATSFixture, "typeof_any") ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 2); - LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::FuncArg); - LUAU_ASSERT(module->ats.typeInfo[0].node == "function some1(x: typeof(x))\n end"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 2); + LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::FuncArg); + LUAU_ASSERT(module->ats.typeInfo[0].node == "function some1(x: typeof(x))\n end"); } TEST_CASE_FIXTURE(ATSFixture, "table_type_assigned") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( local x: { x: any?} = {x = 1} local z: { x : any, y : number? } -- not catching this @@ -729,16 +805,18 @@ TEST_CASE_FIXTURE(ATSFixture, "table_type_assigned") ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 2); - LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::Assign); - LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: { x: any?} = {x = 1}"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 2); + LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::Assign); + LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: { x: any?} = {x = 1}"); } TEST_CASE_FIXTURE(ATSFixture, "simple_func_wo_ret") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( function some(x: any) end @@ -749,16 +827,18 @@ TEST_CASE_FIXTURE(ATSFixture, "simple_func_wo_ret") ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); - LUAU_ASSERT(module->ats.typeInfo[0].node == "function some(x: any)\n end"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 1); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); + LUAU_ASSERT(module->ats.typeInfo[0].node == "function some(x: any)\n end"); } TEST_CASE_FIXTURE(ATSFixture, "simple_func_w_ret") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( function other(y: number): any return "gotcha!" @@ -770,16 +850,18 @@ TEST_CASE_FIXTURE(ATSFixture, "simple_func_w_ret") ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncRet); - LUAU_ASSERT(module->ats.typeInfo[0].node == "function other(y: number): any\n return 'gotcha!'\n end"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 1); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncRet); + LUAU_ASSERT(module->ats.typeInfo[0].node == "function other(y: number): any\n return 'gotcha!'\n end"); } TEST_CASE_FIXTURE(ATSFixture, "nested_local") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( function cool(y: number): number local g: any = "gratatataaa" @@ -792,16 +874,18 @@ TEST_CASE_FIXTURE(ATSFixture, "nested_local") ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot); - LUAU_ASSERT(module->ats.typeInfo[0].node == "function cool(y: number): number\n local g: any = 'gratatataaa'\n return y\n end"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 1); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot); + LUAU_ASSERT(module->ats.typeInfo[0].node == "function cool(y: number): number\n local g: any = 'gratatataaa'\n return y\n end"); } TEST_CASE_FIXTURE(ATSFixture, "generic_func") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( function reverse(a: {T}, b: any): {T} return a @@ -813,16 +897,18 @@ TEST_CASE_FIXTURE(ATSFixture, "generic_func") ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 1); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); - LUAU_ASSERT(module->ats.typeInfo[0].node == "function reverse(a: {T}, b: any): {T}\n return a\n end"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 1); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); + LUAU_ASSERT(module->ats.typeInfo[0].node == "function reverse(a: {T}, b: any): {T}\n return a\n end"); } TEST_CASE_FIXTURE(ATSFixture, "type_alias_any") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/Gui/Modules/A"] = R"( type Clear = any local z: Clear = "zip" @@ -833,16 +919,18 @@ TEST_CASE_FIXTURE(ATSFixture, "type_alias_any") ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 2); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); - LUAU_ASSERT(module->ats.typeInfo[0].node == "type Clear = any"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 2); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); + LUAU_ASSERT(module->ats.typeInfo[0].node == "type Clear = any"); } TEST_CASE_FIXTURE(ATSFixture, "multi_module_any") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/A"] = R"( export type MyFunction = (number, string) -> (any) )"; @@ -864,16 +952,18 @@ TEST_CASE_FIXTURE(ATSFixture, "multi_module_any") ModulePtr module = frontend.moduleResolver.getModule("game/B"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 2); - LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); - LUAU_ASSERT(module->ats.typeInfo[0].node == "type Clear = any"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 2); + LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); + LUAU_ASSERT(module->ats.typeInfo[0].node == "type Clear = any"); } TEST_CASE_FIXTURE(ATSFixture, "cast_on_cyclic_req") { + ScopedFastFlag sff[] = { + {FFlag::DebugLuauDeferredConstraintResolution, true}, + {FFlag::StudioReportLuauAny2, true}, + }; + fileResolver.source["game/A"] = R"( local a = require(script.Parent.B) -- not resolving this module export type MyFunction = (number, string) -> (any) @@ -890,12 +980,9 @@ TEST_CASE_FIXTURE(ATSFixture, "cast_on_cyclic_req") ModulePtr module = frontend.moduleResolver.getModule("game/B"); - if (FFlag::StudioReportLuauAny) - { - LUAU_ASSERT(module->ats.typeInfo.size() == 3); - LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::Alias); - LUAU_ASSERT(module->ats.typeInfo[1].node == "type Clear = any"); - } + LUAU_ASSERT(module->ats.typeInfo.size() == 3); + LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::Alias); + LUAU_ASSERT(module->ats.typeInfo[1].node == "type Clear = any"); } diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index c1b62d7a..1a0e4b42 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -1607,6 +1607,9 @@ return target(a.@1 TEST_CASE_FIXTURE(ACFixture, "type_correct_suggestion_in_table") { + if (FFlag::DebugLuauDeferredConstraintResolution) // CLI-116815 Autocomplete cannot suggest keys while autocompleting inside of a table + return; + check(R"( type Foo = { a: number, b: string } local a = { one = 4, two = "hello" } @@ -2261,6 +2264,9 @@ local ec = e(f@5) TEST_CASE_FIXTURE(ACFixture, "type_correct_suggestion_for_overloads") { + if (FFlag::DebugLuauDeferredConstraintResolution) // CLI-116814 Autocomplete needs to populate expected types for function arguments correctly + // (overloads and singletons) + return; check(R"( local target: ((number) -> string) & ((string) -> number)) @@ -2608,6 +2614,10 @@ end TEST_CASE_FIXTURE(ACFixture, "suggest_table_keys") { + if (FFlag::DebugLuauDeferredConstraintResolution) // CLI-116812 AutocompleteTest.suggest_table_keys needs to populate expected types for nested + // tables without an annotation + return; + check(R"( type Test = { first: number, second: number } local t: Test = { f@1 } @@ -3091,6 +3101,10 @@ TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_on_string_singletons") TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singletons") { + if (FFlag::DebugLuauDeferredConstraintResolution) // CLI-116814 Autocomplete needs to populate expected types for function arguments correctly + // (overloads and singletons) + return; + check(R"( type tag = "cat" | "dog" local function f(a: tag) end @@ -4247,6 +4261,9 @@ foo(@1) TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_generic_type_pack_vararg") { + // CLI-116932 - Autocomplete on a anonymous function in a function argument should not recommend a function with a generic parameter. + if (FFlag::DebugLuauDeferredConstraintResolution) + return; check(R"( local function foo(a: (...A) -> number, ...: A) return a(...) @@ -4276,7 +4293,8 @@ end foo(@1) )"); - const std::optional EXPECTED_INSERT = "function(...): number end"; + const std::optional EXPECTED_INSERT = + FFlag::DebugLuauDeferredConstraintResolution ? "function(...: number): number end" : "function(...): number end"; auto ac = autocomplete('1'); diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 59f862cb..9e23f734 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -35,6 +35,7 @@ LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTFLAG(LuauNativeAttribute) LUAU_FASTFLAG(LuauPreserveLudataRenaming) +LUAU_FASTFLAG(LuauCodegenArmNumToVecFix) static lua_CompileOptions defaultOptions() { @@ -809,6 +810,8 @@ TEST_CASE("Pack") TEST_CASE("Vector") { + ScopedFastFlag luauCodegenArmNumToVecFix{FFlag::LuauCodegenArmNumToVecFix, true}; + lua_CompileOptions copts = defaultOptions(); Luau::CodeGen::CompilationOptions nativeOpts = defaultCodegenOptions(); diff --git a/tests/IrBuilder.test.cpp b/tests/IrBuilder.test.cpp index 2984fde1..d02fd9f1 100644 --- a/tests/IrBuilder.test.cpp +++ b/tests/IrBuilder.test.cpp @@ -13,8 +13,6 @@ #include LUAU_FASTFLAG(DebugLuauAbortingChecks) -LUAU_FASTFLAG(LuauCodegenFastcall3) -LUAU_FASTFLAG(LuauCodegenMathSign) using namespace Luau::CodeGen; @@ -334,8 +332,6 @@ TEST_SUITE_BEGIN("ConstantFolding"); TEST_CASE_FIXTURE(IrBuilderFixture, "Numeric") { - ScopedFastFlag luauCodegenMathSign{FFlag::LuauCodegenMathSign, true}; - IrOp block = build.block(IrBlockKind::Internal); build.beginBlock(block); @@ -2631,8 +2627,6 @@ bb_1: TEST_CASE_FIXTURE(IrBuilderFixture, "FastCallEffects1") { - ScopedFastFlag luauCodegenFastcall3{FFlag::LuauCodegenFastcall3, true}; - IrOp entry = build.block(IrBlockKind::Internal); build.beginBlock(entry); @@ -2656,8 +2650,6 @@ bb_0: TEST_CASE_FIXTURE(IrBuilderFixture, "FastCallEffects2") { - ScopedFastFlag luauCodegenFastcall3{FFlag::LuauCodegenFastcall3, true}; - IrOp entry = build.block(IrBlockKind::Internal); build.beginBlock(entry); @@ -2852,8 +2844,6 @@ bb_1: TEST_CASE_FIXTURE(IrBuilderFixture, "ExplicitUseOfRegisterInVarargSequence") { - ScopedFastFlag luauCodegenFastcall3{FFlag::LuauCodegenFastcall3, true}; - IrOp entry = build.block(IrBlockKind::Internal); IrOp exit = build.block(IrBlockKind::Internal); diff --git a/tests/IrLowering.test.cpp b/tests/IrLowering.test.cpp index 6d4aafeb..721969e2 100644 --- a/tests/IrLowering.test.cpp +++ b/tests/IrLowering.test.cpp @@ -17,7 +17,6 @@ LUAU_FASTFLAG(LuauCompileUserdataInfo) LUAU_FASTFLAG(LuauCompileFastcall3) -LUAU_FASTFLAG(LuauCodegenFastcall3) static std::string getCodegenAssembly(const char* source, bool includeIrTypes = false, int debugLevel = 1) { @@ -425,8 +424,6 @@ bb_5: TEST_CASE("DseInitialStackState2") { - ScopedFastFlag luauCodegenFastcall3{FFlag::LuauCodegenFastcall3, true}; - CHECK_EQ( "\n" + getCodegenAssembly(R"( local function foo(a) @@ -945,7 +942,7 @@ bb_bytecode_0: TEST_CASE("FastcallTypeInferThroughLocal") { - ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}, {FFlag::LuauCodegenFastcall3, true}}; + ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}}; CHECK_EQ( "\n" + getCodegenAssembly( @@ -997,7 +994,7 @@ bb_bytecode_1: TEST_CASE("FastcallTypeInferThroughUpvalue") { - ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}, {FFlag::LuauCodegenFastcall3, true}}; + ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}}; CHECK_EQ( "\n" + getCodegenAssembly( @@ -1127,7 +1124,7 @@ bb_bytecode_4: TEST_CASE("ArgumentTypeRefinement") { - ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}, {FFlag::LuauCodegenFastcall3, true}}; + ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}}; CHECK_EQ( "\n" + getCodegenAssembly( @@ -1436,7 +1433,7 @@ bb_2: TEST_CASE("UnaryTypeResolve") { - ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}, {FFlag::LuauCodegenFastcall3, true}}; + ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}}; CHECK_EQ( "\n" + getCodegenHeader(R"( diff --git a/tests/Module.test.cpp b/tests/Module.test.cpp index bcad38d3..03aef1de 100644 --- a/tests/Module.test.cpp +++ b/tests/Module.test.cpp @@ -316,6 +316,9 @@ TEST_CASE_FIXTURE(Fixture, "clone_free_tables") TEST_CASE_FIXTURE(BuiltinsFixture, "clone_self_property") { + // CLI-117082 ModuleTests.clone_self_property we don't infer self correctly, instead replacing it with unknown. + if (FFlag::DebugLuauDeferredConstraintResolution) + return; fileResolver.source["Module/A"] = R"( --!nonstrict local a = {} diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index 407aaf4e..1d135d54 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -415,7 +415,15 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "error_suppression") CHECK(!isSubtype(any, unk)); } - CHECK(!isSubtype(err, str)); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CHECK(isSubtype(err, str)); + } + else + { + CHECK(!isSubtype(err, str)); + } + CHECK(!isSubtype(str, err)); CHECK(!isSubtype(err, unk)); @@ -701,6 +709,10 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function") TEST_CASE_FIXTURE(Fixture, "higher_order_function_with_annotation") { + // CLI-117088 - Inferring the type of a higher order function with an annotation sometimes doesn't fully constrain the type (there are free types + // left over). + if (FFlag::DebugLuauDeferredConstraintResolution) + return; check(R"( function apply(f: (a) -> b, x) return f(x) diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 01ddac0c..f4ce7eb1 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -72,12 +72,12 @@ struct SubtypeFixture : Fixture ScopePtr rootScope{new Scope(builtinTypes->emptyTypePack)}; ScopePtr moduleScope{new Scope(rootScope)}; - Subtyping subtyping = mkSubtyping(rootScope); + Subtyping subtyping = mkSubtyping(); BuiltinTypeFunctions builtinTypeFunctions{}; - Subtyping mkSubtyping(const ScopePtr& scope) + Subtyping mkSubtyping() { - return Subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&iceReporter}, NotNull{scope.get()}}; + return Subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&iceReporter}}; } TypePackId pack(std::initializer_list tys) @@ -184,7 +184,7 @@ struct SubtypeFixture : Fixture SubtypingResult isSubtype(TypeId subTy, TypeId superTy) { - return subtyping.isSubtype(subTy, superTy); + return subtyping.isSubtype(subTy, superTy, NotNull{rootScope.get()}); } TypeId helloType = arena.addType(SingletonType{StringSingleton{"hello"}}); @@ -1210,7 +1210,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(...any) -> () <: (T...) -> ()") TypeId anysToNothing = arena.addType(FunctionType{builtinTypes->anyTypePack, builtinTypes->emptyTypePack}); TypeId genericTToAnys = arena.addType(FunctionType{genericAs, builtinTypes->emptyTypePack}); - CHECK_MESSAGE(subtyping.isSubtype(anysToNothing, genericTToAnys).isSubtype, "(...any) -> () <: (T...) -> ()"); + CHECK_MESSAGE(isSubtype(anysToNothing, genericTToAnys).isSubtype, "(...any) -> () <: (T...) -> ()"); } // See https://github.com/luau-lang/luau/issues/767 @@ -1220,7 +1220,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(...unknown) -> () <: (T...) -> ()") arena.addType(FunctionType{arena.addTypePack(VariadicTypePack{builtinTypes->unknownType}), builtinTypes->emptyTypePack}); TypeId genericTToAnys = arena.addType(FunctionType{genericAs, builtinTypes->emptyTypePack}); - CHECK_MESSAGE(subtyping.isSubtype(unknownsToNothing, genericTToAnys).isSubtype, "(...unknown) -> () <: (T...) -> ()"); + CHECK_MESSAGE(isSubtype(unknownsToNothing, genericTToAnys).isSubtype, "(...unknown) -> () <: (T...) -> ()"); } TEST_CASE_FIXTURE(SubtypeFixture, "bill") @@ -1233,8 +1233,8 @@ TEST_CASE_FIXTURE(SubtypeFixture, "bill") {{"a", builtinTypes->stringType}}, TableIndexer{builtinTypes->stringType, builtinTypes->numberType}, TypeLevel{}, nullptr, TableState::Sealed }); - CHECK(subtyping.isSubtype(a, b).isSubtype); - CHECK(subtyping.isSubtype(b, a).isSubtype); + CHECK(isSubtype(a, b).isSubtype); + CHECK(isSubtype(b, a).isSubtype); } // TEST_CASE_FIXTURE(SubtypeFixture, "({[string]: number, a: string}) -> () <: ({[string]: number, a: string}) -> ()") @@ -1256,7 +1256,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "fred") TypeId a = makeTheType(); TypeId b = makeTheType(); - CHECK_MESSAGE(subtyping.isSubtype(a, b).isSubtype, "({[string]: number, a: string}) -> () <: ({[string]: number, a: string}) -> ()"); + CHECK_MESSAGE(isSubtype(a, b).isSubtype, "({[string]: number, a: string}) -> () <: ({[string]: number, a: string}) -> ()"); } /* @@ -1273,17 +1273,17 @@ TEST_CASE_FIXTURE(SubtypeFixture, "unknown <: X") TypeId genericX = arena.addType(GenericType(childScope.get(), "X")); - SubtypingResult usingGlobalScope = subtyping.isSubtype(builtinTypes->unknownType, genericX); + SubtypingResult usingGlobalScope = isSubtype(builtinTypes->unknownType, genericX); CHECK_MESSAGE(!usingGlobalScope.isSubtype, "Expected " << builtinTypes->unknownType << " unknownType, genericX); + SubtypingResult usingChildScope = childSubtyping.isSubtype(builtinTypes->unknownType, genericX, NotNull{childScope.get()}); CHECK_MESSAGE(usingChildScope.isSubtype, "Expected " << builtinTypes->unknownType << " <: " << genericX); - Subtyping grandChildSubtyping{mkSubtyping(grandChildScope)}; + Subtyping grandChildSubtyping{mkSubtyping()}; - SubtypingResult usingGrandChildScope = grandChildSubtyping.isSubtype(builtinTypes->unknownType, genericX); + SubtypingResult usingGrandChildScope = grandChildSubtyping.isSubtype(builtinTypes->unknownType, genericX, NotNull{grandChildScope.get()}); CHECK_MESSAGE(usingGrandChildScope.isSubtype, "Expected " << builtinTypes->unknownType << " <: " << genericX); } diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 773636c6..cd7076d7 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -490,7 +490,6 @@ TEST_CASE_FIXTURE(Fixture, "another_other_higher_order_function") )"); LUAU_REQUIRE_NO_ERRORS(result); - } else { @@ -778,11 +777,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "higher_order_function_4") end )"); - // This function currently has a bug in the new solver reporting `{T} | {T}` is not a table. - if (FFlag::DebugLuauDeferredConstraintResolution) - LUAU_REQUIRE_ERRORS(result); - else - LUAU_REQUIRE_NO_ERRORS(result); + LUAU_REQUIRE_NO_ERRORS(result); /* * mergesort takes two arguments: an array of some type T and a function that takes two Ts. @@ -1678,8 +1673,14 @@ end if (FFlag::DebugLuauDeferredConstraintResolution) { LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK_EQ(toString(result.errors[0]), R"(Type function instance add depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)"); - CHECK_EQ(toString(result.errors[1]), R"(Type function instance add depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)"); + CHECK_EQ( + toString(result.errors[0]), + R"(Type function instance add depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)" + ); + CHECK_EQ( + toString(result.errors[1]), + R"(Type function instance add depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)" + ); } else { @@ -1717,7 +1718,8 @@ TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_th TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_the_right_time3") { - // This test regresses in the new solver, but is sort of nonsensical insofar as `foo` is known to be `nil`, so it's "right" to not be able to call it. + // This test regresses in the new solver, but is sort of nonsensical insofar as `foo` is known to be `nil`, so it's "right" to not be able to call + // it. ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; CheckResult result = check(R"( @@ -1749,7 +1751,10 @@ end if (FFlag::DebugLuauDeferredConstraintResolution) { LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type function instance add depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)"); + CHECK_EQ( + toString(result.errors[0]), + R"(Type function instance add depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)" + ); } else { @@ -2477,6 +2482,9 @@ a = function(a, b) return a + b end TEST_CASE_FIXTURE(BuiltinsFixture, "simple_unannotated_mutual_recursion") { + // CLI-117118 - TypeInferFunctions.simple_unannotated_mutual_recursion relies on unstable assertions to pass. + if (FFlag::DebugLuauDeferredConstraintResolution) + return; CheckResult result = check(R"( function even(n) if n == 0 then @@ -2500,11 +2508,13 @@ end if (FFlag::DebugLuauDeferredConstraintResolution) { LUAU_REQUIRE_ERROR_COUNT(5, result); + // CLI-117117 Constraint solving is incomplete inTypeInferFunctions.simple_unannotated_mutual_recursion CHECK(get(result.errors[0])); - CHECK( - toString(result.errors[1]) == - "Type pack '*blocked-tp-1*' could not be converted into 'boolean'; type *blocked-tp-1*.tail() (*blocked-tp-1*) is not a subtype of boolean (boolean)" - ); + // This check is unstable between different machines and different runs of DCR because it depends on string equality between + // blocked type numbers, which is not guaranteed. + bool r = toString(result.errors[1]) == "Type pack '*blocked-tp-1*' could not be converted into 'boolean'; type *blocked-tp-1*.tail() " + "(*blocked-tp-1*) is not a subtype of boolean (boolean)"; + CHECK(r); CHECK( toString(result.errors[2]) == "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub" diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index 8741da6a..97696f23 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -142,6 +142,8 @@ TEST_CASE_FIXTURE(Fixture, "properties_can_be_polytypes") TEST_CASE_FIXTURE(Fixture, "properties_can_be_instantiated_polytypes") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( local t: { m: (number)->number } = { m = function(x:number) return x+1 end } local function id(x:a):a return x end @@ -256,8 +258,10 @@ TEST_CASE_FIXTURE(Fixture, "check_mutual_generic_functions_errors") } } -TEST_CASE_FIXTURE(Fixture, "generic_functions_in_types") +TEST_CASE_FIXTURE(Fixture, "generic_functions_in_types_old_solver") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( type T = { id: (a) -> a } local x: T = { id = function(x:a):a return x end } @@ -267,8 +271,23 @@ TEST_CASE_FIXTURE(Fixture, "generic_functions_in_types") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "generic_functions_in_types_new_solver") +{ + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true}; + + CheckResult result = check(R"( + type T = { read id: (a) -> a } + local x: T = { id = function(x:a):a return x end } + local y: string = x.id("hi") + local z: number = x.id(37) + )"); + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_CASE_FIXTURE(Fixture, "generic_factories") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( type T = { id: (a) -> a } type Factory = { build: () -> T } @@ -290,6 +309,8 @@ TEST_CASE_FIXTURE(Fixture, "generic_factories") TEST_CASE_FIXTURE(Fixture, "factories_of_generics") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( type T = { id: (a) -> a } type Factory = { build: () -> T } @@ -445,7 +466,14 @@ TEST_CASE_FIXTURE(Fixture, "dont_leak_generic_types") local b: boolean = f(true) )"); - LUAU_REQUIRE_ERRORS(result); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_NO_ERRORS(result); + } + else + { + LUAU_REQUIRE_ERRORS(result); + } } TEST_CASE_FIXTURE(Fixture, "dont_leak_inferred_generic_types") @@ -461,7 +489,14 @@ TEST_CASE_FIXTURE(Fixture, "dont_leak_inferred_generic_types") local y: number = id(37) end )"); - LUAU_REQUIRE_ERRORS(result); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_NO_ERRORS(result); + } + else + { + LUAU_REQUIRE_ERRORS(result); + } } TEST_CASE_FIXTURE(Fixture, "dont_substitute_bound_types") @@ -737,17 +772,19 @@ return exports LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(Fixture, "instantiated_function_argument_names") +TEST_CASE_FIXTURE(Fixture, "instantiated_function_argument_names_old_solver") { - CheckResult result = check(R"( -local function f(a: T, ...: U...) end + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; -f(1, 2, 3) + CheckResult result = check(R"( + local function f(a: T, ...: U...) end + + f(1, 2, 3) )"); LUAU_REQUIRE_NO_ERRORS(result); - auto ty = findTypeAtPosition(Position(3, 0)); + auto ty = findTypeAtPosition(Position(3, 8)); REQUIRE(ty); ToStringOptions opts; opts.functionTypeArguments = true; @@ -756,6 +793,8 @@ f(1, 2, 3) TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_generic_types") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( type C = () -> () type D = () -> () @@ -771,6 +810,8 @@ local d: D = c TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_generic_pack") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( type C = () -> () type D = () -> () @@ -845,6 +886,8 @@ Type 'number' could not be converted into 'string' in an invariant context)"; TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification1") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( --!strict type Dispatcher = { @@ -863,6 +906,8 @@ local TheDispatcher: Dispatcher = { TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification2") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( --!strict type Dispatcher = { @@ -881,6 +926,8 @@ local TheDispatcher: Dispatcher = { TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification3") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( --!strict type Dispatcher = { @@ -899,6 +946,8 @@ local TheDispatcher: Dispatcher = { TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_few") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( function test(a: number) return 1 @@ -916,6 +965,8 @@ wrapper(test) TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( function test2(a: number, b: string) return 1 @@ -1370,6 +1421,8 @@ TEST_CASE_FIXTURE(Fixture, "apply_type_function_nested_generics3") TEST_CASE_FIXTURE(Fixture, "quantify_functions_even_if_they_have_an_explicit_generic") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( function foo(f, x: X) return f(x) @@ -1381,6 +1434,8 @@ TEST_CASE_FIXTURE(Fixture, "quantify_functions_even_if_they_have_an_explicit_gen TEST_CASE_FIXTURE(Fixture, "do_not_always_instantiate_generic_intersection_types") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + CheckResult result = check(R"( --!strict type Array = { [number]: T } @@ -1415,6 +1470,7 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "higher_rank_polymorphism_should_not_accept_instantiated_arguments") { ScopedFastFlag sffs[] = { + {FFlag::DebugLuauDeferredConstraintResolution, false}, {FFlag::LuauInstantiateInSubtyping, true}, }; @@ -1481,6 +1537,8 @@ TEST_CASE_FIXTURE(Fixture, "missing_generic_type_parameter") TEST_CASE_FIXTURE(BuiltinsFixture, "generic_type_functions_work_in_subtyping") { + ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false}; + if (!FFlag::DebugLuauDeferredConstraintResolution) return; diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index baef9245..b2de0128 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -554,17 +554,17 @@ TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions") { LUAU_REQUIRE_ERROR_COUNT(2, result); const std::string expected1 = R"(Type - '(nil) -> nil' + '((number?) -> number?) & ((string?) -> string?)' could not be converted into - '((number?) -> number?) & ((string?) -> string?)'; type (nil) -> nil.arguments()[0] (nil) is not a supertype of ((number?) -> number?) & ((string?) -> string?)[0].arguments()[0][0] (number) - type (nil) -> nil.arguments()[0] (nil) is not a supertype of ((number?) -> number?) & ((string?) -> string?)[1].arguments()[0][0] (string))"; + '(nil) -> nil'; type ((number?) -> number?) & ((string?) -> string?)[0].returns()[0][0] (number) is not a subtype of (nil) -> nil.returns()[0] (nil) + type ((number?) -> number?) & ((string?) -> string?)[1].returns()[0][0] (string) is not a subtype of (nil) -> nil.returns()[0] (nil))"; const std::string expected2 = R"(Type - '(number) -> number' + '((number?) -> number?) & ((string?) -> string?)' could not be converted into - '((number?) -> number?) & ((string?) -> string?)'; type (number) -> number.arguments()[0] (number) is not a supertype of ((number?) -> number?) & ((string?) -> string?)[0].arguments()[0][1] (nil) - type (number) -> number.arguments()[0] (number) is not a supertype of ((number?) -> number?) & ((string?) -> string?)[1].arguments()[0][0] (string) - type (number) -> number.arguments()[0] (number) is not a supertype of ((number?) -> number?) & ((string?) -> string?)[1].arguments()[0][1] (nil) - type (number) -> number.returns()[0] (number) is not a subtype of ((number?) -> number?) & ((string?) -> string?)[1].returns()[0] (string?))"; + '(number) -> number'; type ((number?) -> number?) & ((string?) -> string?)[0].returns()[0][1] (nil) is not a subtype of (number) -> number.returns()[0] (number) + type ((number?) -> number?) & ((string?) -> string?)[1].arguments()[0] (string?) is not a supertype of (number) -> number.arguments()[0] (number) + type ((number?) -> number?) & ((string?) -> string?)[1].returns()[0][0] (string) is not a subtype of (number) -> number.returns()[0] (number) + type ((number?) -> number?) & ((string?) -> string?)[1].returns()[0][1] (nil) is not a subtype of (number) -> number.returns()[0] (number))"; CHECK_EQ(expected1, toString(result.errors[0])); CHECK_EQ(expected2, toString(result.errors[1])); } @@ -609,13 +609,12 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = (FFlag::DebugLuauDeferredConstraintResolution) - ? "Type " - "'{ p: number?, q: number?, r: number? } & { p: number?, q: string? }'" - " could not be converted into " - "'{ p: nil }'; none of the intersection parts are compatible" - : - R"(Type + const std::string expected = + (FFlag::DebugLuauDeferredConstraintResolution) + ? R"(Type '{ p: number?, q: number?, r: number? } & { p: number?, q: string? }' could not be converted into '{ p: nil }'; type { p: number?, q: number?, r: number? } & { p: number?, q: string? }[0][read "p"][0] (number) is not exactly { p: nil }[read "p"] (nil) + type { p: number?, q: number?, r: number? } & { p: number?, q: string? }[1][read "p"][0] (number) is not exactly { p: nil }[read "p"] (nil))" + : + R"(Type '{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}' could not be converted into '{| p: nil |}'; none of the intersection parts are compatible)"; @@ -633,28 +632,18 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties") if (FFlag::DebugLuauDeferredConstraintResolution) { - LUAU_REQUIRE_ERROR_COUNT(2, result); - + LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ( - toString(result.errors[0]), - "Type '{| p: number?, q: string? |}' could not be converted into '{| p: string?, q: number? |}'\n" - "caused by:\n" - " Property 'p' is not compatible. Type 'number?' could not be converted into 'string?'\n" - "caused by:\n" - " Not all union options are compatible. Type 'number' could not be converted into 'string?'\n" - "caused by:\n" - " None of the union options are compatible. For example: Type 'number' could not be converted into 'string' in an invariant context" - ); - - CHECK_EQ( - toString(result.errors[1]), - "Type '{| p: number?, q: string? |}' could not be converted into '{| p: string?, q: number? |}'\n" - "caused by:\n" - " Property 'q' is not compatible. Type 'string?' could not be converted into 'number?'\n" - "caused by:\n" - " Not all union options are compatible. Type 'string' could not be converted into 'number?'\n" - "caused by:\n" - " None of the union options are compatible. For example: Type 'string' could not be converted into 'number' in an invariant context" + R"(Type + '{ p: number?, q: any } & { p: unknown, q: string? }' +could not be converted into + '{ p: string?, q: number? }'; type { p: number?, q: any } & { p: unknown, q: string? }[0][read "p"] (number?) is not exactly { p: string?, q: number? }[read "p"][0] (string) + type { p: number?, q: any } & { p: unknown, q: string? }[0][read "p"][0] (number) is not exactly { p: string?, q: number? }[read "p"] (string?) + type { p: number?, q: any } & { p: unknown, q: string? }[0][read "q"] (any) is not exactly { p: string?, q: number? }[read "q"] (number?) + type { p: number?, q: any } & { p: unknown, q: string? }[1][read "p"] (unknown) is not exactly { p: string?, q: number? }[read "p"] (string?) + type { p: number?, q: any } & { p: unknown, q: string? }[1][read "q"] (string?) is not exactly { p: string?, q: number? }[read "q"][0] (number) + type { p: number?, q: any } & { p: unknown, q: string? }[1][read "q"][0] (string) is not exactly { p: string?, q: number? }[read "q"] (number?))", + toString(result.errors[0]) ); } else @@ -689,18 +678,42 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections") end )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = (FFlag::DebugLuauDeferredConstraintResolution) ? - R"(Type + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_ERROR_COUNT(2, result); + CHECK_EQ( + R"(Type '((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })' could not be converted into - '(number?) -> { p: number, q: number, r: number }'; none of the intersection parts are compatible)" - : - R"(Type + '(nil) -> { p: number, q: number, r: number }'; type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[0].returns()[0][0] ({ p: number }) is not a subtype of (nil) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }) + type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[0].returns()[0][1] ({ q: number }) is not a subtype of (nil) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }) + type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].returns()[0][0] ({ p: number }) is not a subtype of (nil) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }) + type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].returns()[0][1] ({ r: number }) is not a subtype of (nil) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }))", + toString(result.errors[0]) + ); + CHECK_EQ( + R"(Type + '((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })' +could not be converted into + '(number?) -> { p: number, q: number, r: number }'; type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[0].returns()[0][0] ({ p: number }) is not a subtype of (number?) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }) + type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[0].returns()[0][1] ({ q: number }) is not a subtype of (number?) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }) + type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].arguments()[0] (string?) is not a supertype of (number?) -> { p: number, q: number, r: number }.arguments()[0][0] (number) + type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].returns()[0][0] ({ p: number }) is not a subtype of (number?) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }) + type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].returns()[0][1] ({ r: number }) is not a subtype of (number?) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }))", + toString(result.errors[1]) + ); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ( + R"(Type '((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})' could not be converted into - '(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); + '(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible)", + toString(result.errors[0]) + ); + } } TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic") @@ -713,13 +726,19 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic") end end )"); - - LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_ERROR_COUNT(0, result); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + const std::string expected = R"(Type '((number?) -> a | number) & ((string?) -> a | string)' could not be converted into '(number?) -> a'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); + CHECK_EQ(expected, toString(result.errors[0])); + } } TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics") @@ -733,12 +752,20 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics") end )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_NO_ERRORS(result); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + const std::string expected = R"(Type '((a?) -> a | b) & ((c?) -> b | c)' could not be converted into '(a?) -> (a & c) | b'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); + CHECK_EQ(expected, toString(result.errors[0])); + } } TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") @@ -751,13 +778,35 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") end end )"); - - LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_ERROR_COUNT(2, result); + CHECK_EQ( + R"(Type + '((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))' +could not be converted into + '(nil, a...) -> (nil, b...)'; type ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))[0].returns()[0][0] (number) is not a subtype of (nil, a...) -> (nil, b...).returns()[0] (nil) + type ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))[1].returns()[0][0] (string) is not a subtype of (nil, a...) -> (nil, b...).returns()[0] (nil))", + toString(result.errors[0]) + ); + CHECK_EQ( + R"(Type + '((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))' +could not be converted into + '(nil, b...) -> (nil, a...)'; type ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))[0].returns()[0][0] (number) is not a subtype of (nil, b...) -> (nil, a...).returns()[0] (nil) + type ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))[1].returns()[0][0] (string) is not a subtype of (nil, b...) -> (nil, a...).returns()[0] (nil))", + toString(result.errors[1]) + ); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + const std::string expected = R"(Type '((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))' could not be converted into '(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); + CHECK_EQ(expected, toString(result.errors[0])); + } } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result") @@ -858,12 +907,34 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments") end )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_ERROR_COUNT(2, result); + const std::string expected1 = R"(Type + '((never) -> string?) & ((number) -> number?)' +could not be converted into + '(never) -> nil'; type ((never) -> string?) & ((number) -> number?)[0].returns()[0][0] (number) is not a subtype of (never) -> nil.returns()[0] (nil) + type ((never) -> string?) & ((number) -> number?)[1].returns()[0][0] (string) is not a subtype of (never) -> nil.returns()[0] (nil))"; + const std::string expected2 = R"(Type + '((never) -> string?) & ((number) -> number?)' +could not be converted into + '(number?) -> nil'; type ((never) -> string?) & ((number) -> number?)[0].arguments()[0] (number) is not a supertype of (number?) -> nil.arguments()[0][1] (nil) + type ((never) -> string?) & ((number) -> number?)[0].returns()[0][0] (number) is not a subtype of (number?) -> nil.returns()[0] (nil) + type ((never) -> string?) & ((number) -> number?)[1].arguments()[0] (never) is not a supertype of (number?) -> nil.arguments()[0][0] (number) + type ((never) -> string?) & ((number) -> number?)[1].arguments()[0] (never) is not a supertype of (number?) -> nil.arguments()[0][1] (nil) + type ((never) -> string?) & ((number) -> number?)[1].returns()[0][0] (string) is not a subtype of (number?) -> nil.returns()[0] (nil))"; + CHECK_EQ(expected1, toString(result.errors[0])); + CHECK_EQ(expected2, toString(result.errors[1])); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + const std::string expected = R"(Type '((never) -> string?) & ((number) -> number?)' could not be converted into '(number?) -> nil'; none of the intersection parts are compatible)"; - CHECK_EQ(expected, toString(result.errors[0])); + CHECK_EQ(expected, toString(result.errors[0])); + } } TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_variadics") @@ -999,6 +1070,10 @@ could not be converted into TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables") { + // CLI-117121 - Intersection of types are not compatible with the equivalent alias + if (FFlag::DebugLuauDeferredConstraintResolution) + return; + if (FFlag::DebugLuauDeferredConstraintResolution) { CheckResult result = check(R"( @@ -1165,10 +1240,9 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_intersection_types") end )"); - LUAU_REQUIRE_NO_ERRORS(result); + LUAU_REQUIRE_ERROR_COUNT(3, result); - // TODO? We do not simplify types from explicit annotations. - CHECK_EQ("({| x: number |} & {| x: string |}) -> {| x: number |} & {| x: string |}", toString(requireType("f"))); + CHECK_EQ("(never) -> { x: number } & { x: string }", toString(requireType("f"))); } TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_intersection_types_2") diff --git a/tests/TypeInfer.negations.test.cpp b/tests/TypeInfer.negations.test.cpp index af73607a..53f32147 100644 --- a/tests/TypeInfer.negations.test.cpp +++ b/tests/TypeInfer.negations.test.cpp @@ -50,6 +50,9 @@ TEST_CASE_FIXTURE(NegationFixture, "string_is_not_a_subtype_of_negated_string") TEST_CASE_FIXTURE(Fixture, "cofinite_strings_can_be_compared_for_equality") { + // CLI-117082 Cofinite strings cannot be compared for equality because normalization produces a large type with cycles + if (FFlag::DebugLuauDeferredConstraintResolution) + return; CheckResult result = check(R"( function f(e) if e == 'strictEqual' then diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 37c5e8d2..214bcdfd 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -1500,6 +1500,9 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "narrow_from_subclasses_of_instance_or TEST_CASE_FIXTURE(RefinementClassFixture, "x_as_any_if_x_is_instance_elseif_x_is_table") { + // CLI-117136 - this code doesn't finish constraint solving and has blocked types in the output + if (FFlag::DebugLuauDeferredConstraintResolution) + return; CheckResult result = check(R"( --!nonstrict @@ -1588,6 +1591,9 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "isa_type_refinement_must_be_known_ahe TEST_CASE_FIXTURE(RefinementClassFixture, "x_is_not_instance_or_else_not_part") { + // CLI-117135 - RefinementTests.x_is_not_instance_or_else_not_part not correctly applying refinements to a function parameter + if (FFlag::DebugLuauDeferredConstraintResolution) + return; CheckResult result = check(R"( local function f(x: Part | Folder | string) if typeof(x) ~= "Instance" or not x:IsA("Part") then @@ -1807,6 +1813,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_clone_it") TEST_CASE_FIXTURE(RefinementClassFixture, "refine_a_param_that_got_resolved_during_constraint_solving_stage") { + // CLI-117134 - Applying a refinement causes an optional value access error. + if (FFlag::DebugLuauDeferredConstraintResolution) + return; CheckResult result = check(R"( type Id = T diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 6256ede6..04417fe1 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -15,9 +15,10 @@ using namespace Luau; -LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); -LUAU_FASTFLAG(LuauInstantiateInSubtyping); -LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering); +LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) +LUAU_FASTFLAG(LuauInstantiateInSubtyping) +LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) +LUAU_FASTFLAG(LuauAcceptIndexingTableUnionsIntersections) LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError) @@ -4794,4 +4795,41 @@ end LUAU_REQUIRE_NO_ERRORS(result); } + +TEST_CASE_FIXTURE(BuiltinsFixture, "indexing_branching_table") +{ + ScopedFastFlag sff{FFlag::LuauAcceptIndexingTableUnionsIntersections, true}; + + CheckResult result = check(R"( + local test = if true then { "meow", "woof" } else { 4, 81 } + local test2 = test[1] + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + // unfortunate type duplication in the union + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK("number | string | string" == toString(requireType("test2"))); + else + CHECK("number | string" == toString(requireType("test2"))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "indexing_branching_table2") +{ + ScopedFastFlag sff{FFlag::LuauAcceptIndexingTableUnionsIntersections, true}; + + CheckResult result = check(R"( + local test = if true then {} else {} + local test2 = test[1] + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + // unfortunate type duplication in the union + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK("unknown | unknown" == toString(requireType("test2"))); + else + CHECK("any" == toString(requireType("test2"))); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.typestates.test.cpp b/tests/TypeInfer.typestates.test.cpp index 19117447..22a30865 100644 --- a/tests/TypeInfer.typestates.test.cpp +++ b/tests/TypeInfer.typestates.test.cpp @@ -459,7 +459,9 @@ TEST_CASE_FIXTURE(TypeStateFixture, "typestates_preserve_error_suppression") TEST_CASE_FIXTURE(BuiltinsFixture, "typestates_preserve_error_suppression_properties") { // early return if the flag isn't set since this is blocking gated commits - if (!FFlag::DebugLuauDeferredConstraintResolution) + // unconditional return + // CLI-117098 Type states with error suppressing properties doesn't infer the correct type for properties. + if (!FFlag::DebugLuauDeferredConstraintResolution || FFlag::DebugLuauDeferredConstraintResolution) return; CheckResult result = check(R"( diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 3a880d6d..fedf155a 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -8,7 +8,8 @@ using namespace Luau; -LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); +LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) +LUAU_FASTFLAG(LuauAcceptIndexingTableUnionsIntersections) TEST_SUITE_BEGIN("UnionTypes"); @@ -636,7 +637,12 @@ TEST_CASE_FIXTURE(Fixture, "indexing_into_a_cyclic_union_doesnt_crash") end )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); + // this is a cyclic union of number arrays, so it _is_ a table, even if it's a nonsense type. + // no need to generate a NotATable error here. + if (FFlag::LuauAcceptIndexingTableUnionsIntersections) + LUAU_REQUIRE_NO_ERRORS(result); + else + LUAU_REQUIRE_ERROR_COUNT(1, result); } TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect") diff --git a/tests/TypeInfer.unknownnever.test.cpp b/tests/TypeInfer.unknownnever.test.cpp index 42007078..23375680 100644 --- a/tests/TypeInfer.unknownnever.test.cpp +++ b/tests/TypeInfer.unknownnever.test.cpp @@ -193,12 +193,20 @@ TEST_CASE_FIXTURE(Fixture, "call_never") TEST_CASE_FIXTURE(Fixture, "assign_to_local_which_is_never") { + // CLI-117119 - What do we do about assigning to never? CheckResult result = check(R"( local t: never t = 3 )"); - LUAU_REQUIRE_NO_ERRORS(result); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + } + else + { + LUAU_REQUIRE_NO_ERRORS(result); + } } TEST_CASE_FIXTURE(Fixture, "assign_to_global_which_is_never") @@ -257,6 +265,9 @@ TEST_CASE_FIXTURE(Fixture, "pick_never_from_variadic_type_pack") TEST_CASE_FIXTURE(Fixture, "index_on_union_of_tables_for_properties_that_is_never") { + // CLI-117116 - We are erroneously warning when passing a valid table literal where we expect a union of tables. + if (FFlag::DebugLuauDeferredConstraintResolution) + return; CheckResult result = check(R"( type Disjoint = {foo: never, bar: unknown, tag: "ok"} | {foo: never, baz: unknown, tag: "err"} @@ -274,6 +285,9 @@ TEST_CASE_FIXTURE(Fixture, "index_on_union_of_tables_for_properties_that_is_neve TEST_CASE_FIXTURE(Fixture, "index_on_union_of_tables_for_properties_that_is_sorta_never") { + // CLI-117116 - We are erroneously warning when passing a valid table literal where we expect a union of tables. + if (FFlag::DebugLuauDeferredConstraintResolution) + return; CheckResult result = check(R"( type Disjoint = {foo: string, bar: unknown, tag: "ok"} | {foo: never, baz: unknown, tag: "err"} diff --git a/tests/VisitType.test.cpp b/tests/VisitType.test.cpp index a1a17e8e..c5e66829 100644 --- a/tests/VisitType.test.cpp +++ b/tests/VisitType.test.cpp @@ -15,15 +15,28 @@ TEST_SUITE_BEGIN("VisitType"); TEST_CASE_FIXTURE(Fixture, "throw_when_limit_is_exceeded") { - ScopedFastInt sfi{FInt::LuauVisitRecursionLimit, 3}; + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CheckResult result = check(R"( + local t : {a: {b: {c: {d: {e: boolean}}}}} + )"); + ScopedFastInt sfi{FInt::LuauVisitRecursionLimit, 3}; + TypeId tType = requireType("t"); - CheckResult result = check(R"( - local t : {a: {b: {c: {d: {e: boolean}}}}} - )"); + CHECK_THROWS_AS(toString(tType), RecursionLimitException); + } + else + { + ScopedFastInt sfi{FInt::LuauVisitRecursionLimit, 3}; - TypeId tType = requireType("t"); + CheckResult result = check(R"( + local t : {a: {b: {c: {d: {e: boolean}}}}} + )"); - CHECK_THROWS_AS(toString(tType), RecursionLimitException); + TypeId tType = requireType("t"); + + CHECK_THROWS_AS(toString(tType), RecursionLimitException); + } } TEST_CASE_FIXTURE(Fixture, "dont_throw_when_limit_is_high_enough") diff --git a/tests/conformance/vector.lua b/tests/conformance/vector.lua index 7e4a9a3e..3c70cfb0 100644 --- a/tests/conformance/vector.lua +++ b/tests/conformance/vector.lua @@ -164,4 +164,23 @@ do assert(larget[vector(-0, 0, 0)] == 42) end +local function numvectemporary() + local proptab = {} + + proptab.vec3compsum = function(vec: vector) + local num = vec.X + vec.Y + local tmp = vec / num + local num2 = num * 2 + return tmp, num2 + end + + local a, b = proptab.vec3compsum(vector(2, 6, 0)) + + assert(a.X == 0.25) + assert(a.Y == 0.75) + assert(b == 16) +end + +numvectemporary() + return 'OK' diff --git a/tests/main.cpp b/tests/main.cpp index 42c3ad80..4612829b 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -51,7 +51,7 @@ static bool skipFastFlag(const char* flagName) if (strncmp(flagName, "Debug", 5) == 0) return true; - if (strcmp(flagName, "StudioReportLuauAny") == 0) + if (strcmp(flagName, "StudioReportLuauAny2") == 0) return true; return false; diff --git a/tools/faillist.txt b/tools/faillist.txt index d785d14d..e69de29b 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -1,45 +0,0 @@ -AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg -AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg -AutocompleteTest.autocomplete_string_singletons -AutocompleteTest.suggest_table_keys -AutocompleteTest.type_correct_suggestion_for_overloads -AutocompleteTest.type_correct_suggestion_in_table -GenericsTests.do_not_always_instantiate_generic_intersection_types -GenericsTests.error_detailed_function_mismatch_generic_pack -GenericsTests.error_detailed_function_mismatch_generic_types -GenericsTests.factories_of_generics -GenericsTests.generic_argument_count_too_few -GenericsTests.generic_argument_count_too_many -GenericsTests.generic_factories -GenericsTests.generic_functions_in_types -GenericsTests.generic_type_functions_work_in_subtyping -GenericsTests.generic_type_pack_unification1 -GenericsTests.generic_type_pack_unification2 -GenericsTests.generic_type_pack_unification3 -GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments -GenericsTests.instantiated_function_argument_names -GenericsTests.properties_can_be_instantiated_polytypes -GenericsTests.quantify_functions_even_if_they_have_an_explicit_generic -IntersectionTypes.intersect_metatables -IntersectionTypes.intersect_saturate_overloaded_functions -IntersectionTypes.intersection_of_tables -IntersectionTypes.intersection_of_tables_with_top_properties -IntersectionTypes.less_greedy_unification_with_intersection_types -IntersectionTypes.overloaded_functions_mentioning_generic -IntersectionTypes.overloaded_functions_mentioning_generic_packs -IntersectionTypes.overloaded_functions_mentioning_generics -IntersectionTypes.overloaded_functions_returning_intersections -IntersectionTypes.overloadeded_functions_with_never_arguments -ModuleTests.clone_self_property -Negations.cofinite_strings_can_be_compared_for_equality -Normalize.higher_order_function_with_annotation -RefinementTest.refine_a_param_that_got_resolved_during_constraint_solving_stage -RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table -RefinementTest.x_is_not_instance_or_else_not_part -TypeInferFunctions.simple_unannotated_mutual_recursion -TypeInferUnknownNever.assign_to_local_which_is_never -TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never -TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never -TypePackTests.fuzz_typepack_iter_follow_2 -TypeStatesTest.typestates_preserve_error_suppression_properties -VisitType.throw_when_limit_is_exceeded