From 62483d40f0f2b818886fafe4b68e85cb762e793e Mon Sep 17 00:00:00 2001 From: vegorov-rbx <75688451+vegorov-rbx@users.noreply.github.com> Date: Fri, 3 Feb 2023 21:26:13 +0200 Subject: [PATCH] Sync to upstream/release/562 (#828) * Fixed rare use-after-free in analysis during table unification A lot of work these past months went into two new Luau components: * A near full rewrite of the typechecker using a new deferred constraint resolution system * Native code generation for AoT/JiT compilation of VM bytecode into x64 (avx)/arm64 instructions Both of these components are far from finished and we don't provide documentation on building and using them at this point. However, curious community members expressed interest in learning about changes that go into these components each week, so we are now listing them here in the 'sync' pull request descriptions. --- New typechecker can be enabled by setting DebugLuauDeferredConstraintResolution flag to 'true'. It is considered unstable right now, so try it at your own risk. Even though it already provides better type inference than the current one in some cases, our main goal right now is to reach feature parity with current typechecker. Features which improve over the capabilities of the current typechecker are marked as '(NEW)'. Changes to new typechecker: * Regular for loop index and parameters are now typechecked * Invalid type annotations on local variables are ignored to improve autocomplete * Fixed missing autocomplete type suggestions for function arguments * Type reduction is now performed to produce simpler types to be presented to the user (error messages, custom LSPs) * Internally, complex types like '((number | string) & ~(false?)) | string' can be produced, which is just 'string | number' when simplified * Fixed spots where support for unknown and never types was missing * (NEW) Length operator '#' is now valid to use on top table type, this type comes up when doing typeof(x) == "table" guards and isn't available in current typechecker --- Changes to native code generation: * Additional math library fast calls are now lowered to x64: math.ldexp, math.round, math.frexp, math.modf, math.sign and math.clamp --- Analysis/include/Luau/Connective.h | 70 --- Analysis/include/Luau/Constraint.h | 6 +- .../include/Luau/ConstraintGraphBuilder.h | 46 +- Analysis/include/Luau/Module.h | 9 +- Analysis/include/Luau/Refinement.h | 68 +++ Analysis/include/Luau/Type.h | 8 +- Analysis/include/Luau/TypeReduction.h | 15 + Analysis/include/Luau/Unifier.h | 3 +- Analysis/src/Autocomplete.cpp | 22 +- Analysis/src/BuiltinDefinitions.cpp | 8 +- Analysis/src/ConstraintGraphBuilder.cpp | 165 +++--- Analysis/src/ConstraintSolver.cpp | 4 +- Analysis/src/Frontend.cpp | 19 +- Analysis/src/Linter.cpp | 4 - Analysis/src/Module.cpp | 3 - .../src/{Connective.cpp => Refinement.cpp} | 14 +- Analysis/src/Type.cpp | 2 +- Analysis/src/TypeChecker2.cpp | 179 +++++-- Analysis/src/TypeInfer.cpp | 2 + Analysis/src/TypeReduction.cpp | 170 ++++-- Analysis/src/Unifier.cpp | 97 ++-- Ast/include/Luau/Ast.h | 1 - Ast/src/Parser.cpp | 28 - CodeGen/include/Luau/AssemblyBuilderX64.h | 7 + CodeGen/{src => include/Luau}/IrAnalysis.h | 0 CodeGen/{src => include/Luau}/IrBuilder.h | 3 +- CodeGen/{src => include/Luau}/IrData.h | 0 CodeGen/{src => include/Luau}/IrDump.h | 2 +- CodeGen/{src => include/Luau}/IrUtils.h | 3 +- CodeGen/src/AssemblyBuilderX64.cpp | 29 ++ CodeGen/src/CodeGen.cpp | 4 +- CodeGen/src/EmitBuiltinsX64.cpp | 203 ++++++++ CodeGen/src/EmitCommonX64.h | 1 + CodeGen/src/IrAnalysis.cpp | 6 +- CodeGen/src/IrBuilder.cpp | 4 +- CodeGen/src/IrDump.cpp | 4 +- CodeGen/src/IrLoweringX64.cpp | 4 +- CodeGen/src/IrLoweringX64.h | 3 +- CodeGen/src/IrTranslation.cpp | 3 +- CodeGen/src/NativeState.cpp | 4 + CodeGen/src/NativeState.h | 4 + Compiler/src/Compiler.cpp | 58 +-- Makefile | 4 +- Sources.cmake | 14 +- VM/src/lperf.cpp | 2 + VM/src/lstrlib.cpp | 20 +- fuzz/proto.cpp | 38 +- tests/AssemblyBuilderX64.test.cpp | 9 + tests/Autocomplete.test.cpp | 68 +-- tests/Compiler.test.cpp | 12 - tests/Conformance.test.cpp | 2 - tests/Linter.test.cpp | 17 - tests/Normalize.test.cpp | 25 +- tests/Parser.test.cpp | 9 - tests/ToDot.test.cpp | 57 +- tests/ToString.test.cpp | 12 +- tests/TypeInfer.aliases.test.cpp | 2 - tests/TypeInfer.intersectionTypes.test.cpp | 160 ++++-- tests/TypeInfer.loops.test.cpp | 28 + tests/TypeInfer.operators.test.cpp | 92 +--- tests/TypeInfer.provisional.test.cpp | 62 ++- tests/TypeInfer.refinements.test.cpp | 492 ++++-------------- tests/TypeInfer.tables.test.cpp | 58 +++ tests/TypeInfer.typePacks.cpp | 2 +- tests/TypeInfer.unknownnever.test.cpp | 48 +- tests/TypeReduction.test.cpp | 32 +- tools/faillist.txt | 35 +- 67 files changed, 1382 insertions(+), 1203 deletions(-) delete mode 100644 Analysis/include/Luau/Connective.h create mode 100644 Analysis/include/Luau/Refinement.h rename Analysis/src/{Connective.cpp => Refinement.cpp} (50%) rename CodeGen/{src => include/Luau}/IrAnalysis.h (100%) rename CodeGen/{src => include/Luau}/IrBuilder.h (98%) rename CodeGen/{src => include/Luau}/IrData.h (100%) rename CodeGen/{src => include/Luau}/IrDump.h (96%) rename CodeGen/{src => include/Luau}/IrUtils.h (99%) diff --git a/Analysis/include/Luau/Connective.h b/Analysis/include/Luau/Connective.h deleted file mode 100644 index d82bc4d..0000000 --- a/Analysis/include/Luau/Connective.h +++ /dev/null @@ -1,70 +0,0 @@ -// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#pragma once - -#include "Luau/Def.h" -#include "Luau/TypedAllocator.h" -#include "Luau/Variant.h" - -#include - -namespace Luau -{ - -struct Type; -using TypeId = const Type*; - -struct Negation; -struct Conjunction; -struct Disjunction; -struct Equivalence; -struct Proposition; -using Connective = Variant; -using ConnectiveId = Connective*; // Can and most likely is nullptr. - -struct Negation -{ - ConnectiveId connective; -}; - -struct Conjunction -{ - ConnectiveId lhs; - ConnectiveId rhs; -}; - -struct Disjunction -{ - ConnectiveId lhs; - ConnectiveId rhs; -}; - -struct Equivalence -{ - ConnectiveId lhs; - ConnectiveId rhs; -}; - -struct Proposition -{ - DefId def; - TypeId discriminantTy; -}; - -template -const T* get(ConnectiveId connective) -{ - return get_if(connective); -} - -struct ConnectiveArena -{ - TypedAllocator allocator; - - ConnectiveId negation(ConnectiveId connective); - ConnectiveId conjunction(ConnectiveId lhs, ConnectiveId rhs); - ConnectiveId disjunction(ConnectiveId lhs, ConnectiveId rhs); - ConnectiveId equivalence(ConnectiveId lhs, ConnectiveId rhs); - ConnectiveId proposition(DefId def, TypeId discriminantTy); -}; - -} // namespace Luau diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index f814cb9..8159b76 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -71,9 +71,9 @@ struct BinaryConstraint // When we dispatch this constraint, we update the key at this map to record // the overload that we selected. - const void* astFragment; - DenseHashMap* astOriginalCallTypes; - DenseHashMap* astOverloadResolvedTypes; + const AstNode* astFragment; + DenseHashMap* astOriginalCallTypes; + DenseHashMap* astOverloadResolvedTypes; }; // iteratee is iterable diff --git a/Analysis/include/Luau/ConstraintGraphBuilder.h b/Analysis/include/Luau/ConstraintGraphBuilder.h index aac99af..29afabf 100644 --- a/Analysis/include/Luau/ConstraintGraphBuilder.h +++ b/Analysis/include/Luau/ConstraintGraphBuilder.h @@ -2,7 +2,7 @@ #pragma once #include "Luau/Ast.h" -#include "Luau/Connective.h" +#include "Luau/Refinement.h" #include "Luau/Constraint.h" #include "Luau/DataFlowGraph.h" #include "Luau/Module.h" @@ -27,13 +27,13 @@ struct DcrLogger; struct Inference { TypeId ty = nullptr; - ConnectiveId connective = nullptr; + RefinementId refinement = nullptr; Inference() = default; - explicit Inference(TypeId ty, ConnectiveId connective = nullptr) + explicit Inference(TypeId ty, RefinementId refinement = nullptr) : ty(ty) - , connective(connective) + , refinement(refinement) { } }; @@ -41,13 +41,13 @@ struct Inference struct InferencePack { TypePackId tp = nullptr; - std::vector connectives; + std::vector refinements; InferencePack() = default; - explicit InferencePack(TypePackId tp, const std::vector& connectives = {}) + explicit InferencePack(TypePackId tp, const std::vector& refinements = {}) : tp(tp) - , connectives(connectives) + , refinements(refinements) { } }; @@ -74,35 +74,11 @@ struct ConstraintGraphBuilder // will enqueue them during solving. std::vector unqueuedConstraints; - // A mapping of AST node to TypeId. - DenseHashMap astTypes{nullptr}; - - // A mapping of AST node to TypePackId. - DenseHashMap astTypePacks{nullptr}; - - DenseHashMap astExpectedTypes{nullptr}; - - // If the node was applied as a function, this is the unspecialized type of - // that expression. - DenseHashMap astOriginalCallTypes{nullptr}; - - // If overload resolution was performed on this element, this is the - // overload that was selected. - DenseHashMap astOverloadResolvedTypes{nullptr}; - - - - // Types resolved from type annotations. Analogous to astTypes. - DenseHashMap astResolvedTypes{nullptr}; - - // Type packs resolved from type annotations. Analogous to astTypePacks. - DenseHashMap astResolvedTypePacks{nullptr}; - - // Defining scopes for AST nodes. + // The private scope of type aliases for which the type parameters belong to. DenseHashMap astTypeAliasDefiningScopes{nullptr}; NotNull dfg; - ConnectiveArena connectiveArena; + RefinementArena refinementArena; int recursionCount = 0; @@ -156,7 +132,7 @@ struct ConstraintGraphBuilder */ NotNull addConstraint(const ScopePtr& scope, std::unique_ptr c); - void applyRefinements(const ScopePtr& scope, Location location, ConnectiveId connective); + void applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement); /** * The entry point to the ConstraintGraphBuilder. This will construct a set @@ -213,7 +189,7 @@ struct ConstraintGraphBuilder Inference check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert); Inference check(const ScopePtr& scope, AstExprInterpString* interpString); Inference check(const ScopePtr& scope, AstExprTable* expr, std::optional expectedType); - std::tuple checkBinary(const ScopePtr& scope, AstExprBinary* binary, std::optional expectedType); + std::tuple checkBinary(const ScopePtr& scope, AstExprBinary* binary, std::optional expectedType); TypePackId checkLValues(const ScopePtr& scope, AstArray exprs); diff --git a/Analysis/include/Luau/Module.h b/Analysis/include/Luau/Module.h index 2cd6802..2faa029 100644 --- a/Analysis/include/Luau/Module.h +++ b/Analysis/include/Luau/Module.h @@ -74,14 +74,13 @@ struct Module DenseHashMap astTypePacks{nullptr}; DenseHashMap astExpectedTypes{nullptr}; - // Pointers are either AstExpr or AstStat. - DenseHashMap astOriginalCallTypes{nullptr}; - - // Pointers are either AstExpr or AstStat. - DenseHashMap astOverloadResolvedTypes{nullptr}; + DenseHashMap astOriginalCallTypes{nullptr}; + DenseHashMap astOverloadResolvedTypes{nullptr}; DenseHashMap astResolvedTypes{nullptr}; + DenseHashMap astOriginalResolvedTypes{nullptr}; DenseHashMap astResolvedTypePacks{nullptr}; + // Map AST nodes to the scope they create. Cannot be NotNull because we need a sentinel value for the map. DenseHashMap astScopes{nullptr}; diff --git a/Analysis/include/Luau/Refinement.h b/Analysis/include/Luau/Refinement.h new file mode 100644 index 0000000..3e1f234 --- /dev/null +++ b/Analysis/include/Luau/Refinement.h @@ -0,0 +1,68 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/Def.h" +#include "Luau/TypedAllocator.h" +#include "Luau/Variant.h" + +namespace Luau +{ + +struct Type; +using TypeId = const Type*; + +struct Negation; +struct Conjunction; +struct Disjunction; +struct Equivalence; +struct Proposition; +using Refinement = Variant; +using RefinementId = Refinement*; // Can and most likely is nullptr. + +struct Negation +{ + RefinementId refinement; +}; + +struct Conjunction +{ + RefinementId lhs; + RefinementId rhs; +}; + +struct Disjunction +{ + RefinementId lhs; + RefinementId rhs; +}; + +struct Equivalence +{ + RefinementId lhs; + RefinementId rhs; +}; + +struct Proposition +{ + DefId def; + TypeId discriminantTy; +}; + +template +const T* get(RefinementId refinement) +{ + return get_if(refinement); +} + +struct RefinementArena +{ + TypedAllocator allocator; + + RefinementId negation(RefinementId refinement); + RefinementId conjunction(RefinementId lhs, RefinementId rhs); + RefinementId disjunction(RefinementId lhs, RefinementId rhs); + RefinementId equivalence(RefinementId lhs, RefinementId rhs); + RefinementId proposition(DefId def, TypeId discriminantTy); +}; + +} // namespace Luau diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index 0136327..6c8e1bc 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -3,7 +3,7 @@ #include "Luau/Ast.h" #include "Luau/Common.h" -#include "Luau/Connective.h" +#include "Luau/Refinement.h" #include "Luau/DataFlowGraph.h" #include "Luau/DenseHash.h" #include "Luau/Def.h" @@ -266,12 +266,12 @@ struct MagicRefinementContext ScopePtr scope; NotNull cgb; NotNull dfg; - NotNull connectiveArena; - std::vector argumentConnectives; + NotNull refinementArena; + std::vector argumentRefinements; const class AstExprCall* callSite; }; -using DcrMagicRefinement = std::vector (*)(const MagicRefinementContext&); +using DcrMagicRefinement = std::vector (*)(const MagicRefinementContext&); struct FunctionType { diff --git a/Analysis/include/Luau/TypeReduction.h b/Analysis/include/Luau/TypeReduction.h index 7cc1697..0ad034a 100644 --- a/Analysis/include/Luau/TypeReduction.h +++ b/Analysis/include/Luau/TypeReduction.h @@ -32,11 +32,23 @@ struct TypeReduction explicit TypeReduction( NotNull arena, NotNull builtinTypes, NotNull handle, const TypeReductionOptions& opts = {}); + TypeReduction(const TypeReduction&) = delete; + TypeReduction& operator=(const TypeReduction&) = delete; + + TypeReduction(TypeReduction&&) = default; + TypeReduction& operator=(TypeReduction&&) = default; + std::optional reduce(TypeId ty); std::optional reduce(TypePackId tp); std::optional reduce(const TypeFun& fun); + /// Creating a child TypeReduction will allow the parent TypeReduction to share its memoization with the child TypeReductions. + /// This is safe as long as the parent's TypeArena continues to outlive both TypeReduction memoization. + TypeReduction fork(NotNull arena, const TypeReductionOptions& opts = {}) const; + private: + const TypeReduction* parent = nullptr; + NotNull arena; NotNull builtinTypes; NotNull handle; @@ -50,6 +62,9 @@ private: bool hasExceededCartesianProductLimit(TypeId ty) const; bool hasExceededCartesianProductLimit(TypePackId tp) const; + + std::optional memoizedof(TypeId ty) const; + std::optional memoizedof(TypePackId tp) const; }; } // namespace Luau diff --git a/Analysis/include/Luau/Unifier.h b/Analysis/include/Luau/Unifier.h index cd3e856..988ad9c 100644 --- a/Analysis/include/Luau/Unifier.h +++ b/Analysis/include/Luau/Unifier.h @@ -95,8 +95,7 @@ private: void tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed); void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed); void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed); - void tryUnifyTypeWithNegation(TypeId subTy, TypeId superTy); - void tryUnifyNegationWithType(TypeId subTy, TypeId superTy); + void tryUnifyNegations(TypeId subTy, TypeId superTy); TypePackId tryApplyOverloadedFunction(TypeId function, const NormalizedFunctionType& overloads, TypePackId args); diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 4e5403f..dd6e114 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -7,13 +7,13 @@ #include "Luau/ToString.h" #include "Luau/TypeInfer.h" #include "Luau/TypePack.h" +#include "Luau/TypeReduction.h" #include #include #include LUAU_FASTFLAGVARIABLE(LuauCompleteTableKeysBetter, false); -LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInIf, false); LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInWhile, false); LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInFor, false); LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringContent, false); @@ -1534,20 +1534,13 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M } else if (AstStatIf* statIf = extractStat(ancestry); statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position)) && - (!FFlag::LuauFixAutocompleteInIf || (statIf->condition && !statIf->condition->location.containsClosed(position)))) + (statIf->condition && !statIf->condition->location.containsClosed(position))) { - if (FFlag::LuauFixAutocompleteInIf) - { - AutocompleteEntryMap ret; - ret["then"] = {AutocompleteEntryKind::Keyword}; - ret["and"] = {AutocompleteEntryKind::Keyword}; - ret["or"] = {AutocompleteEntryKind::Keyword}; - return {std::move(ret), ancestry, AutocompleteContext::Keyword}; - } - else - { - return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword}; - } + AutocompleteEntryMap ret; + ret["then"] = {AutocompleteEntryKind::Keyword}; + ret["and"] = {AutocompleteEntryKind::Keyword}; + ret["or"] = {AutocompleteEntryKind::Keyword}; + return {std::move(ret), ancestry, AutocompleteContext::Keyword}; } else if (AstStatRepeat* statRepeat = node->as(); statRepeat && statRepeat->condition->is()) return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position); @@ -1671,7 +1664,6 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName return {}; ModulePtr module = frontend.moduleResolverForAutocomplete.getModule(moduleName); - if (!module) return {}; diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 1a5a6bf..006df6e 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -42,7 +42,7 @@ static bool dcrMagicFunctionSelect(MagicFunctionCallContext context); static bool dcrMagicFunctionRequire(MagicFunctionCallContext context); static bool dcrMagicFunctionPack(MagicFunctionCallContext context); -static std::vector dcrMagicRefinementAssert(const MagicRefinementContext& context); +static std::vector dcrMagicRefinementAssert(const MagicRefinementContext& context); TypeId makeUnion(TypeArena& arena, std::vector&& types) { @@ -624,12 +624,12 @@ static std::optional> magicFunctionAssert( return WithPredicate{arena.addTypePack(TypePack{std::move(head), tail})}; } -static std::vector dcrMagicRefinementAssert(const MagicRefinementContext& ctx) +static std::vector dcrMagicRefinementAssert(const MagicRefinementContext& ctx) { - if (ctx.argumentConnectives.empty()) + if (ctx.argumentRefinements.empty()) return {}; - ctx.cgb->applyRefinements(ctx.scope, ctx.callSite->location, ctx.argumentConnectives[0]); + ctx.cgb->applyRefinements(ctx.scope, ctx.callSite->location, ctx.argumentRefinements[0]); return {}; } diff --git a/Analysis/src/ConstraintGraphBuilder.cpp b/Analysis/src/ConstraintGraphBuilder.cpp index b6184e3..09182f5 100644 --- a/Analysis/src/ConstraintGraphBuilder.cpp +++ b/Analysis/src/ConstraintGraphBuilder.cpp @@ -191,16 +191,16 @@ static void unionRefinements(const std::unordered_map& lhs, const } } -static void computeRefinement(const ScopePtr& scope, ConnectiveId connective, std::unordered_map* refis, bool sense, +static void computeRefinement(const ScopePtr& scope, RefinementId refinement, std::unordered_map* refis, bool sense, NotNull arena, bool eq, std::vector* constraints) { using RefinementMap = std::unordered_map; - if (!connective) + if (!refinement) return; - else if (auto negation = get(connective)) - return computeRefinement(scope, negation->connective, refis, !sense, arena, eq, constraints); - else if (auto conjunction = get(connective)) + else if (auto negation = get(refinement)) + return computeRefinement(scope, negation->refinement, refis, !sense, arena, eq, constraints); + else if (auto conjunction = get(refinement)) { RefinementMap lhsRefis; RefinementMap rhsRefis; @@ -211,7 +211,7 @@ static void computeRefinement(const ScopePtr& scope, ConnectiveId connective, st if (!sense) unionRefinements(lhsRefis, rhsRefis, *refis, arena); } - else if (auto disjunction = get(connective)) + else if (auto disjunction = get(refinement)) { RefinementMap lhsRefis; RefinementMap rhsRefis; @@ -222,12 +222,12 @@ static void computeRefinement(const ScopePtr& scope, ConnectiveId connective, st if (sense) unionRefinements(lhsRefis, rhsRefis, *refis, arena); } - else if (auto equivalence = get(connective)) + else if (auto equivalence = get(refinement)) { computeRefinement(scope, equivalence->lhs, refis, sense, arena, true, constraints); computeRefinement(scope, equivalence->rhs, refis, sense, arena, true, constraints); } - else if (auto proposition = get(connective)) + else if (auto proposition = get(refinement)) { TypeId discriminantTy = proposition->discriminantTy; if (!sense && !eq) @@ -264,14 +264,14 @@ static std::pair computeDiscriminantType(NotNull arena return {def, discriminantTy}; } -void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location location, ConnectiveId connective) +void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement) { - if (!connective) + if (!refinement) return; std::unordered_map refinements; std::vector constraints; - computeRefinement(scope, connective, &refinements, /*sense*/ true, arena, /*eq*/ false, &constraints); + computeRefinement(scope, refinement, &refinements, /*sense*/ true, arena, /*eq*/ false, &constraints); for (auto [def, discriminantTy] : refinements) { @@ -559,7 +559,10 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local) void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_) { - auto checkNumber = [&](AstExpr* expr) { + if (for_->var->annotation) + resolveType(scope, for_->var->annotation, /* inTypeArguments */ false); + + auto inferNumber = [&](AstExpr* expr) { if (!expr) return; @@ -567,9 +570,9 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_) addConstraint(scope, expr->location, SubtypeConstraint{t, builtinTypes->numberType}); }; - checkNumber(for_->from); - checkNumber(for_->to); - checkNumber(for_->step); + inferNumber(for_->from); + inferNumber(for_->to); + inferNumber(for_->step); ScopePtr forScope = childScope(for_, scope); forScope->bindings[for_->var] = Binding{builtinTypes->numberType, for_->var->location}; @@ -770,23 +773,23 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatCompoundAssign* TypeId resultType = arena->addType(BlockedType{}); addConstraint(scope, assign->location, - BinaryConstraint{assign->op, varId, valueInf.ty, resultType, assign, &astOriginalCallTypes, &astOverloadResolvedTypes}); + BinaryConstraint{assign->op, varId, valueInf.ty, resultType, assign, &module->astOriginalCallTypes, &module->astOverloadResolvedTypes}); addConstraint(scope, assign->location, SubtypeConstraint{resultType, varId}); } void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifStatement) { ScopePtr condScope = childScope(ifStatement->condition, scope); - auto [_, connective] = check(condScope, ifStatement->condition, std::nullopt); + auto [_, refinement] = check(condScope, ifStatement->condition, std::nullopt); ScopePtr thenScope = childScope(ifStatement->thenbody, scope); - applyRefinements(thenScope, Location{}, connective); + applyRefinements(thenScope, Location{}, refinement); visit(thenScope, ifStatement->thenbody); if (ifStatement->elsebody) { ScopePtr elseScope = childScope(ifStatement->elsebody, scope); - applyRefinements(elseScope, Location{}, connectiveArena.negation(connective)); + applyRefinements(elseScope, Location{}, refinementArena.negation(refinement)); visit(elseScope, ifStatement->elsebody); } } @@ -1049,7 +1052,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* } LUAU_ASSERT(result.tp); - astTypePacks[expr] = result.tp; + module->astTypePacks[expr] = result.tp; return result; } @@ -1096,7 +1099,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa std::vector args; std::optional argTail; - std::vector argumentConnectives; + std::vector argumentRefinements; Checkpoint argCheckpoint = checkpoint(this); @@ -1113,7 +1116,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa // computing fnType. If computing that did not cause us to exceed a // recursion limit, we can fetch it from astTypes rather than // recomputing it. - TypeId* selfTy = astTypes.find(exprArgs[0]); + TypeId* selfTy = module->astTypes.find(exprArgs[0]); if (selfTy) args.push_back(*selfTy); else @@ -1121,9 +1124,9 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa } else if (i < exprArgs.size() - 1 || !(arg->is() || arg->is())) { - auto [ty, connective] = check(scope, arg, expectedType); + auto [ty, refinement] = check(scope, arg, expectedType); args.push_back(ty); - argumentConnectives.push_back(connective); + argumentRefinements.push_back(refinement); } else argTail = checkPack(scope, arg, {}).tp; // FIXME? not sure about expectedTypes here @@ -1137,11 +1140,11 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa constraint->dependencies.push_back(extractArgsConstraint); }); - std::vector returnConnectives; + std::vector returnRefinements; if (auto ftv = get(follow(fnType)); ftv && ftv->dcrMagicRefinement) { - MagicRefinementContext ctx{scope, NotNull{this}, dfg, NotNull{&connectiveArena}, std::move(argumentConnectives), call}; - returnConnectives = ftv->dcrMagicRefinement(ctx); + MagicRefinementContext ctx{scope, NotNull{this}, dfg, NotNull{&refinementArena}, std::move(argumentRefinements), call}; + returnRefinements = ftv->dcrMagicRefinement(ctx); } if (matchSetmetatable(*call)) @@ -1169,11 +1172,11 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa } - return InferencePack{arena->addTypePack({resultTy}), std::move(returnConnectives)}; + return InferencePack{arena->addTypePack({resultTy}), std::move(returnRefinements)}; } else { - astOriginalCallTypes[call->func] = fnType; + module->astOriginalCallTypes[call->func] = fnType; TypeId instantiatedType = arena->addType(BlockedType{}); // TODO: How do expectedTypes play into this? Do they? @@ -1208,7 +1211,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa fcc->dependencies.emplace_back(constraint.get()); }); - return InferencePack{rets, std::move(returnConnectives)}; + return InferencePack{rets, std::move(returnRefinements)}; } } @@ -1261,7 +1264,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr, st gc->dependencies.emplace_back(constraint.get()); }); - return Inference{generalizedTy}; + result = Inference{generalizedTy}; } else if (auto indexName = expr->as()) result = check(scope, indexName); @@ -1294,9 +1297,9 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr, st } LUAU_ASSERT(result.ty); - astTypes[expr] = result.ty; + module->astTypes[expr] = result.ty; if (expectedType) - astExpectedTypes[expr] = *expectedType; + module->astExpectedTypes[expr] = *expectedType; return result; } @@ -1366,7 +1369,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprLocal* loc return Inference{builtinTypes->errorRecoveryType()}; // TODO: replace with ice, locals should never exist before its definition. if (def) - return Inference{*resultTy, connectiveArena.proposition(*def, builtinTypes->truthyType)}; + return Inference{*resultTy, refinementArena.proposition(*def, builtinTypes->truthyType)}; else return Inference{*resultTy}; } @@ -1456,7 +1459,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* if (def) { if (auto ty = scope->lookup(*def)) - return Inference{*ty, connectiveArena.proposition(*def, builtinTypes->truthyType)}; + return Inference{*ty, refinementArena.proposition(*def, builtinTypes->truthyType)}; else scope->dcrRefinements[*def] = result; } @@ -1470,7 +1473,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* addConstraint(scope, indexName->expr->location, SubtypeConstraint{obj, expectedTableType}); if (def) - return Inference{result, connectiveArena.proposition(*def, builtinTypes->truthyType)}; + return Inference{result, refinementArena.proposition(*def, builtinTypes->truthyType)}; else return Inference{result}; } @@ -1492,48 +1495,40 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr* Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary) { - auto [operandType, connective] = check(scope, unary->expr); + auto [operandType, refinement] = check(scope, unary->expr); TypeId resultType = arena->addType(BlockedType{}); addConstraint(scope, unary->location, UnaryConstraint{unary->op, operandType, resultType}); if (unary->op == AstExprUnary::Not) - return Inference{resultType, connectiveArena.negation(connective)}; + return Inference{resultType, refinementArena.negation(refinement)}; else return Inference{resultType}; } Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binary, std::optional expectedType) { - auto [leftType, rightType, connective] = checkBinary(scope, binary, expectedType); + auto [leftType, rightType, refinement] = checkBinary(scope, binary, expectedType); TypeId resultType = arena->addType(BlockedType{}); addConstraint(scope, binary->location, - BinaryConstraint{binary->op, leftType, rightType, resultType, binary, &astOriginalCallTypes, &astOverloadResolvedTypes}); - return Inference{resultType, std::move(connective)}; + BinaryConstraint{binary->op, leftType, rightType, resultType, binary, &module->astOriginalCallTypes, &module->astOverloadResolvedTypes}); + return Inference{resultType, std::move(refinement)}; } Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional expectedType) { ScopePtr condScope = childScope(ifElse->condition, scope); - auto [_, connective] = check(scope, ifElse->condition); + auto [_, refinement] = check(scope, ifElse->condition); ScopePtr thenScope = childScope(ifElse->trueExpr, scope); - applyRefinements(thenScope, ifElse->trueExpr->location, connective); + applyRefinements(thenScope, ifElse->trueExpr->location, refinement); TypeId thenType = check(thenScope, ifElse->trueExpr, expectedType).ty; ScopePtr elseScope = childScope(ifElse->falseExpr, scope); - applyRefinements(elseScope, ifElse->falseExpr->location, connectiveArena.negation(connective)); + applyRefinements(elseScope, ifElse->falseExpr->location, refinementArena.negation(refinement)); TypeId elseType = check(elseScope, ifElse->falseExpr, expectedType).ty; - if (ifElse->hasElse) - { - TypeId resultType = expectedType ? *expectedType : freshType(scope); - addConstraint(scope, ifElse->trueExpr->location, SubtypeConstraint{thenType, resultType}); - addConstraint(scope, ifElse->falseExpr->location, SubtypeConstraint{elseType, resultType}); - return Inference{resultType}; - } - - return Inference{thenType}; + return Inference{expectedType ? *expectedType : arena->addType(UnionType{{thenType, elseType}})}; } Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert) @@ -1550,28 +1545,28 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprInterpStri return Inference{builtinTypes->stringType}; } -std::tuple ConstraintGraphBuilder::checkBinary( +std::tuple ConstraintGraphBuilder::checkBinary( const ScopePtr& scope, AstExprBinary* binary, std::optional expectedType) { if (binary->op == AstExprBinary::And) { - auto [leftType, leftConnective] = check(scope, binary->left, expectedType); + auto [leftType, leftRefinement] = check(scope, binary->left, expectedType); ScopePtr rightScope = childScope(binary->right, scope); - applyRefinements(rightScope, binary->right->location, leftConnective); - auto [rightType, rightConnective] = check(rightScope, binary->right, expectedType); + applyRefinements(rightScope, binary->right->location, leftRefinement); + auto [rightType, rightRefinement] = check(rightScope, binary->right, expectedType); - return {leftType, rightType, connectiveArena.conjunction(leftConnective, rightConnective)}; + return {leftType, rightType, refinementArena.conjunction(leftRefinement, rightRefinement)}; } else if (binary->op == AstExprBinary::Or) { - auto [leftType, leftConnective] = check(scope, binary->left, expectedType); + auto [leftType, leftRefinement] = check(scope, binary->left, expectedType); ScopePtr rightScope = childScope(binary->right, scope); - applyRefinements(rightScope, binary->right->location, connectiveArena.negation(leftConnective)); - auto [rightType, rightConnective] = check(rightScope, binary->right, expectedType); + applyRefinements(rightScope, binary->right->location, refinementArena.negation(leftRefinement)); + auto [rightType, rightRefinement] = check(rightScope, binary->right, expectedType); - return {leftType, rightType, connectiveArena.disjunction(leftConnective, rightConnective)}; + return {leftType, rightType, refinementArena.disjunction(leftRefinement, rightRefinement)}; } else if (auto typeguard = matchTypeGuard(binary)) { @@ -1613,11 +1608,11 @@ std::tuple ConstraintGraphBuilder::checkBinary( discriminantTy = ty; } - ConnectiveId proposition = connectiveArena.proposition(*def, discriminantTy); + RefinementId proposition = refinementArena.proposition(*def, discriminantTy); if (binary->op == AstExprBinary::CompareEq) return {leftType, rightType, proposition}; else if (binary->op == AstExprBinary::CompareNe) - return {leftType, rightType, connectiveArena.negation(proposition)}; + return {leftType, rightType, refinementArena.negation(proposition)}; else ice->ice("matchTypeGuard should only return a Some under `==` or `~=`!"); } @@ -1626,21 +1621,21 @@ std::tuple ConstraintGraphBuilder::checkBinary( TypeId leftType = check(scope, binary->left, expectedType, true).ty; TypeId rightType = check(scope, binary->right, expectedType, true).ty; - ConnectiveId leftConnective = nullptr; + RefinementId leftRefinement = nullptr; if (auto def = dfg->getDef(binary->left)) - leftConnective = connectiveArena.proposition(*def, rightType); + leftRefinement = refinementArena.proposition(*def, rightType); - ConnectiveId rightConnective = nullptr; + RefinementId rightRefinement = nullptr; if (auto def = dfg->getDef(binary->right)) - rightConnective = connectiveArena.proposition(*def, leftType); + rightRefinement = refinementArena.proposition(*def, leftType); if (binary->op == AstExprBinary::CompareNe) { - leftConnective = connectiveArena.negation(leftConnective); - rightConnective = connectiveArena.negation(rightConnective); + leftRefinement = refinementArena.negation(leftRefinement); + rightRefinement = refinementArena.negation(rightRefinement); } - return {leftType, rightType, connectiveArena.equivalence(leftConnective, rightConnective)}; + return {leftType, rightType, refinementArena.equivalence(leftRefinement, rightRefinement)}; } else { @@ -1737,13 +1732,13 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr) for (size_t i = 0; i < segments.size(); ++i) { TypeId segmentTy = arena->addType(BlockedType{}); - astTypes[exprs[i]] = segmentTy; + module->astTypes[exprs[i]] = segmentTy; addConstraint(scope, expr->location, HasPropConstraint{segmentTy, prevSegmentTy, segments[i]}); prevSegmentTy = segmentTy; } - astTypes[expr] = prevSegmentTy; - astTypes[e] = updatedType; + module->astTypes[expr] = prevSegmentTy; + module->astTypes[e] = updatedType; // astTypes[expr] = propTy; return propTy; @@ -1895,6 +1890,10 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS if (local->annotation) { annotationTy = resolveType(signatureScope, local->annotation, /* inTypeArguments */ false); + // If we provide an annotation that is wrong, type inference should ignore the annotation + // and try to infer a fresh type, like in the old solver + if (get(follow(annotationTy))) + annotationTy = freshType(signatureScope); addConstraint(signatureScope, local->annotation->location, SubtypeConstraint{t, annotationTy}); } else if (i < expectedArgPack.head.size()) @@ -1964,7 +1963,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS TypeId actualFunctionType = arena->addType(std::move(actualFunction)); LUAU_ASSERT(actualFunctionType); - astTypes[fn] = actualFunctionType; + module->astTypes[fn] = actualFunctionType; if (expectedType && get(*expectedType)) { @@ -2214,7 +2213,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b result = builtinTypes->errorRecoveryType(); } - astResolvedTypes[ty] = result; + module->astResolvedTypes[ty] = result; return result; } @@ -2248,7 +2247,7 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTyp result = builtinTypes->errorRecoveryTypePack(); } - astResolvedTypePacks[tp] = result; + module->astResolvedTypePacks[tp] = result; return result; } @@ -2307,13 +2306,13 @@ std::vector> ConstraintGraphBuilder:: Inference ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location location, InferencePack pack) { - const auto& [tp, connectives] = pack; - ConnectiveId connective = nullptr; - if (!connectives.empty()) - connective = connectives[0]; + const auto& [tp, refinements] = pack; + RefinementId refinement = nullptr; + if (!refinements.empty()) + refinement = refinements[0]; if (auto f = first(tp)) - return Inference{*f, connective}; + return Inference{*f, refinement}; TypeId typeResult = freshType(scope); TypePack onePack{{typeResult}, freshTypePack(scope)}; @@ -2321,7 +2320,7 @@ Inference ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location lo addConstraint(scope, location, PackSubtypeConstraint{tp, oneTypePack}); - return Inference{typeResult, connective}; + return Inference{typeResult, refinement}; } void ConstraintGraphBuilder::reportError(Location location, TypeErrorData err) diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 0a9b82b..96d16c4 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -528,7 +528,7 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNull(operandType) || get(operandType)) + if (isNumber(operandType) || get(operandType) || get(operandType) || get(operandType)) { asMutable(c.resultType)->ty.emplace(c.operandType); } @@ -1415,7 +1415,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull(subjectType) || get(subjectType)) + else if (get(subjectType) || get(subjectType) || get(subjectType)) { bind(c.resultType, subjectType); return true; diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index d200df3..94342cc 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -579,6 +579,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optionalastOriginalCallTypes.clear(); module->astOverloadResolvedTypes.clear(); module->astResolvedTypes.clear(); + module->astOriginalResolvedTypes.clear(); module->astResolvedTypePacks.clear(); module->astScopes.clear(); @@ -591,6 +592,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optionalastOriginalCallTypes.clear(); module->astResolvedTypes.clear(); module->astResolvedTypePacks.clear(); + module->astOriginalResolvedTypes.clear(); module->scopes.resize(1); } } @@ -922,23 +924,22 @@ ModulePtr Frontend::check( for (TypeError& e : cs.errors) result->errors.emplace_back(std::move(e)); + result->scopes = std::move(cgb.scopes); - result->astTypes = std::move(cgb.astTypes); - result->astTypePacks = std::move(cgb.astTypePacks); - result->astExpectedTypes = std::move(cgb.astExpectedTypes); - result->astOriginalCallTypes = std::move(cgb.astOriginalCallTypes); - result->astOverloadResolvedTypes = std::move(cgb.astOverloadResolvedTypes); - result->astResolvedTypes = std::move(cgb.astResolvedTypes); - result->astResolvedTypePacks = std::move(cgb.astResolvedTypePacks); result->type = sourceModule.type; result->clonePublicInterface(builtinTypes, iceHandler); + Luau::check(builtinTypes, logger.get(), sourceModule, result.get()); + + // Ideally we freeze the arenas before the call into Luau::check, but TypeReduction + // needs to allocate new types while Luau::check is in progress, so here we are. + // + // It does mean that mutations to the type graph can happen after the constraints + // have been solved, which will cause hard-to-debug problems. We should revisit this. freeze(result->internalTypes); freeze(result->interfaceTypes); - Luau::check(builtinTypes, logger.get(), sourceModule, result.get()); - if (FFlag::DebugLuauLogSolverToJson) { std::string output = logger->compileOutput(); diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index 752259b..65ad8a8 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -2616,10 +2616,6 @@ private: emitWarning(*context, LintWarning::Code_IntegerParsing, node->location, "Hexadecimal number literal exceeded available precision and has been truncated to 2^64"); break; - case ConstantNumberParseResult::DoublePrefix: - emitWarning(*context, LintWarning::Code_IntegerParsing, node->location, - "Hexadecimal number literal has a double prefix, which will fail to parse in the future; remove the extra 0x to fix"); - break; } return true; diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index e54a449..a9faded 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -232,9 +232,6 @@ void Module::clonePublicInterface(NotNull builtinTypes, InternalEr this->returnType = moduleScope->returnType; this->exportedTypeBindings = std::move(moduleScope->exportedTypeBindings); } - - freeze(internalTypes); - freeze(interfaceTypes); } bool Module::hasModuleScope() const diff --git a/Analysis/src/Connective.cpp b/Analysis/src/Refinement.cpp similarity index 50% rename from Analysis/src/Connective.cpp rename to Analysis/src/Refinement.cpp index 114b5f2..fb019f1 100644 --- a/Analysis/src/Connective.cpp +++ b/Analysis/src/Refinement.cpp @@ -1,30 +1,30 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Luau/Connective.h" +#include "Luau/Refinement.h" namespace Luau { -ConnectiveId ConnectiveArena::negation(ConnectiveId connective) +RefinementId RefinementArena::negation(RefinementId refinement) { - return NotNull{allocator.allocate(Negation{connective})}; + return NotNull{allocator.allocate(Negation{refinement})}; } -ConnectiveId ConnectiveArena::conjunction(ConnectiveId lhs, ConnectiveId rhs) +RefinementId RefinementArena::conjunction(RefinementId lhs, RefinementId rhs) { return NotNull{allocator.allocate(Conjunction{lhs, rhs})}; } -ConnectiveId ConnectiveArena::disjunction(ConnectiveId lhs, ConnectiveId rhs) +RefinementId RefinementArena::disjunction(RefinementId lhs, RefinementId rhs) { return NotNull{allocator.allocate(Disjunction{lhs, rhs})}; } -ConnectiveId ConnectiveArena::equivalence(ConnectiveId lhs, ConnectiveId rhs) +RefinementId RefinementArena::equivalence(RefinementId lhs, RefinementId rhs) { return NotNull{allocator.allocate(Equivalence{lhs, rhs})}; } -ConnectiveId ConnectiveArena::proposition(DefId def, TypeId discriminantTy) +RefinementId RefinementArena::proposition(DefId def, TypeId discriminantTy) { return NotNull{allocator.allocate(Proposition{def, discriminantTy})}; } diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index f29a022..f874a0b 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -414,7 +414,7 @@ bool hasLength(TypeId ty, DenseHashSet& seen, int* recursionCount) if (seen.contains(ty)) return true; - if (isString(ty) || get(ty) || get(ty) || get(ty)) + if (isString(ty) || isPrim(ty, PrimitiveType::Table) || get(ty) || get(ty) || get(ty)) return true; if (auto uty = get(ty)) diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 59c488f..133e324 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -4,22 +4,24 @@ #include "Luau/Ast.h" #include "Luau/AstQuery.h" #include "Luau/Clone.h" +#include "Luau/DcrLogger.h" #include "Luau/Error.h" #include "Luau/Instantiation.h" #include "Luau/Metamethods.h" #include "Luau/Normalize.h" #include "Luau/ToString.h" #include "Luau/TxnLog.h" -#include "Luau/TypeUtils.h" #include "Luau/Type.h" +#include "Luau/TypeReduction.h" +#include "Luau/TypeUtils.h" #include "Luau/Unifier.h" -#include "Luau/ToString.h" -#include "Luau/DcrLogger.h" #include -LUAU_FASTFLAG(DebugLuauLogSolverToJson); -LUAU_FASTFLAG(DebugLuauMagicTypes); +LUAU_FASTFLAG(DebugLuauLogSolverToJson) +LUAU_FASTFLAG(DebugLuauMagicTypes) +LUAU_FASTFLAG(DebugLuauDontReduceTypes) + LUAU_FASTFLAG(LuauNegatedClassTypes) namespace Luau @@ -223,10 +225,7 @@ struct TypeChecker2 { auto pusher = pushStack(stat); - if (0) - { - } - else if (auto s = stat->as()) + if (auto s = stat->as()) return visit(s); else if (auto s = stat->as()) return visit(s); @@ -340,8 +339,7 @@ struct TypeChecker2 if (value) visit(value, RValue); - TypeId* maybeValueType = value ? module->astTypes.find(value) : nullptr; - if (i != local->values.size - 1 || maybeValueType) + if (i != local->values.size - 1 || value) { AstLocal* var = i < local->vars.size ? local->vars.data[i] : nullptr; @@ -391,13 +389,26 @@ struct TypeChecker2 void visit(AstStatFor* forStatement) { - if (forStatement->var->annotation) - visit(forStatement->var->annotation); + NotNull scope = stack.back(); + + if (forStatement->var->annotation) + { + visit(forStatement->var->annotation); + reportErrors(tryUnify(scope, forStatement->var->location, builtinTypes->numberType, lookupAnnotation(forStatement->var->annotation))); + } + + auto checkNumber = [this, scope](AstExpr* expr) { + if (!expr) + return; + + visit(expr, RValue); + reportErrors(tryUnify(scope, expr->location, lookupType(expr), builtinTypes->numberType)); + }; + + checkNumber(forStatement->from); + checkNumber(forStatement->to); + checkNumber(forStatement->step); - visit(forStatement->from, RValue); - visit(forStatement->to, RValue); - if (forStatement->step) - visit(forStatement->step, RValue); visit(forStatement->body); } @@ -543,7 +554,7 @@ struct TypeChecker2 else reportError(GenericError{"Cannot iterate over a table without indexer"}, forInStatement->values.data[0]->location); } - else if (get(iteratorTy) || get(iteratorTy)) + else if (get(iteratorTy) || get(iteratorTy) || get(iteratorTy)) { // nothing } @@ -624,6 +635,9 @@ struct TypeChecker2 visit(rhs, RValue); TypeId rhsType = lookupType(rhs); + if (get(lhsType)) + continue; + if (!isSubtype(rhsType, lhsType, stack.back())) { reportError(TypeMismatch{lhsType, rhsType}, rhs->location); @@ -715,10 +729,7 @@ struct TypeChecker2 { auto StackPusher = pushStack(expr); - if (0) - { - } - else if (auto e = expr->as()) + if (auto e = expr->as()) return visit(e, context); else if (auto e = expr->as()) return visit(e); @@ -770,34 +781,34 @@ struct TypeChecker2 void visit(AstExprConstantNil* expr) { - // TODO! + NotNull scope = stack.back(); + TypeId actualType = lookupType(expr); + TypeId expectedType = builtinTypes->nilType; + LUAU_ASSERT(isSubtype(actualType, expectedType, scope)); } void visit(AstExprConstantBool* expr) { - // TODO! + NotNull scope = stack.back(); + TypeId actualType = lookupType(expr); + TypeId expectedType = builtinTypes->booleanType; + LUAU_ASSERT(isSubtype(actualType, expectedType, scope)); } - void visit(AstExprConstantNumber* number) + void visit(AstExprConstantNumber* expr) { - TypeId actualType = lookupType(number); - TypeId numberType = builtinTypes->numberType; - - if (!isSubtype(numberType, actualType, stack.back())) - { - reportError(TypeMismatch{actualType, numberType}, number->location); - } + NotNull scope = stack.back(); + TypeId actualType = lookupType(expr); + TypeId expectedType = builtinTypes->numberType; + LUAU_ASSERT(isSubtype(actualType, expectedType, scope)); } - void visit(AstExprConstantString* string) + void visit(AstExprConstantString* expr) { - TypeId actualType = lookupType(string); - TypeId stringType = builtinTypes->stringType; - - if (!isSubtype(actualType, stringType, stack.back())) - { - reportError(TypeMismatch{actualType, stringType}, string->location); - } + NotNull scope = stack.back(); + TypeId actualType = lookupType(expr); + TypeId expectedType = builtinTypes->stringType; + LUAU_ASSERT(isSubtype(actualType, expectedType, scope)); } void visit(AstExprLocal* expr) @@ -832,7 +843,7 @@ struct TypeChecker2 std::vector argLocs; argLocs.reserve(call->args.size + 1); - if (get(functionType) || get(functionType)) + if (get(functionType) || get(functionType) || get(functionType)) return; else if (std::optional callMm = findMetatableEntry(builtinTypes, module->errors, functionType, "__call", call->func->location)) { @@ -1080,7 +1091,7 @@ struct TypeChecker2 } } - TypeId visit(AstExprBinary* expr, void* overrideKey = nullptr) + TypeId visit(AstExprBinary* expr, AstNode* overrideKey = nullptr) { visit(expr->left, LValue); visit(expr->right, LValue); @@ -1164,7 +1175,7 @@ struct TypeChecker2 if (mm) { - void* key = expr; + AstNode* key = expr; if (overrideKey != nullptr) key = overrideKey; @@ -1381,19 +1392,8 @@ struct TypeChecker2 { pack = follow(pack); - while (true) - { - auto tp = get(pack); - if (tp && tp->head.empty() && tp->tail) - pack = *tp->tail; - else - break; - } - - if (auto ty = first(pack)) - return *ty; - else if (auto vtp = get(pack)) - return vtp->ty; + if (auto fst = first(pack, /*ignoreHiddenVariadics*/ false)) + return *fst; else if (auto ftp = get(pack)) { TypeId result = testArena.addType(FreeType{ftp->scope}); @@ -1407,6 +1407,8 @@ struct TypeChecker2 } else if (get(pack)) return builtinTypes->errorRecoveryType(); + else if (finite(pack) && size(pack) == 0) + return builtinTypes->nilType; // `(f())` where `f()` returns no values is coerced into `nil` else ice.ice("flattenPack got a weird pack!"); } @@ -1652,6 +1654,69 @@ struct TypeChecker2 } } + void reduceTypes() + { + if (FFlag::DebugLuauDontReduceTypes) + return; + + for (auto [_, scope] : module->scopes) + { + for (auto& [_, b] : scope->bindings) + { + if (auto reduced = module->reduction->reduce(b.typeId)) + b.typeId = *reduced; + } + + if (auto reduced = module->reduction->reduce(scope->returnType)) + scope->returnType = *reduced; + + if (scope->varargPack) + { + if (auto reduced = module->reduction->reduce(*scope->varargPack)) + scope->varargPack = *reduced; + } + + auto reduceMap = [this](auto& map) { + for (auto& [_, tf] : map) + { + if (auto reduced = module->reduction->reduce(tf)) + tf = *reduced; + } + }; + + reduceMap(scope->exportedTypeBindings); + reduceMap(scope->privateTypeBindings); + reduceMap(scope->privateTypePackBindings); + for (auto& [_, space] : scope->importedTypeBindings) + reduceMap(space); + } + + auto reduceOrError = [this](auto& map) { + for (auto [ast, t] : map) + { + if (!t) + continue; // Reminder: this implies that the recursion limit was exceeded. + else if (auto reduced = module->reduction->reduce(t)) + map[ast] = *reduced; + else + reportError(NormalizationTooComplex{}, ast->location); + } + }; + + module->astOriginalResolvedTypes = module->astResolvedTypes; + + // Both [`Module::returnType`] and [`Module::exportedTypeBindings`] are empty here, and + // is populated by [`Module::clonePublicInterface`] in the future, so by that point these + // two aforementioned fields will only contain types that are irreducible. + reduceOrError(module->astTypes); + reduceOrError(module->astTypePacks); + reduceOrError(module->astExpectedTypes); + reduceOrError(module->astOriginalCallTypes); + reduceOrError(module->astOverloadResolvedTypes); + reduceOrError(module->astResolvedTypes); + reduceOrError(module->astResolvedTypePacks); + } + template bool isSubtype(TID subTy, TID superTy, NotNull scope) { @@ -1797,7 +1862,7 @@ struct TypeChecker2 void check(NotNull builtinTypes, DcrLogger* logger, const SourceModule& sourceModule, Module* module) { TypeChecker2 typeChecker{builtinTypes, logger, &sourceModule, module}; - + typeChecker.reduceTypes(); typeChecker.visit(sourceModule.root); unfreeze(module->interfaceTypes); diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index a25ddc7..07bdbd4 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -323,6 +323,8 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo normalizer.arena = nullptr; currentModule->clonePublicInterface(builtinTypes, *iceHandler); + freeze(currentModule->internalTypes); + freeze(currentModule->interfaceTypes); // Clear unifier cache since it's keyed off internal types that get deallocated // This avoids fake cross-module cache hits and keeps cache size at bay when typechecking large module graphs. diff --git a/Analysis/src/TypeReduction.cpp b/Analysis/src/TypeReduction.cpp index 3404c71..94fb4ad 100644 --- a/Analysis/src/TypeReduction.cpp +++ b/Analysis/src/TypeReduction.cpp @@ -10,7 +10,7 @@ #include LUAU_FASTINTVARIABLE(LuauTypeReductionCartesianProductLimit, 100'000) -LUAU_FASTINTVARIABLE(LuauTypeReductionRecursionLimit, 700) +LUAU_FASTINTVARIABLE(LuauTypeReductionRecursionLimit, 400) LUAU_FASTFLAGVARIABLE(DebugLuauDontReduceTypes, false) namespace Luau @@ -37,7 +37,7 @@ struct TypeReducer DenseHashMap>* memoizedTypes; DenseHashMap>* memoizedTypePacks; - DenseHashSet* cyclicTypes; + DenseHashSet* cyclics; int depth = 0; @@ -68,8 +68,8 @@ struct TypeReducer return {ctx->type, getMutable(ctx->type)}; TypeId copiedTy = arena->addType(*t); - (*memoizedTypes)[ty] = {copiedTy, false}; - (*memoizedTypes)[copiedTy] = {copiedTy, false}; + (*memoizedTypes)[ty] = {copiedTy, true}; + (*memoizedTypes)[copiedTy] = {copiedTy, true}; return {copiedTy, getMutable(copiedTy)}; } @@ -142,31 +142,20 @@ struct TypeReducer std::vector result; bool didReduce = false; foldl_impl(it, endIt, f, &result, &didReduce); - if (!didReduce && ty) - return *ty; + + // If we've done any reduction, then we'll need to reduce it again, e.g. + // `"a" | "b" | string` is reduced into `string | string`, which is then reduced into `string`. + if (!didReduce) + return ty ? *ty : flatten(std::move(result)); else - { - // If we've done any reduction, then we'll need to reduce it again, e.g. - // `"a" | "b" | string` is reduced into `string | string`, which is then reduced into `string`. return reduce(flatten(std::move(result))); - } } template TypeId apply(BinaryFold f, TypeId left, TypeId right) { - left = follow(left); - right = follow(right); - - if (get(left) || get(right)) - { - std::vector types{left, right}; - return foldl(begin(types), end(types), std::nullopt, f); - } - else if (auto reduced = (this->*f)(left, right)) - return *reduced; - else - return arena->addType(T{{left, right}}); + std::vector types{left, right}; + return foldl(begin(types), end(types), std::nullopt, f); } template @@ -188,8 +177,8 @@ TypeId TypeReducer::reduce(TypeId ty) if (auto ctx = memoizedTypes->find(ty); ctx && ctx->irreducible) return ctx->type; - else if (auto cyclicTy = cyclicTypes->find(ty)) - return *cyclicTy; + else if (cyclics->contains(ty)) + return ty; RecursionLimiter rl{&depth, FInt::LuauTypeReductionRecursionLimit}; @@ -216,6 +205,8 @@ TypePackId TypeReducer::reduce(TypePackId tp) if (auto ctx = memoizedTypePacks->find(tp); ctx && ctx->irreducible) return ctx->type; + else if (cyclics->contains(tp)) + return tp; RecursionLimiter rl{&depth, FInt::LuauTypeReductionRecursionLimit}; @@ -356,9 +347,9 @@ std::optional TypeReducer::intersectionType(TypeId left, TypeId right) else if (t1->state == TableState::Generic || t2->state == TableState::Generic) return std::nullopt; // '{ x: T } & { x: U } ~ '{ x: T } & { x: U } - if (cyclicTypes->find(left)) + if (cyclics->contains(left)) return std::nullopt; // (t1 where t1 = { p: t1 }) & {} ~ t1 & {} - else if (cyclicTypes->find(right)) + else if (cyclics->contains(right)) return std::nullopt; // {} & (t1 where t1 = { p: t1 }) ~ {} & t1 TypeId resultTy = arena->addType(TableType{}); @@ -396,10 +387,7 @@ std::optional TypeReducer::intersectionType(TypeId left, TypeId right) return std::nullopt; // { [string]: _ } & { [number]: _ } ~ { [string]: _ } & { [number]: _ } TypeId valueTy = apply(&TypeReducer::intersectionType, t1->indexer->indexResultType, t2->indexer->indexResultType); - if (get(valueTy)) - return builtinTypes->neverType; // { [_]: string } & { [_]: number } ~ { [_]: string & number } ~ { [_]: never } ~ never - - table->indexer = TableIndexer{keyTy, valueTy}; + table->indexer = TableIndexer{keyTy, valueTy}; // { [string]: number } & { [string]: string } ~ { [string]: never } } else if (t1->indexer) { @@ -422,6 +410,45 @@ std::optional TypeReducer::intersectionType(TypeId left, TypeId right) return intersectionType(right, left); // T & M ~ M & T else if (auto [m1, m2] = get2(left, right); m1 && m2) return std::nullopt; // TODO + else if (auto [nl, nr] = get2(left, right); nl && nr) + { + // These should've been reduced already. + TypeId nlTy = follow(nl->ty); + TypeId nrTy = follow(nr->ty); + LUAU_ASSERT(!get(nlTy) && !get(nrTy)); + LUAU_ASSERT(!get(nlTy) && !get(nrTy)); + LUAU_ASSERT(!get(nlTy) && !get(nrTy)); + LUAU_ASSERT(!get(nlTy) && !get(nrTy)); + LUAU_ASSERT(!get(nlTy) && !get(nrTy)); + + if (auto [npl, npr] = get2(nlTy, nrTy); npl && npr) + { + if (npl->type == npr->type) + return left; // ~P1 & ~P2 ~ ~P1 iff P1 == P2 + else + return std::nullopt; // ~P1 & ~P2 ~ ~P1 & ~P2 iff P1 != P2 + } + else if (auto [nsl, nsr] = get2(nlTy, nrTy); nsl && nsr) + { + if (*nsl == *nsr) + return left; // ~"A" & ~"A" ~ ~"A" + else + return std::nullopt; // ~"A" & ~"B" ~ ~"A" & ~"B" + } + else if (auto [ns, np] = get2(nlTy, nrTy); ns && np) + { + if (get(ns) && np->type == PrimitiveType::String) + return right; // ~"A" & ~string ~ ~string + else if (get(ns) && np->type == PrimitiveType::Boolean) + return right; // ~false & ~boolean ~ ~boolean + else + return std::nullopt; // ~"A" | ~P ~ ~"A" & ~P + } + else if (auto [np, ns] = get2(nlTy, nrTy); np && ns) + return intersectionType(right, left); // ~P & ~S ~ ~S & ~P + else + return std::nullopt; // ~T & ~U ~ ~T & ~U + } else if (auto nl = get(left)) { // These should've been reduced already. @@ -477,10 +504,10 @@ std::optional TypeReducer::intersectionType(TypeId left, TypeId right) } else if (auto [nc, c] = get2(nlTy, right); nc && c) { - if (isSubclass(nc, c)) - return std::nullopt; // ~Derived & Base ~ ~Derived & Base - else if (isSubclass(c, nc)) + if (isSubclass(c, nc)) return builtinTypes->neverType; // ~Base & Derived ~ never + else if (isSubclass(nc, c)) + return std::nullopt; // ~Derived & Base ~ ~Derived & Base else return right; // ~Base & Unrelated ~ Unrelated } @@ -499,7 +526,7 @@ std::optional TypeReducer::intersectionType(TypeId left, TypeId right) return right; // ~string & {} ~ {} } else - return std::nullopt; // TODO + return right; // ~T & U ~ U } else if (get(right)) return intersectionType(right, left); // T & ~U ~ ~U & T @@ -679,10 +706,10 @@ std::optional TypeReducer::unionType(TypeId left, TypeId right) } else if (auto [nc, c] = get2(nlTy, right); nc && c) { - if (isSubclass(nc, c)) - return builtinTypes->unknownType; // ~Derived | Base ~ unknown - else if (isSubclass(c, nc)) + if (isSubclass(c, nc)) return std::nullopt; // ~Base | Derived ~ ~Base | Derived + else if (isSubclass(nc, c)) + return builtinTypes->unknownType; // ~Derived | Base ~ unknown else return left; // ~Base | Unrelated ~ ~Base } @@ -777,22 +804,24 @@ TypeId TypeReducer::negationType(TypeId ty) if (!n) return arena->addType(NegationType{ty}); - if (auto nn = get(n->ty)) + TypeId negatedTy = follow(n->ty); + + if (auto nn = get(negatedTy)) return nn->ty; // ~~T ~ T - else if (get(n->ty)) + else if (get(negatedTy)) return builtinTypes->unknownType; // ~never ~ unknown - else if (get(n->ty)) + else if (get(negatedTy)) return builtinTypes->neverType; // ~unknown ~ never - else if (get(n->ty)) + else if (get(negatedTy)) return builtinTypes->anyType; // ~any ~ any - else if (auto ni = get(n->ty)) + else if (auto ni = get(negatedTy)) { std::vector options; for (TypeId part : ni) options.push_back(negationType(arena->addType(NegationType{part}))); return reduce(flatten(std::move(options))); // ~(T & U) ~ (~T | ~U) } - else if (auto nu = get(n->ty)) + else if (auto nu = get(negatedTy)) { std::vector parts; for (TypeId option : nu) @@ -910,16 +939,26 @@ TypePackId TypeReducer::memoize(TypePackId tp, TypePackId reducedTp) struct MarkCycles : TypeVisitor { - DenseHashSet cyclicTypes{nullptr}; + DenseHashSet cyclics{nullptr}; void cycle(TypeId ty) override { - cyclicTypes.insert(ty); + cyclics.insert(follow(ty)); + } + + void cycle(TypePackId tp) override + { + cyclics.insert(follow(tp)); } bool visit(TypeId ty) override { - return !cyclicTypes.find(ty); + return !cyclics.find(follow(ty)); + } + + bool visit(TypePackId tp) override + { + return !cyclics.find(follow(tp)); } }; @@ -942,8 +981,8 @@ std::optional TypeReduction::reduce(TypeId ty) return ty; else if (!options.allowTypeReductionsFromOtherArenas && ty->owningArena != arena) return ty; - else if (auto ctx = memoizedTypes.find(ty); ctx && ctx->irreducible) - return ctx->type; + else if (auto memoized = memoizedof(ty)) + return *memoized; else if (hasExceededCartesianProductLimit(ty)) return std::nullopt; @@ -952,7 +991,7 @@ std::optional TypeReduction::reduce(TypeId ty) MarkCycles finder; finder.traverse(ty); - TypeReducer reducer{arena, builtinTypes, handle, &memoizedTypes, &memoizedTypePacks, &finder.cyclicTypes}; + TypeReducer reducer{arena, builtinTypes, handle, &memoizedTypes, &memoizedTypePacks, &finder.cyclics}; return reducer.reduce(ty); } catch (const RecursionLimitException&) @@ -969,8 +1008,8 @@ std::optional TypeReduction::reduce(TypePackId tp) return tp; else if (!options.allowTypeReductionsFromOtherArenas && tp->owningArena != arena) return tp; - else if (auto ctx = memoizedTypePacks.find(tp); ctx && ctx->irreducible) - return ctx->type; + else if (auto memoized = memoizedof(tp)) + return *memoized; else if (hasExceededCartesianProductLimit(tp)) return std::nullopt; @@ -979,7 +1018,7 @@ std::optional TypeReduction::reduce(TypePackId tp) MarkCycles finder; finder.traverse(tp); - TypeReducer reducer{arena, builtinTypes, handle, &memoizedTypes, &memoizedTypePacks, &finder.cyclicTypes}; + TypeReducer reducer{arena, builtinTypes, handle, &memoizedTypes, &memoizedTypePacks, &finder.cyclics}; return reducer.reduce(tp); } catch (const RecursionLimitException&) @@ -1000,6 +1039,13 @@ std::optional TypeReduction::reduce(const TypeFun& fun) return std::nullopt; } +TypeReduction TypeReduction::fork(NotNull arena, const TypeReductionOptions& opts) const +{ + TypeReduction child{arena, builtinTypes, handle, opts}; + child.parent = this; + return child; +} + size_t TypeReduction::cartesianProductSize(TypeId ty) const { ty = follow(ty); @@ -1047,4 +1093,24 @@ bool TypeReduction::hasExceededCartesianProductLimit(TypePackId tp) const return false; } +std::optional TypeReduction::memoizedof(TypeId ty) const +{ + if (auto ctx = memoizedTypes.find(ty); ctx && ctx->irreducible) + return ctx->type; + else if (parent) + return parent->memoizedof(ty); + else + return std::nullopt; +} + +std::optional TypeReduction::memoizedof(TypePackId tp) const +{ + if (auto ctx = memoizedTypePacks.find(tp); ctx && ctx->irreducible) + return ctx->type; + else if (parent) + return parent->memoizedof(tp); + else + return std::nullopt; +} + } // namespace Luau diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index e6d6144..d48c72f 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false) LUAU_FASTFLAGVARIABLE(LuauScalarShapeUnifyToMtOwner2, false) LUAU_FASTFLAGVARIABLE(LuauUninhabitedSubAnything2, false) LUAU_FASTFLAGVARIABLE(LuauMaintainScopesInUnifier, false) +LUAU_FASTFLAGVARIABLE(LuauTableUnifyInstantiationFix, false) LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(LuauNegatedFunctionTypes) @@ -600,11 +601,8 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool else if (log.getMutable(subTy)) tryUnifyWithClass(subTy, superTy, /*reversed*/ true); - else if (log.get(superTy)) - tryUnifyTypeWithNegation(subTy, superTy); - - else if (log.get(subTy)) - tryUnifyNegationWithType(subTy, superTy); + else if (log.get(superTy) || log.get(subTy)) + tryUnifyNegations(subTy, superTy); else if (FFlag::LuauUninhabitedSubAnything2 && !normalizer->isInhabited(subTy)) { @@ -857,6 +855,22 @@ void Unifier::tryUnifyTypeWithIntersection(TypeId subTy, TypeId superTy, const I reportError(location, TypeMismatch{superTy, subTy, "Not all intersection parts are compatible.", *firstFailedOption, mismatchContext()}); } +struct NegationTypeFinder : TypeOnceVisitor +{ + bool found = false; + + bool visit(TypeId ty) override + { + return !found; + } + + bool visit(TypeId ty, const NegationType&) override + { + found = true; + return !found; + } +}; + void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType* uv, TypeId superTy, bool cacheEnabled, bool isFunctionCall) { // A & B <: T if A <: T or B <: T @@ -881,6 +895,28 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType* } } + if (FFlag::DebugLuauDeferredConstraintResolution && normalize) + { + // Sometimes a negation type is inside one of the types, e.g. { p: number } & { p: ~number }. + NegationTypeFinder finder; + finder.traverse(subTy); + + if (finder.found) + { + // It is possible that A & B <: T even though A normalize(subTy); + const NormalizedType* superNorm = normalizer->normalize(superTy); + if (subNorm && superNorm) + tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the intersection parts are compatible"); + else + reportError(location, UnificationTooComplex{}); + + return; + } + } + std::vector logs; for (size_t i = 0; i < uv->parts.size(); ++i) @@ -1728,9 +1764,10 @@ struct Resetter void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) { + TypeId activeSubTy = subTy; TableType* superTable = log.getMutable(superTy); TableType* subTable = log.getMutable(subTy); - TableType* instantiatedSubTable = subTable; + TableType* instantiatedSubTable = subTable; // TODO: remove with FFlagLuauTableUnifyInstantiationFix if (!superTable || !subTable) ice("passed non-table types to unifyTables"); @@ -1747,8 +1784,16 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) std::optional instantiated = instantiation.substitute(subTy); if (instantiated.has_value()) { - subTable = log.getMutable(*instantiated); - instantiatedSubTable = subTable; + if (FFlag::LuauTableUnifyInstantiationFix) + { + activeSubTy = *instantiated; + subTable = log.getMutable(activeSubTy); + } + else + { + subTable = log.getMutable(*instantiated); + instantiatedSubTable = subTable; + } if (!subTable) ice("instantiation made a table type into a non-table type in tryUnifyTables"); @@ -1838,7 +1883,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) } else if (subTable->state == TableState::Free) { - PendingType* pendingSub = log.queue(subTy); + PendingType* pendingSub = log.queue(activeSubTy); TableType* ttv = getMutable(pendingSub); LUAU_ASSERT(ttv); ttv->props[name] = prop; @@ -1851,12 +1896,12 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) // table. If we detect that this has happened, we start over, with the updated // txn log. TypeId superTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(superTy) : superTy; - TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(subTy) : subTy; + TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(activeSubTy) : activeSubTy; if (FFlag::LuauScalarShapeUnifyToMtOwner2) { // If one of the types stopped being a table altogether, we need to restart from the top - if ((superTy != superTyNew || subTy != subTyNew) && errors.empty()) + if ((superTy != superTyNew || activeSubTy != subTyNew) && errors.empty()) return tryUnify(subTy, superTy, false, isIntersection); } @@ -1864,7 +1909,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) TableType* newSuperTable = log.getMutable(superTyNew); TableType* newSubTable = log.getMutable(subTyNew); - if (superTable != newSuperTable || (subTable != newSubTable && subTable != instantiatedSubTable)) + if (superTable != newSuperTable || (subTable != newSubTable && (FFlag::LuauTableUnifyInstantiationFix || subTable != instantiatedSubTable))) { if (errors.empty()) return tryUnifyTables(subTy, superTy, isIntersection); @@ -1922,12 +1967,12 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) extraProperties.push_back(name); TypeId superTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(superTy) : superTy; - TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(subTy) : subTy; + TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(activeSubTy) : activeSubTy; if (FFlag::LuauScalarShapeUnifyToMtOwner2) { // If one of the types stopped being a table altogether, we need to restart from the top - if ((superTy != superTyNew || subTy != subTyNew) && errors.empty()) + if ((superTy != superTyNew || activeSubTy != subTyNew) && errors.empty()) return tryUnify(subTy, superTy, false, isIntersection); } @@ -1936,7 +1981,8 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) // txn log. TableType* newSuperTable = log.getMutable(superTyNew); TableType* newSubTable = log.getMutable(subTyNew); - if (superTable != newSuperTable || (subTable != newSubTable && subTable != instantiatedSubTable)) + + if (superTable != newSuperTable || (subTable != newSubTable && (FFlag::LuauTableUnifyInstantiationFix || subTable != instantiatedSubTable))) { if (errors.empty()) return tryUnifyTables(subTy, superTy, isIntersection); @@ -1992,7 +2038,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) if (FFlag::LuauScalarShapeUnifyToMtOwner2) { superTable = log.getMutable(log.follow(superTy)); - subTable = log.getMutable(log.follow(subTy)); + subTable = log.getMutable(log.follow(activeSubTy)); if (!superTable || !subTable) return; @@ -2000,7 +2046,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) else { superTable = log.getMutable(superTy); - subTable = log.getMutable(subTy); + subTable = log.getMutable(activeSubTy); } if (!missingProperties.empty()) @@ -2313,11 +2359,10 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed) return fail(); } -void Unifier::tryUnifyTypeWithNegation(TypeId subTy, TypeId superTy) +void Unifier::tryUnifyNegations(TypeId subTy, TypeId superTy) { - const NegationType* ntv = get(superTy); - if (!ntv) - ice("tryUnifyTypeWithNegation superTy must be a negation type"); + if (!log.get(subTy) && !log.get(superTy)) + ice("tryUnifyNegations superTy or subTy must be a negation type"); const NormalizedType* subNorm = normalizer->normalize(subTy); const NormalizedType* superNorm = normalizer->normalize(superTy); @@ -2331,16 +2376,6 @@ void Unifier::tryUnifyTypeWithNegation(TypeId subTy, TypeId superTy) reportError(location, TypeMismatch{superTy, subTy, mismatchContext()}); } -void Unifier::tryUnifyNegationWithType(TypeId subTy, TypeId superTy) -{ - const NegationType* ntv = get(subTy); - if (!ntv) - ice("tryUnifyNegationWithType subTy must be a negation type"); - - // TODO: ~T & queue, DenseHashSet& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack) { while (true) diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index 81221dd..9c352f9 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -251,7 +251,6 @@ enum class ConstantNumberParseResult Malformed, BinOverflow, HexOverflow, - DoublePrefix, }; class AstExprConstantNumber : public AstExpr diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 99a4193..4d61914 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -14,15 +14,8 @@ LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000) LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) -LUAU_FASTFLAGVARIABLE(LuauErrorDoubleHexPrefix, false) -LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false) - LUAU_FASTFLAGVARIABLE(LuauParserErrorsOnMissingDefaultTypePackArgument, false) -bool lua_telemetry_parsed_out_of_range_bin_integer = false; -bool lua_telemetry_parsed_out_of_range_hex_integer = false; -bool lua_telemetry_parsed_double_prefix_hex_integer = false; - #define ERROR_INVALID_INTERP_DOUBLE_BRACE "Double braces are not permitted within interpolated strings. Did you mean '\\{'?" namespace Luau @@ -2093,17 +2086,7 @@ static ConstantNumberParseResult parseInteger(double& result, const char* data, value = strtoull(data, &end, base); if (errno == ERANGE) - { - if (DFFlag::LuaReportParseIntegerIssues) - { - if (base == 2) - lua_telemetry_parsed_out_of_range_bin_integer = true; - else - lua_telemetry_parsed_out_of_range_hex_integer = true; - } - return base == 2 ? ConstantNumberParseResult::BinOverflow : ConstantNumberParseResult::HexOverflow; - } } return ConstantNumberParseResult::Ok; @@ -2117,18 +2100,7 @@ static ConstantNumberParseResult parseDouble(double& result, const char* data) // hexadecimal literal if (data[0] == '0' && (data[1] == 'x' || data[1] == 'X') && data[2]) - { - if (!FFlag::LuauErrorDoubleHexPrefix && data[2] == '0' && (data[3] == 'x' || data[3] == 'X')) - { - if (DFFlag::LuaReportParseIntegerIssues) - lua_telemetry_parsed_double_prefix_hex_integer = true; - - ConstantNumberParseResult parseResult = parseInteger(result, data + 2, 16); - return parseResult == ConstantNumberParseResult::Malformed ? parseResult : ConstantNumberParseResult::DoublePrefix; - } - return parseInteger(result, data, 16); // pass in '0x' prefix, it's handled by 'strtoull' - } char* end = nullptr; double value = strtod(data, &end); diff --git a/CodeGen/include/Luau/AssemblyBuilderX64.h b/CodeGen/include/Luau/AssemblyBuilderX64.h index 918c826..235f1a8 100644 --- a/CodeGen/include/Luau/AssemblyBuilderX64.h +++ b/CodeGen/include/Luau/AssemblyBuilderX64.h @@ -109,8 +109,10 @@ public: void vdivsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vandpd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vandnpd(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vxorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vucomisd(OperandX64 src1, OperandX64 src2); @@ -137,6 +139,10 @@ public: void vmaxsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vminsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vcmpltsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + + void vblendvpd(RegisterX64 dst, RegisterX64 src1, OperandX64 mask, RegisterX64 src3); + // Run final checks void finalize(); @@ -152,6 +158,7 @@ public: OperandX64 f32(float value); OperandX64 f64(double value); OperandX64 f32x4(float x, float y, float z, float w); + OperandX64 f64x2(double x, double y); OperandX64 bytes(const void* ptr, size_t size, size_t align = 8); void logAppend(const char* fmt, ...) LUAU_PRINTF_ATTR(2, 3); diff --git a/CodeGen/src/IrAnalysis.h b/CodeGen/include/Luau/IrAnalysis.h similarity index 100% rename from CodeGen/src/IrAnalysis.h rename to CodeGen/include/Luau/IrAnalysis.h diff --git a/CodeGen/src/IrBuilder.h b/CodeGen/include/Luau/IrBuilder.h similarity index 98% rename from CodeGen/src/IrBuilder.h rename to CodeGen/include/Luau/IrBuilder.h index c8f9b4e..5b51e0a 100644 --- a/CodeGen/src/IrBuilder.h +++ b/CodeGen/include/Luau/IrBuilder.h @@ -3,8 +3,7 @@ #include "Luau/Common.h" #include "Luau/Bytecode.h" - -#include "IrData.h" +#include "Luau/IrData.h" #include diff --git a/CodeGen/src/IrData.h b/CodeGen/include/Luau/IrData.h similarity index 100% rename from CodeGen/src/IrData.h rename to CodeGen/include/Luau/IrData.h diff --git a/CodeGen/src/IrDump.h b/CodeGen/include/Luau/IrDump.h similarity index 96% rename from CodeGen/src/IrDump.h rename to CodeGen/include/Luau/IrDump.h index c803e8d..2f44ea8 100644 --- a/CodeGen/src/IrDump.h +++ b/CodeGen/include/Luau/IrDump.h @@ -1,7 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once -#include "IrData.h" +#include "Luau/IrData.h" #include #include diff --git a/CodeGen/src/IrUtils.h b/CodeGen/include/Luau/IrUtils.h similarity index 99% rename from CodeGen/src/IrUtils.h rename to CodeGen/include/Luau/IrUtils.h index 5588178..8438205 100644 --- a/CodeGen/src/IrUtils.h +++ b/CodeGen/include/Luau/IrUtils.h @@ -3,8 +3,7 @@ #include "Luau/Bytecode.h" #include "Luau/Common.h" - -#include "IrData.h" +#include "Luau/IrData.h" namespace Luau { diff --git a/CodeGen/src/AssemblyBuilderX64.cpp b/CodeGen/src/AssemblyBuilderX64.cpp index 77856e9..71bfaec 100644 --- a/CodeGen/src/AssemblyBuilderX64.cpp +++ b/CodeGen/src/AssemblyBuilderX64.cpp @@ -638,11 +638,21 @@ void AssemblyBuilderX64::vandpd(OperandX64 dst, OperandX64 src1, OperandX64 src2 placeAvx("vandpd", dst, src1, src2, 0x54, false, AVX_0F, AVX_66); } +void AssemblyBuilderX64::vandnpd(OperandX64 dst, OperandX64 src1, OperandX64 src2) +{ + placeAvx("vandnpd", dst, src1, src2, 0x55, false, AVX_0F, AVX_66); +} + void AssemblyBuilderX64::vxorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2) { placeAvx("vxorpd", dst, src1, src2, 0x57, false, AVX_0F, AVX_66); } +void AssemblyBuilderX64::vorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2) +{ + placeAvx("vorpd", dst, src1, src2, 0x56, false, AVX_0F, AVX_66); +} + void AssemblyBuilderX64::vucomisd(OperandX64 src1, OperandX64 src2) { placeAvx("vucomisd", src1, src2, 0x2e, false, AVX_0F, AVX_66); @@ -753,6 +763,17 @@ void AssemblyBuilderX64::vminsd(OperandX64 dst, OperandX64 src1, OperandX64 src2 placeAvx("vminsd", dst, src1, src2, 0x5d, false, AVX_0F, AVX_F2); } +void AssemblyBuilderX64::vcmpltsd(OperandX64 dst, OperandX64 src1, OperandX64 src2) +{ + placeAvx("vcmpltsd", dst, src1, src2, 0x01, 0xc2, false, AVX_0F, AVX_F2); +} + +void AssemblyBuilderX64::vblendvpd(RegisterX64 dst, RegisterX64 src1, OperandX64 mask, RegisterX64 src3) +{ + // bits [7:4] of imm8 are used to select register for operand 4 + placeAvx("vblendvpd", dst, src1, mask, src3.index << 4, 0x4b, false, AVX_0F3A, AVX_66); +} + void AssemblyBuilderX64::finalize() { code.resize(codePos - code.data()); @@ -834,6 +855,14 @@ OperandX64 AssemblyBuilderX64::f32x4(float x, float y, float z, float w) return OperandX64(SizeX64::xmmword, noreg, 1, rip, int32_t(pos - data.size())); } +OperandX64 AssemblyBuilderX64::f64x2(double x, double y) +{ + size_t pos = allocateData(16, 16); + writef64(&data[pos], x); + writef64(&data[pos + 8], y); + return OperandX64(SizeX64::xmmword, noreg, 1, rip, int32_t(pos - data.size())); +} + OperandX64 AssemblyBuilderX64::bytes(const void* ptr, size_t size, size_t align) { size_t pos = allocateData(size, align); diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index 4ed950c..72b2cbb 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -5,6 +5,8 @@ #include "Luau/Common.h" #include "Luau/CodeAllocator.h" #include "Luau/CodeBlockUnwind.h" +#include "Luau/IrAnalysis.h" +#include "Luau/IrBuilder.h" #include "Luau/UnwindBuilder.h" #include "Luau/UnwindBuilderDwarf2.h" #include "Luau/UnwindBuilderWin.h" @@ -13,8 +15,6 @@ #include "CodeGenX64.h" #include "EmitCommonX64.h" #include "EmitInstructionX64.h" -#include "IrAnalysis.h" -#include "IrBuilder.h" #include "IrLoweringX64.h" #include "NativeState.h" diff --git a/CodeGen/src/EmitBuiltinsX64.cpp b/CodeGen/src/EmitBuiltinsX64.cpp index 41a2c26..f0dd003 100644 --- a/CodeGen/src/EmitBuiltinsX64.cpp +++ b/CodeGen/src/EmitBuiltinsX64.cpp @@ -424,6 +424,197 @@ BuiltinImplResult emitBuiltinMathLog(AssemblyBuilderX64& build, int nparams, int return {BuiltinImplType::UsesFallback, 1}; } + +BuiltinImplResult emitBuiltinMathLdexp(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback) +{ + if (nparams < 2 || nresults > 1) + return {BuiltinImplType::None, -1}; + + if (build.logText) + build.logAppend("; inlined LBF_MATH_LDEXP\n"); + + jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback); + + // TODO: jumpIfTagIsNot can be generalized to take OperandX64 and then we can use it here; let's wait until we see this more though + build.cmp(dword[args + offsetof(TValue, tt)], LUA_TNUMBER); + build.jcc(ConditionX64::NotEqual, fallback); + + build.vmovsd(xmm0, luauRegValue(arg)); + + if (build.abi == ABIX64::Windows) + build.vcvttsd2si(rArg2, qword[args + offsetof(TValue, value)]); + else + build.vcvttsd2si(rArg1, qword[args + offsetof(TValue, value)]); + + build.call(qword[rNativeContext + offsetof(NativeContext, libm_ldexp)]); + + build.vmovsd(luauRegValue(ra), xmm0); + + if (ra != arg) + build.mov(luauRegTag(ra), LUA_TNUMBER); + + return {BuiltinImplType::UsesFallback, 1}; +} + +BuiltinImplResult emitBuiltinMathRound(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback) +{ + if (nparams < 1 || nresults > 1) + return {BuiltinImplType::None, -1}; + + if (build.logText) + build.logAppend("; inlined LBF_MATH_ROUND\n"); + + jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback); + + build.vmovsd(xmm0, luauRegValue(arg)); + build.vandpd(xmm1, xmm0, build.f64x2(-0.0, -0.0)); + build.vmovsd(xmm2, build.i64(0x3fdfffffffffffff)); // 0.49999999999999994 + build.vorpd(xmm1, xmm1, xmm2); + build.vaddsd(xmm0, xmm0, xmm1); + build.vroundsd(xmm0, xmm0, xmm0, RoundingModeX64::RoundToZero); + + build.vmovsd(luauRegValue(ra), xmm0); + + if (ra != arg) + build.mov(luauRegTag(ra), LUA_TNUMBER); + + return {BuiltinImplType::UsesFallback, 1}; +} + +BuiltinImplResult emitBuiltinMathFrexp(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback) +{ + if (nparams < 1 || nresults > 2) + return {BuiltinImplType::None, -1}; + + if (build.logText) + build.logAppend("; inlined LBF_MATH_FREXP\n"); + + jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback); + + build.vmovsd(xmm0, luauRegValue(arg)); + + if (build.abi == ABIX64::Windows) + build.lea(rArg2, sTemporarySlot); + else + build.lea(rArg1, sTemporarySlot); + + build.call(qword[rNativeContext + offsetof(NativeContext, libm_frexp)]); + + build.vmovsd(luauRegValue(ra), xmm0); + + if (ra != arg) + build.mov(luauRegTag(ra), LUA_TNUMBER); + + build.vcvtsi2sd(xmm0, xmm0, dword[sTemporarySlot + 0]); + build.vmovsd(luauRegValue(ra + 1), xmm0); + build.mov(luauRegTag(ra + 1), LUA_TNUMBER); + + return {BuiltinImplType::UsesFallback, 2}; +} + +BuiltinImplResult emitBuiltinMathModf(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback) +{ + if (nparams < 1 || nresults > 2) + return {BuiltinImplType::None, -1}; + + if (build.logText) + build.logAppend("; inlined LBF_MATH_MODF\n"); + + jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback); + + build.vmovsd(xmm0, luauRegValue(arg)); + + if (build.abi == ABIX64::Windows) + build.lea(rArg2, sTemporarySlot); + else + build.lea(rArg1, sTemporarySlot); + + build.call(qword[rNativeContext + offsetof(NativeContext, libm_modf)]); + + build.vmovsd(xmm1, qword[sTemporarySlot + 0]); + build.vmovsd(luauRegValue(ra), xmm1); + + if (ra != arg) + build.mov(luauRegTag(ra), LUA_TNUMBER); + + build.vmovsd(luauRegValue(ra + 1), xmm0); + build.mov(luauRegTag(ra + 1), LUA_TNUMBER); + + return {BuiltinImplType::UsesFallback, 2}; +} + +BuiltinImplResult emitBuiltinMathSign(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback) +{ + if (nparams < 1 || nresults > 1) + return {BuiltinImplType::None, -1}; + + if (build.logText) + build.logAppend("; inlined LBF_MATH_SIGN\n"); + + jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback); + + build.vmovsd(xmm0, luauRegValue(arg)); + build.vxorpd(xmm1, xmm1, xmm1); + + // Set xmm2 to -1 if arg < 0, else 0 + build.vcmpltsd(xmm2, xmm0, xmm1); + build.vmovsd(xmm3, build.f64(-1)); + build.vandpd(xmm2, xmm2, xmm3); + + // Set mask bit to 1 if 0 < arg, else 0 + build.vcmpltsd(xmm0, xmm1, xmm0); + + // Result = (mask-bit == 1) ? 1.0 : xmm2 + // If arg < 0 then xmm2 is -1 and mask-bit is 0, result is -1 + // If arg == 0 then xmm2 is 0 and mask-bit is 0, result is 0 + // If arg > 0 then xmm2 is 0 and mask-bit is 1, result is 1 + build.vblendvpd(xmm0, xmm2, build.f64x2(1, 1), xmm0); + + build.vmovsd(luauRegValue(ra), xmm0); + + if (ra != arg) + build.mov(luauRegTag(ra), LUA_TNUMBER); + + return {BuiltinImplType::UsesFallback, 1}; +} + +BuiltinImplResult emitBuiltinMathClamp(AssemblyBuilderX64& build, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback) +{ + if (nparams < 3 || nresults > 1) + return {BuiltinImplType::None, -1}; + + if (build.logText) + build.logAppend("; inlined LBF_MATH_CLAMP\n"); + + jumpIfTagIsNot(build, arg, LUA_TNUMBER, fallback); + + // TODO: jumpIfTagIsNot can be generalized to take OperandX64 and then we can use it here; let's wait until we see this more though + build.cmp(dword[args + offsetof(TValue, tt)], LUA_TNUMBER); + build.jcc(ConditionX64::NotEqual, fallback); + + // TODO: jumpIfTagIsNot can be generalized to take OperandX64 and then we can use it here; let's wait until we see this more though + build.cmp(dword[args + sizeof(TValue) + offsetof(TValue, tt)], LUA_TNUMBER); + build.jcc(ConditionX64::NotEqual, fallback); + + RegisterX64 min = xmm1; + RegisterX64 max = xmm2; + build.vmovsd(min, qword[args + offsetof(TValue, value)]); + build.vmovsd(max, qword[args + sizeof(TValue) + offsetof(TValue, value)]); + + jumpOnNumberCmp(build, noreg, min, max, ConditionX64::NotLessEqual, fallback); + + build.vmaxsd(xmm0, min, luauRegValue(arg)); + build.vminsd(xmm0, max, xmm0); + + build.vmovsd(luauRegValue(ra), xmm0); + + if (ra != arg) + build.mov(luauRegTag(ra), LUA_TNUMBER); + + return {BuiltinImplType::UsesFallback, 1}; +} + + BuiltinImplResult emitBuiltin(AssemblyBuilderX64& build, int bfid, int nparams, int ra, int arg, OperandX64 args, int nresults, Label& fallback) { switch (bfid) @@ -476,6 +667,18 @@ BuiltinImplResult emitBuiltin(AssemblyBuilderX64& build, int bfid, int nparams, return emitBuiltinMathLog10(build, nparams, ra, arg, args, nresults, fallback); case LBF_MATH_LOG: return emitBuiltinMathLog(build, nparams, ra, arg, args, nresults, fallback); + case LBF_MATH_LDEXP: + return emitBuiltinMathLdexp(build, nparams, ra, arg, args, nresults, fallback); + case LBF_MATH_ROUND: + return emitBuiltinMathRound(build, nparams, ra, arg, args, nresults, fallback); + case LBF_MATH_FREXP: + return emitBuiltinMathFrexp(build, nparams, ra, arg, args, nresults, fallback); + case LBF_MATH_MODF: + return emitBuiltinMathModf(build, nparams, ra, arg, args, nresults, fallback); + case LBF_MATH_SIGN: + return emitBuiltinMathSign(build, nparams, ra, arg, args, nresults, fallback); + case LBF_MATH_CLAMP: + return emitBuiltinMathClamp(build, nparams, ra, arg, args, nresults, fallback); default: return {BuiltinImplType::None, -1}; } diff --git a/CodeGen/src/EmitCommonX64.h b/CodeGen/src/EmitCommonX64.h index e475da6..c93c7ae 100644 --- a/CodeGen/src/EmitCommonX64.h +++ b/CodeGen/src/EmitCommonX64.h @@ -38,6 +38,7 @@ constexpr unsigned kLocalsSize = 24; // 3 extra slots for our custom locals constexpr OperandX64 sClosure = qword[rsp + kStackSize + 0]; // Closure* cl constexpr OperandX64 sCode = qword[rsp + kStackSize + 8]; // Instruction* code +constexpr OperandX64 sTemporarySlot = addr[rsp + kStackSize + 16]; // TODO: These should be replaced with a portable call function that checks the ABI at runtime and reorders moves accordingly to avoid conflicts #if defined(_WIN32) diff --git a/CodeGen/src/IrAnalysis.cpp b/CodeGen/src/IrAnalysis.cpp index a088211..b9f3953 100644 --- a/CodeGen/src/IrAnalysis.cpp +++ b/CodeGen/src/IrAnalysis.cpp @@ -1,8 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "IrAnalysis.h" +#include "Luau/IrAnalysis.h" -#include "IrData.h" -#include "IrUtils.h" +#include "Luau/IrData.h" +#include "Luau/IrUtils.h" #include diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index a43acda..25f6d45 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -1,11 +1,11 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "IrBuilder.h" +#include "Luau/IrBuilder.h" #include "Luau/Common.h" +#include "Luau/IrUtils.h" #include "CustomExecUtils.h" #include "IrTranslation.h" -#include "IrUtils.h" #include "lapi.h" diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index 4dc5c6c..eb5a074 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -1,7 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "IrDump.h" +#include "Luau/IrDump.h" -#include "IrUtils.h" +#include "Luau/IrUtils.h" #include "lua.h" diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 98169ac..62b5dea 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -3,11 +3,11 @@ #include "Luau/CodeGen.h" #include "Luau/DenseHash.h" +#include "Luau/IrDump.h" +#include "Luau/IrUtils.h" #include "EmitCommonX64.h" #include "EmitInstructionX64.h" -#include "IrDump.h" -#include "IrUtils.h" #include "NativeState.h" #include "lstate.h" diff --git a/CodeGen/src/IrLoweringX64.h b/CodeGen/src/IrLoweringX64.h index 2835811..fd5357d 100644 --- a/CodeGen/src/IrLoweringX64.h +++ b/CodeGen/src/IrLoweringX64.h @@ -2,8 +2,7 @@ #pragma once #include "Luau/AssemblyBuilderX64.h" - -#include "IrData.h" +#include "Luau/IrData.h" #include #include diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 8225781..32958b5 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -2,8 +2,7 @@ #include "IrTranslation.h" #include "Luau/Bytecode.h" - -#include "IrBuilder.h" +#include "Luau/IrBuilder.h" #include "lobject.h" #include "ltm.h" diff --git a/CodeGen/src/NativeState.cpp b/CodeGen/src/NativeState.cpp index 22d38aa..33db54e 100644 --- a/CodeGen/src/NativeState.cpp +++ b/CodeGen/src/NativeState.cpp @@ -87,6 +87,10 @@ void initHelperFunctions(NativeState& data) data.context.libm_log = log; data.context.libm_log2 = log2; data.context.libm_log10 = log10; + data.context.libm_ldexp = ldexp; + data.context.libm_round = round; + data.context.libm_frexp = frexp; + data.context.libm_modf = modf; data.context.libm_asin = asin; data.context.libm_sin = sin; diff --git a/CodeGen/src/NativeState.h b/CodeGen/src/NativeState.h index 03d8ae3..ad5aca6 100644 --- a/CodeGen/src/NativeState.h +++ b/CodeGen/src/NativeState.h @@ -96,6 +96,10 @@ struct NativeContext double (*libm_log)(double) = nullptr; double (*libm_log2)(double) = nullptr; double (*libm_log10)(double) = nullptr; + double (*libm_ldexp)(double, int) = nullptr; + double (*libm_round)(double) = nullptr; + double (*libm_frexp)(double, int*) = nullptr; + double (*libm_modf)(double, double*) = nullptr; // Helper functions bool (*forgLoopNodeIter)(lua_State* L, Table* h, int index, TValue* ra) = nullptr; diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index ce9a578..56863bf 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -25,10 +25,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) -LUAU_FASTFLAGVARIABLE(LuauMultiAssignmentConflictFix, false) -LUAU_FASTFLAGVARIABLE(LuauSelfAssignmentSkip, false) -LUAU_FASTFLAGVARIABLE(LuauCompileInterpStringLimit, false) - namespace Luau { @@ -1580,8 +1576,7 @@ struct Compiler RegScope rs(this); - uint8_t baseReg = FFlag::LuauCompileInterpStringLimit ? allocReg(expr, unsigned(2 + expr->expressions.size)) - : allocReg(expr, uint8_t(2 + expr->expressions.size)); + uint8_t baseReg = allocReg(expr, unsigned(2 + expr->expressions.size)); emitLoadK(baseReg, formatStringIndex); @@ -2030,7 +2025,7 @@ struct Compiler if (int reg = getExprLocalReg(expr); reg >= 0) { // Optimization: we don't need to move if target happens to be in the same register - if (!FFlag::LuauSelfAssignmentSkip || options.optimizationLevel == 0 || target != reg) + if (options.optimizationLevel == 0 || target != reg) bytecode.emitABC(LOP_MOVE, target, uint8_t(reg), 0); } else @@ -2982,48 +2977,31 @@ struct Compiler Visitor visitor(this); - if (FFlag::LuauMultiAssignmentConflictFix) + // mark any registers that are used *after* assignment as conflicting + + // first we go through assignments to locals, since they are performed before assignments to other l-values + for (size_t i = 0; i < vars.size(); ++i) { - // mark any registers that are used *after* assignment as conflicting + const LValue& li = vars[i].lvalue; - // first we go through assignments to locals, since they are performed before assignments to other l-values - for (size_t i = 0; i < vars.size(); ++i) + if (li.kind == LValue::Kind_Local) { - const LValue& li = vars[i].lvalue; - - if (li.kind == LValue::Kind_Local) - { - if (i < values.size) - values.data[i]->visit(&visitor); - - visitor.assigned[li.reg] = true; - } - } - - // and now we handle all other l-values - for (size_t i = 0; i < vars.size(); ++i) - { - const LValue& li = vars[i].lvalue; - - if (li.kind != LValue::Kind_Local && i < values.size) - values.data[i]->visit(&visitor); - } - } - else - { - // mark any registers that are used *after* assignment as conflicting - for (size_t i = 0; i < vars.size(); ++i) - { - const LValue& li = vars[i].lvalue; - if (i < values.size) values.data[i]->visit(&visitor); - if (li.kind == LValue::Kind_Local) - visitor.assigned[li.reg] = true; + visitor.assigned[li.reg] = true; } } + // and now we handle all other l-values + for (size_t i = 0; i < vars.size(); ++i) + { + const LValue& li = vars[i].lvalue; + + if (li.kind != LValue::Kind_Local && i < values.size) + values.data[i]->visit(&visitor); + } + // mark any registers used in trailing expressions as conflicting as well for (size_t i = vars.size(); i < values.size; ++i) values.data[i]->visit(&visitor); diff --git a/Makefile b/Makefile index 7a8b98a..66d6016 100644 --- a/Makefile +++ b/Makefile @@ -127,7 +127,7 @@ $(ISOCLINE_OBJECTS): CXXFLAGS+=-Wno-unused-function -Iextern/isocline/include $(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -ICodeGen/include -IVM/include -ICLI -Iextern -DDOCTEST_CONFIG_DOUBLE_STRINGIFY $(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -Iextern -Iextern/isocline/include $(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -Iextern -$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IVM/include +$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IVM/include -ICodeGen/include $(TESTS_TARGET): LDFLAGS+=-lpthread $(REPL_CLI_TARGET): LDFLAGS+=-lpthread @@ -192,7 +192,7 @@ $(TESTS_TARGET) $(REPL_CLI_TARGET) $(ANALYZE_CLI_TARGET): $(CXX) $^ $(LDFLAGS) -o $@ # executable targets for fuzzing -fuzz-%: $(BUILD)/fuzz/%.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET) +fuzz-%: $(BUILD)/fuzz/%.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(CXX) $^ $(LDFLAGS) -o $@ fuzz-proto: $(BUILD)/fuzz/proto.cpp.o $(BUILD)/fuzz/protoprint.cpp.o $(BUILD)/fuzz/luau.pb.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET) | build/libprotobuf-mutator diff --git a/Sources.cmake b/Sources.cmake index e2636ff..815301b 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -63,6 +63,11 @@ target_sources(Luau.CodeGen PRIVATE CodeGen/include/Luau/CodeGen.h CodeGen/include/Luau/ConditionA64.h CodeGen/include/Luau/ConditionX64.h + CodeGen/include/Luau/IrAnalysis.h + CodeGen/include/Luau/IrBuilder.h + CodeGen/include/Luau/IrDump.h + CodeGen/include/Luau/IrData.h + CodeGen/include/Luau/IrUtils.h CodeGen/include/Luau/Label.h CodeGen/include/Luau/OperandX64.h CodeGen/include/Luau/RegisterA64.h @@ -100,13 +105,8 @@ target_sources(Luau.CodeGen PRIVATE CodeGen/src/EmitInstructionX64.h CodeGen/src/Fallbacks.h CodeGen/src/FallbacksProlog.h - CodeGen/src/IrAnalysis.h - CodeGen/src/IrBuilder.h - CodeGen/src/IrDump.h - CodeGen/src/IrData.h CodeGen/src/IrLoweringX64.h CodeGen/src/IrTranslation.h - CodeGen/src/IrUtils.h CodeGen/src/NativeState.h ) @@ -120,7 +120,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/BuiltinDefinitions.h Analysis/include/Luau/Clone.h Analysis/include/Luau/Config.h - Analysis/include/Luau/Connective.h + Analysis/include/Luau/Refinement.h Analysis/include/Luau/Constraint.h Analysis/include/Luau/ConstraintGraphBuilder.h Analysis/include/Luau/ConstraintSolver.h @@ -175,7 +175,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/BuiltinDefinitions.cpp Analysis/src/Clone.cpp Analysis/src/Config.cpp - Analysis/src/Connective.cpp + Analysis/src/Refinement.cpp Analysis/src/Constraint.cpp Analysis/src/ConstraintGraphBuilder.cpp Analysis/src/ConstraintSolver.cpp diff --git a/VM/src/lperf.cpp b/VM/src/lperf.cpp index da68e37..5df22bb 100644 --- a/VM/src/lperf.cpp +++ b/VM/src/lperf.cpp @@ -17,6 +17,8 @@ #include #endif + + #include static double clock_period() diff --git a/VM/src/lstrlib.cpp b/VM/src/lstrlib.cpp index 59f16e5..cf7381a 100644 --- a/VM/src/lstrlib.cpp +++ b/VM/src/lstrlib.cpp @@ -8,8 +8,6 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauStringFormatAnyFix, false) - // macro to `unsign' a character #define uchar(c) ((unsigned char)(c)) @@ -1039,21 +1037,11 @@ static int str_format(lua_State* L) if (formatItemSize != 1) luaL_error(L, "'%%*' does not take a form"); - if (FFlag::LuauStringFormatAnyFix) - { - size_t length; - const char* string = luaL_tolstring(L, arg, &length); + size_t length; + const char* string = luaL_tolstring(L, arg, &length); - luaL_addlstring(&b, string, length, -2); - lua_pop(L, 1); - } - else - { - size_t length; - const char* string = luaL_tolstring(L, arg, &length); - - luaL_addlstring(&b, string, length, -1); - } + luaL_addlstring(&b, string, length, -2); + lua_pop(L, 1); continue; // skip the `luaL_addlstring' at the end } diff --git a/fuzz/proto.cpp b/fuzz/proto.cpp index c8ba226..5ca1657 100644 --- a/fuzz/proto.cpp +++ b/fuzz/proto.cpp @@ -4,6 +4,7 @@ #include "Luau/BuiltinDefinitions.h" #include "Luau/BytecodeBuilder.h" +#include "Luau/CodeGen.h" #include "Luau/Common.h" #include "Luau/Compiler.h" #include "Luau/Frontend.h" @@ -25,11 +26,13 @@ const bool kFuzzLinter = true; const bool kFuzzTypeck = true; const bool kFuzzVM = true; const bool kFuzzTranspile = true; +const bool kFuzzCodegen = true; // Should we generate type annotations? const bool kFuzzTypes = true; static_assert(!(kFuzzVM && !kFuzzCompiler), "VM requires the compiler!"); +static_assert(!(kFuzzCodegen && !kFuzzVM), "Codegen requires the VM!"); std::vector protoprint(const luau::ModuleSet& stat, bool types); @@ -83,6 +86,9 @@ lua_State* createGlobalState() { lua_State* L = lua_newstate(allocate, NULL); + if (kFuzzCodegen && Luau::CodeGen::isSupported()) + Luau::CodeGen::create(L); + lua_callbacks(L)->interrupt = interrupt; luaL_openlibs(L); @@ -349,20 +355,30 @@ DEFINE_PROTO_FUZZER(const luau::ModuleSet& message) { static lua_State* globalState = createGlobalState(); - lua_State* L = lua_newthread(globalState); - luaL_sandboxthread(L); + auto runCode = [](const std::string& bytecode, bool useCodegen) { + lua_State* L = lua_newthread(globalState); + luaL_sandboxthread(L); - if (luau_load(L, "=fuzz", bytecode.data(), bytecode.size(), 0) == 0) - { - interruptDeadline = std::chrono::system_clock::now() + kInterruptTimeout; + if (luau_load(L, "=fuzz", bytecode.data(), bytecode.size(), 0) == 0) + { + if (useCodegen) + Luau::CodeGen::compile(L, -1); - lua_resume(L, NULL, 0); - } + interruptDeadline = std::chrono::system_clock::now() + kInterruptTimeout; - lua_pop(globalState, 1); + lua_resume(L, NULL, 0); + } - // we'd expect full GC to reclaim all memory allocated by the script - lua_gc(globalState, LUA_GCCOLLECT, 0); - LUAU_ASSERT(heapSize < 256 * 1024); + lua_pop(globalState, 1); + + // we'd expect full GC to reclaim all memory allocated by the script + lua_gc(globalState, LUA_GCCOLLECT, 0); + LUAU_ASSERT(heapSize < 256 * 1024); + }; + + runCode(bytecode, false); + + if (kFuzzCodegen && Luau::CodeGen::isSupported()) + runCode(bytecode, true); } } diff --git a/tests/AssemblyBuilderX64.test.cpp b/tests/AssemblyBuilderX64.test.cpp index d6d1650..c4d2a1c 100644 --- a/tests/AssemblyBuilderX64.test.cpp +++ b/tests/AssemblyBuilderX64.test.cpp @@ -441,12 +441,16 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXBinaryInstructionForms") SINGLE_COMPARE(vmulsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x59, 0xc6); SINGLE_COMPARE(vdivsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x5e, 0xc6); + SINGLE_COMPARE(vorpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x56, 0xc6); SINGLE_COMPARE(vxorpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x57, 0xc6); SINGLE_COMPARE(vandpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x54, 0xc6); + SINGLE_COMPARE(vandnpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x29, 0x55, 0xc6); SINGLE_COMPARE(vmaxsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x5f, 0xc6); SINGLE_COMPARE(vminsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0x5d, 0xc6); + + SINGLE_COMPARE(vcmpltsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0x2b, 0xc2, 0xc6, 0x01); } TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXUnaryMergeInstructionForms") @@ -510,6 +514,7 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXTernaryInstructionForms") SINGLE_COMPARE( vroundsd(xmm8, xmm13, xmmword[r13 + rdx], RoundingModeX64::RoundToPositiveInfinity), 0xc4, 0x43, 0x11, 0x0b, 0x44, 0x15, 0x00, 0x0a); SINGLE_COMPARE(vroundsd(xmm9, xmm14, xmmword[rcx + r10], RoundingModeX64::RoundToZero), 0xc4, 0x23, 0x09, 0x0b, 0x0c, 0x11, 0x0b); + SINGLE_COMPARE(vblendvpd(xmm7, xmm12, xmmword[rcx + r10], xmm5), 0xc4, 0xa3, 0x19, 0x4b, 0x3c, 0x11, 0x50); } TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "MiscInstructions") @@ -621,6 +626,7 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "Constants") build.vmovaps(xmm4, build.f32x4(1.0f, 2.0f, 4.0f, 8.0f)); char arr[16] = "hello world!123"; build.vmovupd(xmm5, build.bytes(arr, 16, 8)); + build.vmovapd(xmm5, build.f64x2(5.0, 6.0)); build.ret(); }, { @@ -630,9 +636,12 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "Constants") 0xc4, 0xe1, 0x7b, 0x10, 0x1d, 0xcc, 0xff, 0xff, 0xff, 0xc4, 0xe1, 0x78, 0x28, 0x25, 0xab, 0xff, 0xff, 0xff, 0xc4, 0xe1, 0x79, 0x10, 0x2d, 0x92, 0xff, 0xff, 0xff, + 0xc4, 0xe1, 0x79, 0x28, 0x2d, 0x79, 0xff, 0xff, 0xff, 0xc3 }, { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x40, 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!', '1', '2', '3', 0x0, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x40, diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 01e017a..3fd0e9c 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -15,7 +15,6 @@ LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) -LUAU_FASTFLAG(LuauFixAutocompleteInIf) LUAU_FASTFLAG(LuauFixAutocompleteInWhile) LUAU_FASTFLAG(LuauFixAutocompleteInFor) @@ -859,30 +858,16 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords") CHECK_EQ(ac2.entryMap.count("end"), 0); CHECK_EQ(ac2.context, AutocompleteContext::Keyword); - if (FFlag::LuauFixAutocompleteInIf) - { - check(R"( - if x t@1 - )"); + check(R"( + if x t@1 + )"); - auto ac3 = autocomplete('1'); - CHECK_EQ(3, ac3.entryMap.size()); - CHECK_EQ(ac3.entryMap.count("then"), 1); - CHECK_EQ(ac3.entryMap.count("and"), 1); - CHECK_EQ(ac3.entryMap.count("or"), 1); - CHECK_EQ(ac3.context, AutocompleteContext::Keyword); - } - else - { - check(R"( - if x t@1 - )"); - - auto ac3 = autocomplete('1'); - CHECK_EQ(1, ac3.entryMap.size()); - CHECK_EQ(ac3.entryMap.count("then"), 1); - CHECK_EQ(ac3.context, AutocompleteContext::Keyword); - } + auto ac3 = autocomplete('1'); + CHECK_EQ(3, ac3.entryMap.size()); + CHECK_EQ(ac3.entryMap.count("then"), 1); + CHECK_EQ(ac3.entryMap.count("and"), 1); + CHECK_EQ(ac3.entryMap.count("or"), 1); + CHECK_EQ(ac3.context, AutocompleteContext::Keyword); check(R"( if x then @@ -926,22 +911,19 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords") CHECK_EQ(ac5.entryMap.count("end"), 0); CHECK_EQ(ac5.context, AutocompleteContext::Statement); - if (FFlag::LuauFixAutocompleteInIf) - { - check(R"( - if t@1 - )"); + check(R"( + if t@1 + )"); - auto ac6 = autocomplete('1'); - CHECK_EQ(ac6.entryMap.count("true"), 1); - CHECK_EQ(ac6.entryMap.count("false"), 1); - CHECK_EQ(ac6.entryMap.count("then"), 0); - CHECK_EQ(ac6.entryMap.count("function"), 1); - CHECK_EQ(ac6.entryMap.count("else"), 0); - CHECK_EQ(ac6.entryMap.count("elseif"), 0); - CHECK_EQ(ac6.entryMap.count("end"), 0); - CHECK_EQ(ac6.context, AutocompleteContext::Expression); - } + auto ac6 = autocomplete('1'); + CHECK_EQ(ac6.entryMap.count("true"), 1); + CHECK_EQ(ac6.entryMap.count("false"), 1); + CHECK_EQ(ac6.entryMap.count("then"), 0); + CHECK_EQ(ac6.entryMap.count("function"), 1); + CHECK_EQ(ac6.entryMap.count("else"), 0); + CHECK_EQ(ac6.entryMap.count("elseif"), 0); + CHECK_EQ(ac6.entryMap.count("end"), 0); + CHECK_EQ(ac6.context, AutocompleteContext::Expression); } TEST_CASE_FIXTURE(ACFixture, "autocomplete_until_in_repeat") @@ -3428,6 +3410,8 @@ TEST_CASE_FIXTURE(ACFixture, "globals_are_order_independent") TEST_CASE_FIXTURE(ACFixture, "type_reduction_is_hooked_up_to_autocomplete") { + ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true}; + check(R"( type T = { x: (number & string)? } @@ -3447,15 +3431,13 @@ TEST_CASE_FIXTURE(ACFixture, "type_reduction_is_hooked_up_to_autocomplete") REQUIRE(ac1.entryMap.count("x")); std::optional ty1 = ac1.entryMap.at("x").type; REQUIRE(ty1); - CHECK("(number & string)?" == toString(*ty1, opts)); - // CHECK("nil" == toString(*ty1, opts)); + CHECK("nil" == toString(*ty1, opts)); auto ac2 = autocomplete('2'); REQUIRE(ac2.entryMap.count("thingamabob")); std::optional ty2 = ac2.entryMap.at("thingamabob").type; REQUIRE(ty2); - CHECK("{| x: (number & string)? |}" == toString(*ty2, opts)); - // CHECK("{| x: nil |}" == toString(*ty2, opts)); + CHECK("{| x: nil |}" == toString(*ty2, opts)); } TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback") diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 434d983..eefabbf 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -1025,8 +1025,6 @@ L0: RETURN R0 0 TEST_CASE("AndOr") { - ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true}; - // codegen for constant, local, global for and CHECK_EQ("\n" + compileFunction0("local a = 1 a = a and 2 return a"), R"( LOADN R0 1 @@ -1319,8 +1317,6 @@ RETURN R0 0 TEST_CASE("InterpStringRegisterLimit") { - ScopedFastFlag luauCompileInterpStringLimit{"LuauCompileInterpStringLimit", true}; - CHECK_THROWS_AS(compileFunction0(("local a = `" + rep("{1}", 254) + "`").c_str()), std::exception); CHECK_THROWS_AS(compileFunction0(("local a = `" + rep("{1}", 253) + "`").c_str()), std::exception); } @@ -2262,8 +2258,6 @@ L1: RETURN R3 -1 TEST_CASE("UpvaluesLoopsBytecode") { - ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true}; - CHECK_EQ("\n" + compileFunction(R"( function test() for i=1,10 do @@ -5161,8 +5155,6 @@ RETURN R1 1 TEST_CASE("InlineMutate") { - ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true}; - // if the argument is mutated, it gets a register even if the value is constant CHECK_EQ("\n" + compileFunction(R"( local function foo(a) @@ -6756,8 +6748,6 @@ MOVE R1 R3 RETURN R0 0 )"); - ScopedFastFlag luauMultiAssignmentConflictFix{"LuauMultiAssignmentConflictFix", true}; - // because we perform assignments to complex l-values after assignments to locals, we make sure register conflicts are tracked accordingly CHECK_EQ("\n" + compileFunction0(R"( local a, b = ... @@ -6795,8 +6785,6 @@ L0: RETURN R1 -1 TEST_CASE("SkipSelfAssignment") { - ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true}; - CHECK_EQ("\n" + compileFunction0("local a a = a"), R"( LOADNIL R0 RETURN R0 0 diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index a07a9f6..077310a 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -297,8 +297,6 @@ TEST_CASE("Clear") TEST_CASE("Strings") { - ScopedFastFlag luauStringFormatAnyFix{"LuauStringFormatAnyFix", true}; - runConformance("strings.lua"); } diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index 8504a9f..afd5a4e 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -1706,23 +1706,6 @@ local _ = 0x10000000000000000 CHECK_EQ(result.warnings[1].text, "Hexadecimal number literal exceeded available precision and has been truncated to 2^64"); } -// TODO: remove with FFlagLuauErrorDoubleHexPrefix -TEST_CASE_FIXTURE(Fixture, "IntegerParsingDoublePrefix") -{ - ScopedFastFlag luauErrorDoubleHexPrefix{"LuauErrorDoubleHexPrefix", false}; // Lint will be available until we start rejecting code - - LintResult result = lint(R"( -local _ = 0x0x123 -local _ = 0x0xffffffffffffffffffffffffffffffffff -)"); - - REQUIRE(2 == result.warnings.size()); - CHECK_EQ(result.warnings[0].text, - "Hexadecimal number literal has a double prefix, which will fail to parse in the future; remove the extra 0x to fix"); - CHECK_EQ(result.warnings[1].text, - "Hexadecimal number literal has a double prefix, which will fail to parse in the future; remove the extra 0x to fix"); -} - TEST_CASE_FIXTURE(Fixture, "ComparisonPrecedence") { LintResult result = lint(R"( diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index cf58cfc..ca06046 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -2,6 +2,7 @@ #include "Fixture.h" +#include "Luau/AstQuery.h" #include "Luau/Common.h" #include "Luau/Type.h" #include "doctest.h" @@ -9,6 +10,8 @@ #include "Luau/Normalize.h" #include "Luau/BuiltinDefinitions.h" +LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) + using namespace Luau; namespace @@ -377,9 +380,25 @@ struct NormalizeFixture : Fixture normalizer.clearCaches(); CheckResult result = check("type _Res = " + annotation); LUAU_REQUIRE_NO_ERRORS(result); - std::optional ty = lookupType("_Res"); - REQUIRE(ty); - return normalizer.normalize(*ty); + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + SourceModule* sourceModule = getMainSourceModule(); + REQUIRE(sourceModule); + AstNode* node = findNodeAtPosition(*sourceModule, {0, 5}); + REQUIRE(node); + AstStatTypeAlias* alias = node->as(); + REQUIRE(alias); + TypeId* originalTy = getMainModule()->astOriginalResolvedTypes.find(alias->type); + REQUIRE(originalTy); + return normalizer.normalize(*originalTy); + } + else + { + std::optional ty = lookupType("_Res"); + REQUIRE(ty); + return normalizer.normalize(*ty); + } } TypeId normal(const std::string& annotation) diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index e2254ec..c72cbcc 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -683,8 +683,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_numbers_binary") TEST_CASE_FIXTURE(Fixture, "parse_numbers_error") { - ScopedFastFlag luauErrorDoubleHexPrefix{"LuauErrorDoubleHexPrefix", true}; - CHECK_EQ(getParseError("return 0b123"), "Malformed number"); CHECK_EQ(getParseError("return 123x"), "Malformed number"); CHECK_EQ(getParseError("return 0xg"), "Malformed number"); @@ -693,13 +691,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_numbers_error") CHECK_EQ(getParseError("return 0x0xffffffffffffffffffffffffffff"), "Malformed number"); } -TEST_CASE_FIXTURE(Fixture, "parse_numbers_error_soft") -{ - ScopedFastFlag luauErrorDoubleHexPrefix{"LuauErrorDoubleHexPrefix", false}; - - CHECK_EQ(getParseError("return 0x0x0x0x0x0x0x0"), "Malformed number"); -} - TEST_CASE_FIXTURE(Fixture, "break_return_not_last_error") { CHECK_EQ(getParseError("return 0 print(5)"), "Expected , got 'print'"); diff --git a/tests/ToDot.test.cpp b/tests/ToDot.test.cpp index c0cbe17..11dca11 100644 --- a/tests/ToDot.test.cpp +++ b/tests/ToDot.test.cpp @@ -80,14 +80,9 @@ n1 [label="AnyType 1"]; TEST_CASE_FIXTURE(Fixture, "bound") { - CheckResult result = check(R"( -function a(): number return 444 end -local b = a() -)"); - LUAU_REQUIRE_NO_ERRORS(result); + TypeArena arena; - std::optional ty = getType("b"); - REQUIRE(bool(ty)); + TypeId ty = arena.addType(BoundType{builtinTypes->numberType}); ToDotOptions opts; opts.showPointers = false; @@ -96,7 +91,7 @@ n1 [label="BoundType 1"]; n1 -> n2; n2 [label="number"]; })", - toDot(*ty, opts)); + toDot(ty, opts)); } TEST_CASE_FIXTURE(Fixture, "function") @@ -172,10 +167,9 @@ n3 [label="number"]; TEST_CASE_FIXTURE(Fixture, "intersection") { - CheckResult result = check(R"( -local a: string & number -- uninhabited -)"); - LUAU_REQUIRE_NO_ERRORS(result); + TypeArena arena; + + TypeId ty = arena.addType(IntersectionType{{builtinTypes->stringType, builtinTypes->numberType}}); ToDotOptions opts; opts.showPointers = false; @@ -186,7 +180,7 @@ n2 [label="string"]; n1 -> n3; n3 [label="number"]; })", - toDot(requireType("a"), opts)); + toDot(ty, opts)); } TEST_CASE_FIXTURE(Fixture, "table") @@ -396,44 +390,25 @@ n3 [label="number"]; TEST_CASE_FIXTURE(Fixture, "bound_table") { - CheckResult result = check(R"( -local a = {x=2} -local b -b.x = 2 -b = a -)"); - LUAU_REQUIRE_NO_ERRORS(result); + TypeArena arena; - std::optional ty = getType("b"); - REQUIRE(bool(ty)); + TypeId ty = arena.addType(TableType{}); + getMutable(ty)->props["x"] = {builtinTypes->numberType}; + + TypeId boundTy = arena.addType(TableType{}); + getMutable(boundTy)->boundTo = ty; ToDotOptions opts; opts.showPointers = false; - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ(R"(digraph graphname { -n1 [label="BoundType 1"]; -n1 -> n2; -n2 [label="TableType 2"]; -n2 -> n3 [label="boundTo"]; -n3 [label="TableType a"]; -n3 -> n4 [label="x"]; -n4 [label="number"]; -})", - toDot(*ty, opts)); - } - else - { - CHECK_EQ(R"(digraph graphname { + CHECK_EQ(R"(digraph graphname { n1 [label="TableType 1"]; n1 -> n2 [label="boundTo"]; -n2 [label="TableType a"]; +n2 [label="TableType 2"]; n2 -> n3 [label="x"]; n3 [label="number"]; })", - toDot(*ty, opts)); - } + toDot(boundTy, opts)); } TEST_CASE_FIXTURE(Fixture, "builtintypes") diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index 0e51f97..d96b1cc 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -291,9 +291,9 @@ TEST_CASE_FIXTURE(Fixture, "quit_stringifying_type_when_length_is_exceeded") { o.maxTypeLength = 30; CHECK_EQ(toString(requireType("f0"), o), "() -> ()"); - CHECK_EQ(toString(requireType("f1"), o), "(a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*"); - CHECK_EQ(toString(requireType("f2"), o), "(b) -> ((a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*"); - CHECK_EQ(toString(requireType("f3"), o), "(c) -> ((b) -> ((a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*"); + CHECK_EQ(toString(requireType("f1"), o), "(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*"); + CHECK_EQ(toString(requireType("f2"), o), "(b) -> ((a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*"); + CHECK_EQ(toString(requireType("f3"), o), "(c) -> ((b) -> ((a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*"); } else { @@ -321,9 +321,9 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_type_is_still_capped_when_exhaustive") { o.maxTypeLength = 30; CHECK_EQ(toString(requireType("f0"), o), "() -> ()"); - CHECK_EQ(toString(requireType("f1"), o), "(a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*"); - CHECK_EQ(toString(requireType("f2"), o), "(b) -> ((a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*"); - CHECK_EQ(toString(requireType("f3"), o), "(c) -> ((b) -> ((a) -> (() -> ()) | (a & ~(false?))... *TRUNCATED*"); + CHECK_EQ(toString(requireType("f1"), o), "(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*"); + CHECK_EQ(toString(requireType("f2"), o), "(b) -> ((a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*"); + CHECK_EQ(toString(requireType("f3"), o), "(c) -> ((b) -> ((a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*"); } else { diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index cd0a063..1eaec90 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -357,8 +357,6 @@ TEST_CASE_FIXTURE(Fixture, "cli_38393_recursive_intersection_oom") type t0 = ((typeof(_))&((t0)&(((typeof(_))&(t0))->typeof(_))),{n163:any,})->(any,typeof(_)) _(_) )"); - - LUAU_REQUIRE_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "type_alias_fwd_declaration_is_precise") diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index e18a737..ea6fff7 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -132,22 +132,40 @@ TEST_CASE_FIXTURE(Fixture, "should_still_pick_an_overload_whose_arguments_are_un TEST_CASE_FIXTURE(Fixture, "propagates_name") { - const std::string code = R"( - type A={a:number} - type B={b:string} + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CheckResult result = check(R"( + type A={a:number} + type B={b:string} - local c:A&B - local b = c - )"; - const std::string expected = R"( - type A={a:number} - type B={b:string} + local c:A&B + local b = c + )"); - local c:A&B - local b:A&B=c - )"; + LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(expected, decorateWithTypes(code)); + CHECK("{| a: number, b: string |}" == toString(requireType("b"))); + } + else + { + const std::string code = R"( + type A={a:number} + type B={b:string} + + local c:A&B + local b = c + )"; + + const std::string expected = R"( + type A={a:number} + type B={b:string} + + local c:A&B + local b:A&B=c + )"; + + CHECK_EQ(expected, decorateWithTypes(code)); + } } TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_property_guaranteed_to_exist") @@ -161,17 +179,10 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_property_guarante )"); LUAU_REQUIRE_NO_ERRORS(result); - - const IntersectionType* r = get(requireType("r")); - REQUIRE(r); - - TableType* a = getMutable(r->parts[0]); - REQUIRE(a); - CHECK_EQ(typeChecker.numberType, a->props["y"].type); - - TableType* b = getMutable(r->parts[1]); - REQUIRE(b); - CHECK_EQ(typeChecker.numberType, b->props["y"].type); + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK("{| y: number |}" == toString(requireType("r"))); + else + CHECK("{| y: number |} & {| y: number |}" == toString(requireType("r"))); } TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_depth") @@ -207,7 +218,10 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_mixed_types") )"); LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("number & string", toString(requireType("r"))); + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ("never", toString(requireType("r"))); + else + CHECK_EQ("number & string", toString(requireType("r"))); } TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_one_part_missing_the_property") @@ -387,7 +401,10 @@ local a: XYZ = 3 )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'number' could not be converted into 'X & Y & Z' + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ(toString(result.errors[0]), R"(Type 'number' could not be converted into '{| x: number, y: number, z: number |}')"); + else + CHECK_EQ(toString(result.errors[0]), R"(Type 'number' could not be converted into 'X & Y & Z' caused by: Not all intersection parts are compatible. Type 'number' could not be converted into 'X')"); } @@ -404,7 +421,11 @@ local b: number = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Type 'X & Y & Z' could not be converted into 'number'; none of the intersection parts are compatible)"); + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ(toString(result.errors[0]), R"(Type '{| x: number, y: number, z: number |}' could not be converted into 'number')"); + else + CHECK_EQ( + toString(result.errors[0]), R"(Type 'X & Y & Z' could not be converted into 'number'; none of the intersection parts are compatible)"); } TEST_CASE_FIXTURE(Fixture, "overload_is_not_a_function") @@ -444,7 +465,11 @@ TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type 'boolean & false' could not be converted into 'true'; none of the intersection parts are compatible"); + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ(toString(result.errors[0]), "Type 'false' could not be converted into 'true'"); + else + CHECK_EQ( + toString(result.errors[0]), "Type 'boolean & false' could not be converted into 'true'; none of the intersection parts are compatible"); } TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false") @@ -456,9 +481,14 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - // TODO: odd stringification of `false & (boolean & false)`.) - CHECK_EQ(toString(result.errors[0]), - "Type 'boolean & false & false' could not be converted into 'true'; none of the intersection parts are compatible"); + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ(toString(result.errors[0]), "Type 'false' could not be converted into 'true'"); + else + { + // TODO: odd stringification of `false & (boolean & false)`.) + CHECK_EQ(toString(result.errors[0]), + "Type 'boolean & false & false' could not be converted into 'true'; none of the intersection parts are compatible"); + } } TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions") @@ -496,8 +526,21 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "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"); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CHECK_EQ(toString(result.errors[0]), + "Type '{| p: number?, q: nil, r: number? |}' could not be converted into '{| p: nil |}'\n" + "caused by:\n" + " Property 'p' is not compatible. Type 'number?' could not be converted into 'nil'\n" + "caused by:\n" + " Not all union options are compatible. Type 'number' could not be converted into 'nil' in an invariant context"); + } + else + { + CHECK_EQ(toString(result.errors[0]), + "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"); + } } TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties") @@ -508,9 +551,35 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties") local z : { p : string?, q : number? } = x -- Not OK )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type '{| p: number?, q: any |} & {| p: unknown, q: string? |}' could not be converted into '{| p: string?, " - "q: number? |}'; none of the intersection parts are compatible"); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_ERROR_COUNT(2, 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"); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ(toString(result.errors[0]), + "Type '{| p: number?, q: any |} & {| p: unknown, q: string? |}' could not be converted into '{| p: string?, " + "q: number? |}'; none of the intersection parts are compatible"); + } } TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties") @@ -537,9 +606,18 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), - "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"); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CHECK_EQ(toString(result.errors[0]), + "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"); + } + else + { + CHECK_EQ(toString(result.errors[0]), + "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"); + } } TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic") @@ -840,7 +918,7 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_intersection_types") LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("({| x: number |} & {| x: string |}) -> {| x: number |} & {| x: string |}", toString(requireType("f"))); + CHECK_EQ("(never) -> never", toString(requireType("f"))); } TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_intersection_types_2") @@ -856,7 +934,7 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_intersection_types_2") LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("({| x: number |} & {| x: string |}) -> number & string", toString(requireType("f"))); + CHECK_EQ("(never) -> never", toString(requireType("f"))); } TEST_SUITE_END(); diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index 7a89df9..30cbe1d 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -661,4 +661,32 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_ok_with_inference") CHECK(toString(requireType("b")) == "string"); } +TEST_CASE_FIXTURE(Fixture, "for_loop_lower_bound_is_string") +{ + CheckResult result = check(R"( + for i: unknown = 1, 10 do end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(Fixture, "for_loop_lower_bound_is_string_2") +{ + CheckResult result = check(R"( + for i: never = 1, 10 do end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ("Type 'number' could not be converted into 'never'", toString(result.errors[0])); +} + +TEST_CASE_FIXTURE(Fixture, "for_loop_lower_bound_is_string_3") +{ + CheckResult result = check(R"( + for i: number | string = 1, 10 do end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index d4350fd..feb04c2 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -25,16 +25,8 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types") local x:string|number = s )"); LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ(toString(*requireType("s")), "(string & ~(false?)) | number"); - CHECK_EQ(toString(*requireType("x")), "number | string"); - } - else - { - CHECK_EQ(toString(*requireType("s")), "number | string"); - CHECK_EQ(toString(*requireType("x")), "number | string"); - } + CHECK_EQ(toString(*requireType("s")), "number | string"); + CHECK_EQ(toString(*requireType("x")), "number | string"); } TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras") @@ -45,16 +37,8 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras") local y = x or "s" )"); LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ(toString(*requireType("s")), "(string & ~(false?)) | number"); - CHECK_EQ(toString(*requireType("y")), "((number | string) & ~(false?)) | string"); - } - else - { - CHECK_EQ(toString(*requireType("s")), "number | string"); - CHECK_EQ(toString(*requireType("y")), "number | string"); - } + CHECK_EQ(toString(*requireType("s")), "number | string"); + CHECK_EQ(toString(*requireType("y")), "number | string"); } TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union") @@ -78,14 +62,7 @@ TEST_CASE_FIXTURE(Fixture, "and_does_not_always_add_boolean") local x:boolean|number = s )"); LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ(toString(*requireType("s")), "((false?) & string) | number"); - } - else - { - CHECK_EQ(toString(*requireType("s")), "number"); - } + CHECK_EQ(toString(*requireType("s")), "number"); } TEST_CASE_FIXTURE(Fixture, "and_adds_boolean_no_superfluous_union") @@ -104,14 +81,7 @@ TEST_CASE_FIXTURE(Fixture, "and_or_ternary") local s = (1/2) > 0.5 and "a" or 10 )"); LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ(toString(*requireType("s")), "((((false?) & boolean) | string) & ~(false?)) | number"); - } - else - { - CHECK_EQ(toString(*requireType("s")), "number | string"); - } + CHECK_EQ(toString(*requireType("s")), "number | string"); } TEST_CASE_FIXTURE(BuiltinsFixture, "primitive_arith_no_metatable") @@ -833,14 +803,7 @@ local b: number = 1 or a TypeMismatch* tm = get(result.errors[0]); REQUIRE(tm); CHECK_EQ(typeChecker.numberType, tm->wantedType); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("((number & ~(false?)) | number)?", toString(tm->givenType)); - } - else - { - CHECK_EQ("number?", toString(tm->givenType)); - } + CHECK_EQ("number?", toString(tm->givenType)); } TEST_CASE_FIXTURE(Fixture, "operator_eq_verifies_types_do_intersect") @@ -901,14 +864,7 @@ TEST_CASE_FIXTURE(Fixture, "refine_and_or") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("((((false?) & ({| x: number? |}?)) | a) & ~(false?)) | number", toString(requireType("u"))); - } - else - { - CHECK_EQ("number", toString(requireType("u"))); - } + CHECK_EQ("number", toString(requireType("u"))); } TEST_CASE_FIXTURE(Fixture, "infer_any_in_all_modes_when_lhs_is_unknown") @@ -1095,20 +1051,16 @@ local z = b and 1 local w = c and 1 )"); + CHECK("number?" == toString(requireType("x"))); + CHECK("number" == toString(requireType("y"))); if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK("((false?) & (number?)) | number" == toString(requireType("x"))); - CHECK("((false?) & string) | number" == toString(requireType("y"))); - CHECK("((false?) & boolean) | number" == toString(requireType("z"))); - CHECK("((false?) & a) | number" == toString(requireType("w"))); - } + CHECK("false | number" == toString(requireType("z"))); else - { - CHECK("number?" == toString(requireType("x"))); - CHECK("number" == toString(requireType("y"))); CHECK("boolean | number" == toString(requireType("z"))); // 'false' widened to boolean + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK("((false?) & a) | number" == toString(requireType("w"))); + else CHECK("(boolean | number)?" == toString(requireType("w"))); - } } TEST_CASE_FIXTURE(BuiltinsFixture, "reworked_or") @@ -1133,24 +1085,20 @@ local e1 = e or 'e' local f1 = f or 'f' )"); + CHECK("number | string" == toString(requireType("a1"))); + CHECK("number" == toString(requireType("b1"))); if (FFlag::DebugLuauDeferredConstraintResolution) { - CHECK("((false | number) & ~(false?)) | string" == toString(requireType("a1"))); - CHECK("((number?) & ~(false?)) | number" == toString(requireType("b1"))); - CHECK("(boolean & ~(false?)) | string" == toString(requireType("c1"))); - CHECK("(true & ~(false?)) | string" == toString(requireType("d1"))); - CHECK("(false & ~(false?)) | string" == toString(requireType("e1"))); - CHECK("(nil & ~(false?)) | string" == toString(requireType("f1"))); + CHECK("string | true" == toString(requireType("c1"))); + CHECK("string | true" == toString(requireType("d1"))); } else { - CHECK("number | string" == toString(requireType("a1"))); - CHECK("number" == toString(requireType("b1"))); CHECK("boolean | string" == toString(requireType("c1"))); // 'true' widened to boolean CHECK("boolean | string" == toString(requireType("d1"))); // 'true' widened to boolean - CHECK("string" == toString(requireType("e1"))); - CHECK("string" == toString(requireType("f1"))); } + CHECK("string" == toString(requireType("e1"))); + CHECK("string" == toString(requireType("f1"))); } TEST_SUITE_END(); diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index 9375202..7d629f7 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -594,28 +594,6 @@ return wrapStrictTable(Constants, "Constants") CHECK(get(*result)); } -// We need a simplification step to make this do the right thing. ("normalization-lite") -TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument") -{ - CheckResult result = check(R"( - local function foo(t, x) - if x == "hi" or x == "bye" then - table.insert(t, x) - end - - return t - end - - local t = foo({}, "hi") - table.insert(t, "totally_unrelated_type" :: "totally_unrelated_type") - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - // We'd really like for this to be {string} - CHECK_EQ("{string | string}", toString(requireType("t"))); -} - namespace { struct IsSubtypeFixture : Fixture @@ -814,4 +792,44 @@ caused by: } } +TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument") +{ + CheckResult result = check(R"( + local function foo(t, x) + if x == "hi" or x == "bye" then + table.insert(t, x) + end + + return t + end + + local t = foo({}, "hi") + table.insert(t, "totally_unrelated_type" :: "totally_unrelated_type") + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ("{string}", toString(requireType("t"))); + else + { + // We'd really like for this to be {string} + CHECK_EQ("{string | string}", toString(requireType("t"))); + } +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_clone_it") +{ + CheckResult result = check(R"( + local function f(x: unknown) + if typeof(x) == "table" then + local cloned: {} = table.clone(x) + end + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + // LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 786b07d..43c0b38 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -36,7 +36,7 @@ std::optional> magicFunctionInstanceIsA( return WithPredicate{booleanPack, {IsAPredicate{std::move(*lvalue), expr.location, tfun->type}}}; } -std::vector dcrMagicRefinementInstanceIsA(const MagicRefinementContext& ctx) +std::vector dcrMagicRefinementInstanceIsA(const MagicRefinementContext& ctx) { if (ctx.callSite->args.size != 1) return {}; @@ -54,7 +54,7 @@ std::vector dcrMagicRefinementInstanceIsA(const MagicRefinementCon if (!tfun) return {}; - return {ctx.connectiveArena->proposition(*def, tfun->type)}; + return {ctx.refinementArena->proposition(*def, tfun->type)}; } struct RefinementClassFixture : BuiltinsFixture @@ -122,16 +122,8 @@ TEST_CASE_FIXTURE(Fixture, "is_truthy_constraint") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({3, 26}))); - CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({5, 26}))); - } - else - { - CHECK_EQ("string", toString(requireTypeAtPosition({3, 26}))); - CHECK_EQ("nil", toString(requireTypeAtPosition({5, 26}))); - } + CHECK_EQ("string", toString(requireTypeAtPosition({3, 26}))); + CHECK_EQ("nil", toString(requireTypeAtPosition({5, 26}))); } TEST_CASE_FIXTURE(Fixture, "invert_is_truthy_constraint") @@ -148,16 +140,8 @@ TEST_CASE_FIXTURE(Fixture, "invert_is_truthy_constraint") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({3, 26}))); - CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({5, 26}))); - } - else - { - CHECK_EQ("nil", toString(requireTypeAtPosition({3, 26}))); - CHECK_EQ("string", toString(requireTypeAtPosition({5, 26}))); - } + CHECK_EQ("nil", toString(requireTypeAtPosition({3, 26}))); + CHECK_EQ("string", toString(requireTypeAtPosition({5, 26}))); } TEST_CASE_FIXTURE(Fixture, "parenthesized_expressions_are_followed_through") @@ -174,16 +158,8 @@ TEST_CASE_FIXTURE(Fixture, "parenthesized_expressions_are_followed_through") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({3, 26}))); - CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({5, 26}))); - } - else - { - CHECK_EQ("nil", toString(requireTypeAtPosition({3, 26}))); - CHECK_EQ("string", toString(requireTypeAtPosition({5, 26}))); - } + CHECK_EQ("nil", toString(requireTypeAtPosition({3, 26}))); + CHECK_EQ("string", toString(requireTypeAtPosition({5, 26}))); } TEST_CASE_FIXTURE(Fixture, "and_constraint") @@ -202,16 +178,8 @@ TEST_CASE_FIXTURE(Fixture, "and_constraint") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({3, 26}))); - CHECK_EQ("(number?) & ~(false?)", toString(requireTypeAtPosition({4, 26}))); - } - else - { - CHECK_EQ("string", toString(requireTypeAtPosition({3, 26}))); - CHECK_EQ("number", toString(requireTypeAtPosition({4, 26}))); - } + CHECK_EQ("string", toString(requireTypeAtPosition({3, 26}))); + CHECK_EQ("number", toString(requireTypeAtPosition({4, 26}))); CHECK_EQ("string?", toString(requireTypeAtPosition({6, 26}))); CHECK_EQ("number?", toString(requireTypeAtPosition({7, 26}))); @@ -236,16 +204,8 @@ TEST_CASE_FIXTURE(Fixture, "not_and_constraint") CHECK_EQ("string?", toString(requireTypeAtPosition({3, 26}))); CHECK_EQ("number?", toString(requireTypeAtPosition({4, 26}))); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({6, 26}))); - CHECK_EQ("(number?) & ~(false?)", toString(requireTypeAtPosition({7, 26}))); - } - else - { - CHECK_EQ("string", toString(requireTypeAtPosition({6, 26}))); - CHECK_EQ("number", toString(requireTypeAtPosition({7, 26}))); - } + CHECK_EQ("string", toString(requireTypeAtPosition({6, 26}))); + CHECK_EQ("number", toString(requireTypeAtPosition({7, 26}))); } TEST_CASE_FIXTURE(Fixture, "or_predicate_with_truthy_predicates") @@ -267,16 +227,8 @@ TEST_CASE_FIXTURE(Fixture, "or_predicate_with_truthy_predicates") CHECK_EQ("string?", toString(requireTypeAtPosition({3, 26}))); CHECK_EQ("number?", toString(requireTypeAtPosition({4, 26}))); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({6, 26}))); - CHECK_EQ("(number?) & ~~(false?)", toString(requireTypeAtPosition({7, 26}))); - } - else - { - CHECK_EQ("nil", toString(requireTypeAtPosition({6, 26}))); - CHECK_EQ("nil", toString(requireTypeAtPosition({7, 26}))); - } + CHECK_EQ("nil", toString(requireTypeAtPosition({6, 26}))); + CHECK_EQ("nil", toString(requireTypeAtPosition({7, 26}))); } TEST_CASE_FIXTURE(Fixture, "a_and_b_or_a_and_c") @@ -297,26 +249,17 @@ TEST_CASE_FIXTURE(Fixture, "a_and_b_or_a_and_c") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(string?) & (~(false?) | ~(false?))", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("number?", toString(requireTypeAtPosition({4, 28}))); - CHECK_EQ("boolean", toString(requireTypeAtPosition({5, 28}))); - CHECK_EQ("string?", toString(requireTypeAtPosition({7, 28}))); - CHECK_EQ("number?", toString(requireTypeAtPosition({8, 28}))); - CHECK_EQ("boolean", toString(requireTypeAtPosition({9, 28}))); - } + CHECK_EQ("string", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("number?", toString(requireTypeAtPosition({4, 28}))); + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ("boolean", toString(requireTypeAtPosition({5, 28}))); else - { - CHECK_EQ("string", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("number?", toString(requireTypeAtPosition({4, 28}))); CHECK_EQ("true", toString(requireTypeAtPosition({5, 28}))); // oh no! :( - CHECK_EQ("string?", toString(requireTypeAtPosition({7, 28}))); - CHECK_EQ("number?", toString(requireTypeAtPosition({8, 28}))); - CHECK_EQ("boolean", toString(requireTypeAtPosition({9, 28}))); - } + CHECK_EQ("string?", toString(requireTypeAtPosition({7, 28}))); + CHECK_EQ("number?", toString(requireTypeAtPosition({8, 28}))); + CHECK_EQ("boolean", toString(requireTypeAtPosition({9, 28}))); } TEST_CASE_FIXTURE(Fixture, "type_assertion_expr_carry_its_constraints") @@ -357,14 +300,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_if_condition_position") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("any & number", toString(requireTypeAtPosition({3, 26}))); - } - else - { - CHECK_EQ("number", toString(requireTypeAtPosition({3, 26}))); - } + CHECK_EQ("number", toString(requireTypeAtPosition({3, 26}))); } TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_assert_position") @@ -433,8 +369,8 @@ TEST_CASE_FIXTURE(Fixture, "truthy_constraint_on_properties") if (FFlag::DebugLuauDeferredConstraintResolution) { - CHECK("{| x: number? |} & {| x: ~(false?) |}" == toString(requireTypeAtPosition({4, 23}))); - CHECK("(number?) & ~(false?)" == toString(requireTypeAtPosition({5, 26}))); + CHECK("{| x: number |}" == toString(requireTypeAtPosition({4, 23}))); + CHECK("number" == toString(requireTypeAtPosition({5, 26}))); } CHECK_EQ("number?", toString(requireType("bar"))); @@ -478,22 +414,11 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_another_lvalue") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "((number | string)?) & unknown"); // a == b - CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "(boolean?) & unknown"); // a == b + CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "(number | string)?"); // a == b + CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "boolean?"); // a == b - CHECK_EQ(toString(requireTypeAtPosition({5, 33})), "((number | string)?) & unknown"); // a ~= b - CHECK_EQ(toString(requireTypeAtPosition({5, 36})), "(boolean?) & unknown"); // a ~= b - } - else - { - CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "(number | string)?"); // a == b - CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "boolean?"); // a == b - - CHECK_EQ(toString(requireTypeAtPosition({5, 33})), "(number | string)?"); // a ~= b - CHECK_EQ(toString(requireTypeAtPosition({5, 36})), "boolean?"); // a ~= b - } + CHECK_EQ(toString(requireTypeAtPosition({5, 33})), "(number | string)?"); // a ~= b + CHECK_EQ(toString(requireTypeAtPosition({5, 36})), "boolean?"); // a ~= b } TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_a_term") @@ -510,16 +435,8 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_a_term") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "((number | string)?) & unknown"); // a == 1 - CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "((number | string)?) & unknown"); // a ~= 1 - } - else - { - CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "(number | string)?"); // a == 1; - CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a ~= 1 - } + CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "(number | string)?"); // a == 1; + CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a ~= 1 } TEST_CASE_FIXTURE(Fixture, "term_is_equal_to_an_lvalue") @@ -538,8 +455,8 @@ TEST_CASE_FIXTURE(Fixture, "term_is_equal_to_an_lvalue") if (FFlag::DebugLuauDeferredConstraintResolution) { - CHECK_EQ(toString(requireTypeAtPosition({3, 28})), R"("hello" & ((number | string)?))"); // a == "hello" - CHECK_EQ(toString(requireTypeAtPosition({5, 28})), R"(((number | string)?) & ~"hello")"); // a ~= "hello" + CHECK_EQ(toString(requireTypeAtPosition({3, 28})), R"("hello")"); // a == "hello" + CHECK_EQ(toString(requireTypeAtPosition({5, 28})), R"(((string & ~"hello") | number)?)"); // a ~= "hello" } else { @@ -562,16 +479,8 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "((number | string)?) & ~nil"); // a ~= nil - CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "((number | string)?) & unknown"); // a == nil - } - else - { - CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil - CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a == nil - } + CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil + CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a == nil } TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue") @@ -586,17 +495,8 @@ TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - ToStringOptions opts; - CHECK_EQ(toString(requireTypeAtPosition({3, 33}), opts), "a & unknown"); // a == b - CHECK_EQ(toString(requireTypeAtPosition({3, 36}), opts), "(string?) & unknown"); // a == b - } - else - { - CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "a"); // a == b - CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "string?"); // a == b - } + CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "a"); // a == b + CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "string?"); // a == b } TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_equal") @@ -611,16 +511,8 @@ TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_e LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any & unknown"); // a ~= b - CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "({| x: number |}?) & unknown"); // a ~= b - } - else - { - CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b - CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}?"); // a ~= b - } + CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b + CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}?"); // a ~= b } TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil") @@ -639,22 +531,11 @@ TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ(toString(requireTypeAtPosition({6, 29})), "string & unknown"); // a ~= b - CHECK_EQ(toString(requireTypeAtPosition({6, 32})), "(string?) & unknown"); // a ~= b + CHECK_EQ(toString(requireTypeAtPosition({6, 29})), "string"); // a ~= b + CHECK_EQ(toString(requireTypeAtPosition({6, 32})), "string?"); // a ~= b - CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "string & unknown"); // a == b - CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "(string?) & unknown"); // a == b - } - else - { - CHECK_EQ(toString(requireTypeAtPosition({6, 29})), "string"); // a ~= b - CHECK_EQ(toString(requireTypeAtPosition({6, 32})), "string?"); // a ~= b - - CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "string"); // a == b - CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "string?"); // a == b - } + CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "string"); // a == b + CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "string?"); // a == b } TEST_CASE_FIXTURE(Fixture, "narrow_property_of_a_bounded_variable") @@ -729,16 +610,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_not_to_be_string") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(boolean | number | string) & ~string", toString(requireTypeAtPosition({3, 28}))); // type(x) ~= "string" - CHECK_EQ("(boolean | number | string) & string", toString(requireTypeAtPosition({5, 28}))); // type(x) == "string" - } - else - { - CHECK_EQ("boolean | number", toString(requireTypeAtPosition({3, 28}))); // type(x) ~= "string" - CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) == "string" - } + CHECK_EQ("boolean | number", toString(requireTypeAtPosition({3, 28}))); // type(x) ~= "string" + CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) == "string" } TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_table") @@ -773,16 +646,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_functions") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(((number) -> string) | string) & function", toString(requireTypeAtPosition({3, 28}))); // type(x) == "function" - CHECK_EQ("(((number) -> string) | string) & ~function", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "function" - } - else - { - CHECK_EQ("(number) -> string", toString(requireTypeAtPosition({3, 28}))); // type(x) == "function" - CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "function" - } + CHECK_EQ("(number) -> string", toString(requireTypeAtPosition({3, 28}))); // type(x) == "function" + CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "function" } TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_can_filter_for_intersection_of_tables") @@ -821,16 +686,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_can_filter_for_overloaded_functio LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("((((number) -> string) & ((string) -> number))?) & function", toString(requireTypeAtPosition({4, 28}))); - CHECK_EQ("((((number) -> string) & ((string) -> number))?) & ~function", toString(requireTypeAtPosition({6, 28}))); - } - else - { - CHECK_EQ("((number) -> string) & ((string) -> number)", toString(requireTypeAtPosition({4, 28}))); - CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28}))); - } + CHECK_EQ("((number) -> string) & ((string) -> number)", toString(requireTypeAtPosition({4, 28}))); + CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28}))); } TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_narrowed_into_nothingness") @@ -898,16 +755,8 @@ TEST_CASE_FIXTURE(Fixture, "not_a_and_not_b") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(number?) & ~~(false?)", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("(number?) & ~~(false?)", toString(requireTypeAtPosition({4, 28}))); - } - else - { - CHECK_EQ("nil", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("nil", toString(requireTypeAtPosition({4, 28}))); - } + CHECK_EQ("nil", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("nil", toString(requireTypeAtPosition({4, 28}))); } TEST_CASE_FIXTURE(Fixture, "not_a_and_not_b2") @@ -923,16 +772,8 @@ TEST_CASE_FIXTURE(Fixture, "not_a_and_not_b2") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(number?) & ~~(false?)", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("(number?) & ~~(false?)", toString(requireTypeAtPosition({4, 28}))); - } - else - { - CHECK_EQ("nil", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("nil", toString(requireTypeAtPosition({4, 28}))); - } + CHECK_EQ("nil", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("nil", toString(requireTypeAtPosition({4, 28}))); } TEST_CASE_FIXTURE(BuiltinsFixture, "either_number_or_string") @@ -947,14 +788,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "either_number_or_string") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(number | string) & any", toString(requireTypeAtPosition({3, 28}))); - } - else - { - CHECK_EQ("number | string", toString(requireTypeAtPosition({3, 28}))); - } + CHECK_EQ("number | string", toString(requireTypeAtPosition({3, 28}))); } TEST_CASE_FIXTURE(Fixture, "not_t_or_some_prop_of_t") @@ -984,16 +818,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_a_to_be_truthy_then_assert_a_to_be_nu LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("((number | string)?) & ~(false?)", toString(requireTypeAtPosition({3, 18}))); - CHECK_EQ("((number | string)?) & ~(false?) & number", toString(requireTypeAtPosition({5, 18}))); - } - else - { - CHECK_EQ("number | string", toString(requireTypeAtPosition({3, 18}))); - CHECK_EQ("number", toString(requireTypeAtPosition({5, 18}))); - } + CHECK_EQ("number | string", toString(requireTypeAtPosition({3, 18}))); + CHECK_EQ("number", toString(requireTypeAtPosition({5, 18}))); } TEST_CASE_FIXTURE(BuiltinsFixture, "merge_should_be_fully_agnostic_of_hashmap_ordering") @@ -1012,14 +838,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "merge_should_be_fully_agnostic_of_hashmap_or LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(string | table) & (string | {| x: string |}) & string", toString(requireTypeAtPosition({6, 28}))); - } - else - { - CHECK_EQ("string", toString(requireTypeAtPosition({6, 28}))); - } + CHECK_EQ("string", toString(requireTypeAtPosition({6, 28}))); } TEST_CASE_FIXTURE(BuiltinsFixture, "refine_the_correct_types_opposite_of_when_a_is_not_number_or_string") @@ -1036,16 +855,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_the_correct_types_opposite_of_when_a_ LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(boolean | number | string) & ~number & ~string", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("(boolean | number | string) & (number | string)", toString(requireTypeAtPosition({5, 28}))); - } - else - { - CHECK_EQ("boolean", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("number | string", toString(requireTypeAtPosition({5, 28}))); - } + CHECK_EQ("boolean", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("number | string", toString(requireTypeAtPosition({5, 28}))); } TEST_CASE_FIXTURE(BuiltinsFixture, "is_truthy_constraint_ifelse_expression") @@ -1058,16 +869,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "is_truthy_constraint_ifelse_expression") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({2, 29}))); - CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({2, 45}))); - } - else - { - CHECK_EQ("string", toString(requireTypeAtPosition({2, 29}))); - CHECK_EQ("nil", toString(requireTypeAtPosition({2, 45}))); - } + CHECK_EQ("string", toString(requireTypeAtPosition({2, 29}))); + CHECK_EQ("nil", toString(requireTypeAtPosition({2, 45}))); } TEST_CASE_FIXTURE(BuiltinsFixture, "invert_is_truthy_constraint_ifelse_expression") @@ -1080,16 +883,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "invert_is_truthy_constraint_ifelse_expressio LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(string?) & ~~(false?)", toString(requireTypeAtPosition({2, 42}))); - CHECK_EQ("(string?) & ~(false?)", toString(requireTypeAtPosition({2, 50}))); - } - else - { - CHECK_EQ("nil", toString(requireTypeAtPosition({2, 42}))); - CHECK_EQ("string", toString(requireTypeAtPosition({2, 50}))); - } + CHECK_EQ("nil", toString(requireTypeAtPosition({2, 42}))); + CHECK_EQ("string", toString(requireTypeAtPosition({2, 50}))); } TEST_CASE_FIXTURE(BuiltinsFixture, "type_comparison_ifelse_expression") @@ -1106,16 +901,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_comparison_ifelse_expression") LUAU_REQUIRE_NO_ERRORS(result); + CHECK_EQ("number", toString(requireTypeAtPosition({6, 49}))); if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("any & number", toString(requireTypeAtPosition({6, 49}))); - CHECK_EQ("any & ~number", toString(requireTypeAtPosition({6, 66}))); - } + CHECK_EQ("~number", toString(requireTypeAtPosition({6, 66}))); else - { - CHECK_EQ("number", toString(requireTypeAtPosition({6, 49}))); CHECK_EQ("any", toString(requireTypeAtPosition({6, 66}))); - } } TEST_CASE_FIXTURE(BuiltinsFixture, "correctly_lookup_a_shadowed_local_that_which_was_previously_refined") @@ -1196,17 +986,11 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x") LUAU_REQUIRE_NO_ERRORS(result); + CHECK_EQ(R"({| tag: "exists", x: string |})", toString(requireTypeAtPosition({5, 28}))); if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ(R"(({| tag: "exists", x: string |} | {| tag: "missing", x: nil |}) & {| x: ~(false?) |})", toString(requireTypeAtPosition({5, 28}))); - CHECK_EQ( - R"(({| tag: "exists", x: string |} | {| tag: "missing", x: nil |}) & {| x: ~~(false?) |})", toString(requireTypeAtPosition({7, 28}))); - } + CHECK_EQ(R"({| tag: "missing", x: nil |})", toString(requireTypeAtPosition({7, 28}))); else - { - CHECK_EQ(R"({| tag: "exists", x: string |})", toString(requireTypeAtPosition({5, 28}))); CHECK_EQ(R"({| tag: "exists", x: string |} | {| tag: "missing", x: nil |})", toString(requireTypeAtPosition({7, 28}))); - } } TEST_CASE_FIXTURE(Fixture, "discriminate_tag") @@ -1229,8 +1013,8 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_tag") if (FFlag::DebugLuauDeferredConstraintResolution) { - CHECK_EQ(R"((Cat | Dog) & {| tag: "Cat" |})", toString(requireTypeAtPosition({7, 33}))); - CHECK_EQ(R"((Cat | Dog) & {| tag: ~"Cat" |} & {| tag: "Dog" |})", toString(requireTypeAtPosition({9, 33}))); + CHECK_EQ(R"({| catfood: string, name: string, tag: "Cat" |})", toString(requireTypeAtPosition({7, 33}))); + CHECK_EQ(R"({| dogfood: string, name: string, tag: "Dog" |})", toString(requireTypeAtPosition({9, 33}))); } else { @@ -1259,8 +1043,8 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_tag_with_implicit_else") if (FFlag::DebugLuauDeferredConstraintResolution) { - CHECK_EQ(R"((Cat | Dog) & {| tag: "Cat" |})", toString(requireTypeAtPosition({7, 33}))); - CHECK_EQ(R"((Cat | Dog) & {| tag: ~"Cat" |})", toString(requireTypeAtPosition({9, 33}))); + CHECK_EQ(R"({| catfood: string, name: string, tag: "Cat" |})", toString(requireTypeAtPosition({7, 33}))); + CHECK_EQ(R"({| dogfood: string, name: string, tag: "Dog" |})", toString(requireTypeAtPosition({9, 33}))); } else { @@ -1294,16 +1078,8 @@ TEST_CASE_FIXTURE(Fixture, "narrow_boolean_to_true_or_false") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("boolean & ~(false?)", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("boolean & ~~(false?)", toString(requireTypeAtPosition({5, 28}))); - } - else - { - CHECK_EQ("true", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("false", toString(requireTypeAtPosition({5, 28}))); - } + CHECK_EQ("true", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("false", toString(requireTypeAtPosition({5, 28}))); } TEST_CASE_FIXTURE(Fixture, "discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false") @@ -1355,16 +1131,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ(R"(({| tag: "Folder", x: Folder |} | {| tag: "Part", x: Part |}) & {| x: Part |})", toString(requireTypeAtPosition({5, 28}))); - CHECK_EQ(R"(({| tag: "Folder", x: Folder |} | {| tag: "Part", x: Part |}) & {| x: ~Part |})", toString(requireTypeAtPosition({7, 28}))); - } - else - { - CHECK_EQ(R"({| tag: "Part", x: Part |})", toString(requireTypeAtPosition({5, 28}))); - CHECK_EQ(R"({| tag: "Folder", x: Folder |})", toString(requireTypeAtPosition({7, 28}))); - } + CHECK_EQ(R"({| tag: "Part", x: Part |})", toString(requireTypeAtPosition({5, 28}))); + CHECK_EQ(R"({| tag: "Folder", x: Folder |})", toString(requireTypeAtPosition({7, 28}))); } TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_free_table_to_vector") @@ -1406,16 +1174,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_instance_or_vector3_to LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(Instance | Vector3) & Vector3", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("(Instance | Vector3) & ~Vector3", toString(requireTypeAtPosition({5, 28}))); - } - else - { - CHECK_EQ("Vector3", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28}))); - } + CHECK_EQ("Vector3", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28}))); } TEST_CASE_FIXTURE(RefinementClassFixture, "type_narrow_for_all_the_userdata") @@ -1452,8 +1212,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "type_narrow_but_the_discriminant_type if (FFlag::DebugLuauDeferredConstraintResolution) { - CHECK_EQ("(Instance | Vector3 | number | string) & never", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("(Instance | Vector3 | number | string) & ~never", toString(requireTypeAtPosition({5, 28}))); + CHECK_EQ("never", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("Instance | Vector3 | number | string", toString(requireTypeAtPosition({5, 28}))); } else { @@ -1476,16 +1236,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "eliminate_subclasses_of_instance") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(Folder | Part | string) & Instance", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("(Folder | Part | string) & ~Instance", toString(requireTypeAtPosition({5, 28}))); - } - else - { - CHECK_EQ("Folder | Part", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); - } + CHECK_EQ("Folder | Part", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); } TEST_CASE_FIXTURE(RefinementClassFixture, "narrow_from_subclasses_of_instance_or_string_or_vector3") @@ -1502,16 +1254,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "narrow_from_subclasses_of_instance_or LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(Folder | Part | Vector3 | string) & Instance", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("(Folder | Part | Vector3 | string) & ~Instance", toString(requireTypeAtPosition({5, 28}))); - } - else - { - CHECK_EQ("Folder | Part", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("Vector3 | string", toString(requireTypeAtPosition({5, 28}))); - } + CHECK_EQ("Folder | Part", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("Vector3 | string", toString(requireTypeAtPosition({5, 28}))); } TEST_CASE_FIXTURE(RefinementClassFixture, "x_as_any_if_x_is_instance_elseif_x_is_table") @@ -1556,16 +1300,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_instance_without LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("Folder & Instance", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("Instance & ~Folder & table", toString(requireTypeAtPosition({5, 28}))); - } - else - { - CHECK_EQ("Folder", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("never", toString(requireTypeAtPosition({5, 28}))); - } + CHECK_EQ("Folder", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("never", toString(requireTypeAtPosition({5, 28}))); } TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_folder_or_part_without_using_typeof") @@ -1582,16 +1318,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_folder_or_part_w LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("(Folder | Part) & Folder", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("(Folder | Part) & ~Folder", toString(requireTypeAtPosition({5, 28}))); - } - else - { - CHECK_EQ("Folder", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("Part", toString(requireTypeAtPosition({5, 28}))); - } + CHECK_EQ("Folder", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("Part", toString(requireTypeAtPosition({5, 28}))); } TEST_CASE_FIXTURE(RefinementClassFixture, "isa_type_refinement_must_be_known_ahead_of_time") @@ -1610,16 +1338,8 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "isa_type_refinement_must_be_known_ahe LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("Instance", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28}))); - } - else - { - CHECK_EQ("Instance", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28}))); - } + CHECK_EQ("Instance", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28}))); } TEST_CASE_FIXTURE(RefinementClassFixture, "x_is_not_instance_or_else_not_part") @@ -1673,8 +1393,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknowns") if (FFlag::DebugLuauDeferredConstraintResolution) { - CHECK_EQ("unknown & string", toString(requireTypeAtPosition({3, 28}))); - CHECK_EQ("unknown & ~string", toString(requireTypeAtPosition({5, 28}))); + CHECK_EQ("string", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("~string", toString(requireTypeAtPosition({5, 28}))); } else { @@ -1714,14 +1434,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "what_nonsensical_condition") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - CHECK_EQ("a & number & string", toString(requireTypeAtPosition({3, 28}))); - } - else - { - CHECK_EQ("never", toString(requireTypeAtPosition({3, 28}))); - } + CHECK_EQ("never", toString(requireTypeAtPosition({3, 28}))); } TEST_CASE_FIXTURE(Fixture, "else_with_no_explicit_expression_should_also_refine_the_tagged_union") @@ -1752,7 +1465,30 @@ local _ = _ ~= _ or _ or _ end )"); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + // Without a realistic motivating case, it's hard to tell if it's important for this to work without errors. + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); + } + else + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_take_the_length") +{ + ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true}; + + CheckResult result = check(R"( + local function f(x: unknown) + if typeof(x) == "table" then + local len = #x + end + end + )"); + LUAU_REQUIRE_NO_ERRORS(result); + CHECK_EQ("table", toString(requireTypeAtPosition({3, 29}))); } TEST_SUITE_END(); diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 626a4c5..e3c1ab1 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -3435,4 +3435,62 @@ _ = _._ LUAU_REQUIRE_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_table_unify_instantiated_table") +{ + ScopedFastFlag sff[]{ + {"LuauInstantiateInSubtyping", true}, + {"LuauScalarShapeUnifyToMtOwner2", true}, + {"LuauTableUnifyInstantiationFix", true}, + }; + + CheckResult result = check(R"( +function _(...) +end +local function l0():typeof(_()()[_()()[_]]) +end +return _[_()()[_]] <= _ + )"); + + LUAU_REQUIRE_ERRORS(result); +} + +TEST_CASE_FIXTURE(Fixture, "fuzz_table_unify_instantiated_table_with_prop_realloc") +{ + ScopedFastFlag sff[]{ + {"LuauInstantiateInSubtyping", true}, + {"LuauScalarShapeUnifyToMtOwner2", true}, + {"LuauTableUnifyInstantiationFix", true}, + }; + + CheckResult result = check(R"( +function _(l0,l0) +do +_ = _().n0 +end +l0(_()._,_) +end +_(_,function(...) +end) + )"); + + LUAU_REQUIRE_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_table_unify_prop_realloc") +{ + // For this test, we don't need LuauInstantiateInSubtyping + ScopedFastFlag sff[]{ + {"LuauScalarShapeUnifyToMtOwner2", true}, + {"LuauTableUnifyInstantiationFix", true}, + }; + + CheckResult result = check(R"( +n3,_ = nil +_ = _[""]._,_[l0][_._][{[_]=_,_=_,}][_G].number +_ = {_,} + )"); + + LUAU_REQUIRE_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.typePacks.cpp b/tests/TypeInfer.typePacks.cpp index 5486b96..78eb6d4 100644 --- a/tests/TypeInfer.typePacks.cpp +++ b/tests/TypeInfer.typePacks.cpp @@ -1014,7 +1014,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_packs_with_tails_in_vararg_adjustment") end) return result end - end + end )"); LUAU_REQUIRE_NO_ERRORS(result); diff --git a/tests/TypeInfer.unknownnever.test.cpp b/tests/TypeInfer.unknownnever.test.cpp index 6caa46e..6bfb93b 100644 --- a/tests/TypeInfer.unknownnever.test.cpp +++ b/tests/TypeInfer.unknownnever.test.cpp @@ -116,11 +116,23 @@ TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable" local x, y, z = f() )"); - LUAU_REQUIRE_NO_ERRORS(result); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ("Function only returns 2 values, but 3 are required here", toString(result.errors[0])); - CHECK_EQ("never", toString(requireType("x"))); - CHECK_EQ("never", toString(requireType("y"))); - CHECK_EQ("never", toString(requireType("z"))); + CHECK_EQ("string", toString(requireType("x"))); + CHECK_EQ("never", toString(requireType("y"))); + CHECK_EQ("*error-type*", toString(requireType("z"))); + } + else + { + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ("never", toString(requireType("x"))); + CHECK_EQ("never", toString(requireType("y"))); + CHECK_EQ("never", toString(requireType("z"))); + } } TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable2") @@ -135,10 +147,20 @@ TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable2 LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("never", toString(requireType("x1"))); - CHECK_EQ("never", toString(requireType("x2"))); - CHECK_EQ("never", toString(requireType("y1"))); - CHECK_EQ("never", toString(requireType("y2"))); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CHECK_EQ("string", toString(requireType("x1"))); + CHECK_EQ("never", toString(requireType("x2"))); + CHECK_EQ("never", toString(requireType("y1"))); + CHECK_EQ("string", toString(requireType("y2"))); + } + else + { + CHECK_EQ("never", toString(requireType("x1"))); + CHECK_EQ("never", toString(requireType("x2"))); + CHECK_EQ("never", toString(requireType("y1"))); + CHECK_EQ("never", toString(requireType("y2"))); + } } TEST_CASE_FIXTURE(Fixture, "index_on_never") @@ -290,8 +312,14 @@ TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_i )"); LUAU_REQUIRE_NO_ERRORS(result); - // Widening doesn't normalize yet, so the result is a bit strange - CHECK_EQ("(nil, a) -> boolean | boolean", toString(requireType("ord"))); + + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ("(nil, a) -> boolean", toString(requireType("ord"))); + else + { + // Widening doesn't normalize yet, so the result is a bit strange + CHECK_EQ("(nil, a) -> boolean | boolean", toString(requireType("ord"))); + } } TEST_CASE_FIXTURE(Fixture, "math_operators_and_never") diff --git a/tests/TypeReduction.test.cpp b/tests/TypeReduction.test.cpp index e078f25..582725b 100644 --- a/tests/TypeReduction.test.cpp +++ b/tests/TypeReduction.test.cpp @@ -482,6 +482,24 @@ TEST_CASE_FIXTURE(ReductionFixture, "intersections_without_negations") CHECK("{| [string]: number, p: string |}" == toStringFull(ty)); } + SUBCASE("array_number_and_array_string") + { + TypeId ty = reductionof("{number} & {string}"); + CHECK("{never}" == toStringFull(ty)); + } + + SUBCASE("array_string_and_array_string") + { + TypeId ty = reductionof("{string} & {string}"); + CHECK("{string}" == toStringFull(ty)); + } + + SUBCASE("array_string_or_number_and_array_string") + { + TypeId ty = reductionof("{string | number} & {string}"); + CHECK("{string}" == toStringFull(ty)); + } + SUBCASE("fresh_type_and_string") { TypeId freshTy = arena.freshType(nullptr); @@ -690,7 +708,7 @@ TEST_CASE_FIXTURE(ReductionFixture, "intersections_with_negations") SUBCASE("string_and_not_error") { TypeId ty = reductionof("string & Not"); - CHECK("string & ~*error-type*" == toStringFull(ty)); + CHECK("string" == toStringFull(ty)); } SUBCASE("table_p_string_and_table_p_not_number") @@ -711,6 +729,12 @@ TEST_CASE_FIXTURE(ReductionFixture, "intersections_with_negations") CHECK("{| x: {| p: string |} |}" == toStringFull(ty)); } + SUBCASE("table_or_nil_and_truthy") + { + TypeId ty = reductionof("({ x: number | string }?) & Not"); + CHECK("{| x: number | string |}" == toString(ty)); + } + SUBCASE("not_top_table_and_table") { TypeId ty = reductionof("Not & {}"); @@ -1251,6 +1275,12 @@ TEST_CASE_FIXTURE(ReductionFixture, "tables") TypeId ty = reductionof("{ x: { y: string & number } }"); CHECK("never" == toStringFull(ty)); } + + SUBCASE("array_of_never") + { + TypeId ty = reductionof("{never}"); + CHECK("{never}" == toStringFull(ty)); + } } TEST_CASE_FIXTURE(ReductionFixture, "metatables") diff --git a/tools/faillist.txt b/tools/faillist.txt index 1984838..3766687 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -1,6 +1,5 @@ AnnotationTests.corecursive_types_error_on_tight_loop AnnotationTests.duplicate_type_param_name -AnnotationTests.for_loop_counter_annotation_is_checked AnnotationTests.generic_aliases_are_cloned_properly AnnotationTests.occurs_check_on_cyclic_intersection_type AnnotationTests.occurs_check_on_cyclic_union_type @@ -18,12 +17,8 @@ AutocompleteTest.keyword_methods AutocompleteTest.no_incompatible_self_calls AutocompleteTest.no_wrong_compatible_self_calls_with_generics AutocompleteTest.string_singleton_as_table_key -AutocompleteTest.suggest_external_module_type AutocompleteTest.suggest_table_keys -AutocompleteTest.type_correct_argument_type_suggestion AutocompleteTest.type_correct_expected_argument_type_pack_suggestion -AutocompleteTest.type_correct_expected_argument_type_suggestion -AutocompleteTest.type_correct_expected_argument_type_suggestion_optional AutocompleteTest.type_correct_expected_argument_type_suggestion_self AutocompleteTest.type_correct_expected_return_type_pack_suggestion AutocompleteTest.type_correct_expected_return_type_suggestion @@ -118,37 +113,28 @@ ParserTests.parse_nesting_based_end_detection_failsafe_earlier ParserTests.parse_nesting_based_end_detection_local_function ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal ProvisionalTests.bail_early_if_unification_is_too_complicated -ProvisionalTests.discriminate_from_x_not_equal_to_nil ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean ProvisionalTests.free_options_cannot_be_unified_together ProvisionalTests.generic_type_leak_to_module_interface_variadic ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns -ProvisionalTests.lvalue_equals_another_lvalue_with_no_overlap ProvisionalTests.pcall_returns_at_least_two_value_but_function_returns_nothing +ProvisionalTests.refine_unknown_to_table_then_clone_it ProvisionalTests.setmetatable_constrains_free_type_into_free_table ProvisionalTests.specialization_binds_with_prototypes_too_early ProvisionalTests.table_insert_with_a_singleton_argument ProvisionalTests.typeguard_inference_incomplete ProvisionalTests.weirditer_should_not_loop_forever RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string -RefinementTest.call_an_incompatible_function_after_using_typeguard -RefinementTest.correctly_lookup_property_whose_base_was_previously_refined2 -RefinementTest.discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false -RefinementTest.discriminate_tag RefinementTest.else_with_no_explicit_expression_should_also_refine_the_tagged_union RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil RefinementTest.narrow_property_of_a_bounded_variable RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true -RefinementTest.refine_a_property_not_to_be_nil_through_an_intersection_table -RefinementTest.refine_unknowns RefinementTest.type_guard_can_filter_for_intersection_of_tables -RefinementTest.type_guard_narrowed_into_nothingness RefinementTest.type_narrow_for_all_the_userdata RefinementTest.type_narrow_to_vector RefinementTest.typeguard_cast_free_table_to_vector RefinementTest.typeguard_in_assert_position -RefinementTest.typeguard_narrows_for_table RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table RefinementTest.x_is_not_instance_or_else_not_part RuntimeLimits.typescript_port_of_Result_type @@ -178,6 +164,8 @@ TableTests.found_like_key_in_table_function_call TableTests.found_like_key_in_table_property_access TableTests.found_multiple_like_keys TableTests.function_calls_produces_sealed_table_given_unsealed_table +TableTests.fuzz_table_unify_instantiated_table +TableTests.fuzz_table_unify_instantiated_table_with_prop_realloc TableTests.generic_table_instantiation_potential_regression TableTests.give_up_after_one_metatable_index_look_up TableTests.indexer_on_sealed_table_must_unify_with_free_table @@ -220,9 +208,9 @@ TableTests.table_param_row_polymorphism_3 TableTests.table_simple_call TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors +TableTests.table_unification_4 TableTests.tc_member_function TableTests.tc_member_function_2 -TableTests.unification_of_unions_in_a_self_referential_type TableTests.unifying_tables_shouldnt_uaf1 TableTests.unifying_tables_shouldnt_uaf2 TableTests.used_colon_correctly @@ -357,9 +345,7 @@ TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_ TypeInferOperators.in_nonstrict_mode_strip_nil_from_intersections_when_considering_relational_operators TypeInferOperators.infer_any_in_all_modes_when_lhs_is_unknown TypeInferOperators.operator_eq_completely_incompatible -TypeInferOperators.or_joins_types_with_no_superfluous_union TypeInferOperators.produce_the_correct_error_message_when_comparing_a_table_with_a_metatable_with_one_that_does_not -TypeInferOperators.refine_and_or TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs TypeInferOperators.UnknownGlobalCompoundAssign @@ -368,16 +354,8 @@ TypeInferOperators.unrelated_primitives_cannot_be_compared TypeInferPrimitives.CheckMethodsOfNumber TypeInferPrimitives.string_index TypeInferUnknownNever.assign_to_global_which_is_never -TypeInferUnknownNever.assign_to_local_which_is_never -TypeInferUnknownNever.assign_to_prop_which_is_never -TypeInferUnknownNever.assign_to_subscript_which_is_never -TypeInferUnknownNever.call_never TypeInferUnknownNever.dont_unify_operands_if_one_of_the_operand_is_never_in_any_ordering_operators -TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never TypeInferUnknownNever.math_operators_and_never -TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable -TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable2 -TypeInferUnknownNever.unary_minus_of_never TypePackTests.detect_cyclic_typepacks2 TypePackTests.pack_tail_unification_check TypePackTests.self_and_varargs_should_work @@ -401,24 +379,19 @@ TypePackTests.type_pack_type_parameters TypePackTests.unify_variadic_tails_in_arguments TypePackTests.unify_variadic_tails_in_arguments_free TypePackTests.variadic_packs -TypeReductionTests.negations TypeSingletons.function_call_with_singletons TypeSingletons.function_call_with_singletons_mismatch -TypeSingletons.indexing_on_string_singletons TypeSingletons.indexing_on_union_of_string_singletons TypeSingletons.overloaded_function_call_with_singletons TypeSingletons.overloaded_function_call_with_singletons_mismatch TypeSingletons.return_type_of_f_is_not_widened TypeSingletons.table_properties_singleton_strings_mismatch TypeSingletons.table_properties_type_error_escapes -TypeSingletons.taking_the_length_of_string_singleton TypeSingletons.taking_the_length_of_union_of_string_singleton TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton TypeSingletons.widening_happens_almost_everywhere TypeSingletons.widening_happens_almost_everywhere_except_for_tables UnionTypes.index_on_a_union_type_with_missing_property -UnionTypes.index_on_a_union_type_with_one_optional_property -UnionTypes.index_on_a_union_type_with_one_property_of_type_any UnionTypes.optional_assignment_errors UnionTypes.optional_call_error UnionTypes.optional_field_access_error