diff --git a/Analysis/include/Luau/ConstraintGraphBuilder.h b/Analysis/include/Luau/ConstraintGraphBuilder.h index c484b686..526101a4 100644 --- a/Analysis/include/Luau/ConstraintGraphBuilder.h +++ b/Analysis/include/Luau/ConstraintGraphBuilder.h @@ -187,12 +187,12 @@ private: * @param generalize If true, generalize any lambdas that are encountered. * @return the type of the expression. */ - Inference check(const ScopePtr& scope, AstExpr* expr, ValueContext context = ValueContext::RValue, std::optional expectedType = {}, - bool forceSingleton = false, bool generalize = true); + Inference check( + const ScopePtr& scope, AstExpr* expr, std::optional expectedType = {}, bool forceSingleton = false, bool generalize = true); Inference check(const ScopePtr& scope, AstExprConstantString* string, std::optional expectedType, bool forceSingleton); Inference check(const ScopePtr& scope, AstExprConstantBool* bool_, std::optional expectedType, bool forceSingleton); - Inference check(const ScopePtr& scope, AstExprLocal* local, ValueContext context); + Inference check(const ScopePtr& scope, AstExprLocal* local); Inference check(const ScopePtr& scope, AstExprGlobal* global); Inference check(const ScopePtr& scope, AstExprIndexName* indexName); Inference check(const ScopePtr& scope, AstExprIndexExpr* indexExpr); @@ -208,6 +208,11 @@ private: std::vector checkLValues(const ScopePtr& scope, AstArray exprs); TypeId checkLValue(const ScopePtr& scope, AstExpr* expr); + TypeId checkLValue(const ScopePtr& scope, AstExprLocal* local); + TypeId checkLValue(const ScopePtr& scope, AstExprGlobal* global); + TypeId checkLValue(const ScopePtr& scope, AstExprIndexName* indexName); + TypeId checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr); + TypeId updateProperty(const ScopePtr& scope, AstExpr* expr); struct FunctionSignature { diff --git a/Analysis/include/Luau/Scope.h b/Analysis/include/Luau/Scope.h index c3038fac..a0b115ad 100644 --- a/Analysis/include/Luau/Scope.h +++ b/Analysis/include/Luau/Scope.h @@ -52,6 +52,7 @@ struct Scope void addBuiltinTypeBinding(const Name& name, const TypeFun& tyFun); std::optional lookup(Symbol sym) const; + std::optional lookupLValue(DefId def) const; std::optional lookup(DefId def) const; std::optional> lookupEx(Symbol sym); @@ -65,7 +66,15 @@ struct Scope std::optional linearSearchForBinding(const std::string& name, bool traverseScopeChain = true) const; RefinementMap refinements; - DenseHashMap dcrRefinements{nullptr}; + + // This can be viewed as the "unrefined" type of each binding. + DenseHashMap lvalueTypes{nullptr}; + + // Luau values are routinely refined more narrowly than their actual + // inferred type through control flow statements. We retain those refined + // types here. + DenseHashMap rvalueRefinements{nullptr}; + void inheritRefinements(const ScopePtr& childScope); // For mutually recursive type aliases, it's important that diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index ee0b96aa..2eb7d749 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -355,6 +355,7 @@ struct FunctionType // `hasNoFreeOrGenericTypes` should be true if and only if the type does not have any free or generic types present inside it. // this flag is used as an optimization to exit early from procedures that manipulate free or generic types. bool hasNoFreeOrGenericTypes = false; + bool isCheckedFunction = false; }; enum class TableState diff --git a/Analysis/include/Luau/TypeFamily.h b/Analysis/include/Luau/TypeFamily.h index 4ffb6d1a..ad8fdbef 100644 --- a/Analysis/include/Luau/TypeFamily.h +++ b/Analysis/include/Luau/TypeFamily.h @@ -1,9 +1,13 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once +#include "ConstraintSolver.h" +#include "Error.h" #include "Luau/Error.h" #include "Luau/NotNull.h" #include "Luau/Variant.h" +#include "NotNull.h" +#include "TypeCheckLimits.h" #include #include @@ -23,6 +27,42 @@ struct BuiltinTypes; struct TxnLog; class Normalizer; +struct TypeFamilyContext +{ + NotNull arena; + NotNull builtins; + NotNull scope; + NotNull normalizer; + NotNull ice; + NotNull limits; + + // nullptr if the type family is being reduced outside of the constraint solver. + ConstraintSolver* solver; + + TypeFamilyContext(NotNull cs, NotNull scope) + : arena(cs->arena) + , builtins(cs->builtinTypes) + , scope(scope) + , normalizer(cs->normalizer) + , ice(NotNull{&cs->iceReporter}) + , limits(NotNull{&cs->limits}) + , solver(cs.get()) + { + } + + TypeFamilyContext(NotNull arena, NotNull builtins, NotNull scope, NotNull normalizer, + NotNull ice, NotNull limits) + : arena(arena) + , builtins(builtins) + , scope(scope) + , normalizer(normalizer) + , ice(ice) + , limits(limits) + , solver(nullptr) + { + } +}; + /// Represents a reduction result, which may have successfully reduced the type, /// may have concretely failed to reduce the type, or may simply be stuck /// without more information. @@ -53,9 +93,7 @@ struct TypeFamily std::string name; /// The reducer function for the type family. - std::function(std::vector, std::vector, NotNull, NotNull, - NotNull, NotNull, NotNull, ConstraintSolver*)> - reducer; + std::function(std::vector, std::vector, NotNull)> reducer; }; /// Represents a type function that may be applied to map a series of types and @@ -67,9 +105,7 @@ struct TypePackFamily std::string name; /// The reducer function for the type pack family. - std::function(std::vector, std::vector, NotNull, NotNull, - NotNull, NotNull, NotNull, ConstraintSolver*)> - reducer; + std::function(std::vector, std::vector, NotNull)> reducer; }; struct FamilyGraphReductionResult @@ -82,76 +118,53 @@ struct FamilyGraphReductionResult }; /** - * Attempt to reduce all instances of any type or type pack family in the type - * graph provided. - * - * @param entrypoint the entry point to the type graph. - * @param location the location the reduction is occurring at; used to populate - * type errors. - * @param arena an arena to allocate types into. - * @param builtins the built-in types. - * @param normalizer the normalizer to use when normalizing types - * @param log a TxnLog to use. If one is provided, substitution will take place - * against the TxnLog, otherwise substitutions will directly mutate the type - * graph. Do not provide the empty TxnLog, as a result. - */ -FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, NotNull arena, NotNull builtins, - NotNull scope, NotNull normalizer, TxnLog* log = nullptr, bool force = false); - - /** - * Attempt to reduce all instances of any type or type pack family in the type - * graph provided. - * - * @param entrypoint the entry point to the type graph. - * @param location the location the reduction is occurring at; used to populate - * type errors. - * @param arena an arena to allocate types into. - * @param builtins the built-in types. - * @param normalizer the normalizer to use when normalizing types - * @param log a TxnLog to use. If one is provided, substitution will take place - * against the TxnLog, otherwise substitutions will directly mutate the type - * graph. Do not provide the empty TxnLog, as a result. - */ -FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, NotNull arena, NotNull builtins, - NotNull scope, NotNull normalizer, TxnLog* log = nullptr, bool force = false); + * Attempt to reduce all instances of any type or type pack family in the type + * graph provided. + * + * @param entrypoint the entry point to the type graph. + * @param location the location the reduction is occurring at; used to populate + * type errors. + * @param arena an arena to allocate types into. + * @param builtins the built-in types. + * @param normalizer the normalizer to use when normalizing types + * @param ice the internal error reporter to use for ICEs + */ +FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, TypeFamilyContext, bool force = false); /** * Attempt to reduce all instances of any type or type pack family in the type * graph provided. * - * @param solver the constraint solver this reduction is being performed in. * @param entrypoint the entry point to the type graph. * @param location the location the reduction is occurring at; used to populate * type errors. - * @param log a TxnLog to use. If one is provided, substitution will take place - * against the TxnLog, otherwise substitutions will directly mutate the type - * graph. Do not provide the empty TxnLog, as a result. + * @param arena an arena to allocate types into. + * @param builtins the built-in types. + * @param normalizer the normalizer to use when normalizing types + * @param ice the internal error reporter to use for ICEs */ -FamilyGraphReductionResult reduceFamilies( - NotNull solver, TypeId entrypoint, Location location, NotNull scope, TxnLog* log = nullptr, bool force = false); - -/** - * Attempt to reduce all instances of any type or type pack family in the type - * graph provided. - * - * @param solver the constraint solver this reduction is being performed in. - * @param entrypoint the entry point to the type graph. - * @param location the location the reduction is occurring at; used to populate - * type errors. - * @param log a TxnLog to use. If one is provided, substitution will take place - * against the TxnLog, otherwise substitutions will directly mutate the type - * graph. Do not provide the empty TxnLog, as a result. - */ -FamilyGraphReductionResult reduceFamilies( - NotNull solver, TypePackId entrypoint, Location location, NotNull scope, TxnLog* log = nullptr, bool force = false); +FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, TypeFamilyContext, bool force = false); struct BuiltinTypeFamilies { BuiltinTypeFamilies(); TypeFamily addFamily; + TypeFamily subFamily; + TypeFamily mulFamily; + TypeFamily divFamily; + TypeFamily idivFamily; + TypeFamily powFamily; + TypeFamily modFamily; + + TypeFamily andFamily; + TypeFamily orFamily; + + void addToScope(NotNull arena, NotNull scope) const; }; + + const BuiltinTypeFamilies kBuiltinTypeFamilies{}; } // namespace Luau diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 0200ee3e..b7631460 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -8,7 +8,9 @@ #include "Luau/ToString.h" #include "Luau/ConstraintSolver.h" #include "Luau/ConstraintGraphBuilder.h" +#include "Luau/NotNull.h" #include "Luau/TypeInfer.h" +#include "Luau/TypeFamily.h" #include "Luau/TypePack.h" #include "Luau/Type.h" #include "Luau/TypeUtils.h" @@ -21,6 +23,8 @@ * about a function that takes any number of values, but where each value must have some specific type. */ +LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) + namespace Luau { @@ -209,6 +213,9 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC TypeArena& arena = globals.globalTypes; NotNull builtinTypes = globals.builtinTypes; + if (FFlag::DebugLuauDeferredConstraintResolution) + kBuiltinTypeFamilies.addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()}); + LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile( globals, globals.globalScope, getBuiltinDefinitionSource(), "@luau", /* captureComments */ false, typeCheckForAutocomplete); LUAU_ASSERT(loadResult.success); diff --git a/Analysis/src/ConstraintGraphBuilder.cpp b/Analysis/src/ConstraintGraphBuilder.cpp index c0022246..5c96abb7 100644 --- a/Analysis/src/ConstraintGraphBuilder.cpp +++ b/Analysis/src/ConstraintGraphBuilder.cpp @@ -455,7 +455,7 @@ void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location lo } } - scope->dcrRefinements[def] = ty; + scope->rvalueRefinements[def] = ty; } } @@ -627,7 +627,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* l if (hasAnnotation) expectedType = varTypes.at(i); - TypeId exprType = check(scope, value, ValueContext::RValue, expectedType, /*forceSingleton*/ false, /*generalize*/ true).ty; + TypeId exprType = check(scope, value, expectedType, /*forceSingleton*/ false, /*generalize*/ true).ty; if (i < varTypes.size()) { if (varTypes[i]) @@ -699,7 +699,8 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* l // HACK: In the greedy solver, we say the type state of a variable is the type annotation itself, but // the actual type state is the corresponding initializer expression (if it exists) or nil otherwise. BreadcrumbId bc = dfg->getBreadcrumb(l); - scope->dcrRefinements[bc->def] = varTypes[i]; + scope->lvalueTypes[bc->def] = varTypes[i]; + scope->rvalueRefinements[bc->def] = varTypes[i]; } if (local->values.size > 0) @@ -764,7 +765,8 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for forScope->bindings[for_->var] = Binding{annotationTy, for_->var->location}; BreadcrumbId bc = dfg->getBreadcrumb(for_->var); - forScope->dcrRefinements[bc->def] = annotationTy; + forScope->lvalueTypes[bc->def] = annotationTy; + forScope->rvalueRefinements[bc->def] = annotationTy; visit(forScope, for_->body); @@ -791,7 +793,8 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* f variableTypes.push_back(ty); BreadcrumbId bc = dfg->getBreadcrumb(var); - loopScope->dcrRefinements[bc->def] = ty; + loopScope->lvalueTypes[bc->def] = ty; + loopScope->rvalueRefinements[bc->def] = ty; } // It is always ok to provide too few variables, so we give this pack a free tail. @@ -845,8 +848,10 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFun sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->func->location}; BreadcrumbId bc = dfg->getBreadcrumb(function->name); - scope->dcrRefinements[bc->def] = functionType; - sig.bodyScope->dcrRefinements[bc->def] = sig.signature; + scope->lvalueTypes[bc->def] = functionType; + scope->rvalueRefinements[bc->def] = functionType; + sig.bodyScope->lvalueTypes[bc->def] = sig.signature; + sig.bodyScope->rvalueRefinements[bc->def] = sig.signature; Checkpoint start = checkpoint(this); checkFunctionBody(sig.bodyScope, function->func); @@ -889,9 +894,12 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction const NullableBreadcrumbId functionBreadcrumb = dfg->getBreadcrumb(function->name); + std::optional existingFunctionTy; + if (functionBreadcrumb) + existingFunctionTy = scope->lookupLValue(functionBreadcrumb->def); + if (AstExprLocal* localName = function->name->as()) { - std::optional existingFunctionTy = scope->lookup(localName->local); if (existingFunctionTy) { addConstraint(scope, function->name->location, SubtypeConstraint{generalizedType, *existingFunctionTy}); @@ -905,11 +913,13 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction sig.bodyScope->bindings[localName->local] = Binding{sig.signature, localName->location}; if (functionBreadcrumb) - sig.bodyScope->dcrRefinements[functionBreadcrumb->def] = sig.signature; + { + sig.bodyScope->lvalueTypes[functionBreadcrumb->def] = sig.signature; + sig.bodyScope->rvalueRefinements[functionBreadcrumb->def] = sig.signature; + } } else if (AstExprGlobal* globalName = function->name->as()) { - std::optional existingFunctionTy = scope->lookup(globalName->name); if (!existingFunctionTy) ice->ice("prepopulateGlobalScope did not populate a global name", globalName->location); @@ -918,7 +928,10 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location}; if (functionBreadcrumb) - sig.bodyScope->dcrRefinements[functionBreadcrumb->def] = sig.signature; + { + sig.bodyScope->lvalueTypes[functionBreadcrumb->def] = sig.signature; + sig.bodyScope->rvalueRefinements[functionBreadcrumb->def] = sig.signature; + } } else if (AstExprIndexName* indexName = function->name->as()) { @@ -946,7 +959,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction ice->ice("generalizedType == nullptr", function->location); if (functionBreadcrumb) - scope->dcrRefinements[functionBreadcrumb->def] = generalizedType; + scope->rvalueRefinements[functionBreadcrumb->def] = generalizedType; checkFunctionBody(sig.bodyScope, function->func); Checkpoint end = checkpoint(this); @@ -1059,7 +1072,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatCompound ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifStatement) { - RefinementId refinement = check(scope, ifStatement->condition, ValueContext::RValue, std::nullopt).refinement; + RefinementId refinement = check(scope, ifStatement->condition, std::nullopt).refinement; ScopePtr thenScope = childScope(ifStatement->thenbody, scope); applyRefinements(thenScope, ifStatement->condition->location, refinement); @@ -1164,7 +1177,8 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareG rootScope->bindings[global->name] = Binding{globalTy, global->location}; BreadcrumbId bc = dfg->getBreadcrumb(global); - rootScope->dcrRefinements[bc->def] = globalTy; + rootScope->lvalueTypes[bc->def] = globalTy; + rootScope->rvalueRefinements[bc->def] = globalTy; return ControlFlow::None; } @@ -1323,6 +1337,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareF TypePackId retPack = resolveTypePack(funScope, global->retTypes, /* inTypeArguments */ false); TypeId fnType = arena->addType(FunctionType{TypeLevel{}, funScope.get(), std::move(genericTys), std::move(genericTps), paramPack, retPack}); FunctionType* ftv = getMutable(fnType); + ftv->isCheckedFunction = global->checkedFunction; ftv->argNames.reserve(global->paramNames.size); for (const auto& el : global->paramNames) @@ -1334,7 +1349,8 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareF scope->bindings[global->name] = Binding{fnType, global->location}; BreadcrumbId bc = dfg->getBreadcrumb(global); - rootScope->dcrRefinements[bc->def] = fnType; + rootScope->lvalueTypes[bc->def] = fnType; + rootScope->rvalueRefinements[bc->def] = fnType; return ControlFlow::None; } @@ -1363,7 +1379,7 @@ InferencePack ConstraintGraphBuilder::checkPack( std::optional expectedType; if (i < expectedTypes.size()) expectedType = expectedTypes[i]; - head.push_back(check(scope, expr, ValueContext::RValue, expectedType).ty); + head.push_back(check(scope, expr, expectedType).ty); } else { @@ -1407,7 +1423,7 @@ InferencePack ConstraintGraphBuilder::checkPack( std::optional expectedType; if (!expectedTypes.empty()) expectedType = expectedTypes[0]; - TypeId t = check(scope, expr, ValueContext::RValue, expectedType, /*forceSingletons*/ false, generalize).ty; + TypeId t = check(scope, expr, expectedType, /*forceSingletons*/ false, generalize).ty; result = InferencePack{arena->addTypePack({t})}; } @@ -1489,8 +1505,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa } else if (i < exprArgs.size() - 1 || !(arg->is() || arg->is())) { - auto [ty, refinement] = - check(scope, arg, ValueContext::RValue, /*expectedType*/ std::nullopt, /*forceSingleton*/ false, /*generalize*/ false); + auto [ty, refinement] = check(scope, arg, /*expectedType*/ std::nullopt, /*forceSingleton*/ false, /*generalize*/ false); args.push_back(ty); argumentRefinements.push_back(refinement); } @@ -1549,7 +1564,8 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa scope->bindings[targetLocal->local].typeId = resultTy; BreadcrumbId bc = dfg->getBreadcrumb(targetLocal); - scope->dcrRefinements[bc->def] = resultTy; // TODO: typestates: track this as an assignment + scope->lvalueTypes[bc->def] = resultTy; // TODO: typestates: track this as an assignment + scope->rvalueRefinements[bc->def] = resultTy; // TODO: typestates: track this as an assignment } return InferencePack{arena->addTypePack({resultTy}), {refinementArena.variadic(returnRefinements)}}; @@ -1588,7 +1604,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa } Inference ConstraintGraphBuilder::check( - const ScopePtr& scope, AstExpr* expr, ValueContext context, std::optional expectedType, bool forceSingleton, bool generalize) + const ScopePtr& scope, AstExpr* expr, std::optional expectedType, bool forceSingleton, bool generalize) { RecursionCounter counter{&recursionCount}; @@ -1601,7 +1617,7 @@ Inference ConstraintGraphBuilder::check( Inference result; if (auto group = expr->as()) - result = check(scope, group->expr, ValueContext::RValue, expectedType, forceSingleton); + result = check(scope, group->expr, expectedType, forceSingleton); else if (auto stringExpr = expr->as()) result = check(scope, stringExpr, expectedType, forceSingleton); else if (expr->is()) @@ -1611,7 +1627,7 @@ Inference ConstraintGraphBuilder::check( else if (expr->is()) result = Inference{builtinTypes->nilType}; else if (auto local = expr->as()) - result = check(scope, local, context); + result = check(scope, local); else if (auto global = expr->as()) result = check(scope, global); else if (expr->is()) @@ -1706,13 +1722,11 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprConstantBo return Inference{builtinTypes->booleanType}; } -Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprLocal* local, ValueContext context) +Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprLocal* local) { BreadcrumbId bc = dfg->getBreadcrumb(local); - if (auto ty = scope->lookup(bc->def); ty && context == ValueContext::RValue) - return Inference{*ty, refinementArena.proposition(bc, builtinTypes->truthyType)}; - else if (auto ty = scope->lookup(local->local)) + if (auto ty = scope->lookup(bc->def)) return Inference{*ty, refinementArena.proposition(bc, builtinTypes->truthyType)}; else ice->ice("AstExprLocal came before its declaration?"); @@ -1729,7 +1743,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprGlobal* gl return Inference{*ty, refinementArena.proposition(bc, builtinTypes->truthyType)}; else if (auto ty = scope->lookup(global->name)) { - rootScope->dcrRefinements[bc->def] = *ty; + rootScope->rvalueRefinements[bc->def] = *ty; return Inference{*ty, refinementArena.proposition(bc, builtinTypes->truthyType)}; } else @@ -1750,7 +1764,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* if (auto ty = scope->lookup(bc->def)) return Inference{*ty, refinementArena.proposition(NotNull{bc}, builtinTypes->truthyType)}; - scope->dcrRefinements[bc->def] = result; + scope->rvalueRefinements[bc->def] = result; } addConstraint(scope, indexName->expr->location, HasPropConstraint{result, obj, indexName->index.value}); @@ -1773,7 +1787,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr* if (auto ty = scope->lookup(bc->def)) return Inference{*ty, refinementArena.proposition(NotNull{bc}, builtinTypes->truthyType)}; - scope->dcrRefinements[bc->def] = result; + scope->rvalueRefinements[bc->def] = result; } TableIndexer indexer{indexType, result}; @@ -1836,7 +1850,9 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* bi { auto [leftType, rightType, refinement] = checkBinary(scope, binary, expectedType); - if (binary->op == AstExprBinary::Op::Add) + switch (binary->op) + { + case AstExprBinary::Op::Add: { TypeId resultType = arena->addType(TypeFamilyInstanceType{ NotNull{&kBuiltinTypeFamilies.addFamily}, @@ -1846,11 +1862,94 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* bi addConstraint(scope, binary->location, ReduceConstraint{resultType}); return Inference{resultType, std::move(refinement)}; } - - TypeId resultType = arena->addType(BlockedType{}); - addConstraint(scope, binary->location, - BinaryConstraint{binary->op, leftType, rightType, resultType, binary, &module->astOriginalCallTypes, &module->astOverloadResolvedTypes}); - return Inference{resultType, std::move(refinement)}; + case AstExprBinary::Op::Sub: + { + TypeId resultType = arena->addType(TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.subFamily}, + {leftType, rightType}, + {}, + }); + addConstraint(scope, binary->location, ReduceConstraint{resultType}); + return Inference{resultType, std::move(refinement)}; + } + case AstExprBinary::Op::Mul: + { + TypeId resultType = arena->addType(TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.mulFamily}, + {leftType, rightType}, + {}, + }); + addConstraint(scope, binary->location, ReduceConstraint{resultType}); + return Inference{resultType, std::move(refinement)}; + } + case AstExprBinary::Op::Div: + { + TypeId resultType = arena->addType(TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.divFamily}, + {leftType, rightType}, + {}, + }); + addConstraint(scope, binary->location, ReduceConstraint{resultType}); + return Inference{resultType, std::move(refinement)}; + } + case AstExprBinary::Op::FloorDiv: + { + TypeId resultType = arena->addType(TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.idivFamily}, + {leftType, rightType}, + {}, + }); + addConstraint(scope, binary->location, ReduceConstraint{resultType}); + return Inference{resultType, std::move(refinement)}; + } + case AstExprBinary::Op::Pow: + { + TypeId resultType = arena->addType(TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.powFamily}, + {leftType, rightType}, + {}, + }); + addConstraint(scope, binary->location, ReduceConstraint{resultType}); + return Inference{resultType, std::move(refinement)}; + } + case AstExprBinary::Op::Mod: + { + TypeId resultType = arena->addType(TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.modFamily}, + {leftType, rightType}, + {}, + }); + addConstraint(scope, binary->location, ReduceConstraint{resultType}); + return Inference{resultType, std::move(refinement)}; + } + case AstExprBinary::Op::And: + { + TypeId resultType = arena->addType(TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.andFamily}, + {leftType, rightType}, + {}, + }); + addConstraint(scope, binary->location, ReduceConstraint{resultType}); + return Inference{resultType, std::move(refinement)}; + } + case AstExprBinary::Op::Or: + { + TypeId resultType = arena->addType(TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.orFamily}, + {leftType, rightType}, + {}, + }); + addConstraint(scope, binary->location, ReduceConstraint{resultType}); + return Inference{resultType, std::move(refinement)}; + } + default: + { + TypeId resultType = arena->addType(BlockedType{}); + addConstraint(scope, binary->location, + 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) @@ -1860,18 +1959,18 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* if ScopePtr thenScope = childScope(ifElse->trueExpr, scope); applyRefinements(thenScope, ifElse->trueExpr->location, refinement); - TypeId thenType = check(thenScope, ifElse->trueExpr, ValueContext::RValue, expectedType).ty; + TypeId thenType = check(thenScope, ifElse->trueExpr, expectedType).ty; ScopePtr elseScope = childScope(ifElse->falseExpr, scope); applyRefinements(elseScope, ifElse->falseExpr->location, refinementArena.negation(refinement)); - TypeId elseType = check(elseScope, ifElse->falseExpr, ValueContext::RValue, expectedType).ty; + TypeId elseType = check(elseScope, ifElse->falseExpr, expectedType).ty; return Inference{expectedType ? *expectedType : simplifyUnion(builtinTypes, arena, thenType, elseType).result}; } Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert) { - check(scope, typeAssert->expr, ValueContext::RValue, std::nullopt); + check(scope, typeAssert->expr, std::nullopt); return Inference{resolveType(scope, typeAssert->annotation, /* inTypeArguments */ false)}; } @@ -1893,11 +1992,11 @@ std::tuple ConstraintGraphBuilder::checkBinary( if (expectedType) relaxedExpectedLhs = arena->addType(UnionType{{builtinTypes->falsyType, *expectedType}}); - auto [leftType, leftRefinement] = check(scope, binary->left, ValueContext::RValue, relaxedExpectedLhs); + auto [leftType, leftRefinement] = check(scope, binary->left, relaxedExpectedLhs); ScopePtr rightScope = childScope(binary->right, scope); applyRefinements(rightScope, binary->right->location, leftRefinement); - auto [rightType, rightRefinement] = check(rightScope, binary->right, ValueContext::RValue, expectedType); + auto [rightType, rightRefinement] = check(rightScope, binary->right, expectedType); return {leftType, rightType, refinementArena.conjunction(leftRefinement, rightRefinement)}; } @@ -1908,11 +2007,11 @@ std::tuple ConstraintGraphBuilder::checkBinary( if (expectedType) relaxedExpectedLhs = arena->addType(UnionType{{builtinTypes->falsyType, *expectedType}}); - auto [leftType, leftRefinement] = check(scope, binary->left, ValueContext::RValue, relaxedExpectedLhs); + auto [leftType, leftRefinement] = check(scope, binary->left, relaxedExpectedLhs); ScopePtr rightScope = childScope(binary->right, scope); applyRefinements(rightScope, binary->right->location, refinementArena.negation(leftRefinement)); - auto [rightType, rightRefinement] = check(rightScope, binary->right, ValueContext::RValue, expectedType); + auto [rightType, rightRefinement] = check(rightScope, binary->right, expectedType); return {leftType, rightType, refinementArena.disjunction(leftRefinement, rightRefinement)}; } @@ -1973,8 +2072,8 @@ std::tuple ConstraintGraphBuilder::checkBinary( { // We are checking a binary expression of the form a op b // Just because a op b is epxected to return a bool, doesn't mean a, b are expected to be bools too - TypeId leftType = check(scope, binary->left, ValueContext::RValue, {}, true).ty; - TypeId rightType = check(scope, binary->right, ValueContext::RValue, {}, true).ty; + TypeId leftType = check(scope, binary->left, {}, true).ty; + TypeId rightType = check(scope, binary->right, {}, true).ty; RefinementId leftRefinement = nullptr; if (auto bc = dfg->getBreadcrumb(binary->left)) @@ -1994,8 +2093,8 @@ std::tuple ConstraintGraphBuilder::checkBinary( } else { - TypeId leftType = check(scope, binary->left, ValueContext::RValue).ty; - TypeId rightType = check(scope, binary->right, ValueContext::RValue).ty; + TypeId leftType = check(scope, binary->left).ty; + TypeId rightType = check(scope, binary->right).ty; return {leftType, rightType, nullptr}; } } @@ -2011,6 +2110,47 @@ std::vector ConstraintGraphBuilder::checkLValues(const ScopePtr& scope, return types; } +TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr) +{ + if (auto local = expr->as()) + return checkLValue(scope, local); + else if (auto global = expr->as()) + return checkLValue(scope, global); + else if (auto indexName = expr->as()) + return checkLValue(scope, indexName); + else if (auto indexExpr = expr->as()) + return checkLValue(scope, indexExpr); + else if (auto error = expr->as()) + { + check(scope, error); + return builtinTypes->errorRecoveryType(); + } + else + ice->ice("checkLValue is inexhaustive"); +} + +TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExprLocal* local) +{ + std::optional upperBound = scope->lookup(Symbol{local->local}); + LUAU_ASSERT(upperBound); + return *upperBound; +} + +TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExprGlobal* global) +{ + return scope->lookup(Symbol{global->name}).value_or(builtinTypes->errorRecoveryType()); +} + +TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExprIndexName* indexName) +{ + return updateProperty(scope, indexName); +} + +TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr) +{ + return updateProperty(scope, indexExpr); +} + static bool isIndexNameEquivalent(AstExpr* expr) { if (expr->is()) @@ -2031,7 +2171,7 @@ static bool isIndexNameEquivalent(AstExpr* expr) * * If expr has the form name.a.b.c */ -TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr) +TypeId ConstraintGraphBuilder::updateProperty(const ScopePtr& scope, AstExpr* expr) { if (auto indexExpr = expr->as(); indexExpr && !indexExpr->index->is()) { @@ -2058,7 +2198,7 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr) return propType; } else if (!isIndexNameEquivalent(expr)) - return check(scope, expr, ValueContext::LValue).ty; + return check(scope, expr).ty; Symbol sym; std::vector segments; @@ -2086,7 +2226,7 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr) else if (auto indexExpr = e->as()) { // We need to populate the type for the index value - check(scope, indexExpr->index, ValueContext::RValue); + check(scope, indexExpr->index); if (auto strIndex = indexExpr->index->as()) { segments.push_back(std::string(strIndex->value.data, strIndex->value.size)); @@ -2095,11 +2235,11 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr) } else { - return check(scope, expr, ValueContext::LValue).ty; + return check(scope, expr).ty; } } else - return check(scope, expr, ValueContext::LValue).ty; + return check(scope, expr).ty; } LUAU_ASSERT(!segments.empty()); @@ -2109,7 +2249,7 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr) auto lookupResult = scope->lookupEx(sym); if (!lookupResult) - return check(scope, expr, ValueContext::LValue).ty; + return check(scope, expr).ty; const auto [subjectBinding, symbolScope] = std::move(*lookupResult); TypeId subjectType = subjectBinding->typeId; @@ -2139,7 +2279,10 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr) // This can fail if the user is erroneously trying to augment a builtin // table like os or string. if (auto bc = dfg->getBreadcrumb(e)) - symbolScope->dcrRefinements[bc->def] = updatedType; + { + symbolScope->lvalueTypes[bc->def] = updatedType; + symbolScope->rvalueRefinements[bc->def] = updatedType; + } } return propTy; @@ -2232,7 +2375,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* exp checkExpectedIndexResultType = pinnedIndexResultType; } - TypeId itemTy = check(scope, item.value, ValueContext::RValue, checkExpectedIndexResultType).ty; + TypeId itemTy = check(scope, item.value, checkExpectedIndexResultType).ty; if (isIndexedResultType && !pinnedIndexResultType) pinnedIndexResultType = itemTy; @@ -2242,7 +2385,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* exp // Even though we don't need to use the type of the item's key if // it's a string constant, we still want to check it to populate // astTypes. - TypeId keyTy = check(scope, item.key, ValueContext::RValue, annotatedKeyType).ty; + TypeId keyTy = check(scope, item.key, annotatedKeyType).ty; if (AstExprConstantString* key = item.key->as()) { @@ -2346,7 +2489,8 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS signatureScope->bindings[fn->self] = Binding{selfType, fn->self->location}; BreadcrumbId bc = dfg->getBreadcrumb(fn->self); - signatureScope->dcrRefinements[bc->def] = selfType; + signatureScope->lvalueTypes[bc->def] = selfType; + signatureScope->rvalueRefinements[bc->def] = selfType; } for (size_t i = 0; i < fn->args.size; ++i) @@ -2370,7 +2514,8 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS signatureScope->bindings[local] = Binding{argTy, local->location}; BreadcrumbId bc = dfg->getBreadcrumb(local); - signatureScope->dcrRefinements[bc->def] = argTy; + signatureScope->lvalueTypes[bc->def] = argTy; + signatureScope->rvalueRefinements[bc->def] = argTy; } TypePackId varargPack = nullptr; @@ -2612,6 +2757,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b // TODO: FunctionType needs a pointer to the scope so that we know // how to quantify/instantiate it. FunctionType ftv{TypeLevel{}, scope.get(), {}, {}, argTypes, returnTypes}; + ftv.isCheckedFunction = fn->checkedFunction; // This replicates the behavior of the appropriate FunctionType // constructors. @@ -2836,17 +2982,27 @@ struct GlobalPrepopulator : AstVisitor { const NotNull globalScope; const NotNull arena; + const NotNull dfg; - GlobalPrepopulator(NotNull globalScope, NotNull arena) + GlobalPrepopulator(NotNull globalScope, NotNull arena, NotNull dfg) : globalScope(globalScope) , arena(arena) + , dfg(dfg) { } bool visit(AstStatFunction* function) override { if (AstExprGlobal* g = function->name->as()) - globalScope->bindings[g->name] = Binding{arena->addType(BlockedType{})}; + { + TypeId bt = arena->addType(BlockedType{}); + globalScope->bindings[g->name] = Binding{bt}; + + NullableBreadcrumbId bc = dfg->getBreadcrumb(function->name); + LUAU_ASSERT(bc); + + globalScope->lvalueTypes[bc->def] = bt; + } return true; } @@ -2854,7 +3010,7 @@ struct GlobalPrepopulator : AstVisitor void ConstraintGraphBuilder::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program) { - GlobalPrepopulator gp{NotNull{globalScope.get()}, arena}; + GlobalPrepopulator gp{NotNull{globalScope.get()}, arena, dfg}; if (prepareModuleScope) prepareModuleScope(module->name, globalScope); diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index c1544bd7..320ff917 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -1344,7 +1344,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNulladdTypePack(TypePack{std::move(head), tail}); - fn = *callMm; + fn = follow(*callMm); asMutable(c.result)->ty.emplace(constraint->scope); } else @@ -1887,6 +1887,9 @@ bool ConstraintSolver::tryDispatch(const RefineConstraint& c, NotNull(c.resultType)); if (type == c.resultType) @@ -1946,8 +1949,7 @@ bool ConstraintSolver::tryDispatch(const RefineConstraint& c, NotNull constraint, bool force) { TypeId ty = follow(c.ty); - FamilyGraphReductionResult result = - reduceFamilies(NotNull{this}, ty, constraint->location, constraint->scope, nullptr, force); + FamilyGraphReductionResult result = reduceFamilies(ty, constraint->location, TypeFamilyContext{NotNull{this}, constraint->scope}, force); for (TypeId r : result.reducedTypes) unblock(r, constraint->location); @@ -1970,8 +1972,7 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull constraint, bool force) { TypePackId tp = follow(c.tp); - FamilyGraphReductionResult result = - reduceFamilies(NotNull{this}, tp, constraint->location, constraint->scope, nullptr, force); + FamilyGraphReductionResult result = reduceFamilies(tp, constraint->location, TypeFamilyContext{NotNull{this}, constraint->scope}, force); for (TypeId r : result.reducedTypes) unblock(r, constraint->location); diff --git a/Analysis/src/Scope.cpp b/Analysis/src/Scope.cpp index bcd21d26..92912530 100644 --- a/Analysis/src/Scope.cpp +++ b/Analysis/src/Scope.cpp @@ -55,12 +55,25 @@ std::optional> Scope::lookupEx(Symbol sym) } } +std::optional Scope::lookupLValue(DefId def) const +{ + for (const Scope* current = this; current; current = current->parent.get()) + { + if (auto ty = current->lvalueTypes.find(def)) + return *ty; + } + + return std::nullopt; +} + // TODO: We might kill Scope::lookup(Symbol) once data flow is fully fleshed out with type states and control flow analysis. std::optional Scope::lookup(DefId def) const { for (const Scope* current = this; current; current = current->parent.get()) { - if (auto ty = current->dcrRefinements.find(def)) + if (auto ty = current->rvalueRefinements.find(def)) + return *ty; + if (auto ty = current->lvalueTypes.find(def)) return *ty; } @@ -156,10 +169,10 @@ void Scope::inheritRefinements(const ScopePtr& childScope) { if (FFlag::DebugLuauDeferredConstraintResolution) { - for (const auto& [k, a] : childScope->dcrRefinements) + for (const auto& [k, a] : childScope->rvalueRefinements) { if (lookup(NotNull{k})) - dcrRefinements[k] = a; + rvalueRefinements[k] = a; } } diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index e4b14712..8ee1d2c8 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -101,6 +101,27 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy) TypeId lowerBound = makeAggregateType(lb, builtinTypes->neverType); TypeId upperBound = makeAggregateType(ub, builtinTypes->unknownType); + const NormalizedType* nt = normalizer->normalize(upperBound); + if (!nt) + result.normalizationTooComplex = true; + else if (!normalizer->isInhabited(nt)) + { + /* If the normalized upper bound we're mapping to a generic is + * uninhabited, then we must consider the subtyping relation not to + * hold. + * + * This happens eg in () -> (T, T) <: () -> (string, number) + * + * T appears in covariant position and would have to be both string + * and number at once. + * + * No actual value is both a string and a number, so the test fails. + * + * TODO: We'll need to add explanitory context here. + */ + result.isSubtype = false; + } + result.andAlso(isCovariantWith(lowerBound, upperBound)); } diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index dd48b216..1859131b 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -1044,6 +1044,14 @@ void persist(TypeId ty) else if (get(t) || get(t) || get(t) || get(t) || get(t) || get(t)) { } + else if (auto tfit = get(t)) + { + for (auto ty : tfit->typeArguments) + queue.push_back(ty); + + for (auto tp : tfit->packArguments) + persist(tp); + } else { LUAU_ASSERT(!"TypeId is not supported in a persist call"); @@ -1072,6 +1080,14 @@ void persist(TypePackId tp) else if (get(tp)) { } + else if (auto tfitp = get(tp)) + { + for (auto ty : tfitp->typeArguments) + persist(ty); + + for (auto tp : tfitp->packArguments) + persist(tp); + } else { LUAU_ASSERT(!"TypePackId is not supported in a persist call"); diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 02e7ecdc..24925a1a 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -17,7 +17,6 @@ #include "Luau/Type.h" #include "Luau/TypePack.h" #include "Luau/TypeUtils.h" -#include "Luau/Unifier.h" #include "Luau/TypeFamily.h" #include "Luau/VisitType.h" @@ -242,7 +241,8 @@ struct TypeChecker2 DenseHashSet noTypeFamilyErrors{nullptr}; Normalizer normalizer; - Subtyping subtyping; + Subtyping _subtyping; + NotNull subtyping; TypeChecker2(NotNull builtinTypes, NotNull unifierState, NotNull limits, DcrLogger* logger, const SourceModule* sourceModule, Module* module) @@ -253,7 +253,9 @@ struct TypeChecker2 , sourceModule(sourceModule) , module(module) , normalizer{&testArena, builtinTypes, unifierState, /* cacheInhabitance */ true} - , subtyping{builtinTypes, NotNull{&testArena}, NotNull{&normalizer}, NotNull{unifierState->iceHandler}, NotNull{module->getModuleScope().get()}} + , _subtyping{builtinTypes, NotNull{&testArena}, NotNull{&normalizer}, NotNull{unifierState->iceHandler}, + NotNull{module->getModuleScope().get()}} + , subtyping(&_subtyping) { } @@ -282,9 +284,9 @@ struct TypeChecker2 if (noTypeFamilyErrors.find(instance)) return instance; - TxnLog fake{}; - ErrorVec errors = - reduceFamilies(instance, location, NotNull{&testArena}, builtinTypes, stack.back(), NotNull{&normalizer}, &fake, true).errors; + ErrorVec errors = reduceFamilies( + instance, location, TypeFamilyContext{NotNull{&testArena}, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true) + .errors; if (errors.empty()) noTypeFamilyErrors.insert(instance); @@ -491,17 +493,7 @@ struct TypeChecker2 TypeArena* arena = &testArena; TypePackId actualRetType = reconstructPack(ret->list, *arena); - Unifier u{NotNull{&normalizer}, stack.back(), ret->location, Covariant}; - u.hideousFixMeGenericsAreActuallyFree = true; - - u.tryUnify(actualRetType, expectedRetType); - const bool ok = (u.errors.empty() && u.log.empty()) || isErrorSuppressing(ret->location, actualRetType, ret->location, expectedRetType); - - if (!ok) - { - for (const TypeError& e : u.errors) - reportError(e); - } + testIsSubtype(actualRetType, expectedRetType, ret->location); for (AstExpr* expr : ret->list) visit(expr, ValueContext::RValue); @@ -532,9 +524,7 @@ struct TypeChecker2 TypeId annotationType = lookupAnnotation(var->annotation); TypeId valueType = value ? lookupType(value) : nullptr; if (valueType) - { - reportErrors(tryUnify(stack.back(), value->location, valueType, annotationType)); - } + testIsSubtype(valueType, annotationType, value->location); visit(var->annotation); } @@ -559,7 +549,7 @@ struct TypeChecker2 if (var->annotation) { TypeId varType = lookupAnnotation(var->annotation); - reportErrors(tryUnify(stack.back(), value->location, valueTypes.head[j - i], varType)); + testIsSubtype(valueTypes.head[j - i], varType, value->location); visit(var->annotation); } @@ -586,20 +576,20 @@ struct TypeChecker2 void visit(AstStatFor* forStatement) { - NotNull scope = stack.back(); - if (forStatement->var->annotation) { visit(forStatement->var->annotation); - reportErrors(tryUnify(scope, forStatement->var->location, builtinTypes->numberType, lookupAnnotation(forStatement->var->annotation))); + + TypeId annotatedType = lookupAnnotation(forStatement->var->annotation); + testIsSubtype(builtinTypes->numberType, annotatedType, forStatement->var->location); } - auto checkNumber = [this, scope](AstExpr* expr) { + auto checkNumber = [this](AstExpr* expr) { if (!expr) return; visit(expr, ValueContext::RValue); - reportErrors(tryUnify(scope, expr->location, lookupType(expr), builtinTypes->numberType)); + testIsSubtype(lookupType(expr), builtinTypes->numberType, expr->location); }; checkNumber(forStatement->from); @@ -689,8 +679,7 @@ struct TypeChecker2 } TypeId iteratorTy = follow(iteratorTypes.head[0]); - auto checkFunction = [this, &arena, &scope, &forInStatement, &variableTypes]( - const FunctionType* iterFtv, std::vector iterTys, bool isMm) { + auto checkFunction = [this, &arena, &forInStatement, &variableTypes](const FunctionType* iterFtv, std::vector iterTys, bool isMm) { if (iterTys.size() < 1 || iterTys.size() > 3) { if (isMm) @@ -713,7 +702,7 @@ struct TypeChecker2 } for (size_t i = 0; i < std::min(expectedVariableTypes.head.size(), variableTypes.size()); ++i) - reportErrors(tryUnify(scope, forInStatement->vars.data[i]->location, variableTypes[i], expectedVariableTypes.head[i])); + testIsSubtype(variableTypes[i], expectedVariableTypes.head[i], forInStatement->vars.data[i]->location); // nextFn is going to be invoked with (arrayTy, startIndexTy) @@ -757,13 +746,13 @@ struct TypeChecker2 if (iterTys.size() >= 2 && flattenedArgTypes.head.size() > 0) { size_t valueIndex = forInStatement->values.size > 1 ? 1 : 0; - reportErrors(tryUnify(scope, forInStatement->values.data[valueIndex]->location, iterTys[1], flattenedArgTypes.head[0])); + testIsSubtype(iterTys[1], flattenedArgTypes.head[0], forInStatement->values.data[valueIndex]->location); } if (iterTys.size() == 3 && flattenedArgTypes.head.size() > 1) { size_t valueIndex = forInStatement->values.size > 2 ? 2 : 0; - reportErrors(tryUnify(scope, forInStatement->values.data[valueIndex]->location, iterTys[2], flattenedArgTypes.head[1])); + testIsSubtype(iterTys[2], flattenedArgTypes.head[1], forInStatement->values.data[valueIndex]->location); } }; @@ -795,9 +784,9 @@ struct TypeChecker2 { if ((forInStatement->vars.size == 1 || forInStatement->vars.size == 2) && ttv->indexer) { - reportErrors(tryUnify(scope, forInStatement->vars.data[0]->location, variableTypes[0], ttv->indexer->indexType)); + testIsSubtype(variableTypes[0], ttv->indexer->indexType, forInStatement->vars.data[0]->location); if (variableTypes.size() == 2) - reportErrors(tryUnify(scope, forInStatement->vars.data[1]->location, variableTypes[1], ttv->indexer->indexResultType)); + testIsSubtype(variableTypes[1], ttv->indexer->indexResultType, forInStatement->vars.data[1]->location); } else reportError(GenericError{"Cannot iterate over a table without indexer"}, forInStatement->values.data[0]->location); @@ -820,7 +809,7 @@ struct TypeChecker2 if (const FunctionType* iterMmFtv = get(*instantiatedIterMmTy)) { TypePackId argPack = arena.addTypePack({iteratorTy}); - reportErrors(tryUnify(scope, forInStatement->values.data[0]->location, argPack, iterMmFtv->argTypes)); + testIsSubtype(argPack, iterMmFtv->argTypes, forInStatement->values.data[0]->location); TypePack mmIteratorTypes = extendTypePack(arena, builtinTypes, iterMmFtv->retTypes, 3); @@ -894,12 +883,7 @@ struct TypeChecker2 if (get(lhsType)) continue; - - if (!isSubtype(rhsType, lhsType, stack.back()) && - !isErrorSuppressing(assign->vars.data[i]->location, lhsType, assign->values.data[i]->location, rhsType)) - { - reportError(TypeMismatch{lhsType, rhsType}, rhs->location); - } + testIsSubtype(rhsType, lhsType, rhs->location); } } @@ -909,7 +893,7 @@ struct TypeChecker2 TypeId resultTy = visit(&fake, stat); TypeId varTy = lookupType(stat->var); - reportErrors(tryUnify(stack.back(), stat->location, resultTy, varTy)); + testIsSubtype(resultTy, varTy, stat->location); } void visit(AstStatFunction* stat) @@ -1029,34 +1013,38 @@ struct TypeChecker2 void visit(AstExprConstantNil* expr) { - NotNull scope = stack.back(); TypeId actualType = lookupType(expr); TypeId expectedType = builtinTypes->nilType; - LUAU_ASSERT(isSubtype(actualType, expectedType, scope)); + + SubtypingResult r = subtyping->isSubtype(actualType, expectedType); + LUAU_ASSERT(r.isSubtype || r.isErrorSuppressing); } void visit(AstExprConstantBool* expr) { - NotNull scope = stack.back(); TypeId actualType = lookupType(expr); TypeId expectedType = builtinTypes->booleanType; - LUAU_ASSERT(isSubtype(actualType, expectedType, scope)); + + SubtypingResult r = subtyping->isSubtype(actualType, expectedType); + LUAU_ASSERT(r.isSubtype || r.isErrorSuppressing); } void visit(AstExprConstantNumber* expr) { - NotNull scope = stack.back(); TypeId actualType = lookupType(expr); TypeId expectedType = builtinTypes->numberType; - LUAU_ASSERT(isSubtype(actualType, expectedType, scope)); + + SubtypingResult r = subtyping->isSubtype(actualType, expectedType); + LUAU_ASSERT(r.isSubtype || r.isErrorSuppressing); } void visit(AstExprConstantString* expr) { - NotNull scope = stack.back(); TypeId actualType = lookupType(expr); TypeId expectedType = builtinTypes->stringType; - LUAU_ASSERT(isSubtype(actualType, expectedType, scope)); + + SubtypingResult r = subtyping->isSubtype(actualType, expectedType); + LUAU_ASSERT(r.isSubtype || r.isErrorSuppressing); } void visit(AstExprLocal* expr) @@ -1089,7 +1077,7 @@ struct TypeChecker2 TypeId fnTy = *originalCallTy; if (selectedOverloadTy) { - SubtypingResult result = subtyping.isSubtype(*originalCallTy, *selectedOverloadTy); + SubtypingResult result = subtyping->isSubtype(*originalCallTy, *selectedOverloadTy); if (result.isSubtype) fnTy = *selectedOverloadTy; @@ -1152,6 +1140,8 @@ struct TypeChecker2 NotNull{&normalizer}, NotNull{stack.back()}, ice, + limits, + subtyping, call->location, }; @@ -1243,6 +1233,8 @@ struct TypeChecker2 NotNull normalizer; NotNull scope; NotNull ice; + NotNull limits; + NotNull subtyping; Location callLoc; std::vector ok; @@ -1252,32 +1244,38 @@ struct TypeChecker2 InsertionOrderedMap> resolution; private: - std::optional tryUnify(const Location& location, TypeId subTy, TypeId superTy, const LiteralProperties* literalProperties = nullptr) + std::optional testIsSubtype(const Location& location, TypeId subTy, TypeId superTy) { - Unifier u{normalizer, scope, location, Covariant}; - u.ctx = CountMismatch::Arg; - u.hideousFixMeGenericsAreActuallyFree = true; - u.enableNewSolver(); - u.tryUnify(subTy, superTy, /*isFunctionCall*/ false, /*isIntersection*/ false, literalProperties); + auto r = subtyping->isSubtype(subTy, superTy); + ErrorVec errors; - if (u.errors.empty()) + if (r.normalizationTooComplex) + errors.push_back(TypeError{location, NormalizationTooComplex{}}); + + if (!r.isSubtype && !r.isErrorSuppressing) + errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}}); + + if (errors.empty()) return std::nullopt; - return std::move(u.errors); + return errors; } - std::optional tryUnify(const Location& location, TypePackId subTy, TypePackId superTy) + std::optional testIsSubtype(const Location& location, TypePackId subTy, TypePackId superTy) { - Unifier u{normalizer, scope, location, Covariant}; - u.ctx = CountMismatch::Arg; - u.hideousFixMeGenericsAreActuallyFree = true; - u.enableNewSolver(); - u.tryUnify(subTy, superTy); + auto r = subtyping->isSubtype(subTy, superTy); + ErrorVec errors; - if (u.errors.empty()) + if (r.normalizationTooComplex) + errors.push_back(TypeError{location, NormalizationTooComplex{}}); + + if (!r.isSubtype && !r.isErrorSuppressing) + errors.push_back(TypeError{location, TypePackMismatch{superTy, subTy}}); + + if (errors.empty()) return std::nullopt; - return std::move(u.errors); + return errors; } std::pair checkOverload( @@ -1316,35 +1314,12 @@ struct TypeChecker2 expr->is() || expr->is() || expr->is(); } - static std::unique_ptr buildLiteralPropertiesSet(AstExpr* expr) - { - const AstExprTable* table = expr->as(); - if (!table) - return nullptr; - - std::unique_ptr result = std::make_unique(Name{}); - - for (const AstExprTable::Item& item : table->items) - { - if (item.kind != AstExprTable::Item::Record) - continue; - - AstExprConstantString* keyExpr = item.key->as(); - LUAU_ASSERT(keyExpr); - - if (isLiteral(item.value)) - result->insert(Name{keyExpr->value.begin(), keyExpr->value.end()}); - } - - return result; - } - LUAU_NOINLINE std::pair checkOverload_( TypeId fnTy, const FunctionType* fn, const TypePack* args, AstExpr* fnExpr, const std::vector* argExprs) { - TxnLog fake; - FamilyGraphReductionResult result = reduceFamilies(fnTy, callLoc, arena, builtinTypes, scope, normalizer, &fake, /*force=*/true); + FamilyGraphReductionResult result = + reduceFamilies(fnTy, callLoc, TypeFamilyContext{arena, builtinTypes, scope, normalizer, ice, limits}, /*force=*/true); if (!result.errors.empty()) return {OverloadIsNonviable, result.errors}; @@ -1363,9 +1338,7 @@ struct TypeChecker2 TypeId argTy = args->head[argOffset]; AstExpr* argLoc = argExprs->at(argOffset >= argExprs->size() ? argExprs->size() - 1 : argOffset); - std::unique_ptr literalProperties{buildLiteralPropertiesSet(argLoc)}; - - if (auto errors = tryUnify(argLoc->location, argTy, paramTy, literalProperties.get())) + if (auto errors = testIsSubtype(argLoc->location, argTy, paramTy)) { // Since we're stopping right here, we need to decide if this is a nonviable overload or if there is an arity mismatch. // If it's a nonviable overload, then we need to keep going to get all type errors. @@ -1395,7 +1368,7 @@ struct TypeChecker2 } else if (auto vtp = get(follow(paramIter.tail()))) { - if (auto errors = tryUnify(argExpr->location, args->head[argOffset], vtp->ty)) + if (auto errors = testIsSubtype(argExpr->location, args->head[argOffset], vtp->ty)) argumentErrors.insert(argumentErrors.end(), errors->begin(), errors->end()); } else if (get(follow(paramIter.tail()))) @@ -1414,7 +1387,7 @@ struct TypeChecker2 { AstExpr* argExpr = argExprs->at(argExprs->size() - 1); - if (auto errors = tryUnify(argExpr->location, vtp->ty, *paramIter)) + if (auto errors = testIsSubtype(argExpr->location, vtp->ty, *paramIter)) argumentErrors.insert(argumentErrors.end(), errors->begin(), errors->end()); } else if (!isOptional(*paramIter)) @@ -1439,7 +1412,7 @@ struct TypeChecker2 if (paramIter.tail() && args->tail) { - if (auto errors = tryUnify(argLoc, *args->tail, *paramIter.tail())) + if (auto errors = testIsSubtype(argLoc, *args->tail, *paramIter.tail())) argumentErrors.insert(argumentErrors.end(), errors->begin(), errors->end()); } else if (paramIter.tail()) @@ -1604,20 +1577,18 @@ struct TypeChecker2 visit(indexExpr->expr, ValueContext::RValue); visit(indexExpr->index, ValueContext::RValue); - NotNull scope = stack.back(); - TypeId exprType = lookupType(indexExpr->expr); TypeId indexType = lookupType(indexExpr->index); if (auto tt = get(exprType)) { if (tt->indexer) - reportErrors(tryUnify(scope, indexExpr->index->location, indexType, tt->indexer->indexType)); + testIsSubtype(indexType, tt->indexer->indexType, indexExpr->index->location); else reportError(CannotExtendTable{exprType, CannotExtendTable::Indexer, "indexer??"}, indexExpr->location); } else if (auto cls = get(exprType); cls && cls->indexer) - reportErrors(tryUnify(scope, indexExpr->index->location, indexType, cls->indexer->indexType)); + testIsSubtype(indexType, cls->indexer->indexType, indexExpr->index->location); else if (get(exprType) && isOptional(exprType)) reportError(OptionalValueAccess{exprType}, indexExpr->location); } @@ -1668,11 +1639,7 @@ struct TypeChecker2 TypeId inferredArgTy = *argIt; TypeId annotatedArgTy = lookupAnnotation(arg->annotation); - if (!isSubtype(inferredArgTy, annotatedArgTy, stack.back()) && - !isErrorSuppressing(arg->location, inferredArgTy, arg->annotation->location, annotatedArgTy)) - { - reportError(TypeMismatch{inferredArgTy, annotatedArgTy}, arg->location); - } + testIsSubtype(inferredArgTy, annotatedArgTy, arg->location); } ++argIt; @@ -1699,7 +1666,6 @@ struct TypeChecker2 { visit(expr->expr, ValueContext::RValue); - NotNull scope = stack.back(); TypeId operandType = lookupType(expr->expr); TypeId resultType = lookupType(expr); @@ -1717,7 +1683,7 @@ struct TypeChecker2 { if (expr->op == AstExprUnary::Op::Len) { - reportErrors(tryUnify(scope, expr->location, follow(*ret), builtinTypes->numberType)); + testIsSubtype(follow(*ret), builtinTypes->numberType, expr->location); } } else @@ -1737,12 +1703,9 @@ struct TypeChecker2 TypeId expectedFunction = testArena.addType(FunctionType{expectedArgs, expectedRet}); - ErrorVec errors = tryUnify(scope, expr->location, *mm, expectedFunction); - if (!errors.empty() && !isErrorSuppressing(expr->expr->location, *firstArg, expr->expr->location, operandType)) - { - reportError(TypeMismatch{*firstArg, operandType}, expr->location); + bool success = testIsSubtype(*mm, expectedFunction, expr->location); + if (!success) return; - } } return; @@ -1768,7 +1731,7 @@ struct TypeChecker2 } else if (expr->op == AstExprUnary::Op::Minus) { - reportErrors(tryUnify(scope, expr->location, operandType, builtinTypes->numberType)); + testIsSubtype(operandType, builtinTypes->numberType, expr->location); } else if (expr->op == AstExprUnary::Op::Not) { @@ -1792,7 +1755,7 @@ struct TypeChecker2 TypeId leftType = lookupType(expr->left); TypeId rightType = lookupType(expr->right); - TypeId expectedResult = lookupType(expr); + TypeId expectedResult = follow(lookupType(expr)); if (get(expectedResult)) { @@ -1928,7 +1891,7 @@ struct TypeChecker2 TypeId expectedTy = testArena.addType(FunctionType(expectedArgs, expectedRets)); - reportErrors(tryUnify(scope, expr->location, follow(*mm), expectedTy)); + testIsSubtype(follow(*mm), expectedTy, expr->location); std::optional ret = first(ftv->retTypes); if (ret) @@ -2022,13 +1985,13 @@ struct TypeChecker2 case AstExprBinary::Op::Mod: LUAU_ASSERT(FFlag::LuauFloorDivision || expr->op != AstExprBinary::Op::FloorDiv); - reportErrors(tryUnify(scope, expr->left->location, leftType, builtinTypes->numberType)); - reportErrors(tryUnify(scope, expr->right->location, rightType, builtinTypes->numberType)); + testIsSubtype(leftType, builtinTypes->numberType, expr->left->location); + testIsSubtype(rightType, builtinTypes->numberType, expr->right->location); return builtinTypes->numberType; case AstExprBinary::Op::Concat: - reportErrors(tryUnify(scope, expr->left->location, leftType, builtinTypes->stringType)); - reportErrors(tryUnify(scope, expr->right->location, rightType, builtinTypes->stringType)); + testIsSubtype(leftType, builtinTypes->stringType, expr->left->location); + testIsSubtype(rightType, builtinTypes->stringType, expr->right->location); return builtinTypes->stringType; case AstExprBinary::Op::CompareGe: @@ -2041,12 +2004,12 @@ struct TypeChecker2 if (normLeft && normLeft->isExactlyNumber()) { - reportErrors(tryUnify(scope, expr->right->location, rightType, builtinTypes->numberType)); + testIsSubtype(rightType, builtinTypes->numberType, expr->right->location); return builtinTypes->numberType; } else if (normLeft && normLeft->isSubtypeOfString()) { - reportErrors(tryUnify(scope, expr->right->location, rightType, builtinTypes->stringType)); + testIsSubtype(rightType, builtinTypes->stringType, expr->right->location); return builtinTypes->stringType; } else @@ -2081,10 +2044,10 @@ struct TypeChecker2 TypeId computedType = lookupType(expr->expr); // Note: As an optimization, we try 'number <: number | string' first, as that is the more likely case. - if (isSubtype(annotationType, computedType, stack.back(), true)) + if (auto r = subtyping->isSubtype(annotationType, computedType); r.isSubtype || r.isErrorSuppressing) return; - if (isSubtype(computedType, annotationType, stack.back(), true)) + if (auto r = subtyping->isSubtype(computedType, annotationType); r.isSubtype || r.isErrorSuppressing) return; reportError(TypesAreUnrelated{computedType, annotationType}, expr->location); @@ -2424,33 +2387,30 @@ struct TypeChecker2 } } - template - bool isSubtype(TID subTy, TID superTy, NotNull scope, bool genericsOkay = false) + bool testIsSubtype(TypeId subTy, TypeId superTy, Location location) { - TypeArena arena; - Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant}; - u.hideousFixMeGenericsAreActuallyFree = genericsOkay; - u.enableNewSolver(); + SubtypingResult r = subtyping->isSubtype(subTy, superTy); - u.tryUnify(subTy, superTy); - const bool ok = u.errors.empty() && u.log.empty(); - return ok; + if (r.normalizationTooComplex) + reportError(NormalizationTooComplex{}, location); + + if (!r.isSubtype && !r.isErrorSuppressing) + reportError(TypeMismatch{superTy, subTy}, location); + + return r.isSubtype; } - template - ErrorVec tryUnify(NotNull scope, const Location& location, TID subTy, TID superTy, CountMismatch::Context context = CountMismatch::Arg, - bool genericsOkay = false) + bool testIsSubtype(TypePackId subTy, TypePackId superTy, Location location) { - Unifier u{NotNull{&normalizer}, scope, location, Covariant}; - u.ctx = context; - u.hideousFixMeGenericsAreActuallyFree = genericsOkay; - u.enableNewSolver(); - u.tryUnify(subTy, superTy); + SubtypingResult r = subtyping->isSubtype(subTy, superTy); - if (isErrorSuppressing(location, subTy, location, superTy)) - return {}; + if (r.normalizationTooComplex) + reportError(NormalizationTooComplex{}, location); - return std::move(u.errors); + if (!r.isSubtype && !r.isErrorSuppressing) + reportError(TypePackMismatch{superTy, subTy}, location); + + return r.isSubtype; } void reportError(TypeErrorData data, const Location& location) diff --git a/Analysis/src/TypeFamily.cpp b/Analysis/src/TypeFamily.cpp index 9652f353..06574d97 100644 --- a/Analysis/src/TypeFamily.cpp +++ b/Analysis/src/TypeFamily.cpp @@ -6,12 +6,13 @@ #include "Luau/DenseHash.h" #include "Luau/Instantiation.h" #include "Luau/Normalize.h" +#include "Luau/Simplify.h" #include "Luau/Substitution.h" #include "Luau/ToString.h" #include "Luau/TxnLog.h" #include "Luau/TypeCheckLimits.h" #include "Luau/TypeUtils.h" -#include "Luau/Unifier.h" +#include "Luau/Unifier2.h" #include "Luau/VisitType.h" LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyGraphReductionMaximumSteps, 1'000'000); @@ -56,38 +57,23 @@ struct InstanceCollector : TypeOnceVisitor struct FamilyReducer { - ConstraintSolver* solver = nullptr; - - // Conditionally from the solver if one is provided. - NotNull arena; - NotNull builtins; - NotNull normalizer; + TypeFamilyContext ctx; std::deque queuedTys; std::deque queuedTps; DenseHashSet irreducible{nullptr}; FamilyGraphReductionResult result; - TxnLog* parentLog = nullptr; - TxnLog log; bool force = false; // Local to the constraint being reduced. Location location; - NotNull scope; - FamilyReducer(std::deque queuedTys, std::deque queuedTps, Location location, NotNull arena, - NotNull builtins, NotNull scope, NotNull normalizer, ConstraintSolver* solver, TxnLog* parentLog = nullptr, bool force = false) - : solver(solver) - , arena(arena) - , builtins(builtins) - , normalizer(normalizer) + FamilyReducer(std::deque queuedTys, std::deque queuedTps, Location location, TypeFamilyContext ctx, bool force = false) + : ctx(ctx) , queuedTys(std::move(queuedTys)) , queuedTps(std::move(queuedTps)) - , parentLog(parentLog) - , log(parentLog) , force(force) , location(location) - , scope(scope) { } @@ -100,16 +86,16 @@ struct FamilyReducer SkipTestResult testForSkippability(TypeId ty) { - ty = log.follow(ty); + ty = follow(ty); - if (log.is(ty)) + if (is(ty)) { if (!irreducible.contains(ty)) return SkipTestResult::Defer; else return SkipTestResult::Irreducible; } - else if (log.is(ty)) + else if (is(ty)) { return SkipTestResult::Irreducible; } @@ -119,16 +105,16 @@ struct FamilyReducer SkipTestResult testForSkippability(TypePackId ty) { - ty = log.follow(ty); + ty = follow(ty); - if (log.is(ty)) + if (is(ty)) { if (!irreducible.contains(ty)) return SkipTestResult::Defer; else return SkipTestResult::Irreducible; } - else if (log.is(ty)) + else if (is(ty)) { return SkipTestResult::Irreducible; } @@ -139,10 +125,7 @@ struct FamilyReducer template void replace(T subject, T replacement) { - if (parentLog) - parentLog->replace(subject, Unifiable::Bound{replacement}); - else - asMutable(subject)->ty.template emplace>(replacement); + asMutable(subject)->ty.template emplace>(replacement); if constexpr (std::is_same_v) result.reducedTypes.insert(subject); @@ -230,38 +213,36 @@ struct FamilyReducer void stepType() { - TypeId subject = log.follow(queuedTys.front()); + TypeId subject = follow(queuedTys.front()); queuedTys.pop_front(); if (irreducible.contains(subject)) return; - if (const TypeFamilyInstanceType* tfit = log.get(subject)) + if (const TypeFamilyInstanceType* tfit = get(subject)) { if (!testParameters(subject, tfit)) return; - TypeFamilyReductionResult result = - tfit->family->reducer(tfit->typeArguments, tfit->packArguments, arena, builtins, NotNull{&log}, scope, normalizer, solver); + TypeFamilyReductionResult result = tfit->family->reducer(tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); handleFamilyReduction(subject, result); } } void stepPack() { - TypePackId subject = log.follow(queuedTps.front()); + TypePackId subject = follow(queuedTps.front()); queuedTps.pop_front(); if (irreducible.contains(subject)) return; - if (const TypeFamilyInstanceTypePack* tfit = log.get(subject)) + if (const TypeFamilyInstanceTypePack* tfit = get(subject)) { if (!testParameters(subject, tfit)) return; - TypeFamilyReductionResult result = - tfit->family->reducer(tfit->typeArguments, tfit->packArguments, arena, builtins, NotNull{&log}, scope, normalizer, solver); + TypeFamilyReductionResult result = tfit->family->reducer(tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); handleFamilyReduction(subject, result); } } @@ -275,11 +256,10 @@ struct FamilyReducer } }; -static FamilyGraphReductionResult reduceFamiliesInternal(std::deque queuedTys, std::deque queuedTps, Location location, - NotNull arena, NotNull builtins, NotNull scope, NotNull normalizer, ConstraintSolver* solver, - TxnLog* log, bool force) +static FamilyGraphReductionResult reduceFamiliesInternal( + std::deque queuedTys, std::deque queuedTps, Location location, TypeFamilyContext ctx, bool force) { - FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), location, arena, builtins, scope, normalizer, solver, log, force}; + FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), location, ctx, force}; int iterationCount = 0; while (!reducer.done()) @@ -297,8 +277,7 @@ static FamilyGraphReductionResult reduceFamiliesInternal(std::deque queu return std::move(reducer.result); } -FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, NotNull arena, NotNull builtins, - NotNull scope, NotNull normalizer, TxnLog* log, bool force) +FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, TypeFamilyContext ctx, bool force) { InstanceCollector collector; @@ -314,11 +293,10 @@ FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, if (collector.tys.empty() && collector.tps.empty()) return {}; - return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), location, arena, builtins, scope, normalizer, nullptr, log, force); + return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), location, ctx, force); } -FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, NotNull arena, NotNull builtins, - NotNull scope, NotNull normalizer, TxnLog* log, bool force) +FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, TypeFamilyContext ctx, bool force) { InstanceCollector collector; @@ -334,94 +312,52 @@ FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location locati if (collector.tys.empty() && collector.tps.empty()) return {}; - return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), location, arena, builtins, scope, normalizer, nullptr, log, force); + return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), location, ctx, force); } -FamilyGraphReductionResult reduceFamilies( - NotNull solver, TypeId entrypoint, Location location, NotNull scope, TxnLog* log, bool force) +bool isPending(TypeId ty, ConstraintSolver* solver) { - InstanceCollector collector; - - try - { - collector.traverse(entrypoint); - } - catch (RecursionLimitException&) - { - return FamilyGraphReductionResult{}; - } - - if (collector.tys.empty() && collector.tps.empty()) - return {}; - - return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), location, solver->arena, solver->builtinTypes, scope, solver->normalizer, solver.get(), log, force); + return is(ty) || is(ty) || is(ty) || (solver && solver->hasUnresolvedConstraints(ty)); } -FamilyGraphReductionResult reduceFamilies( - NotNull solver, TypePackId entrypoint, Location location, NotNull scope, TxnLog* log, bool force) -{ - InstanceCollector collector; - - try - { - collector.traverse(entrypoint); - } - catch (RecursionLimitException&) - { - return FamilyGraphReductionResult{}; - } - - if (collector.tys.empty() && collector.tps.empty()) - return {}; - - return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), location, solver->arena, solver->builtinTypes, scope, solver->normalizer, solver.get(), log, force); -} - -bool isPending(TypeId ty, NotNull log, ConstraintSolver* solver) -{ - return log->is(ty) || log->is(ty) || log->is(ty) - || (solver && solver->hasUnresolvedConstraints(ty)); -} - -TypeFamilyReductionResult addFamilyFn(std::vector typeParams, std::vector packParams, NotNull arena, - NotNull builtins, NotNull log, NotNull scope, NotNull normalizer, ConstraintSolver* solver) +TypeFamilyReductionResult numericBinopFamilyFn( + std::vector typeParams, std::vector packParams, NotNull ctx, const std::string metamethod) { if (typeParams.size() != 2 || !packParams.empty()) { - // TODO: ICE? + ctx->ice->ice("encountered a type family instance without the required argument structure"); LUAU_ASSERT(false); - return {std::nullopt, true, {}, {}}; } - TypeId lhsTy = log->follow(typeParams.at(0)); - TypeId rhsTy = log->follow(typeParams.at(1)); - const NormalizedType* normLhsTy = normalizer->normalize(lhsTy); - const NormalizedType* normRhsTy = normalizer->normalize(rhsTy); + TypeId lhsTy = follow(typeParams.at(0)); + TypeId rhsTy = follow(typeParams.at(1)); + const NormalizedType* normLhsTy = ctx->normalizer->normalize(lhsTy); + const NormalizedType* normRhsTy = ctx->normalizer->normalize(rhsTy); if (!normLhsTy || !normRhsTy) { return {std::nullopt, false, {}, {}}; } - else if (log->is(normLhsTy->tops) || log->is(normRhsTy->tops)) + else if (is(normLhsTy->tops) || is(normRhsTy->tops)) { - return {builtins->anyType, false, {}, {}}; + return {ctx->builtins->anyType, false, {}, {}}; } else if ((normLhsTy->hasNumbers() || normLhsTy->hasTops()) && (normRhsTy->hasNumbers() || normRhsTy->hasTops())) { - return {builtins->numberType, false, {}, {}}; + return {ctx->builtins->numberType, false, {}, {}}; } - else if (log->is(lhsTy) || log->is(rhsTy)) + else if (is(lhsTy) || is(rhsTy)) { - return {builtins->errorRecoveryType(), false, {}, {}}; + return {ctx->builtins->errorRecoveryType(), false, {}, {}}; } - else if (log->is(lhsTy) || log->is(rhsTy)) + else if (is(lhsTy) || is(rhsTy)) { - return {builtins->neverType, false, {}, {}}; + return {ctx->builtins->neverType, false, {}, {}}; } - else if (isPending(lhsTy, log, solver)) + else if (isPending(lhsTy, ctx->solver)) { return {std::nullopt, false, {lhsTy}, {}}; } - else if (isPending(rhsTy, log, solver)) + else if (isPending(rhsTy, ctx->solver)) { return {std::nullopt, false, {rhsTy}, {}}; } @@ -430,28 +366,28 @@ TypeFamilyReductionResult addFamilyFn(std::vector typeParams, st // the necessary state to do that, even if we intend to just eat the errors. ErrorVec dummy; - std::optional addMm = findMetatableEntry(builtins, dummy, lhsTy, "__add", Location{}); + std::optional mmType = findMetatableEntry(ctx->builtins, dummy, lhsTy, metamethod, Location{}); bool reversed = false; - if (!addMm) + if (!mmType) { - addMm = findMetatableEntry(builtins, dummy, rhsTy, "__add", Location{}); + mmType = findMetatableEntry(ctx->builtins, dummy, rhsTy, metamethod, Location{}); reversed = true; } - if (!addMm) + if (!mmType) return {std::nullopt, true, {}, {}}; - if (isPending(log->follow(*addMm), log, solver)) - return {std::nullopt, false, {log->follow(*addMm)}, {}}; + mmType = follow(*mmType); + if (isPending(*mmType, ctx->solver)) + return {std::nullopt, false, {*mmType}, {}}; - const FunctionType* mmFtv = log->get(log->follow(*addMm)); + const FunctionType* mmFtv = get(*mmType); if (!mmFtv) return {std::nullopt, true, {}, {}}; - TypeCheckLimits limits; // TODO: We need to thread TypeCheckLimits in from Frontend to here. - if (std::optional instantiatedAddMm = instantiate(builtins, arena, NotNull{&limits}, scope, log->follow(*addMm))) + if (std::optional instantiatedMmType = instantiate(ctx->builtins, ctx->arena, ctx->limits, ctx->scope, *mmType)) { - if (const FunctionType* instantiatedMmFtv = get(*instantiatedAddMm)) + if (const FunctionType* instantiatedMmFtv = get(*instantiatedMmType)) { std::vector inferredArgs; if (!reversed) @@ -459,22 +395,19 @@ TypeFamilyReductionResult addFamilyFn(std::vector typeParams, st else inferredArgs = {rhsTy, lhsTy}; - TypePackId inferredArgPack = arena->addTypePack(std::move(inferredArgs)); - Unifier u{normalizer, scope, Location{}, Variance::Covariant, log.get()}; - u.tryUnify(inferredArgPack, instantiatedMmFtv->argTypes); + TypePackId inferredArgPack = ctx->arena->addTypePack(std::move(inferredArgs)); + Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice}; + if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes)) + return {std::nullopt, true, {}, {}}; // occurs check failed - if (std::optional ret = first(instantiatedMmFtv->retTypes); ret && u.errors.empty()) - { - return {u.log.follow(*ret), false, {}, {}}; - } + if (std::optional ret = first(instantiatedMmFtv->retTypes)) + return {*ret, false, {}, {}}; else - { return {std::nullopt, true, {}, {}}; - } } else { - return {builtins->errorRecoveryType(), false, {}, {}}; + return {ctx->builtins->errorRecoveryType(), false, {}, {}}; } } else @@ -484,9 +417,171 @@ TypeFamilyReductionResult addFamilyFn(std::vector typeParams, st } } +TypeFamilyReductionResult addFamilyFn(std::vector typeParams, std::vector packParams, NotNull ctx) +{ + if (typeParams.size() != 2 || !packParams.empty()) + { + ctx->ice->ice("add type family: encountered a type family instance without the required argument structure"); + LUAU_ASSERT(false); + } + + return numericBinopFamilyFn(typeParams, packParams, ctx, "__add"); +} + +TypeFamilyReductionResult subFamilyFn(std::vector typeParams, std::vector packParams, NotNull ctx) +{ + if (typeParams.size() != 2 || !packParams.empty()) + { + ctx->ice->ice("sub type family: encountered a type family instance without the required argument structure"); + LUAU_ASSERT(false); + } + + return numericBinopFamilyFn(typeParams, packParams, ctx, "__sub"); +} + +TypeFamilyReductionResult mulFamilyFn(std::vector typeParams, std::vector packParams, NotNull ctx) +{ + if (typeParams.size() != 2 || !packParams.empty()) + { + ctx->ice->ice("mul type family: encountered a type family instance without the required argument structure"); + LUAU_ASSERT(false); + } + + return numericBinopFamilyFn(typeParams, packParams, ctx, "__mul"); +} + +TypeFamilyReductionResult divFamilyFn(std::vector typeParams, std::vector packParams, NotNull ctx) +{ + if (typeParams.size() != 2 || !packParams.empty()) + { + ctx->ice->ice("div type family: encountered a type family instance without the required argument structure"); + LUAU_ASSERT(false); + } + + return numericBinopFamilyFn(typeParams, packParams, ctx, "__div"); +} + +TypeFamilyReductionResult idivFamilyFn(std::vector typeParams, std::vector packParams, NotNull ctx) +{ + if (typeParams.size() != 2 || !packParams.empty()) + { + ctx->ice->ice("integer div type family: encountered a type family instance without the required argument structure"); + LUAU_ASSERT(false); + } + + return numericBinopFamilyFn(typeParams, packParams, ctx, "__idiv"); +} + +TypeFamilyReductionResult powFamilyFn(std::vector typeParams, std::vector packParams, NotNull ctx) +{ + if (typeParams.size() != 2 || !packParams.empty()) + { + ctx->ice->ice("pow type family: encountered a type family instance without the required argument structure"); + LUAU_ASSERT(false); + } + + return numericBinopFamilyFn(typeParams, packParams, ctx, "__pow"); +} + +TypeFamilyReductionResult modFamilyFn(std::vector typeParams, std::vector packParams, NotNull ctx) +{ + if (typeParams.size() != 2 || !packParams.empty()) + { + ctx->ice->ice("modulo type family: encountered a type family instance without the required argument structure"); + LUAU_ASSERT(false); + } + + return numericBinopFamilyFn(typeParams, packParams, ctx, "__mod"); +} + +TypeFamilyReductionResult andFamilyFn(std::vector typeParams, std::vector packParams, NotNull ctx) +{ + if (typeParams.size() != 2 || !packParams.empty()) + { + ctx->ice->ice("and type family: encountered a type family instance without the required argument structure"); + LUAU_ASSERT(false); + } + + TypeId lhsTy = follow(typeParams.at(0)); + TypeId rhsTy = follow(typeParams.at(1)); + + if (isPending(lhsTy, ctx->solver)) + { + return {std::nullopt, false, {lhsTy}, {}}; + } + else if (isPending(rhsTy, ctx->solver)) + { + return {std::nullopt, false, {rhsTy}, {}}; + } + + // And evalutes to a boolean if the LHS is falsey, and the RHS type if LHS is truthy. + SimplifyResult filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->falsyType); + SimplifyResult overallResult = simplifyUnion(ctx->builtins, ctx->arena, rhsTy, filteredLhs.result); + std::vector blockedTypes(filteredLhs.blockedTypes.begin(), filteredLhs.blockedTypes.end()); + blockedTypes.insert(blockedTypes.end(), overallResult.blockedTypes.begin(), overallResult.blockedTypes.end()); + return {overallResult.result, false, std::move(blockedTypes), {}}; +} + +TypeFamilyReductionResult orFamilyFn(std::vector typeParams, std::vector packParams, NotNull ctx) +{ + if (typeParams.size() != 2 || !packParams.empty()) + { + ctx->ice->ice("or type family: encountered a type family instance without the required argument structure"); + LUAU_ASSERT(false); + } + + TypeId lhsTy = follow(typeParams.at(0)); + TypeId rhsTy = follow(typeParams.at(1)); + + if (isPending(lhsTy, ctx->solver)) + { + return {std::nullopt, false, {lhsTy}, {}}; + } + else if (isPending(rhsTy, ctx->solver)) + { + return {std::nullopt, false, {rhsTy}, {}}; + } + + // Or evalutes to the LHS type if the LHS is truthy, and the RHS type if LHS is falsy. + SimplifyResult filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->truthyType); + SimplifyResult overallResult = simplifyUnion(ctx->builtins, ctx->arena, rhsTy, filteredLhs.result); + std::vector blockedTypes(filteredLhs.blockedTypes.begin(), filteredLhs.blockedTypes.end()); + blockedTypes.insert(blockedTypes.end(), overallResult.blockedTypes.begin(), overallResult.blockedTypes.end()); + return {overallResult.result, false, std::move(blockedTypes), {}}; +} + BuiltinTypeFamilies::BuiltinTypeFamilies() : addFamily{"Add", addFamilyFn} + , subFamily{"Sub", subFamilyFn} + , mulFamily{"Mul", mulFamilyFn} + , divFamily{"Div", divFamilyFn} + , idivFamily{"FloorDiv", idivFamilyFn} + , powFamily{"Exp", powFamilyFn} + , modFamily{"Mod", modFamilyFn} + , andFamily{"And", andFamilyFn} + , orFamily{"Or", orFamilyFn} { } +void BuiltinTypeFamilies::addToScope(NotNull arena, NotNull scope) const +{ + // make a type function for a two-argument type family + auto mkBinaryTypeFamily = [&](const TypeFamily* family) { + TypeId t = arena->addType(GenericType{"T"}); + TypeId u = arena->addType(GenericType{"U"}); + GenericTypeDefinition genericT{t}; + GenericTypeDefinition genericU{u}; + + return TypeFun{{genericT, genericU}, arena->addType(TypeFamilyInstanceType{NotNull{family}, {t, u}, {}})}; + }; + + scope->exportedTypeBindings[addFamily.name] = mkBinaryTypeFamily(&addFamily); + scope->exportedTypeBindings[subFamily.name] = mkBinaryTypeFamily(&subFamily); + scope->exportedTypeBindings[mulFamily.name] = mkBinaryTypeFamily(&mulFamily); + scope->exportedTypeBindings[divFamily.name] = mkBinaryTypeFamily(&divFamily); + scope->exportedTypeBindings[idivFamily.name] = mkBinaryTypeFamily(&idivFamily); + scope->exportedTypeBindings[powFamily.name] = mkBinaryTypeFamily(&powFamily); + scope->exportedTypeBindings[modFamily.name] = mkBinaryTypeFamily(&modFamily); +} + } // namespace Luau diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index f454d948..223cd3df 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -454,17 +454,21 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool if (log.get(superTy)) { - // We do not report errors from reducing here. This is because we will - // "double-report" errors in some cases, like when trying to unify - // identical type family instantiations like Add with - // Add. - reduceFamilies(superTy, location, NotNull(types), builtinTypes, scope, normalizer, &log); + // FIXME: we should be be ICEing here because the old unifier is legacy and should not interact with type families at all. + // Unfortunately, there are, at the time of writing, still uses of the old unifier under local type inference. + TypeCheckLimits limits; + reduceFamilies( + superTy, location, TypeFamilyContext{NotNull(types), builtinTypes, scope, normalizer, NotNull{sharedState.iceHandler}, NotNull{&limits}}); superTy = log.follow(superTy); } if (log.get(subTy)) { - reduceFamilies(subTy, location, NotNull(types), builtinTypes, scope, normalizer, &log); + // FIXME: we should be be ICEing here because the old unifier is legacy and should not interact with type families at all. + // Unfortunately, there are, at the time of writing, still uses of the old unifier under local type inference. + TypeCheckLimits limits; + reduceFamilies( + subTy, location, TypeFamilyContext{NotNull(types), builtinTypes, scope, normalizer, NotNull{sharedState.iceHandler}, NotNull{&limits}}); subTy = log.follow(subTy); } diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index e9e13c2a..11f96ea1 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -154,6 +154,9 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn) { if (shouldInstantiate) { std::optional instantiated = instantiate(builtinTypes, arena, NotNull{&limits}, scope, subTy); + if (!instantiated) + return false; + subFn = get(*instantiated); LUAU_ASSERT(subFn); // instantiation should not make a function type _not_ a function type. diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index f3b5b149..3e55a91e 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -795,6 +795,11 @@ public: const AstArray& genericPacks, const AstTypeList& params, const AstArray& paramNames, const AstTypeList& retTypes); + AstStatDeclareFunction(const Location& location, const AstName& name, const AstArray& generics, + const AstArray& genericPacks, const AstTypeList& params, const AstArray& paramNames, + const AstTypeList& retTypes, bool checkedFunction); + + void visit(AstVisitor* visitor) override; AstName name; @@ -803,6 +808,7 @@ public: AstTypeList params; AstArray paramNames; AstTypeList retTypes; + bool checkedFunction; }; struct AstDeclaredClassProp @@ -903,6 +909,9 @@ public: AstTypeFunction(const Location& location, const AstArray& generics, const AstArray& genericPacks, const AstTypeList& argTypes, const AstArray>& argNames, const AstTypeList& returnTypes); + AstTypeFunction(const Location& location, const AstArray& generics, const AstArray& genericPacks, + const AstTypeList& argTypes, const AstArray>& argNames, const AstTypeList& returnTypes, bool checkedFunction); + void visit(AstVisitor* visitor) override; AstArray generics; @@ -910,6 +919,7 @@ public: AstTypeList argTypes; AstArray> argNames; AstTypeList returnTypes; + bool checkedFunction; }; class AstTypeTypeof : public AstType diff --git a/Ast/include/Luau/Lexer.h b/Ast/include/Luau/Lexer.h index 7d15212a..e111030d 100644 --- a/Ast/include/Luau/Lexer.h +++ b/Ast/include/Luau/Lexer.h @@ -91,7 +91,6 @@ struct Lexeme BrokenComment, BrokenUnicode, BrokenInterpDoubleBrace, - Error, Reserved_BEGIN, @@ -116,6 +115,7 @@ struct Lexeme ReservedTrue, ReservedUntil, ReservedWhile, + ReservedChecked, Reserved_END }; diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index 0b9d8c46..96e9639c 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -176,15 +176,16 @@ private: AstTableIndexer* parseTableIndexer(); - AstTypeOrPack parseFunctionType(bool allowPack); + AstTypeOrPack parseFunctionType(bool allowPack, bool isCheckedFunction = false); AstType* parseFunctionTypeTail(const Lexeme& begin, AstArray generics, AstArray genericPacks, - AstArray params, AstArray> paramNames, AstTypePack* varargAnnotation); + AstArray params, AstArray> paramNames, AstTypePack* varargAnnotation, + bool isCheckedFunction = false); - AstType* parseTableType(); - AstTypeOrPack parseSimpleType(bool allowPack); + AstType* parseTableType(bool inDeclarationContext = false); + AstTypeOrPack parseSimpleType(bool allowPack, bool inDeclarationContext = false); AstTypeOrPack parseTypeOrPack(); - AstType* parseType(); + AstType* parseType(bool inDeclarationContext = false); AstTypePack* parseTypePack(); AstTypePack* parseVariadicArgumentTypePack(); diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index 52b77de3..ec5700dc 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -709,6 +709,21 @@ AstStatDeclareFunction::AstStatDeclareFunction(const Location& location, const A , params(params) , paramNames(paramNames) , retTypes(retTypes) + , checkedFunction(false) +{ +} + +AstStatDeclareFunction::AstStatDeclareFunction(const Location& location, const AstName& name, const AstArray& generics, + const AstArray& genericPacks, const AstTypeList& params, const AstArray& paramNames, + const AstTypeList& retTypes, bool checkedFunction) + : AstStat(ClassIndex(), location) + , name(name) + , generics(generics) + , genericPacks(genericPacks) + , params(params) + , paramNames(paramNames) + , retTypes(retTypes) + , checkedFunction(checkedFunction) { } @@ -817,6 +832,20 @@ AstTypeFunction::AstTypeFunction(const Location& location, const AstArray& generics, const AstArray& genericPacks, + const AstTypeList& argTypes, const AstArray>& argNames, const AstTypeList& returnTypes, bool checkedFunction) + : AstType(ClassIndex(), location) + , generics(generics) + , genericPacks(genericPacks) + , argTypes(argTypes) + , argNames(argNames) + , returnTypes(returnTypes) + , checkedFunction(checkedFunction) { LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size); } diff --git a/Ast/src/Lexer.cpp b/Ast/src/Lexer.cpp index 1795243c..a493acfe 100644 --- a/Ast/src/Lexer.cpp +++ b/Ast/src/Lexer.cpp @@ -1,6 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Lexer.h" +#include "Luau/Common.h" #include "Luau/Confusables.h" #include "Luau/StringUtils.h" @@ -8,6 +9,7 @@ LUAU_FASTFLAGVARIABLE(LuauFloorDivision, false) LUAU_FASTFLAGVARIABLE(LuauLexerLookaheadRemembersBraceType, false) +LUAU_FASTFLAGVARIABLE(LuauCheckedFunctionSyntax, false) namespace Luau { @@ -106,7 +108,7 @@ Lexeme::Lexeme(const Location& location, Type type, const char* name) } static const char* kReserved[] = {"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", - "repeat", "return", "then", "true", "until", "while"}; + "repeat", "return", "then", "true", "until", "while", "@checked"}; std::string Lexeme::toString() const { @@ -709,7 +711,7 @@ Lexeme Lexer::readNumber(const Position& start, unsigned int startOffset) std::pair Lexer::readName() { - LUAU_ASSERT(isAlpha(peekch()) || peekch() == '_'); + LUAU_ASSERT(isAlpha(peekch()) || peekch() == '_' || peekch() == '@'); unsigned int startOffset = offset; @@ -1007,7 +1009,20 @@ Lexeme Lexer::readNext() return Lexeme(Location(start, 1), ch); } + case '@': + { + if (FFlag::LuauCheckedFunctionSyntax) + { + // We're trying to lex the token @checked + LUAU_ASSERT(peekch() == '@'); + std::pair maybeChecked = readName(); + if (maybeChecked.second != Lexeme::ReservedChecked) + return Lexeme(Location(start, position()), Lexeme::Error); + + return Lexeme(Location(start, position()), maybeChecked.second, maybeChecked.first.value); + } + } default: if (isDigit(peekch())) { diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index d59b6b40..dfb0a540 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -1,6 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Parser.h" +#include "Luau/Common.h" #include "Luau/TimeTrace.h" #include @@ -15,6 +16,7 @@ LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000) LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) LUAU_FASTFLAGVARIABLE(LuauParseDeclareClassIndexer, false) LUAU_FASTFLAG(LuauFloorDivision) +LUAU_FASTFLAG(LuauCheckedFunctionSyntax) namespace Luau { @@ -823,8 +825,14 @@ AstStat* Parser::parseDeclaration(const Location& start) if (lexer.current().type == Lexeme::ReservedFunction) { nextLexeme(); - Name globalName = parseName("global function name"); + bool checkedFunction = false; + if (FFlag::LuauCheckedFunctionSyntax && lexer.current().type == Lexeme::ReservedChecked) + { + checkedFunction = true; + nextLexeme(); + } + Name globalName = parseName("global function name"); auto [generics, genericPacks] = parseGenericTypeList(/* withDefaultValues= */ false); MatchLexeme matchParen = lexer.current(); @@ -860,8 +868,8 @@ AstStat* Parser::parseDeclaration(const Location& start) if (vararg && !varargAnnotation) return reportStatError(Location(start, end), {}, {}, "All declaration parameters must be annotated"); - return allocator.alloc( - Location(start, end), globalName.name, generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes); + return allocator.alloc(Location(start, end), globalName.name, generics, genericPacks, + AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes, checkedFunction); } else if (AstName(lexer.current().name) == "class") { @@ -940,7 +948,7 @@ AstStat* Parser::parseDeclaration(const Location& start) { expectAndConsume(':', "global variable declaration"); - AstType* type = parseType(); + AstType* type = parseType(/* in declaration context */ true); return allocator.alloc(Location(start, type->location), globalName->name, type); } else @@ -1302,7 +1310,7 @@ AstTableIndexer* Parser::parseTableIndexer() // TablePropOrIndexer ::= TableProp | TableIndexer // PropList ::= TablePropOrIndexer {fieldsep TablePropOrIndexer} [fieldsep] // TableType ::= `{' PropList `}' -AstType* Parser::parseTableType() +AstType* Parser::parseTableType(bool inDeclarationContext) { incrementRecursionCounter("type annotation"); @@ -1370,7 +1378,7 @@ AstType* Parser::parseTableType() expectAndConsume(':', "table field"); - AstType* type = parseType(); + AstType* type = parseType(inDeclarationContext); props.push_back({name->name, name->location, type}); } @@ -1396,7 +1404,7 @@ AstType* Parser::parseTableType() // ReturnType ::= Type | `(' TypeList `)' // FunctionType ::= [`<' varlist `>'] `(' [TypeList] `)' `->` ReturnType -AstTypeOrPack Parser::parseFunctionType(bool allowPack) +AstTypeOrPack Parser::parseFunctionType(bool allowPack, bool isCheckedFunction) { incrementRecursionCounter("type annotation"); @@ -1444,11 +1452,11 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack) AstArray> paramNames = copy(names); - return {parseFunctionTypeTail(begin, generics, genericPacks, paramTypes, paramNames, varargAnnotation), {}}; + return {parseFunctionTypeTail(begin, generics, genericPacks, paramTypes, paramNames, varargAnnotation, isCheckedFunction), {}}; } AstType* Parser::parseFunctionTypeTail(const Lexeme& begin, AstArray generics, AstArray genericPacks, - AstArray params, AstArray> paramNames, AstTypePack* varargAnnotation) + AstArray params, AstArray> paramNames, AstTypePack* varargAnnotation, bool isCheckedFunction) { incrementRecursionCounter("type annotation"); @@ -1472,7 +1480,8 @@ AstType* Parser::parseFunctionTypeTail(const Lexeme& begin, AstArray(Location(begin.location, endLocation), generics, genericPacks, paramTypes, paramNames, returnTypeList); + return allocator.alloc( + Location(begin.location, endLocation), generics, genericPacks, paramTypes, paramNames, returnTypeList, isCheckedFunction); } // Type ::= @@ -1565,14 +1574,14 @@ AstTypeOrPack Parser::parseTypeOrPack() return {parseTypeSuffix(type, begin), {}}; } -AstType* Parser::parseType() +AstType* Parser::parseType(bool inDeclarationContext) { unsigned int oldRecursionCount = recursionCounter; incrementRecursionCounter("type annotation"); Location begin = lexer.current().location; - AstType* type = parseSimpleType(/* allowPack= */ false).type; + AstType* type = parseSimpleType(/* allowPack= */ false, /* in declaration context */ inDeclarationContext).type; recursionCounter = oldRecursionCount; @@ -1581,7 +1590,7 @@ AstType* Parser::parseType() // Type ::= nil | Name[`.' Name] [ `<' Type [`,' ...] `>' ] | `typeof' `(' expr `)' | `{' [PropList] `}' // | [`<' varlist `>'] `(' [TypeList] `)' `->` ReturnType -AstTypeOrPack Parser::parseSimpleType(bool allowPack) +AstTypeOrPack Parser::parseSimpleType(bool allowPack, bool inDeclarationContext) { incrementRecursionCounter("type annotation"); @@ -1673,7 +1682,13 @@ AstTypeOrPack Parser::parseSimpleType(bool allowPack) } else if (lexer.current().type == '{') { - return {parseTableType(), {}}; + return {parseTableType(/* inDeclarationContext */ inDeclarationContext), {}}; + } + else if (FFlag::LuauCheckedFunctionSyntax && inDeclarationContext && lexer.current().type == Lexeme::ReservedChecked) + { + LUAU_ASSERT(FFlag::LuauCheckedFunctionSyntax); + nextLexeme(); + return parseFunctionType(allowPack, /* isCheckedFunction */ true); } else if (lexer.current().type == '(' || lexer.current().type == '<') { diff --git a/CLI/Compile.cpp b/CLI/Compile.cpp index c35f9c3d..2059deaf 100644 --- a/CLI/Compile.cpp +++ b/CLI/Compile.cpp @@ -369,10 +369,9 @@ int main(int argc, char** argv) stats.bytecode == 0 ? 0.0 : double(stats.codegen) / double(stats.bytecode), stats.readTime, stats.parseTime, stats.compileTime, stats.codegenTime); - printf("Lowering stats:\n"); - printf("- spills to stack: %d, spills to restore: %d, max spill slot %u\n", stats.lowerStats.spillsToSlot, stats.lowerStats.spillsToRestore, + printf("Lowering: regalloc failed: %d, lowering failed %d; spills to stack: %d, spills to restore: %d, max spill slot %u\n", + stats.lowerStats.regAllocErrors, stats.lowerStats.loweringErrors, stats.lowerStats.spillsToSlot, stats.lowerStats.spillsToRestore, stats.lowerStats.maxSpillSlotsUsed); - printf("- regalloc failed: %d, lowering failed %d\n", stats.lowerStats.regAllocErrors, stats.lowerStats.loweringErrors); } return failed ? 1 : 0; diff --git a/CodeGen/include/Luau/AssemblyBuilderX64.h b/CodeGen/include/Luau/AssemblyBuilderX64.h index aea01eec..6bf5ad3d 100644 --- a/CodeGen/include/Luau/AssemblyBuilderX64.h +++ b/CodeGen/include/Luau/AssemblyBuilderX64.h @@ -85,6 +85,7 @@ public: void test(OperandX64 lhs, OperandX64 rhs); void lea(OperandX64 lhs, OperandX64 rhs); void setcc(ConditionX64 cond, OperandX64 op); + void cmov(ConditionX64 cond, RegisterX64 lhs, OperandX64 rhs); void push(OperandX64 op); void pop(OperandX64 op); diff --git a/CodeGen/include/Luau/CodeGen.h b/CodeGen/include/Luau/CodeGen.h index 031779f7..04a65e36 100644 --- a/CodeGen/include/Luau/CodeGen.h +++ b/CodeGen/include/Luau/CodeGen.h @@ -17,6 +17,8 @@ enum CodeGenFlags { // Only run native codegen for modules that have been marked with --!native CodeGen_OnlyNativeModules = 1 << 0, + // Run native codegen for functions that the compiler considers not profitable + CodeGen_ColdFunctions = 1 << 1, }; enum class CodeGenCompilationResult diff --git a/CodeGen/src/AssemblyBuilderX64.cpp b/CodeGen/src/AssemblyBuilderX64.cpp index bd2568d2..8abccac7 100644 --- a/CodeGen/src/AssemblyBuilderX64.cpp +++ b/CodeGen/src/AssemblyBuilderX64.cpp @@ -27,6 +27,11 @@ static const char* setccTextForCondition[] = {"seto", "setno", "setc", "setnc", "setge", "setnb", "setnbe", "setna", "setnae", "setne", "setnl", "setnle", "setng", "setnge", "setz", "setnz", "setp", "setnp"}; static_assert(sizeof(setccTextForCondition) / sizeof(setccTextForCondition[0]) == size_t(ConditionX64::Count), "all conditions have to be covered"); +static const char* cmovTextForCondition[] = {"cmovo", "cmovno", "cmovc", "cmovnc", "cmovb", "cmovbe", "cmova", "cmovae", "cmove", "cmovl", "cmovle", + "cmovg", "cmovge", "cmovnb", "cmovnbe", "cmovna", "cmovnae", "cmovne", "cmovnl", "cmovnle", "cmovng", "cmovnge", "cmovz", "cmovnz", "cmovp", + "cmovnp"}; +static_assert(sizeof(cmovTextForCondition) / sizeof(cmovTextForCondition[0]) == size_t(ConditionX64::Count), "all conditions have to be covered"); + #define OP_PLUS_REG(op, reg) ((op) + (reg & 0x7)) #define OP_PLUS_CC(op, cc) ((op) + uint8_t(cc)) @@ -404,6 +409,20 @@ void AssemblyBuilderX64::setcc(ConditionX64 cond, OperandX64 op) commit(); } +void AssemblyBuilderX64::cmov(ConditionX64 cond, RegisterX64 lhs, OperandX64 rhs) +{ + SizeX64 size = rhs.cat == CategoryX64::reg ? rhs.base.size : rhs.memSize; + LUAU_ASSERT(size != SizeX64::byte && size == lhs.size); + + if (logText) + log(cmovTextForCondition[size_t(cond)], lhs, rhs); + placeRex(lhs, rhs); + place(0x0f); + place(0x40 | codeForCondition[size_t(cond)]); + placeRegAndModRegMem(lhs, rhs); + commit(); +} + void AssemblyBuilderX64::jcc(ConditionX64 cond, Label& label) { placeJcc(jccTextForCondition[size_t(cond)], label, codeForCondition[size_t(cond)]); diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index a25e1046..274b583d 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -259,7 +259,7 @@ CodeGenCompilationResult compile(lua_State* L, int idx, unsigned int flags, Comp return CodeGenCompilationResult::CodeGenNotInitialized; std::vector protos; - gatherFunctions(protos, root); + gatherFunctions(protos, root, flags); // Skip protos that have been compiled during previous invocations of CodeGen::compile protos.erase(std::remove_if(protos.begin(), protos.end(), diff --git a/CodeGen/src/CodeGenA64.cpp b/CodeGen/src/CodeGenA64.cpp index 2e268d26..37ee462b 100644 --- a/CodeGen/src/CodeGenA64.cpp +++ b/CodeGen/src/CodeGenA64.cpp @@ -5,6 +5,7 @@ #include "Luau/UnwindBuilder.h" #include "BitUtils.h" +#include "CodeGenUtils.h" #include "NativeState.h" #include "EmitCommonA64.h" @@ -96,42 +97,27 @@ static void emitInterrupt(AssemblyBuilderA64& build) build.br(x0); } -static void emitReentry(AssemblyBuilderA64& build, ModuleHelpers& helpers) +static void emitContinueCall(AssemblyBuilderA64& build, ModuleHelpers& helpers) { // x0 = closure object to reentry (equal to clvalue(L->ci->func)) - // If the fallback requested an exit, we need to do this right away - build.cbz(x0, helpers.exitNoContinueVm); - - emitUpdateBase(build); + // If the fallback yielded, we need to do this right away + // note: it's slightly cheaper to check x0 LSB; a valid Closure pointer must be aligned to 8 bytes + LUAU_ASSERT(CALL_FALLBACK_YIELD == 1); + build.tbnz(x0, 0, helpers.exitNoContinueVm); // Need to update state of the current function before we jump away build.ldr(x1, mem(x0, offsetof(Closure, l.p))); // cl->l.p aka proto - build.ldr(x2, mem(rState, offsetof(lua_State, ci))); // L->ci - - // We need to check if the new frame can be executed natively - // TODO: .flags and .savedpc load below can be fused with ldp - build.ldr(w3, mem(x2, offsetof(CallInfo, flags))); - build.tbz(x3, countrz(LUA_CALLINFO_NATIVE), helpers.exitContinueVm); + build.ldr(x2, mem(x1, offsetof(Proto, exectarget))); + build.cbz(x2, helpers.exitContinueVm); build.mov(rClosure, x0); LUAU_ASSERT(offsetof(Proto, code) == offsetof(Proto, k) + 8); build.ldp(rConstants, rCode, mem(x1, offsetof(Proto, k))); // proto->k, proto->code - // Get instruction index from instruction pointer - // To get instruction index from instruction pointer, we need to divide byte offset by 4 - // But we will actually need to scale instruction index by 4 back to byte offset later so it cancels out - build.ldr(x2, mem(x2, offsetof(CallInfo, savedpc))); // L->ci->savedpc - build.sub(x2, x2, rCode); - - // Get new instruction location and jump to it - LUAU_ASSERT(offsetof(Proto, exectarget) == offsetof(Proto, execdata) + 8); - build.ldp(x3, x4, mem(x1, offsetof(Proto, execdata))); - build.ldr(w2, mem(x3, x2)); - build.add(x4, x4, x2); - build.br(x4); + build.br(x2); } void emitReturn(AssemblyBuilderA64& build, ModuleHelpers& helpers) @@ -326,11 +312,6 @@ void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers) build.setLabel(helpers.exitNoContinueVm); emitExit(build, /* continueInVm */ false); - if (build.logText) - build.logAppend("; reentry\n"); - build.setLabel(helpers.reentry); - emitReentry(build, helpers); - if (build.logText) build.logAppend("; interrupt\n"); build.setLabel(helpers.interrupt); @@ -340,6 +321,11 @@ void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers) build.logAppend("; return\n"); build.setLabel(helpers.return_); emitReturn(build, helpers); + + if (build.logText) + build.logAppend("; continueCall\n"); + build.setLabel(helpers.continueCall); + emitContinueCall(build, helpers); } } // namespace A64 diff --git a/CodeGen/src/CodeGenAssembly.cpp b/CodeGen/src/CodeGenAssembly.cpp index 4fd24cd9..d55713f0 100644 --- a/CodeGen/src/CodeGenAssembly.cpp +++ b/CodeGen/src/CodeGenAssembly.cpp @@ -46,7 +46,15 @@ template static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, AssemblyOptions options, LoweringStats* stats) { std::vector protos; - gatherFunctions(protos, clvalue(func)->l.p); + gatherFunctions(protos, clvalue(func)->l.p, /* flags= */ 0); + + protos.erase(std::remove_if(protos.begin(), protos.end(), [](Proto* p) { return p == nullptr; }), protos.end()); + + if (protos.empty()) + { + build.finalize(); // to avoid assertion in AssemblyBuilder dtor + return std::string(); + } ModuleHelpers helpers; assembleHelpers(build, helpers); @@ -58,24 +66,23 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A } for (Proto* p : protos) - if (p) + { + IrBuilder ir; + ir.buildFunctionIr(p); + + if (options.includeAssembly || options.includeIr) + logFunctionHeader(build, p); + + if (!lowerFunction(ir, build, helpers, p, options, stats)) { - IrBuilder ir; - ir.buildFunctionIr(p); - - if (options.includeAssembly || options.includeIr) - logFunctionHeader(build, p); - - if (!lowerFunction(ir, build, helpers, p, options, stats)) - { - if (build.logText) - build.logAppend("; skipping (can't lower)\n"); - } - if (build.logText) - build.logAppend("\n"); + build.logAppend("; skipping (can't lower)\n"); } + if (build.logText) + build.logAppend("\n"); + } + if (!build.finalize()) return std::string(); diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index 9b729a92..e8eaa502 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -29,7 +29,7 @@ namespace Luau namespace CodeGen { -inline void gatherFunctions(std::vector& results, Proto* proto) +inline void gatherFunctions(std::vector& results, Proto* proto, unsigned int flags) { if (results.size() <= size_t(proto->bytecodeid)) results.resize(proto->bytecodeid + 1); @@ -38,10 +38,13 @@ inline void gatherFunctions(std::vector& results, Proto* proto) if (results[proto->bytecodeid]) return; - results[proto->bytecodeid] = proto; + // Only compile cold functions if requested + if ((proto->flags & LPF_NATIVE_COLD) == 0 || (flags & CodeGen_ColdFunctions) != 0) + results[proto->bytecodeid] = proto; + // Recursively traverse child protos even if we aren't compiling this one for (int i = 0; i < proto->sizep; i++) - gatherFunctions(results, proto->p[i]); + gatherFunctions(results, proto->p[i], flags); } template diff --git a/CodeGen/src/CodeGenUtils.cpp b/CodeGen/src/CodeGenUtils.cpp index c7f24c80..3cdd20b3 100644 --- a/CodeGen/src/CodeGenUtils.cpp +++ b/CodeGen/src/CodeGenUtils.cpp @@ -275,7 +275,7 @@ Closure* callFallback(lua_State* L, StkId ra, StkId argtop, int nresults) // yield if (n < 0) - return NULL; + return (Closure*)CALL_FALLBACK_YIELD; // ci is our callinfo, cip is our parent CallInfo* ci = L->ci; @@ -299,8 +299,7 @@ Closure* callFallback(lua_State* L, StkId ra, StkId argtop, int nresults) L->top = (nresults == LUA_MULTRET) ? res : cip->top; // keep executing current function - LUAU_ASSERT(isLua(cip)); - return clvalue(cip->func); + return NULL; } } diff --git a/CodeGen/src/CodeGenUtils.h b/CodeGen/src/CodeGenUtils.h index 15b794d2..7075e348 100644 --- a/CodeGen/src/CodeGenUtils.h +++ b/CodeGen/src/CodeGenUtils.h @@ -17,6 +17,8 @@ void forgPrepXnextFallback(lua_State* L, TValue* ra, int pc); Closure* callProlog(lua_State* L, TValue* ra, StkId argtop, int nresults); void callEpilogC(lua_State* L, int nresults, int n); +#define CALL_FALLBACK_YIELD 1 + Closure* callFallback(lua_State* L, StkId ra, StkId argtop, int nresults); const Instruction* executeGETGLOBAL(lua_State* L, const Instruction* pc, StkId base, TValue* k); diff --git a/CodeGen/src/CodeGenX64.cpp b/CodeGen/src/CodeGenX64.cpp index a8cf2e73..b7f70258 100644 --- a/CodeGen/src/CodeGenX64.cpp +++ b/CodeGen/src/CodeGenX64.cpp @@ -240,11 +240,6 @@ void assembleHelpers(X64::AssemblyBuilderX64& build, ModuleHelpers& helpers) build.setLabel(helpers.exitNoContinueVm); emitExit(build, /* continueInVm */ false); - if (build.logText) - build.logAppend("; continueCallInVm\n"); - build.setLabel(helpers.continueCallInVm); - emitContinueCallInVm(build); - if (build.logText) build.logAppend("; interrupt\n"); build.setLabel(helpers.interrupt); diff --git a/CodeGen/src/EmitCommon.h b/CodeGen/src/EmitCommon.h index 8ac746ee..013ba88f 100644 --- a/CodeGen/src/EmitCommon.h +++ b/CodeGen/src/EmitCommon.h @@ -29,11 +29,8 @@ struct ModuleHelpers Label return_; Label interrupt; - // X64 - Label continueCallInVm; - // A64 - Label reentry; // x0: closure + Label continueCall; // x0: closure }; } // namespace CodeGen diff --git a/CodeGen/src/EmitCommonX64.cpp b/CodeGen/src/EmitCommonX64.cpp index 97749fbe..008dadd5 100644 --- a/CodeGen/src/EmitCommonX64.cpp +++ b/CodeGen/src/EmitCommonX64.cpp @@ -378,17 +378,6 @@ void emitUpdatePcForExit(AssemblyBuilderX64& build) build.mov(qword[rax + offsetof(CallInfo, savedpc)], rdx); } -void emitContinueCallInVm(AssemblyBuilderX64& build) -{ - RegisterX64 proto = rcx; // Sync with emitInstCall - - build.mov(rdx, qword[proto + offsetof(Proto, code)]); - build.mov(rax, qword[rState + offsetof(lua_State, ci)]); - build.mov(qword[rax + offsetof(CallInfo, savedpc)], rdx); - - emitExit(build, /* continueInVm */ true); -} - void emitReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers) { // input: res in rdi, number of written values in ecx diff --git a/CodeGen/src/EmitCommonX64.h b/CodeGen/src/EmitCommonX64.h index 3d9a59ff..3418a09f 100644 --- a/CodeGen/src/EmitCommonX64.h +++ b/CodeGen/src/EmitCommonX64.h @@ -43,7 +43,7 @@ constexpr RegisterX64 rNativeContext = r13; // NativeContext* context constexpr RegisterX64 rConstants = r12; // TValue* k constexpr unsigned kExtraLocals = 3; // Number of 8 byte slots available for specialized local variables specified below -constexpr unsigned kSpillSlots = 5; // Number of 8 byte slots available for register allocator to spill data into +constexpr unsigned kSpillSlots = 13; // Number of 8 byte slots available for register allocator to spill data into static_assert((kExtraLocals + kSpillSlots) * 8 % 16 == 0, "locals have to preserve 16 byte alignment"); constexpr uint8_t kWindowsFirstNonVolXmmReg = 6; @@ -216,7 +216,6 @@ void emitInterrupt(AssemblyBuilderX64& build); void emitFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int offset, int pcpos); void emitUpdatePcForExit(AssemblyBuilderX64& build); -void emitContinueCallInVm(AssemblyBuilderX64& build); void emitReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers); diff --git a/CodeGen/src/EmitInstructionX64.cpp b/CodeGen/src/EmitInstructionX64.cpp index bccdc8f0..13be20b9 100644 --- a/CodeGen/src/EmitInstructionX64.cpp +++ b/CodeGen/src/EmitInstructionX64.cpp @@ -79,36 +79,34 @@ void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int // Set L->top to ci->top as most function expect (no vararg) build.mov(rax, qword[ci + offsetof(CallInfo, top)]); - build.mov(qword[rState + offsetof(lua_State, top)], rax); // But if it is vararg, update it to 'argi' Label skipVararg; build.test(byte[proto + offsetof(Proto, is_vararg)], 1); build.jcc(ConditionX64::Zero, skipVararg); + build.mov(rax, argi); - build.mov(qword[rState + offsetof(lua_State, top)], argi); build.setLabel(skipVararg); - // Keep executing new function + build.mov(qword[rState + offsetof(lua_State, top)], rax); + + // Switch current code // ci->savedpc = p->code; build.mov(rax, qword[proto + offsetof(Proto, code)]); + build.mov(sCode, rax); // note: this needs to be before the next store for optimal performance build.mov(qword[ci + offsetof(CallInfo, savedpc)], rax); - // Get native function entry - build.mov(rax, qword[proto + offsetof(Proto, exectarget)]); - build.test(rax, rax); - build.jcc(ConditionX64::Zero, helpers.continueCallInVm); - - // Mark call frame as native - build.mov(dword[ci + offsetof(CallInfo, flags)], LUA_CALLINFO_NATIVE); - // Switch current constants build.mov(rConstants, qword[proto + offsetof(Proto, k)]); - // Switch current code - build.mov(rdx, qword[proto + offsetof(Proto, code)]); - build.mov(sCode, rdx); + // Get native function entry + build.mov(rax, qword[proto + offsetof(Proto, exectarget)]); + build.test(rax, rax); + build.jcc(ConditionX64::Zero, helpers.exitContinueVm); + + // Mark call frame as native + build.mov(dword[ci + offsetof(CallInfo, flags)], LUA_CALLINFO_NATIVE); build.jmp(rax); } diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index 1aca2993..40aee13a 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -146,6 +146,11 @@ void IrBuilder::buildFunctionIr(Proto* proto) if (instIndexToBlock[i] != kNoAssociatedBlockIndex) beginBlock(blockAtInst(i)); + // Numeric for loops require additional processing to maintain loop stack + // Notably, this must be performed even when the block is dead so that we maintain the pairing FORNPREP-FORNLOOP + if (op == LOP_FORNPREP) + beforeInstForNPrep(*this, pc); + // We skip dead bytecode instructions when they appear after block was already terminated if (!inTerminatedBlock) { @@ -164,6 +169,10 @@ void IrBuilder::buildFunctionIr(Proto* proto) } } + // See above for FORNPREP..FORNLOOP processing + if (op == LOP_FORNLOOP) + afterInstForNLoop(*this, pc); + i = nexti; LUAU_ASSERT(i <= proto->sizecode); diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 6e51b0d5..68378caa 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -1515,8 +1515,10 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, callFallback))); build.blr(x4); - // reentry with x0=closure (NULL will trigger exit) - build.b(helpers.reentry); + emitUpdateBase(build); + + // reentry with x0=closure (NULL implies C function; CALL_FALLBACK_YIELD will trigger exit) + build.cbnz(x0, helpers.continueCall); break; case IrCmd::RETURN: regs.spill(build, index); diff --git a/CodeGen/src/IrRegAllocA64.cpp b/CodeGen/src/IrRegAllocA64.cpp index d2b7ff14..41f392c5 100644 --- a/CodeGen/src/IrRegAllocA64.cpp +++ b/CodeGen/src/IrRegAllocA64.cpp @@ -10,7 +10,7 @@ #include -LUAU_FASTFLAGVARIABLE(DebugLuauCodegenChaosA64, false) +LUAU_FASTFLAGVARIABLE(DebugCodegenChaosA64, false) namespace Luau { @@ -146,7 +146,7 @@ RegisterA64 IrRegAllocA64::allocReg(KindA64 kind, uint32_t index) int reg = 31 - countlz(set.free); - if (FFlag::DebugLuauCodegenChaosA64) + if (FFlag::DebugCodegenChaosA64) reg = countrz(set.free); // allocate from low end; this causes extra conflicts for calls set.free &= ~(1u << reg); @@ -167,7 +167,7 @@ RegisterA64 IrRegAllocA64::allocTemp(KindA64 kind) int reg = 31 - countlz(set.free); - if (FFlag::DebugLuauCodegenChaosA64) + if (FFlag::DebugCodegenChaosA64) reg = countrz(set.free); // allocate from low end; this causes extra conflicts for calls set.free &= ~(1u << reg); @@ -278,7 +278,7 @@ size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::init uint32_t poisongpr = 0; uint32_t poisonsimd = 0; - if (FFlag::DebugLuauCodegenChaosA64) + if (FFlag::DebugCodegenChaosA64) { poisongpr = gpr.base & ~gpr.free; poisonsimd = simd.base & ~simd.free; @@ -370,7 +370,7 @@ size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::init LUAU_ASSERT(set.free == set.base); } - if (FFlag::DebugLuauCodegenChaosA64) + if (FFlag::DebugCodegenChaosA64) { for (int reg = 0; reg < 32; ++reg) { diff --git a/CodeGen/src/IrTranslateBuiltins.cpp b/CodeGen/src/IrTranslateBuiltins.cpp index 279eabea..2a4fd93f 100644 --- a/CodeGen/src/IrTranslateBuiltins.cpp +++ b/CodeGen/src/IrTranslateBuiltins.cpp @@ -83,6 +83,9 @@ static BuiltinImplResult translateBuiltin2NumberToNumberLibm( IrOp va = builtinLoadDouble(build, build.vmReg(arg)); IrOp vb = builtinLoadDouble(build, args); + if (bfid == LBF_MATH_LDEXP) + vb = build.inst(IrCmd::NUM_TO_INT, vb); + IrOp res = build.inst(IrCmd::INVOKE_LIBM, build.constUint(bfid), va, vb); build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), res); @@ -93,30 +96,6 @@ static BuiltinImplResult translateBuiltin2NumberToNumberLibm( return {BuiltinImplType::Full, 1}; } -static BuiltinImplResult translateBuiltinMathLdexp( - IrBuilder& build, LuauBuiltinFunction bfid, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos) -{ - if (nparams < 2 || nresults > 1) - return {BuiltinImplType::None, -1}; - - builtinCheckDouble(build, build.vmReg(arg), pcpos); - builtinCheckDouble(build, args, pcpos); - - IrOp va = builtinLoadDouble(build, build.vmReg(arg)); - IrOp vb = builtinLoadDouble(build, args); - - IrOp vbi = build.inst(IrCmd::NUM_TO_INT, vb); - - IrOp res = build.inst(IrCmd::INVOKE_LIBM, build.constUint(bfid), va, vbi); - - build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), res); - - if (ra != arg) - build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); - - return {BuiltinImplType::Full, 1}; -} - // (number, ...) -> (number, number) static BuiltinImplResult translateBuiltinNumberTo2Number( IrBuilder& build, LuauBuiltinFunction bfid, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos) @@ -152,7 +131,7 @@ static BuiltinImplResult translateBuiltinAssert(IrBuilder& build, int nparams, i return {BuiltinImplType::UsesFallback, 0}; } -static BuiltinImplResult translateBuiltinMathDeg(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos) +static BuiltinImplResult translateBuiltinMathDegRad(IrBuilder& build, IrCmd cmd, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos) { if (nparams < 1 || nresults > 1) return {BuiltinImplType::None, -1}; @@ -162,26 +141,7 @@ static BuiltinImplResult translateBuiltinMathDeg(IrBuilder& build, int nparams, const double rpd = (3.14159265358979323846 / 180.0); IrOp varg = builtinLoadDouble(build, build.vmReg(arg)); - IrOp value = build.inst(IrCmd::DIV_NUM, varg, build.constDouble(rpd)); - build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), value); - - if (ra != arg) - build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); - - return {BuiltinImplType::Full, 1}; -} - -static BuiltinImplResult translateBuiltinMathRad(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos) -{ - if (nparams < 1 || nresults > 1) - return {BuiltinImplType::None, -1}; - - builtinCheckDouble(build, build.vmReg(arg), pcpos); - - const double rpd = (3.14159265358979323846 / 180.0); - - IrOp varg = builtinLoadDouble(build, build.vmReg(arg)); - IrOp value = build.inst(IrCmd::MUL_NUM, varg, build.constDouble(rpd)); + IrOp value = build.inst(cmd, varg, build.constDouble(rpd)); build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), value); if (ra != arg) @@ -231,7 +191,7 @@ static BuiltinImplResult translateBuiltinMathLog( return {BuiltinImplType::Full, 1}; } -static BuiltinImplResult translateBuiltinMathMin(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos) +static BuiltinImplResult translateBuiltinMathMinMax(IrBuilder& build, IrCmd cmd, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos) { if (nparams < 2 || nparams > kMinMaxUnrolledParams || nresults > 1) return {BuiltinImplType::None, -1}; @@ -245,42 +205,12 @@ static BuiltinImplResult translateBuiltinMathMin(IrBuilder& build, int nparams, IrOp varg1 = builtinLoadDouble(build, build.vmReg(arg)); IrOp varg2 = builtinLoadDouble(build, args); - IrOp res = build.inst(IrCmd::MIN_NUM, varg2, varg1); // Swapped arguments are required for consistency with VM builtins + IrOp res = build.inst(cmd, varg2, varg1); // Swapped arguments are required for consistency with VM builtins for (int i = 3; i <= nparams; ++i) { IrOp arg = builtinLoadDouble(build, build.vmReg(vmRegOp(args) + (i - 2))); - res = build.inst(IrCmd::MIN_NUM, arg, res); - } - - build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), res); - - if (ra != arg) - build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); - - return {BuiltinImplType::Full, 1}; -} - -static BuiltinImplResult translateBuiltinMathMax(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos) -{ - if (nparams < 2 || nparams > kMinMaxUnrolledParams || nresults > 1) - return {BuiltinImplType::None, -1}; - - builtinCheckDouble(build, build.vmReg(arg), pcpos); - builtinCheckDouble(build, args, pcpos); - - for (int i = 3; i <= nparams; ++i) - builtinCheckDouble(build, build.vmReg(vmRegOp(args) + (i - 2)), pcpos); - - IrOp varg1 = builtinLoadDouble(build, build.vmReg(arg)); - IrOp varg2 = builtinLoadDouble(build, args); - - IrOp res = build.inst(IrCmd::MAX_NUM, varg2, varg1); // Swapped arguments are required for consistency with VM builtins - - for (int i = 3; i <= nparams; ++i) - { - IrOp arg = builtinLoadDouble(build, build.vmReg(vmRegOp(args) + (i - 2))); - res = build.inst(IrCmd::MAX_NUM, arg, res); + res = build.inst(cmd, arg, res); } build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), res); @@ -823,15 +753,15 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg, case LBF_ASSERT: return translateBuiltinAssert(build, nparams, ra, arg, args, nresults, pcpos); case LBF_MATH_DEG: - return translateBuiltinMathDeg(build, nparams, ra, arg, args, nresults, pcpos); + return translateBuiltinMathDegRad(build, IrCmd::DIV_NUM, nparams, ra, arg, args, nresults, pcpos); case LBF_MATH_RAD: - return translateBuiltinMathRad(build, nparams, ra, arg, args, nresults, pcpos); + return translateBuiltinMathDegRad(build, IrCmd::MUL_NUM, nparams, ra, arg, args, nresults, pcpos); case LBF_MATH_LOG: return translateBuiltinMathLog(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos); case LBF_MATH_MIN: - return translateBuiltinMathMin(build, nparams, ra, arg, args, nresults, pcpos); + return translateBuiltinMathMinMax(build, IrCmd::MIN_NUM, nparams, ra, arg, args, nresults, pcpos); case LBF_MATH_MAX: - return translateBuiltinMathMax(build, nparams, ra, arg, args, nresults, pcpos); + return translateBuiltinMathMinMax(build, IrCmd::MAX_NUM, nparams, ra, arg, args, nresults, pcpos); case LBF_MATH_CLAMP: return translateBuiltinMathClamp(build, nparams, ra, arg, args, nresults, fallback, pcpos); case LBF_MATH_FLOOR: @@ -861,9 +791,8 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg, case LBF_MATH_POW: case LBF_MATH_FMOD: case LBF_MATH_ATAN2: - return translateBuiltin2NumberToNumberLibm(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos); case LBF_MATH_LDEXP: - return translateBuiltinMathLdexp(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos); + return translateBuiltin2NumberToNumberLibm(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos); case LBF_MATH_FREXP: case LBF_MATH_MODF: return translateBuiltinNumberTo2Number(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos); diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 4c3fcaa7..d0c2ad54 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -12,8 +12,9 @@ #include "lstate.h" #include "ltm.h" -LUAU_FASTFLAGVARIABLE(LuauImproveForN, false) +LUAU_FASTFLAGVARIABLE(LuauImproveForN2, false) LUAU_FASTFLAG(LuauReduceStackSpills) +LUAU_FASTFLAGVARIABLE(LuauInlineArrConstOffset, false) namespace Luau { @@ -631,6 +632,26 @@ static IrOp getLoopStepK(IrBuilder& build, int ra) return build.undef(); } +void beforeInstForNPrep(IrBuilder& build, const Instruction* pc) +{ + if (FFlag::LuauImproveForN2) + { + int ra = LUAU_INSN_A(*pc); + + IrOp stepK = getLoopStepK(build, ra); + build.loopStepStack.push_back(stepK); + } +} + +void afterInstForNLoop(IrBuilder& build, const Instruction* pc) +{ + if (FFlag::LuauImproveForN2) + { + LUAU_ASSERT(!build.loopStepStack.empty()); + build.loopStepStack.pop_back(); + } +} + void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos) { int ra = LUAU_INSN_A(*pc); @@ -638,10 +659,10 @@ void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos) IrOp loopStart = build.blockAtInst(pcpos + getOpLength(LuauOpcode(LUAU_INSN_OP(*pc)))); IrOp loopExit = build.blockAtInst(getJumpTarget(*pc, pcpos)); - if (FFlag::LuauImproveForN) + if (FFlag::LuauImproveForN2) { - IrOp stepK = getLoopStepK(build, ra); - build.loopStepStack.push_back(stepK); + LUAU_ASSERT(!build.loopStepStack.empty()); + IrOp stepK = build.loopStepStack.back(); // When loop parameters are not numbers, VM tries to perform type coercion from string and raises an exception if that fails // Performing that fallback in native code increases code size and complicates CFG, obscuring the values when they are constant @@ -733,7 +754,7 @@ void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos) // VM places interrupt in FORNLOOP, but that creates a likely spill point for short loops that use loop index as INTERRUPT always spills // We place the interrupt at the beginning of the loop body instead; VM uses FORNLOOP because it doesn't want to waste an extra instruction. // Because loop block may not have been started yet (as it's started when lowering the first instruction!), we need to defer INTERRUPT placement. - if (FFlag::LuauImproveForN) + if (FFlag::LuauImproveForN2) build.interruptRequested = true; } @@ -744,11 +765,10 @@ void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos) IrOp loopRepeat = build.blockAtInst(getJumpTarget(*pc, pcpos)); IrOp loopExit = build.blockAtInst(pcpos + getOpLength(LuauOpcode(LUAU_INSN_OP(*pc)))); - if (FFlag::LuauImproveForN) + if (FFlag::LuauImproveForN2) { LUAU_ASSERT(!build.loopStepStack.empty()); IrOp stepK = build.loopStepStack.back(); - build.loopStepStack.pop_back(); IrOp zero = build.constDouble(0.0); IrOp limit = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 0)); @@ -950,10 +970,20 @@ void translateInstGetTableN(IrBuilder& build, const Instruction* pc, int pcpos) build.inst(IrCmd::CHECK_ARRAY_SIZE, vb, build.constInt(c), fallback); build.inst(IrCmd::CHECK_NO_METATABLE, vb, fallback); - IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constInt(c)); + if (FFlag::LuauInlineArrConstOffset) + { + IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constInt(0)); - IrOp arrElTval = build.inst(IrCmd::LOAD_TVALUE, arrEl); - build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), arrElTval); + IrOp arrElTval = build.inst(IrCmd::LOAD_TVALUE, arrEl, build.constInt(c * sizeof(TValue))); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), arrElTval); + } + else + { + IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constInt(c)); + + IrOp arrElTval = build.inst(IrCmd::LOAD_TVALUE, arrEl); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), arrElTval); + } IrOp next = build.blockAtInst(pcpos + 1); FallbackStreamScope scope(build, fallback, next); @@ -980,10 +1010,20 @@ void translateInstSetTableN(IrBuilder& build, const Instruction* pc, int pcpos) build.inst(IrCmd::CHECK_NO_METATABLE, vb, fallback); build.inst(IrCmd::CHECK_READONLY, vb, fallback); - IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constInt(c)); + if (FFlag::LuauInlineArrConstOffset) + { + IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constInt(0)); - IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra)); - build.inst(IrCmd::STORE_TVALUE, arrEl, tva); + IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra)); + build.inst(IrCmd::STORE_TVALUE, arrEl, tva, build.constInt(c * sizeof(TValue))); + } + else + { + IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constInt(c)); + + IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra)); + build.inst(IrCmd::STORE_TVALUE, arrEl, tva); + } build.inst(IrCmd::BARRIER_TABLE_FORWARD, vb, build.vmReg(ra), build.undef()); diff --git a/CodeGen/src/IrTranslation.h b/CodeGen/src/IrTranslation.h index 3736b44d..29c14356 100644 --- a/CodeGen/src/IrTranslation.h +++ b/CodeGen/src/IrTranslation.h @@ -65,5 +65,8 @@ void translateInstAndX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp void translateInstOrX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp c); void translateInstNewClosure(IrBuilder& build, const Instruction* pc, int pcpos); +void beforeInstForNPrep(IrBuilder& build, const Instruction* pc); +void afterInstForNLoop(IrBuilder& build, const Instruction* pc); + } // namespace CodeGen } // namespace Luau diff --git a/Common/include/Luau/Bytecode.h b/Common/include/Luau/Bytecode.h index 36b570ce..a4f1d67e 100644 --- a/Common/include/Luau/Bytecode.h +++ b/Common/include/Luau/Bytecode.h @@ -427,7 +427,7 @@ enum LuauBytecodeTag // Bytecode version; runtime supports [MIN, MAX], compiler emits TARGET by default but may emit a higher version when flags are enabled LBC_VERSION_MIN = 3, LBC_VERSION_MAX = 4, - LBC_VERSION_TARGET = 3, + LBC_VERSION_TARGET = 4, // Type encoding version LBC_TYPE_VERSION = 1, // Types of constant table entries @@ -575,4 +575,6 @@ enum LuauProtoFlag { // used to tag main proto for modules with --!native LPF_NATIVE_MODULE = 1 << 0, + // used to tag individual protos as not profitable to compile natively + LPF_NATIVE_COLD = 1 << 1, }; diff --git a/Compiler/src/BytecodeBuilder.cpp b/Compiler/src/BytecodeBuilder.cpp index 4ebed69a..6b88162d 100644 --- a/Compiler/src/BytecodeBuilder.cpp +++ b/Compiler/src/BytecodeBuilder.cpp @@ -7,8 +7,6 @@ #include #include -LUAU_FASTFLAGVARIABLE(BytecodeVersion4, false) - LUAU_FASTFLAG(LuauFloorDivision) namespace Luau @@ -586,12 +584,9 @@ void BytecodeBuilder::finalize() bytecode = char(version); - if (FFlag::BytecodeVersion4) - { - uint8_t typesversion = getTypeEncodingVersion(); - LUAU_ASSERT(typesversion == 1); - writeByte(bytecode, typesversion); - } + uint8_t typesversion = getTypeEncodingVersion(); + LUAU_ASSERT(typesversion == 1); + writeByte(bytecode, typesversion); writeStringTable(bytecode); @@ -615,13 +610,10 @@ void BytecodeBuilder::writeFunction(std::string& ss, uint32_t id, uint8_t flags) writeByte(ss, func.numupvalues); writeByte(ss, func.isvararg); - if (FFlag::BytecodeVersion4) - { - writeByte(ss, flags); + writeByte(ss, flags); - writeVarInt(ss, uint32_t(func.typeinfo.size())); - ss.append(func.typeinfo); - } + writeVarInt(ss, uint32_t(func.typeinfo.size())); + ss.append(func.typeinfo); // instructions writeVarInt(ss, uint32_t(insns.size())); @@ -1074,10 +1066,6 @@ std::string BytecodeBuilder::getError(const std::string& message) uint8_t BytecodeBuilder::getVersion() { // This function usually returns LBC_VERSION_TARGET but may sometimes return a higher number (within LBC_VERSION_MIN/MAX) under fast flags - - if (FFlag::BytecodeVersion4) - return 4; - return LBC_VERSION_TARGET; } diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 4fdf659b..ef5c2369 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -26,7 +26,13 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) +LUAU_FASTFLAGVARIABLE(LuauCompileFenvNoBuiltinFold, false) +LUAU_FASTFLAGVARIABLE(LuauCompileTopCold, false) + LUAU_FASTFLAG(LuauFloorDivision) +LUAU_FASTFLAGVARIABLE(LuauCompileFixContinueValidation, false) + +LUAU_FASTFLAGVARIABLE(LuauCompileContinueCloseUpvals, false) namespace Luau { @@ -259,6 +265,10 @@ struct Compiler if (bytecode.getInstructionCount() > kMaxInstructionCount) CompileError::raise(func->location, "Exceeded function instruction limit; split the function into parts to compile"); + // since top-level code only executes once, it can be marked as cold if it has no loops (top-level code with loops might be profitable to compile natively) + if (FFlag::LuauCompileTopCold && func->functionDepth == 0 && !hasLoops) + protoflags |= LPF_NATIVE_COLD; + bytecode.endFunction(uint8_t(stackSize), uint8_t(upvals.size()), protoflags); Function& f = functions[func]; @@ -283,6 +293,7 @@ struct Compiler upvals.clear(); // note: instead of std::move above, we copy & clear to preserve capacity for future pushes stackSize = 0; + hasLoops = false; return fid; } @@ -646,11 +657,25 @@ struct Compiler // apply all evaluated arguments to the compiler state // note: locals use current startpc for debug info, although some of them have been computed earlier; this is similar to compileStatLocal for (InlineArg& arg : args) + { if (arg.value.type == Constant::Type_Unknown) + { pushLocal(arg.local, arg.reg); + } else + { locstants[arg.local] = arg.value; + if (FFlag::LuauCompileFixContinueValidation) + { + // Mark that optimization skipped allocation of this local + Local& l = locals[arg.local]; + LUAU_ASSERT(!l.skipped); + l.skipped = true; + } + } + } + // the inline frame will be used to compile return statements as well as to reject recursive inlining attempts inlineFrames.push_back({func, oldLocals, target, targetCount}); @@ -693,8 +718,27 @@ struct Compiler // clean up constant state for future inlining attempts for (size_t i = 0; i < func->args.size; ++i) - if (Constant* var = locstants.find(func->args.data[i])) - var->type = Constant::Type_Unknown; + { + AstLocal* local = func->args.data[i]; + + if (FFlag::LuauCompileFixContinueValidation) + { + if (Constant* var = locstants.find(local); var && var->type != Constant::Type_Unknown) + { + var->type = Constant::Type_Unknown; + + // Restore local allocation skip flag as well + Local& l = locals[local]; + LUAU_ASSERT(l.skipped); + l.skipped = false; + } + } + else + { + if (Constant* var = locstants.find(local)) + var->type = Constant::Type_Unknown; + } + } foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldMathK, func->body); } @@ -2469,7 +2513,7 @@ struct Compiler AstStat* continueStatement = extractStatContinue(stat->thenbody); // Optimization: body is a "continue" statement with no "else" => we can directly continue in "then" case - if (!stat->elsebody && continueStatement != nullptr && !areLocalsCaptured(loops.back().localOffset)) + if (!stat->elsebody && continueStatement != nullptr && !areLocalsCaptured(loops.back().localOffsetContinue)) { if (loops.back().untilCondition) validateContinueUntil(continueStatement, loops.back().untilCondition); @@ -2533,7 +2577,8 @@ struct Compiler size_t oldJumps = loopJumps.size(); size_t oldLocals = localStack.size(); - loops.push_back({oldLocals, nullptr}); + loops.push_back({oldLocals, oldLocals, nullptr}); + hasLoops = true; size_t loopLabel = bytecode.emitLabel(); @@ -2568,7 +2613,8 @@ struct Compiler size_t oldJumps = loopJumps.size(); size_t oldLocals = localStack.size(); - loops.push_back({oldLocals, stat->condition}); + loops.push_back({oldLocals, oldLocals, stat->condition}); + hasLoops = true; size_t loopLabel = bytecode.emitLabel(); @@ -2579,8 +2625,17 @@ struct Compiler RegScope rs(this); for (size_t i = 0; i < body->body.size; ++i) + { compileStat(body->body.data[i]); + // continue statement inside the repeat..until loop should not close upvalues defined directly in the loop body + // (but it must still close upvalues defined in more nested blocks) + // this is because the upvalues defined inside the loop body may be captured by a closure defined in the until + // expression that continue will jump to. + if (FFlag::LuauCompileContinueCloseUpvals) + loops.back().localOffsetContinue = localStack.size(); + } + size_t contLabel = bytecode.emitLabel(); size_t endLabel; @@ -2707,7 +2762,19 @@ struct Compiler { // Optimization: we don't need to allocate and assign const locals, since their uses will be constant-folded if (options.optimizationLevel >= 1 && options.debugLevel <= 1 && areLocalsRedundant(stat)) + { + if (FFlag::LuauCompileFixContinueValidation) + { + // Mark that optimization skipped allocation of this local + for (AstLocal* local : stat->vars) + { + Local& l = locals[local]; + l.skipped = true; + } + } + return; + } // Optimization: for 1-1 local assignments, we can reuse the register *if* neither local is mutated if (options.optimizationLevel >= 1 && stat->vars.size == 1 && stat->values.size == 1) @@ -2796,7 +2863,7 @@ struct Compiler size_t oldLocals = localStack.size(); size_t oldJumps = loopJumps.size(); - loops.push_back({oldLocals, nullptr}); + loops.push_back({oldLocals, oldLocals, nullptr}); for (int iv = 0; iv < tripCount; ++iv) { @@ -2847,7 +2914,8 @@ struct Compiler size_t oldLocals = localStack.size(); size_t oldJumps = loopJumps.size(); - loops.push_back({oldLocals, nullptr}); + loops.push_back({oldLocals, oldLocals, nullptr}); + hasLoops = true; // register layout: limit, step, index uint8_t regs = allocReg(stat, 3); @@ -2911,7 +2979,8 @@ struct Compiler size_t oldLocals = localStack.size(); size_t oldJumps = loopJumps.size(); - loops.push_back({oldLocals, nullptr}); + loops.push_back({oldLocals, oldLocals, nullptr}); + hasLoops = true; // register layout: generator, state, index, variables... uint8_t regs = allocReg(stat, 3); @@ -3327,7 +3396,7 @@ struct Compiler // before continuing, we need to close all local variables that were captured in closures since loop start // normally they are closed by the enclosing blocks, including the loop block, but we're skipping that here - closeLocals(loops.back().localOffset); + closeLocals(loops.back().localOffsetContinue); size_t label = bytecode.emitLabel(); @@ -3640,6 +3709,9 @@ struct Compiler { Local& l = self->locals[local]; + if (FFlag::LuauCompileFixContinueValidation && l.skipped) + return; + if (!l.allocated && !undef) undef = local; } @@ -3765,6 +3837,7 @@ struct Compiler uint8_t reg = 0; bool allocated = false; bool captured = false; + bool skipped = false; uint32_t debugpc = 0; }; @@ -3783,6 +3856,7 @@ struct Compiler struct Loop { size_t localOffset; + size_t localOffsetContinue; AstExpr* untilCondition; }; @@ -3830,8 +3904,10 @@ struct Compiler const DenseHashMap* builtinsFold = nullptr; bool builtinsFoldMathK = false; + // compileFunction state, gets reset for every function unsigned int regTop = 0; unsigned int stackSize = 0; + bool hasLoops = false; bool getfenvUsed = false; bool setfenvUsed = false; @@ -3877,8 +3953,15 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c // this pass analyzes mutability of locals/globals and associates locals with their initial values trackValues(compiler.globals, compiler.variables, root); + // this visitor tracks calls to getfenv/setfenv and disables some optimizations when they are found + if (options.optimizationLevel >= 1 && (names.get("getfenv").value || names.get("setfenv").value)) + { + Compiler::FenvVisitor fenvVisitor(compiler.getfenvUsed, compiler.setfenvUsed); + root->visit(&fenvVisitor); + } + // builtin folding is enabled on optimization level 2 since we can't deoptimize folding at runtime - if (options.optimizationLevel >= 2) + if (options.optimizationLevel >= 2 && (!FFlag::LuauCompileFenvNoBuiltinFold || (!compiler.getfenvUsed && !compiler.setfenvUsed))) { compiler.builtinsFold = &compiler.builtins; @@ -3898,13 +3981,6 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c predictTableShapes(compiler.tableShapes, root); } - // this visitor tracks calls to getfenv/setfenv and disables some optimizations when they are found - if (options.optimizationLevel >= 1 && (names.get("getfenv").value || names.get("setfenv").value)) - { - Compiler::FenvVisitor fenvVisitor(compiler.getfenvUsed, compiler.setfenvUsed); - root->visit(&fenvVisitor); - } - // gathers all functions with the invariant that all function references are to functions earlier in the list // for example, function foo() return function() end end will result in two vector entries, [0] = anonymous and [1] = foo std::vector functions; diff --git a/Sources.cmake b/Sources.cmake index 3567b8be..ebd73c4f 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -236,7 +236,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/Linter.cpp Analysis/src/LValue.cpp Analysis/src/Module.cpp - Analysis/src/NonStrictTypeChecker.cpp + Analysis/src/NonStrictTypeChecker.cpp Analysis/src/Normalize.cpp Analysis/src/Quantify.cpp Analysis/src/Refinement.cpp @@ -400,7 +400,7 @@ if(TARGET Luau.UnitTest) tests/LValue.test.cpp tests/Module.test.cpp tests/NonstrictMode.test.cpp - tests/NonStrictTypeChecker.test.cpp + tests/NonStrictTypeChecker.test.cpp tests/Normalize.test.cpp tests/NotNull.test.cpp tests/Parser.test.cpp diff --git a/VM/src/loslib.cpp b/VM/src/loslib.cpp index b5063504..a4da00cc 100644 --- a/VM/src/loslib.cpp +++ b/VM/src/loslib.cpp @@ -2,11 +2,15 @@ // This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details #include "lualib.h" +#include "lcommon.h" + #include #include #define LUA_STRFTIMEOPTIONS "aAbBcdHIjmMpSUwWxXyYzZ%" +LUAU_FASTFLAGVARIABLE(LuauOsTimegm, false) + #if defined(_WIN32) static tm* gmtime_r(const time_t* timep, tm* result) { @@ -20,10 +24,49 @@ static tm* localtime_r(const time_t* timep, tm* result) static time_t timegm(struct tm* timep) { + LUAU_ASSERT(!FFlag::LuauOsTimegm); + return _mkgmtime(timep); } #endif +static time_t os_timegm(struct tm* timep) +{ + LUAU_ASSERT(FFlag::LuauOsTimegm); + + // Julian day number calculation + int day = timep->tm_mday; + int month = timep->tm_mon + 1; + int year = timep->tm_year + 1900; + + // year adjustment, pretend that it starts in March + int a = timep->tm_mon % 12 < 2 ? 1 : 0; + + // also adjust for out-of-range month numbers in input + a -= timep->tm_mon / 12; + + int y = year + 4800 - a; + int m = month + (12 * a) - 3; + + int julianday = day + ((153 * m + 2) / 5) + (365 * y) + (y / 4) - (y / 100) + (y / 400) - 32045; + + const int utcstartasjulianday = 2440588; // Jan 1st 1970 offset in Julian calendar + const int64_t utcstartasjuliansecond = utcstartasjulianday * 86400ll; // same in seconds + + // fail the dates before UTC start + if (julianday < utcstartasjulianday) + return time_t(-1); + + int64_t daysecond = timep->tm_hour * 3600ll + timep->tm_min * 60ll + timep->tm_sec; + int64_t julianseconds = int64_t(julianday) * 86400ull + daysecond; + + if (julianseconds < utcstartasjuliansecond) + return time_t(-1); + + int64_t utc = julianseconds - utcstartasjuliansecond; + return time_t(utc); +} + static int os_clock(lua_State* L) { lua_pushnumber(L, lua_clock()); @@ -163,7 +206,10 @@ static int os_time(lua_State* L) ts.tm_isdst = getboolfield(L, "isdst"); // Note: upstream Lua uses mktime() here which assumes input is local time, but we prefer UTC for consistency - t = timegm(&ts); + if (FFlag::LuauOsTimegm) + t = os_timegm(&ts); + else + t = timegm(&ts); } if (t == (time_t)(-1)) lua_pushnil(L); diff --git a/bench/other/regex.lua b/bench/other/regex.lua index eb659a5e..4f32b408 100644 --- a/bench/other/regex.lua +++ b/bench/other/regex.lua @@ -2061,9 +2061,8 @@ function re.type(...) return proxy[...] and proxy[...].name; end; -for k, f in pairs(re_m) do - re[k] = f; -end; +-- TODO: table.foreach is currently used as top-level loops needlessly increase native code size for this module +table.foreach(re_m, function(k, f) re[k] = f end) re_m = { __index = re_m }; diff --git a/tests/AssemblyBuilderX64.test.cpp b/tests/AssemblyBuilderX64.test.cpp index 177e2493..06d38124 100644 --- a/tests/AssemblyBuilderX64.test.cpp +++ b/tests/AssemblyBuilderX64.test.cpp @@ -264,6 +264,14 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfSetcc") SINGLE_COMPARE(setcc(ConditionX64::BelowEqual, byte[rcx]), 0x0f, 0x96, 0x01); } +TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfCmov") +{ + SINGLE_COMPARE(cmov(ConditionX64::LessEqual, ebx, eax), 0x0f, 0x4e, 0xd8); + SINGLE_COMPARE(cmov(ConditionX64::NotZero, rbx, qword[rax]), 0x48, 0x0f, 0x45, 0x18); + SINGLE_COMPARE(cmov(ConditionX64::Zero, rbx, qword[rax + rcx]), 0x48, 0x0f, 0x44, 0x1c, 0x08); + SINGLE_COMPARE(cmov(ConditionX64::BelowEqual, r14d, r15d), 0x45, 0x0f, 0x46, 0xf7); +} + TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfAbsoluteJumps") { SINGLE_COMPARE(jmp(rax), 0xff, 0xe0); @@ -590,6 +598,7 @@ TEST_CASE("LogTest") build.vroundsd(xmm1, xmm2, xmm3, RoundingModeX64::RoundToNearestEven); build.add(rdx, qword[rcx - 12]); build.pop(r12); + build.cmov(ConditionX64::AboveEqual, rax, rbx); build.ret(); build.int3(); @@ -634,6 +643,7 @@ TEST_CASE("LogTest") vroundsd xmm1,xmm2,xmm3,8 add rdx,qword ptr [rcx-0Ch] pop r12 + cmovae rax,rbx ret int3 nop diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 49498744..68f2a9a0 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -1772,6 +1772,8 @@ RETURN R0 0 TEST_CASE("LoopContinueUntil") { + ScopedFastFlag sff("LuauCompileContinueCloseUpvals", true); + // it's valid to use locals defined inside the loop in until expression if they're defined before continue CHECK_EQ("\n" + compileFunction0("repeat local r = math.random() if r > 0.5 then continue end r = r + 0.3 until r < 0.5"), R"( L0: GETIMPORT R0 2 [math.random] @@ -1833,17 +1835,15 @@ L2: RETURN R0 0 L0: GETIMPORT R0 2 [math.random] CALL R0 0 1 LOADK R1 K3 [0.5] -JUMPIFNOTLT R1 R0 L1 -CLOSEUPVALS R0 -JUMP L2 -L1: ADDK R0 R0 K4 [0.29999999999999999] -L2: NEWCLOSURE R1 P0 +JUMPIFLT R1 R0 L1 +ADDK R0 R0 K4 [0.29999999999999999] +L1: NEWCLOSURE R1 P0 CAPTURE REF R0 CALL R1 0 1 -JUMPIF R1 L3 +JUMPIF R1 L2 CLOSEUPVALS R0 JUMPBACK L0 -L3: CLOSEUPVALS R0 +L2: CLOSEUPVALS R0 RETURN R0 0 )"); @@ -1895,42 +1895,188 @@ L2: RETURN R0 0 L0: GETIMPORT R0 2 [math.random] CALL R0 0 1 LOADK R1 K3 [0.5] -JUMPIFNOTLT R1 R0 L1 -CLOSEUPVALS R0 -JUMP L2 -L1: ADDK R0 R0 K4 [0.29999999999999999] -L2: NEWCLOSURE R1 P0 +JUMPIFLT R1 R0 L1 +ADDK R0 R0 K4 [0.29999999999999999] +L1: NEWCLOSURE R1 P0 CAPTURE UPVAL U0 CAPTURE REF R0 CALL R1 0 1 -JUMPIF R1 L3 +JUMPIF R1 L2 CLOSEUPVALS R0 JUMPBACK L0 -L3: CLOSEUPVALS R0 +L2: CLOSEUPVALS R0 RETURN R0 0 )"); } -TEST_CASE("LoopContinueUntilOops") +TEST_CASE("LoopContinueIgnoresImplicitConstant") { + ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation", true}; + // this used to crash the compiler :( - try - { - Luau::BytecodeBuilder bcb; - Luau::compileOrThrow(bcb, R"( + CHECK_EQ("\n" + compileFunction0(R"( local _ repeat continue until not _ +)"), + R"( +RETURN R0 0 +RETURN R0 0 )"); +} + +TEST_CASE("LoopContinueIgnoresExplicitConstant") +{ + ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation", true}; + + // Constants do not allocate locals and 'continue' validation should skip them if their lifetime already started + CHECK_EQ("\n" + compileFunction0(R"( +local c = true +repeat + continue +until c +)"), + R"( +RETURN R0 0 +RETURN R0 0 +)"); +} + +TEST_CASE("LoopContinueRespectsExplicitConstant") +{ + ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation", true}; + + // If local lifetime hasn't started, even if it's a constant that will not receive an allocation, it cannot be jumped over + try + { + Luau::BytecodeBuilder bcb; + Luau::compileOrThrow(bcb, R"( +repeat + do continue end + + local c = true +until c +)"); + + CHECK(!"Expected CompileError"); } catch (Luau::CompileError& e) { + CHECK_EQ(e.getLocation().begin.line + 1, 6); CHECK_EQ( - std::string(e.what()), "Local _ used in the repeat..until condition is undefined because continue statement on line 4 jumps over it"); + std::string(e.what()), "Local c used in the repeat..until condition is undefined because continue statement on line 3 jumps over it"); } } +TEST_CASE("LoopContinueIgnoresImplicitConstantAfterInline") +{ + ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation", true}; + + // Inlining might also replace some locals with constants instead of allocating them + CHECK_EQ("\n" + compileFunction(R"( +local function inline(f) + repeat + continue + until f +end + +local function test(...) + inline(true) +end + +test() +)", + 1, 2), + R"( +RETURN R0 0 +RETURN R0 0 +)"); +} + +TEST_CASE("LoopContinueUntilCapture") +{ + ScopedFastFlag sff("LuauCompileContinueCloseUpvals", true); + + // validate continue upvalue closing behavior: continue must close locals defined in the nested scopes + // but can't close locals defined in the loop scope - these are visible to the condition and will be closed + // when evaluating the condition instead. + CHECK_EQ("\n" + compileFunction(R"( +local a a = 0 +repeat + local b b = 0 + if a then + local c + print(function() c = 0 end) + if a then + continue -- must close c but not a/b + end + -- must close c + end + -- must close b but not a +until function() a = 0 b = 0 end +-- must close b on loop exit +-- must close a +)", + 2), + R"( +LOADNIL R0 +LOADN R0 0 +L0: LOADNIL R1 +LOADN R1 0 +JUMPIFNOT R0 L2 +LOADNIL R2 +GETIMPORT R3 1 [print] +NEWCLOSURE R4 P0 +CAPTURE REF R2 +CALL R3 1 0 +JUMPIFNOT R0 L1 +CLOSEUPVALS R2 +JUMP L2 +L1: CLOSEUPVALS R2 +L2: NEWCLOSURE R2 P1 +CAPTURE REF R0 +CAPTURE REF R1 +JUMPIF R2 L3 +CLOSEUPVALS R1 +JUMPBACK L0 +L3: CLOSEUPVALS R1 +CLOSEUPVALS R0 +RETURN R0 0 +)"); + + // a simpler version of the above test doesn't need to close anything when evaluating continue + CHECK_EQ("\n" + compileFunction(R"( +local a a = 0 +repeat + local b b = 0 + if a then + continue -- must not close a/b + end + -- must close b but not a +until function() a = 0 b = 0 end +-- must close b on loop exit +-- must close a +)", + 1), + R"( +LOADNIL R0 +LOADN R0 0 +L0: LOADNIL R1 +LOADN R1 0 +JUMPIF R0 L1 +L1: NEWCLOSURE R2 P0 +CAPTURE REF R0 +CAPTURE REF R1 +JUMPIF R2 L2 +CLOSEUPVALS R1 +JUMPBACK L0 +L2: CLOSEUPVALS R1 +CLOSEUPVALS R0 +RETURN R0 0 +)"); +} + TEST_CASE("AndOrOptimizations") { // the OR/ORK optimization triggers for cutoff since lhs is simple @@ -6368,7 +6514,7 @@ return math.log10(100), math.log(1), math.log(4, 2), - math.log(27, 3), + math.log(64, 4), math.max(1, 2, 3), math.min(1, 2, 3), math.pow(3, 3), @@ -7408,4 +7554,27 @@ RETURN R0 1 )"); } +TEST_CASE("NoBuiltinFoldFenv") +{ + ScopedFastFlag sff("LuauCompileFenvNoBuiltinFold", true); + + // builtin folding is disabled when getfenv/setfenv is used in the module + CHECK_EQ("\n" + compileFunction(R"( +getfenv() + +function test() + return math.pi, math.sin(0) +end +)", + 0, 2), + R"( +GETIMPORT R0 2 [math.pi] +LOADN R2 0 +FASTCALL1 24 R2 L0 +GETIMPORT R1 4 [math.sin] +CALL R1 1 1 +L0: RETURN R0 2 +)"); +} + TEST_SUITE_END(); diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index f731eaea..1e520100 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -10,6 +10,7 @@ #include "Luau/TypeInfer.h" #include "Luau/BytecodeBuilder.h" #include "Luau/Frontend.h" +#include "Luau/CodeGen.h" #include "doctest.h" #include "ScopedFlags.h" @@ -224,7 +225,7 @@ static StateRef runConformance(const char* name, void (*setup)(lua_State* L) = n free(bytecode); if (result == 0 && codegen && !skipCodegen && luau_codegen_supported()) - luau_codegen_compile(L, -1); + Luau::CodeGen::compile(L, -1, Luau::CodeGen::CodeGen_ColdFunctions); int status = (result == 0) ? lua_resume(L, nullptr, 0) : LUA_ERRSYNTAX; @@ -288,7 +289,7 @@ TEST_CASE("Assert") TEST_CASE("Basic") { ScopedFastFlag sffs{"LuauFloorDivision", true}; - ScopedFastFlag sfff{"LuauImproveForN", true}; + ScopedFastFlag sfff{"LuauImproveForN2", true}; runConformance("basic.lua"); } @@ -379,6 +380,8 @@ TEST_CASE("Events") TEST_CASE("Constructs") { + ScopedFastFlag sff("LuauCompileContinueCloseUpvals", true); + runConformance("constructs.lua"); } @@ -1809,8 +1812,6 @@ TEST_CASE("Native") TEST_CASE("NativeTypeAnnotations") { - ScopedFastFlag bytecodeVersion4("BytecodeVersion4", true); - // This tests requires code to run natively, otherwise all 'is_native' checks will fail if (!codegen || !luau_codegen_supported()) return; @@ -1891,7 +1892,7 @@ TEST_CASE("HugeFunction") REQUIRE(result == 0); if (codegen && luau_codegen_supported()) - luau_codegen_compile(L, -1); + Luau::CodeGen::compile(L, -1, Luau::CodeGen::CodeGen_ColdFunctions); int status = lua_resume(L, nullptr, 0); REQUIRE(status == 0); diff --git a/tests/NonStrictTypeChecker.test.cpp b/tests/NonStrictTypeChecker.test.cpp index 9021a9af..00fd8ed1 100644 --- a/tests/NonStrictTypeChecker.test.cpp +++ b/tests/NonStrictTypeChecker.test.cpp @@ -3,10 +3,28 @@ #include "Fixture.h" +#include "Luau/Common.h" +#include "Luau/Ast.h" +#include "Luau/ModuleResolver.h" +#include "ScopedFlags.h" #include "doctest.h" +#include using namespace Luau; +struct NonStrictTypeCheckerFixture : Fixture +{ + + ParseResult parse(std::string source) + { + ParseOptions opts; + opts.allowDeclarationSyntax = true; + ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true}; + return tryParse(source, opts); + } +}; + + TEST_SUITE_BEGIN("NonStrictTypeCheckerTest"); TEST_CASE_FIXTURE(Fixture, "basic") @@ -14,4 +32,85 @@ TEST_CASE_FIXTURE(Fixture, "basic") Luau::checkNonStrict(builtinTypes, nullptr); } +TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "parse_top_level_checked_fn") +{ + std::string src = R"BUILTIN_SRC( +declare function @checked abs(n: number): number +)BUILTIN_SRC"; + + ParseResult pr = parse(src); + LUAU_ASSERT(pr.errors.size() == 0); + + LUAU_ASSERT(pr.root->body.size == 1); + AstStat* root = *(pr.root->body.data); + auto func = root->as(); + LUAU_ASSERT(func); + LUAU_ASSERT(func->checkedFunction); +} + +TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "parse_declared_table_checked_member") +{ + std::string src = R"BUILTIN_SRC( + declare math : { + abs : @checked (number) -> number +} +)BUILTIN_SRC"; + + ParseResult pr = parse(src); + LUAU_ASSERT(pr.errors.size() == 0); + + LUAU_ASSERT(pr.root->body.size == 1); + AstStat* root = *(pr.root->body.data); + auto glob = root->as(); + LUAU_ASSERT(glob); + auto tbl = glob->type->as(); + LUAU_ASSERT(tbl); + LUAU_ASSERT(tbl->props.size == 1); + auto prop = *tbl->props.data; + auto func = prop.type->as(); + LUAU_ASSERT(func); + LUAU_ASSERT(func->checkedFunction); +} + +TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "parse_checked_outside_decl_fails") +{ + auto src = R"( + local @checked = 3 +)"; + + ParseResult pr = parse(src); + LUAU_ASSERT(pr.errors.size() > 0); + auto ts = pr.errors[1].getMessage(); +} + +TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "parse_checked_in_and_out_of_decl_fails") +{ + auto src = R"( + local @checked = 3 + declare function @checked abs(n: number): number +)"; + auto pr = parse(src); + LUAU_ASSERT(pr.errors.size() == 2); + LUAU_ASSERT(pr.errors[0].getLocation().begin.line == 1); + LUAU_ASSERT(pr.errors[1].getLocation().begin.line == 1); +} + +TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "parse_checked_as_function_name_fails") +{ + auto pr = parse(R"( + function @checked(x: number) : number + end +)"); + LUAU_ASSERT(pr.errors.size() > 0); +} + +TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "cannot_use_@_as_variable_name") +{ + auto pr = parse(R"( + local @blah = 3 +)"); + + LUAU_ASSERT(pr.errors.size() > 0); +} + TEST_SUITE_END(); diff --git a/tests/RuntimeLimits.test.cpp b/tests/RuntimeLimits.test.cpp index 3fff6920..3ea60ee7 100644 --- a/tests/RuntimeLimits.test.cpp +++ b/tests/RuntimeLimits.test.cpp @@ -18,7 +18,7 @@ using namespace Luau; struct LimitFixture : BuiltinsFixture { #if defined(_NOOPT) || defined(_DEBUG) - ScopedFastInt LuauTypeInferRecursionLimit{"LuauTypeInferRecursionLimit", 100}; + ScopedFastInt LuauTypeInferRecursionLimit{"LuauTypeInferRecursionLimit", 90}; #endif }; diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index b0347cc0..a19b5f19 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -598,6 +598,20 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> () (T) -> ()") CHECK_IS_NOT_SUBTYPE(numberToNothingType, genericTToNothingType); } +TEST_CASE_FIXTURE(SubtypeFixture, "() -> (T, T) (string, number)") +{ + TypeId nothingToTwoTs = arena.addType(FunctionType{ + {genericT}, + {}, + builtinTypes->emptyTypePack, + arena.addTypePack({genericT, genericT}) + }); + + TypeId nothingToStringAndNumber = fn({}, {builtinTypes->stringType, builtinTypes->numberType}); + + CHECK_IS_NOT_SUBTYPE(nothingToTwoTs, nothingToStringAndNumber); +} + TEST_CASE_FIXTURE(SubtypeFixture, "(A...) -> A... <: (number) -> number") { CHECK_IS_SUBTYPE(genericAsToAsType, numberToNumberType); diff --git a/tests/TypeFamily.test.cpp b/tests/TypeFamily.test.cpp index efc6433a..7de508a0 100644 --- a/tests/TypeFamily.test.cpp +++ b/tests/TypeFamily.test.cpp @@ -2,6 +2,7 @@ #include "Luau/TypeFamily.h" #include "Luau/ConstraintSolver.h" +#include "Luau/NotNull.h" #include "Luau/TxnLog.h" #include "Luau/Type.h" @@ -22,21 +23,20 @@ struct FamilyFixture : Fixture { swapFamily = TypeFamily{/* name */ "Swap", /* reducer */ - [](std::vector tys, std::vector tps, NotNull arena, NotNull builtins, - NotNull log, NotNull scope, NotNull normalizer, ConstraintSolver* solver) -> TypeFamilyReductionResult { + [](std::vector tys, std::vector tps, NotNull ctx) -> TypeFamilyReductionResult { LUAU_ASSERT(tys.size() == 1); - TypeId param = log->follow(tys.at(0)); + TypeId param = follow(tys.at(0)); if (isString(param)) { - return TypeFamilyReductionResult{builtins->numberType, false, {}, {}}; + return TypeFamilyReductionResult{ctx->builtins->numberType, false, {}, {}}; } else if (isNumber(param)) { - return TypeFamilyReductionResult{builtins->stringType, false, {}, {}}; + return TypeFamilyReductionResult{ctx->builtins->stringType, false, {}, {}}; } - else if (log->get(param) || log->get(param) || - log->get(param) || (solver && solver->hasUnresolvedConstraints(param))) + else if (is(param) || is(param) || is(param) || + (ctx->solver && ctx->solver->hasUnresolvedConstraints(param))) { return TypeFamilyReductionResult{std::nullopt, false, {param}, {}}; } diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index ba82d8fd..8f09acaf 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -198,10 +198,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type 'bad' could not be converted into 'T' -caused by: - Property 'v' is not compatible. -Type 'string' could not be converted into 'number' in an invariant context)"; + const std::string expected = "Type 'bad' could not be converted into 'T'"; CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}}); CHECK_EQ(expected, toString(result.errors[0])); } @@ -220,13 +217,7 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Type 'bad' could not be converted into 'U' -caused by: - Property 't' is not compatible. -Type '{| v: string |}' could not be converted into 'T' -caused by: - Property 'v' is not compatible. -Type 'string' could not be converted into 'number' in an invariant context)"; + const std::string expected = "Type 'bad' could not be converted into 'U'"; CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}}); CHECK_EQ(expected, toString(result.errors[0])); diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index 87a3e833..158e3682 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -997,7 +997,7 @@ TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_degenerate_intersections") local x: number = obj.x or 3 )"); - LUAU_REQUIRE_NO_ERRORS(result); + LUAU_REQUIRE_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_more_realistic_intersections") @@ -1023,7 +1023,7 @@ TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_more_realistic_intersections") local x: number = obj.x or 3 )"); - LUAU_REQUIRE_NO_ERRORS(result); + LUAU_REQUIRE_ERRORS(result); } TEST_SUITE_END(); diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index 129e25e1..d9cd1631 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -936,6 +936,128 @@ TEST_CASE_FIXTURE(Fixture, "infer_any_in_all_modes_when_lhs_is_unknown") // the case right now, though. } +TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_subtraction") +{ + CheckResult result = check(Mode::Strict, R"( + local function f(x, y) + return x - y + end + )"); + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_NO_ERRORS(result); + CHECK(toString(requireType("f")) == "(a, b) -> Sub"); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ(toString(result.errors[0]), "Unknown type used in - operation; consider adding a type annotation to 'x'"); + } +} + +TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_multiplication") +{ + CheckResult result = check(Mode::Strict, R"( + local function f(x, y) + return x * y + end + )"); + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_NO_ERRORS(result); + CHECK(toString(requireType("f")) == "(a, b) -> Mul"); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ(toString(result.errors[0]), "Unknown type used in * operation; consider adding a type annotation to 'x'"); + } +} + +TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_division") +{ + CheckResult result = check(Mode::Strict, R"( + local function f(x, y) + return x / y + end + )"); + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_NO_ERRORS(result); + CHECK(toString(requireType("f")) == "(a, b) -> Div"); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ(toString(result.errors[0]), "Unknown type used in / operation; consider adding a type annotation to 'x'"); + } +} + +TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_floor_division") +{ + ScopedFastFlag floorDiv{"LuauFloorDivision", true}; + + CheckResult result = check(Mode::Strict, R"( + local function f(x, y) + return x // y + end + )"); + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_NO_ERRORS(result); + CHECK(toString(requireType("f")) == "(a, b) -> FloorDiv"); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ(toString(result.errors[0]), "Unknown type used in // operation; consider adding a type annotation to 'x'"); + } +} + +TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_exponentiation") +{ + CheckResult result = check(Mode::Strict, R"( + local function f(x, y) + return x ^ y + end + )"); + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_NO_ERRORS(result); + CHECK(toString(requireType("f")) == "(a, b) -> Exp"); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ(toString(result.errors[0]), "Unknown type used in ^ operation; consider adding a type annotation to 'x'"); + } +} + +TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_modulo") +{ + CheckResult result = check(Mode::Strict, R"( + local function f(x, y) + return x % y + end + )"); + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_NO_ERRORS(result); + CHECK(toString(requireType("f")) == "(a, b) -> Mod"); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ(toString(result.errors[0]), "Unknown type used in % operation; consider adding a type annotation to 'x'"); + } +} + TEST_CASE_FIXTURE(BuiltinsFixture, "equality_operations_succeed_if_any_union_branch_succeeds") { CheckResult result = check(R"( diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 726a0ffa..374acc60 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -1942,4 +1942,54 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "conditional_refinement_should_stay_error_sup LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "globals_can_be_narrowed_too") +{ + CheckResult result = check(R"( + if typeof(string) == 'string' then + local foo = string + end + )"); + + CHECK("never" == toString(requireTypeAtPosition(Position{2, 24}))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction") +{ + CheckResult result = check(R"( + local function isIndexKey(k, contiguousLength) + return type(k) == "number" + and k <= contiguousLength -- nothing out of bounds + and 1 <= k -- nothing illegal for array indices + and math.floor(k) == k -- no float keys + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_variant") +{ + CheckResult result = check(R"( + local function isIndexKey(k, contiguousLength: number) + return type(k) == "number" + and k <= contiguousLength -- nothing out of bounds + and 1 <= k -- nothing illegal for array indices + and math.floor(k) == k -- no float keys + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "globals_can_be_narrowed_too") +{ + CheckResult result = check(R"( + if typeof(string) == 'string' then + local foo = string + end + )"); + + CHECK("never" == toString(requireTypeAtPosition(Position{2, 24}))); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index d085f900..aeef4bad 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -366,10 +366,7 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias") LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expectedError = R"(Type 'a' could not be converted into 'Err | Ok' -caused by: - None of the union options are compatible. For example: -Table type 'a' not compatible with type 'Err' because the former is missing field 'error')"; + const std::string expectedError = "Type 'a' could not be converted into 'Err | Ok'"; CHECK(toString(result.errors[0]) == expectedError); } diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index b36c2ca4..47ddf9b5 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -2672,7 +2672,7 @@ TEST_CASE_FIXTURE(Fixture, "generalize_table_argument") std::optional fooArg1 = first(fooType->argTypes); REQUIRE(fooArg1); - const TableType* fooArg1Table = get(*fooArg1); + const TableType* fooArg1Table = get(follow(*fooArg1)); REQUIRE(fooArg1Table); CHECK_EQ(fooArg1Table->state, TableState::Generic); diff --git a/tests/conformance/constructs.lua b/tests/conformance/constructs.lua index f133501f..b8b88478 100644 --- a/tests/conformance/constructs.lua +++ b/tests/conformance/constructs.lua @@ -237,4 +237,19 @@ repeat i = i+1 until i==c +-- validate continue upvalue close behavior +local function check_connected(writer, reader) + writer(1) + assert(reader() == 1) + return true +end + +repeat + local value = nil + local function write(n) + value = n + end + continue +until check_connected(write, function() return value end) + return 'OK' diff --git a/tests/conformance/datetime.lua b/tests/conformance/datetime.lua index 81e0daea..8e2103d9 100644 --- a/tests/conformance/datetime.lua +++ b/tests/conformance/datetime.lua @@ -23,6 +23,29 @@ assert(os.time({ year = 1970, month = 1, day = 1, hour = 0, min = 0, sec = 0}) = assert(os.time({ year = 3000, month = 12, day = 31, hour = 23, min = 59, sec = 59}) == 32535215999) -- just before Windows max range assert(os.time({ year = 1970, month = 1, day = 1, hour = 0, min = 0, sec = -1}) == nil) -- going before using time fields +assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = 0, sec = 0}) == 960595200) +assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = 0, sec = -86400}) == 960508800) +assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = 0, sec = 86400}) == 960681600) +assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = -600, sec = 0}) == 960559200) +assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = 600, sec = 0}) == 960631200) +assert(os.time({ year = 2000, month = 6, day = 10, hour = -600, min = 0, sec = 0}) == 958435200) +assert(os.time({ year = 2000, month = 6, day = 10, hour = 600, min = 0, sec = 0}) == 962755200) +assert(os.time({ year = 2000, month = 6, day = -100, hour = 0, min = 0, sec = 0}) == 951091200) +assert(os.time({ year = 2000, month = 6, day = 1000, hour = 0, min = 0, sec = 0}) == 1046131200) +assert(os.time({ year = 2000, month = -60, day = 10, hour = 0, min = 0, sec = 0}) == 787017600) +assert(os.time({ year = 2000, month = 60, day = 10, hour = 0, min = 0, sec = 0}) == 1102636800) + +assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = 0, sec = -86400000}) == 874195200) +assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = 0, sec = 86400000}) == 1046995200) +assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = -600000, sec = 0}) == 924595200) +assert(os.time({ year = 2000, month = 6, day = 10, hour = 0, min = 600000, sec = 0}) == 996595200) +assert(os.time({ year = 2100, month = 6, day = 10, hour = -600000, min = 0, sec = 0}) == 1956268800) +assert(os.time({ year = 2100, month = 6, day = 10, hour = 600000, min = 0, sec = 0}) == 6276268800) +assert(os.time({ year = 2100, month = 6, day = -10000, hour = 0, min = 0, sec = 0}) == 3251404800) +assert(os.time({ year = 2100, month = 6, day = 100000, hour = 0, min = 0, sec = 0}) == 12755404800) +assert(os.time({ year = 2100, month = -600, day = 10, hour = 0, min = 0, sec = 0}) == 2522707200) +assert(os.time({ year = 2100, month = 600, day = 10, hour = 0, min = 0, sec = 0}) == 5678380800) + local function checkDateTable (t) local D = os.date("!*t", t) assert(os.time(D) == t) diff --git a/tests/conformance/math.lua b/tests/conformance/math.lua index 4027bba0..d285df78 100644 --- a/tests/conformance/math.lua +++ b/tests/conformance/math.lua @@ -346,7 +346,7 @@ assert(math.log10("10") == 1) assert(math.log("0") == -inf) assert(math.log("8", 2) == 3) assert(math.log("10", 10) == 1) -assert(math.log("9", 3) == 2) +assert(math.log("16", 4) == 2) assert(math.max("1", 2) == 2) assert(math.max(2, "1") == 2) assert(math.max(1, 2, "3") == 3) diff --git a/tests/conformance/native.lua b/tests/conformance/native.lua index a4369dbf..e89e772a 100644 --- a/tests/conformance/native.lua +++ b/tests/conformance/native.lua @@ -203,6 +203,33 @@ local function arraySizeOpt2(a, i) return a[i] + a[5] end -assert(arraySizeOpt1({1}, 1) == 71) +assert(arraySizeOpt2({1}, 1) == 71) + +function deadLoopBody(n) + local r = 0 + if n and false then + for i = 1, n do + r += 1 + end + end + return r +end + +assert(deadLoopBody(5) == 0) + +function arrayIndexingSpecialNumbers1(a, b, c) + local arr = table.create(100000) + arr[a] = 9 + arr[b-1] = 80 + arr[b] = 700 + arr[b+1] = 6000 + arr[c-1] = 50000 + arr[c] = 400000 + arr[c+1] = 3000000 + + return arr[1] + arr[255] + arr[256] + arr[257] + arr[65535] + arr[65536] + arr[65537] +end + +assert(arrayIndexingSpecialNumbers1(1, 256, 65536) == 3456789) return('OK') diff --git a/tests/conformance/strconv.lua b/tests/conformance/strconv.lua index 85ad0295..8f056eb0 100644 --- a/tests/conformance/strconv.lua +++ b/tests/conformance/strconv.lua @@ -24,6 +24,8 @@ assert(tostring(-0.17) == "-0.17") assert(tostring(math.pi) == "3.141592653589793") -- fuzzing corpus +-- Note: If the assert below fires it may indicate floating point denormalized values +-- are not handled as expected. assert(tostring(5.4536123983019448e-311) == "5.453612398302e-311") assert(tostring(5.4834368411298348e-311) == "5.48343684113e-311") assert(tostring(4.4154895841930002e-305) == "4.415489584193e-305") diff --git a/tests/main.cpp b/tests/main.cpp index 72695fb5..96a6525f 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -18,6 +18,10 @@ #include // IsDebuggerPresent #endif +#if defined(__x86_64__) || defined(_M_X64) +#include +#endif + #ifdef __APPLE__ #include #include @@ -253,8 +257,21 @@ static void setFastFlags(const std::vector& flags) } } +// This function performs system/architecture specific initialization prior to running tests. +static void initSystem() +{ +#if defined(__x86_64__) || defined(_M_X64) + // Some unit tests make use of denormalized numbers. So flags to flush to zero or treat denormals as zero + // must be disabled for expected behavior. + _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_OFF); + _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_OFF); +#endif +} + int main(int argc, char** argv) { + initSystem(); + Luau::assertHandler() = testAssertionHandler; diff --git a/tools/faillist.txt b/tools/faillist.txt index 17d79661..a45b215b 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -1,4 +1,6 @@ AnnotationTests.infer_type_of_value_a_via_typeof_with_assignment +AnnotationTests.two_type_params +AnnotationTests.use_generic_type_alias AstQuery.last_argument_function_call_type AstQuery::getDocumentationSymbolAtPosition.table_overloaded_function_prop AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg @@ -24,6 +26,7 @@ AutocompleteTest.type_correct_suggestion_in_argument AutocompleteTest.unsealed_table_2 BuiltinTests.aliased_string_format BuiltinTests.assert_removes_falsy_types +BuiltinTests.assert_removes_falsy_types2 BuiltinTests.assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type BuiltinTests.assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy BuiltinTests.bad_select_should_not_crash @@ -39,6 +42,7 @@ BuiltinTests.gmatch_capture_types_set_containing_lbracket BuiltinTests.gmatch_definition BuiltinTests.ipairs_iterator_should_infer_types_and_type_check BuiltinTests.next_iterator_should_infer_types_and_type_check +BuiltinTests.os_time_takes_optional_date_table BuiltinTests.pairs_iterator_should_infer_types_and_type_check BuiltinTests.see_thru_select BuiltinTests.see_thru_select_count @@ -47,6 +51,7 @@ BuiltinTests.select_way_out_of_range BuiltinTests.select_with_decimal_argument_is_rounded_down BuiltinTests.set_metatable_needs_arguments BuiltinTests.setmetatable_should_not_mutate_persisted_types +BuiltinTests.sort BuiltinTests.sort_with_bad_predicate BuiltinTests.sort_with_predicate BuiltinTests.string_format_arg_count_mismatch @@ -54,6 +59,7 @@ BuiltinTests.string_format_as_method BuiltinTests.string_format_correctly_ordered_types BuiltinTests.string_format_report_all_type_errors_at_correct_positions BuiltinTests.string_format_use_correct_argument2 +BuiltinTests.table_concat_returns_string BuiltinTests.table_dot_remove_optionally_returns_generic BuiltinTests.table_freeze_is_generic BuiltinTests.table_insert_correctly_infers_type_of_array_2_args_overload @@ -90,92 +96,181 @@ ControlFlowAnalysis.type_alias_does_not_leak_out_continuing DefinitionTests.class_definition_indexer DefinitionTests.class_definition_overload_metamethods DefinitionTests.class_definition_string_props +DefinitionTests.declaring_generic_functions DefinitionTests.definition_file_classes Differ.equal_generictp_cyclic +Differ.equal_table_A_B_C Differ.equal_table_cyclic_diamonds_unraveled +Differ.equal_table_kind_A +Differ.equal_table_kind_B +Differ.equal_table_kind_C +Differ.equal_table_kind_D +Differ.equal_table_measuring_tapes +Differ.equal_table_two_cyclic_tables_are_not_different Differ.equal_table_two_shifted_circles_are_not_different Differ.generictp_normal Differ.generictp_normal_2 Differ.left_cyclic_table_right_table_missing_property Differ.left_cyclic_table_right_table_property_wrong +Differ.negation +Differ.right_cyclic_table_left_table_missing_property Differ.right_cyclic_table_left_table_property_wrong Differ.table_left_circle_right_measuring_tape +FrontendTest.accumulate_cached_errors_in_consistent_order +FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded +GenericsTests.apply_type_function_nested_generics1 +GenericsTests.apply_type_function_nested_generics3 GenericsTests.better_mismatch_error_messages +GenericsTests.bound_tables_do_not_clone_original_fields +GenericsTests.check_generic_function +GenericsTests.check_generic_local_function GenericsTests.check_generic_typepack_function GenericsTests.check_mutual_generic_functions +GenericsTests.check_nested_generic_function +GenericsTests.check_recursive_generic_function GenericsTests.correctly_instantiate_polymorphic_member_functions +GenericsTests.do_not_always_instantiate_generic_intersection_types GenericsTests.do_not_infer_generic_functions GenericsTests.dont_substitute_bound_types +GenericsTests.error_detailed_function_mismatch_generic_pack +GenericsTests.error_detailed_function_mismatch_generic_types +GenericsTests.factories_of_generics +GenericsTests.function_arguments_can_be_polytypes GenericsTests.generic_argument_count_too_few GenericsTests.generic_argument_count_too_many +GenericsTests.generic_factories GenericsTests.generic_functions_dont_cache_type_parameters +GenericsTests.generic_functions_in_types GenericsTests.generic_functions_should_be_memory_safe GenericsTests.generic_type_pack_parentheses +GenericsTests.generic_type_pack_unification1 +GenericsTests.generic_type_pack_unification2 +GenericsTests.generic_type_pack_unification3 GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments GenericsTests.hof_subtype_instantiation_regression +GenericsTests.infer_generic_function GenericsTests.infer_generic_function_function_argument GenericsTests.infer_generic_function_function_argument_2 GenericsTests.infer_generic_function_function_argument_3 GenericsTests.infer_generic_function_function_argument_overloaded GenericsTests.infer_generic_lib_function_function_argument +GenericsTests.infer_generic_local_function GenericsTests.infer_generic_property +GenericsTests.infer_nested_generic_function +GenericsTests.inferred_local_vars_can_be_polytypes GenericsTests.instantiated_function_argument_names -GenericsTests.mutable_state_polymorphism +GenericsTests.instantiation_sharing_types +GenericsTests.local_vars_can_be_polytypes GenericsTests.no_stack_overflow_from_quantifying +GenericsTests.properties_can_be_instantiated_polytypes GenericsTests.properties_can_be_polytypes GenericsTests.quantify_functions_even_if_they_have_an_explicit_generic +GenericsTests.rank_N_types_via_typeof GenericsTests.self_recursive_instantiated_param +GenericsTests.type_parameters_can_be_polytypes +GenericsTests.typefuns_sharing_types +IntersectionTypes.error_detailed_intersection_all +IntersectionTypes.error_detailed_intersection_part +IntersectionTypes.intersect_bool_and_false +IntersectionTypes.intersect_false_and_bool_and_false +IntersectionTypes.intersect_metatables +IntersectionTypes.intersect_saturate_overloaded_functions +IntersectionTypes.intersection_of_tables +IntersectionTypes.intersection_of_tables_with_never_properties IntersectionTypes.intersection_of_tables_with_top_properties IntersectionTypes.less_greedy_unification_with_intersection_types +IntersectionTypes.overloaded_functions_mentioning_generic +IntersectionTypes.overloaded_functions_mentioning_generic_packs +IntersectionTypes.overloaded_functions_mentioning_generics +IntersectionTypes.overloaded_functions_returning_intersections +IntersectionTypes.overloadeded_functions_with_never_arguments +IntersectionTypes.overloadeded_functions_with_never_result +IntersectionTypes.overloadeded_functions_with_overlapping_results_and_variadics +IntersectionTypes.overloadeded_functions_with_unknown_arguments +IntersectionTypes.overloadeded_functions_with_unknown_result +IntersectionTypes.overloadeded_functions_with_weird_typepacks_1 +IntersectionTypes.overloadeded_functions_with_weird_typepacks_2 +IntersectionTypes.overloadeded_functions_with_weird_typepacks_3 +IntersectionTypes.overloadeded_functions_with_weird_typepacks_4 IntersectionTypes.select_correct_union_fn IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions IntersectionTypes.table_intersection_write_sealed_indirect IntersectionTypes.table_write_sealed_indirect +IntersectionTypes.union_saturate_overloaded_functions +Negations.cofinite_strings_can_be_compared_for_equality Normalize.higher_order_function_with_annotation Normalize.negations_of_tables Normalize.specific_functions_cannot_be_negated ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal -ProvisionalTests.bail_early_if_unification_is_too_complicated ProvisionalTests.choose_the_right_overload_for_pcall 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.expected_type_should_be_a_helpful_deduction_guide_for_function_calls +ProvisionalTests.floating_generics_should_not_be_allowed ProvisionalTests.free_is_not_bound_to_any ProvisionalTests.free_options_can_be_unified_together ProvisionalTests.free_options_cannot_be_unified_together ProvisionalTests.function_returns_many_things_but_first_of_it_is_forgotten ProvisionalTests.generic_type_leak_to_module_interface ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns +ProvisionalTests.it_should_be_agnostic_of_actual_size +ProvisionalTests.luau-polyfill.Array.filter +ProvisionalTests.luau-polyfill.Map.entries +ProvisionalTests.optional_class_instances_are_invariant ProvisionalTests.pcall_returns_at_least_two_value_but_function_returns_nothing ProvisionalTests.setmetatable_constrains_free_type_into_free_table ProvisionalTests.specialization_binds_with_prototypes_too_early ProvisionalTests.table_insert_with_a_singleton_argument ProvisionalTests.table_unification_infinite_recursion ProvisionalTests.typeguard_inference_incomplete +ProvisionalTests.while_body_are_also_refined ProvisionalTests.xpcall_returns_what_f_returns -RefinementTest.call_an_incompatible_function_after_using_typeguard +RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string +RefinementTest.correctly_lookup_property_whose_base_was_previously_refined RefinementTest.dataflow_analysis_can_tell_refinements_when_its_appropriate_to_refine_into_nil_or_never RefinementTest.discriminate_from_truthiness_of_x RefinementTest.fail_to_refine_a_property_of_subscript_expression RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil +RefinementTest.impossible_type_narrow_is_not_an_error +RefinementTest.index_on_a_refined_property RefinementTest.isa_type_refinement_must_be_known_ahead_of_time +RefinementTest.luau_polyfill_isindexkey_refine_conjunction +RefinementTest.luau_polyfill_isindexkey_refine_conjunction_variant RefinementTest.narrow_property_of_a_bounded_variable RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true RefinementTest.not_t_or_some_prop_of_t RefinementTest.refine_a_property_of_some_global +RefinementTest.refine_unknown_to_table_then_clone_it +RefinementTest.string_not_equal_to_string_or_nil RefinementTest.truthy_constraint_on_properties +RefinementTest.type_annotations_arent_relevant_when_doing_dataflow_analysis RefinementTest.type_narrow_to_vector RefinementTest.typeguard_cast_free_table_to_vector RefinementTest.typeguard_in_assert_position RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table +TableTests.a_free_shape_can_turn_into_a_scalar_directly TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible +TableTests.accidentally_checked_prop_in_opposite_branch +TableTests.array_factory_function TableTests.call_method TableTests.cannot_change_type_of_unsealed_table_prop +TableTests.casting_tables_with_props_into_table_with_indexer2 TableTests.casting_tables_with_props_into_table_with_indexer3 TableTests.casting_tables_with_props_into_table_with_indexer4 TableTests.casting_unsealed_tables_with_props_into_table_with_indexer +TableTests.certain_properties_of_table_literal_arguments_can_be_covariant TableTests.checked_prop_too_early TableTests.cli_84607_missing_prop_in_array_or_dict +TableTests.common_table_element_general +TableTests.common_table_element_inner_index +TableTests.common_table_element_inner_prop +TableTests.common_table_element_list +TableTests.common_table_element_union_assignment +TableTests.common_table_element_union_in_call +TableTests.common_table_element_union_in_call_tail +TableTests.common_table_element_union_in_prop +TableTests.confusing_indexing TableTests.cyclic_shifted_tables TableTests.disallow_indexing_into_an_unsealed_table_with_no_indexer_in_strict_mode TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar @@ -183,29 +278,54 @@ TableTests.dont_extend_unsealed_tables_in_rvalue_position TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index TableTests.dont_leak_free_table_props TableTests.dont_quantify_table_that_belongs_to_outer_scope +TableTests.dont_seal_an_unsealed_table_by_passing_it_to_a_function_that_takes_a_sealed_table TableTests.dont_suggest_exact_match_keys +TableTests.error_detailed_indexer_key +TableTests.error_detailed_indexer_value TableTests.error_detailed_metatable_prop +TableTests.error_detailed_prop +TableTests.error_detailed_prop_nested +TableTests.expected_indexer_from_table_union +TableTests.expected_indexer_value_type_extra +TableTests.expected_indexer_value_type_extra_2 TableTests.explicitly_typed_table +TableTests.explicitly_typed_table_error TableTests.explicitly_typed_table_with_indexer +TableTests.extend_unsealed_table_with_metatable TableTests.generalize_table_argument TableTests.generic_table_instantiation_potential_regression TableTests.give_up_after_one_metatable_index_look_up +TableTests.indexer_mismatch +TableTests.indexer_on_sealed_table_must_unify_with_free_table TableTests.indexers_get_quantified_too TableTests.inequality_operators_imply_exactly_matching_types TableTests.infer_array_2 +TableTests.infer_indexer_for_left_unsealed_table_from_right_hand_table_with_indexer +TableTests.infer_indexer_from_its_function_return_type +TableTests.infer_indexer_from_value_property_in_literal TableTests.inferred_return_type_of_free_table TableTests.instantiate_table_cloning_3 +TableTests.instantiate_tables_at_scope_level +TableTests.invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound +TableTests.invariant_table_properties_means_instantiating_tables_in_call_is_unsound TableTests.leaking_bad_metatable_errors TableTests.less_exponential_blowup_please +TableTests.metatable_mismatch_should_fail TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred TableTests.mixed_tables_with_implicit_numbered_keys TableTests.ok_to_provide_a_subtype_during_construction +TableTests.ok_to_set_nil_even_on_non_lvalue_base_expr TableTests.okay_to_add_property_to_unsealed_tables_by_assignment TableTests.okay_to_add_property_to_unsealed_tables_by_function_call +TableTests.only_ascribe_synthetic_names_at_module_scope TableTests.oop_indexer_works TableTests.oop_polymorphic +TableTests.open_table_unification_2 TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2 +TableTests.passing_compatible_unions_to_a_generic_table_without_crashing +TableTests.prop_access_on_key_whose_types_mismatches +TableTests.prop_access_on_unions_of_indexers_where_key_whose_types_mismatches TableTests.quantify_even_that_table_was_never_exported_at_all TableTests.quantify_metatables_of_metatables_of_table TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table @@ -214,18 +334,25 @@ TableTests.right_table_missing_key2 TableTests.scalar_is_a_subtype_of_a_compatible_polymorphic_shape_type TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type TableTests.sealed_table_indexers_must_unify +TableTests.sealed_table_value_can_infer_an_indexer TableTests.shared_selfs TableTests.shared_selfs_from_free_param TableTests.shared_selfs_through_metatables +TableTests.subproperties_can_also_be_covariantly_tested TableTests.table_call_metamethod_basic TableTests.table_call_metamethod_generic +TableTests.table_function_check_use_after_free +TableTests.table_insert_should_cope_with_optional_properties_in_strict TableTests.table_param_width_subtyping_1 TableTests.table_param_width_subtyping_2 +TableTests.table_param_width_subtyping_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_subtyping_with_missing_props_dont_report_multiple_errors2 +TableTests.table_unification_4 TableTests.table_unifies_into_map +TableTests.top_table_type TableTests.type_mismatch_on_massive_table_is_cut_short TableTests.unifying_tables_shouldnt_uaf1 TableTests.used_colon_instead_of_dot @@ -236,31 +363,41 @@ ToString.exhaustive_toString_of_cyclic_table ToString.free_types ToString.named_metatable_toStringNamedFunction ToString.pick_distinct_names_for_mixed_explicit_and_implicit_generics +ToString.tostring_error_mismatch ToString.toStringDetailed2 ToString.toStringErrorPack ToString.toStringNamedFunction_generic_pack TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType TryUnifyTests.result_of_failed_typepack_unification_is_constrained TryUnifyTests.typepack_unification_should_trim_free_tails +TryUnifyTests.uninhabited_table_sub_anything +TryUnifyTests.uninhabited_table_sub_never TryUnifyTests.variadics_should_use_reversed_properly +TypeAliases.alias_expands_to_bare_reference_to_imported_type TypeAliases.dont_lose_track_of_PendingExpansionTypes_after_substitution +TypeAliases.free_variables_from_typeof_in_aliases TypeAliases.generic_param_remap TypeAliases.mismatched_generic_type_param +TypeAliases.mutually_recursive_aliases +TypeAliases.mutually_recursive_generic_aliases TypeAliases.mutually_recursive_types_restriction_not_ok_1 TypeAliases.mutually_recursive_types_restriction_not_ok_2 TypeAliases.mutually_recursive_types_swapsies_not_ok TypeAliases.recursive_types_restriction_not_ok TypeAliases.report_shadowed_aliases +TypeAliases.stringify_optional_parameterized_alias TypeAliases.type_alias_local_mutation TypeAliases.type_alias_local_rename TypeAliases.type_alias_locations TypeAliases.type_alias_of_an_imported_recursive_generic_type +TypeAliases.use_table_name_and_generic_params_in_errors TypeFamilyTests.add_family_at_work TypeFamilyTests.family_as_fn_arg TypeFamilyTests.family_as_fn_ret TypeFamilyTests.function_internal_families TypeFamilyTests.internal_families_raise_errors TypeFamilyTests.table_internal_families +TypeFamilyTests.type_families_inhabited_with_normalization TypeFamilyTests.unsolvable_family TypeInfer.bidirectional_checking_of_callback_property TypeInfer.check_expr_recursion_limit @@ -269,13 +406,22 @@ TypeInfer.cli_39932_use_unifier_in_ensure_methods TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error TypeInfer.dont_report_type_errors_within_an_AstExprError TypeInfer.dont_report_type_errors_within_an_AstStatError +TypeInfer.follow_on_new_types_in_substitution TypeInfer.fuzz_free_table_type_change_during_index_check -TypeInfer.infer_assignment_value_types_mutable_lval +TypeInfer.globals_are_banned_in_strict_mode +TypeInfer.if_statement +TypeInfer.infer_locals_via_assignment_from_its_call_site +TypeInfer.infer_locals_with_nil_value +TypeInfer.infer_through_group_expr +TypeInfer.infer_type_assertion_value_type +TypeInfer.interesting_local_type_inference_case TypeInfer.no_stack_overflow_from_isoptional TypeInfer.promote_tail_type_packs TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2 TypeInfer.tc_after_error_recovery_no_replacement_name_in_error +TypeInfer.tc_error +TypeInfer.tc_error_2 TypeInfer.tc_if_else_expressions_expected_type_3 TypeInfer.type_infer_recursion_limit_no_ice TypeInfer.type_infer_recursion_limit_normalizer @@ -288,22 +434,36 @@ TypeInferAnyError.for_in_loop_iterator_returns_any TypeInferAnyError.for_in_loop_iterator_returns_any2 TypeInferAnyError.intersection_of_any_can_have_props TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any -TypeInferAnyError.union_of_types_regression_test +TypeInferAnyError.type_error_addition TypeInferClasses.callable_classes +TypeInferClasses.can_read_prop_of_base_class_using_string +TypeInferClasses.cannot_unify_class_instance_with_primitive TypeInferClasses.class_type_mismatch_with_name_conflict TypeInferClasses.detailed_class_unification_error TypeInferClasses.index_instance_property +TypeInferClasses.indexable_classes TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties +TypeInferClasses.table_indexers_are_invariant +TypeInferClasses.type_mismatch_invariance_required_for_error +TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class TypeInferFunctions.apply_of_lambda_with_inferred_and_explicit_types TypeInferFunctions.cannot_hoist_interior_defns_into_signature +TypeInferFunctions.check_function_bodies +TypeInferFunctions.concrete_functions_are_not_supertypes_of_function TypeInferFunctions.dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization TypeInferFunctions.dont_give_other_overloads_message_if_only_one_argument_matching_overload_exists TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site +TypeInferFunctions.error_detailed_function_mismatch_arg +TypeInferFunctions.error_detailed_function_mismatch_arg_count +TypeInferFunctions.error_detailed_function_mismatch_ret +TypeInferFunctions.error_detailed_function_mismatch_ret_count +TypeInferFunctions.error_detailed_function_mismatch_ret_mult TypeInferFunctions.function_cast_error_uses_correct_language TypeInferFunctions.function_decl_non_self_sealed_overwrite_2 TypeInferFunctions.function_decl_non_self_unsealed_overwrite TypeInferFunctions.function_does_not_return_enough_values TypeInferFunctions.function_exprs_are_generalized_at_signature_scope_not_enclosing +TypeInferFunctions.function_is_supertype_of_concrete_functions TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer TypeInferFunctions.generic_packs_are_not_variadic TypeInferFunctions.higher_order_function_2 @@ -311,14 +471,22 @@ TypeInferFunctions.higher_order_function_3 TypeInferFunctions.higher_order_function_4 TypeInferFunctions.improved_function_arg_mismatch_errors TypeInferFunctions.infer_anonymous_function_arguments +TypeInferFunctions.infer_anonymous_function_arguments_outside_call TypeInferFunctions.infer_generic_function_function_argument TypeInferFunctions.infer_generic_function_function_argument_overloaded TypeInferFunctions.infer_generic_lib_function_function_argument TypeInferFunctions.infer_return_type_from_selected_overload +TypeInferFunctions.infer_return_value_type TypeInferFunctions.infer_that_function_does_not_return_a_table +TypeInferFunctions.inferred_higher_order_functions_are_quantified_at_the_right_time3 +TypeInferFunctions.instantiated_type_packs_must_have_a_non_null_scope TypeInferFunctions.it_is_ok_to_oversaturate_a_higher_order_function_argument TypeInferFunctions.luau_subtyping_is_np_hard TypeInferFunctions.no_lossy_function_type +TypeInferFunctions.num_is_solved_after_num_or_str +TypeInferFunctions.num_is_solved_before_num_or_str +TypeInferFunctions.occurs_check_failure_in_function_return_type +TypeInferFunctions.other_things_are_not_related_to_function TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2 TypeInferFunctions.record_matching_overload @@ -330,7 +498,9 @@ TypeInferFunctions.too_few_arguments_variadic_generic2 TypeInferFunctions.too_many_arguments_error_location TypeInferFunctions.too_many_return_values_in_parentheses TypeInferFunctions.too_many_return_values_no_function +TypeInferFunctions.vararg_function_is_quantified TypeInferLoops.cli_68448_iterators_need_not_accept_nil +TypeInferLoops.dcr_iteration_explore_raycast_minimization TypeInferLoops.dcr_iteration_on_never_gives_never TypeInferLoops.dcr_xpath_candidates TypeInferLoops.for_in_loop @@ -341,8 +511,12 @@ TypeInferLoops.for_in_loop_on_non_function TypeInferLoops.for_in_loop_with_custom_iterator TypeInferLoops.for_in_loop_with_incompatible_args_to_iterator TypeInferLoops.for_in_loop_with_next +TypeInferLoops.for_in_with_generic_next TypeInferLoops.for_in_with_just_one_iterator_is_ok +TypeInferLoops.for_loop TypeInferLoops.ipairs_produces_integral_indices +TypeInferLoops.iteration_no_table_passed +TypeInferLoops.iteration_regression_issue_69967 TypeInferLoops.iteration_regression_issue_69967_alt TypeInferLoops.loop_iter_basic TypeInferLoops.loop_iter_metamethod_nil @@ -351,11 +525,14 @@ TypeInferLoops.loop_iter_metamethod_ok_with_inference TypeInferLoops.loop_iter_trailing_nil TypeInferLoops.loop_typecheck_crash_on_empty_optional TypeInferLoops.properly_infer_iteratee_is_a_free_table +TypeInferLoops.repeat_loop TypeInferLoops.unreachable_code_after_infinite_loop TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free +TypeInferLoops.while_loop TypeInferModules.bound_free_table_export_is_ok TypeInferModules.do_not_modify_imported_types_4 TypeInferModules.do_not_modify_imported_types_5 +TypeInferModules.general_require_call_expression TypeInferModules.module_type_conflict TypeInferModules.module_type_conflict_instantiated TypeInferModules.require_failed_module @@ -371,13 +548,20 @@ TypeInferOOP.table_oop TypeInferOperators.add_type_family_works TypeInferOperators.and_binexps_dont_unify TypeInferOperators.cli_38355_recursive_union +TypeInferOperators.compound_assign_mismatch_metatable +TypeInferOperators.compound_assign_mismatch_op +TypeInferOperators.compound_assign_mismatch_result TypeInferOperators.compound_assign_result_must_be_compatible_with_var TypeInferOperators.concat_op_on_free_lhs_and_string_rhs TypeInferOperators.concat_op_on_string_lhs_and_free_rhs TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops +TypeInferOperators.luau-polyfill.Array.startswith TypeInferOperators.luau_polyfill_is_array +TypeInferOperators.normalize_strings_comparison TypeInferOperators.operator_eq_completely_incompatible +TypeInferOperators.operator_eq_verifies_types_do_intersect TypeInferOperators.reducing_and +TypeInferOperators.refine_and_or TypeInferOperators.strict_binary_op_where_lhs_unknown TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs @@ -388,22 +572,51 @@ TypeInferOperators.unrelated_classes_cannot_be_compared TypeInferOperators.unrelated_primitives_cannot_be_compared TypeInferPrimitives.CheckMethodsOfNumber TypeInferPrimitives.string_index +TypeInferUnknownNever.array_like_table_of_never_is_inhabitable +TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never +TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never TypeInferUnknownNever.length_of_never TypeInferUnknownNever.math_operators_and_never TypePackTests.fuzz_typepack_iter_follow_2 TypePackTests.pack_tail_unification_check +TypePackTests.parenthesized_varargs_returns_any TypePackTests.type_alias_backwards_compatible TypePackTests.type_alias_default_type_errors TypePackTests.type_packs_with_tails_in_vararg_adjustment +TypePackTests.unify_variadic_tails_in_arguments +TypePackTests.unify_variadic_tails_in_arguments_free +TypePackTests.variadic_argument_tail +TypeSingletons.enums_using_singletons_mismatch +TypeSingletons.error_detailed_tagged_union_mismatch_bool +TypeSingletons.error_detailed_tagged_union_mismatch_string TypeSingletons.function_args_infer_singletons TypeSingletons.function_call_with_singletons TypeSingletons.function_call_with_singletons_mismatch TypeSingletons.overloaded_function_call_with_singletons TypeSingletons.return_type_of_f_is_not_widened +TypeSingletons.table_properties_singleton_strings TypeSingletons.table_properties_type_error_escapes TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton TypeSingletons.widening_happens_almost_everywhere +UnionTypes.disallow_less_specific_assign +UnionTypes.disallow_less_specific_assign2 +UnionTypes.error_detailed_optional +UnionTypes.error_detailed_union_all +UnionTypes.error_detailed_union_part +UnionTypes.generic_function_with_optional_arg UnionTypes.index_on_a_union_type_with_missing_property UnionTypes.less_greedy_unification_with_union_types +UnionTypes.optional_arguments_table2 +UnionTypes.optional_index_error +UnionTypes.optional_length_error UnionTypes.table_union_write_indirect -UnionTypes.unify_unsealed_table_union_check +UnionTypes.unify_sealed_table_union_check +UnionTypes.union_of_functions +UnionTypes.union_of_functions_mentioning_generic_typepacks +UnionTypes.union_of_functions_mentioning_generics +UnionTypes.union_of_functions_with_mismatching_arg_arities +UnionTypes.union_of_functions_with_mismatching_arg_variadics +UnionTypes.union_of_functions_with_mismatching_result_arities +UnionTypes.union_of_functions_with_mismatching_result_variadics +UnionTypes.union_of_functions_with_variadics +UnionTypes.union_true_and_false