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