From b570ff0a376f2e86b5a1eb2795209c80d2dbca60 Mon Sep 17 00:00:00 2001 From: vegorov-rbx <75688451+vegorov-rbx@users.noreply.github.com> Date: Sat, 18 Feb 2023 01:41:51 +0200 Subject: [PATCH] Sync to upstream/release/564 (#841) This week we only have updates to new type solver and JIT. Both projects are still in the process of being built out. Neither are ready for general use yet. In the new solver, we fixed issues with recursive type aliases. Duplicated type parameters are once again reported, exported types are being recorder and function argument names are placed inside function types. We also made improvements to restore parts of bidirectional type tracking. On native code generation side, namecall instruction lowering was fixed, we fixed inconsistencies in IR command definitions and added utility function to help with constant folding. --- Analysis/include/Luau/Constraint.h | 3 +- .../include/Luau/ConstraintGraphBuilder.h | 12 +- Analysis/include/Luau/ConstraintSolver.h | 2 +- Analysis/include/Luau/Frontend.h | 14 +- Analysis/src/Autocomplete.cpp | 4 - Analysis/src/BuiltinDefinitions.cpp | 4 - Analysis/src/ConstraintGraphBuilder.cpp | 248 ++++++++------ Analysis/src/ConstraintSolver.cpp | 87 ++--- Analysis/src/DataFlowGraph.cpp | 9 +- Analysis/src/Frontend.cpp | 53 ++- Analysis/src/Module.cpp | 5 +- Analysis/src/Type.cpp | 36 +- Analysis/src/TypeChecker2.cpp | 83 +++-- Analysis/src/Unifier.cpp | 57 +--- CLI/Repl.cpp | 9 +- CodeGen/include/Luau/IrData.h | 27 +- CodeGen/include/Luau/IrUtils.h | 21 ++ CodeGen/src/IrDump.cpp | 6 +- CodeGen/src/IrLoweringX64.cpp | 25 +- CodeGen/src/IrTranslation.cpp | 10 +- CodeGen/src/IrUtils.cpp | 260 ++++++++++++++- Common/include/Luau/ExperimentalFlags.h | 2 +- tests/Autocomplete.test.cpp | 2 - tests/ClassFixture.h | 1 + tests/Fixture.cpp | 17 +- tests/IrBuilder.test.cpp | 315 +++++++++++++++++- tests/TypeInfer.aliases.test.cpp | 2 +- tests/TypeInfer.functions.test.cpp | 20 ++ tests/TypeInfer.generics.test.cpp | 23 +- tests/TypeInfer.operators.test.cpp | 2 +- tests/TypeInfer.refinements.test.cpp | 14 +- tests/TypeInfer.tryUnify.test.cpp | 2 - tools/faillist.txt | 49 +-- 33 files changed, 1044 insertions(+), 380 deletions(-) diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index 18ff3092..65599e49 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -90,6 +90,8 @@ struct NameConstraint TypeId namedType; std::string name; bool synthetic = false; + std::vector typeParameters; + std::vector typePackParameters; }; // target ~ inst target @@ -101,7 +103,6 @@ struct TypeAliasExpansionConstraint struct FunctionCallConstraint { - std::vector> innerConstraints; TypeId fn; TypePackId argsPack; TypePackId result; diff --git a/Analysis/include/Luau/ConstraintGraphBuilder.h b/Analysis/include/Luau/ConstraintGraphBuilder.h index 29afabf3..085b6732 100644 --- a/Analysis/include/Luau/ConstraintGraphBuilder.h +++ b/Analysis/include/Luau/ConstraintGraphBuilder.h @@ -162,10 +162,10 @@ struct ConstraintGraphBuilder void visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction); void visit(const ScopePtr& scope, AstStatError* error); - InferencePack checkPack(const ScopePtr& scope, AstArray exprs, const std::vector& expectedTypes = {}); - InferencePack checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector& expectedTypes = {}); + InferencePack checkPack(const ScopePtr& scope, AstArray exprs, const std::vector>& expectedTypes = {}); + InferencePack checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector>& expectedTypes = {}); - InferencePack checkPack(const ScopePtr& scope, AstExprCall* call, const std::vector& expectedTypes); + InferencePack checkPack(const ScopePtr& scope, AstExprCall* call); /** * Checks an expression that is expected to evaluate to one type. @@ -244,8 +244,10 @@ struct ConstraintGraphBuilder **/ TypePackId resolveTypePack(const ScopePtr& scope, const AstTypeList& list, bool inTypeArguments); - std::vector> createGenerics(const ScopePtr& scope, AstArray generics); - std::vector> createGenericPacks(const ScopePtr& scope, AstArray packs); + std::vector> createGenerics( + const ScopePtr& scope, AstArray generics, bool useCache = false); + std::vector> createGenericPacks( + const ScopePtr& scope, AstArray packs, bool useCache = false); Inference flattenPack(const ScopePtr& scope, Location location, InferencePack pack); diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index 66d3e8f3..de7b3a04 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -183,7 +183,7 @@ struct ConstraintSolver /** Pushes a new solver constraint to the solver. * @param cv the body of the constraint. **/ - void pushConstraint(NotNull scope, const Location& location, ConstraintV cv); + NotNull pushConstraint(NotNull scope, const Location& location, ConstraintV cv); /** * Attempts to resolve a module from its module information. Returns the diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index dfb35cbd..403551f6 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -162,7 +162,7 @@ struct Frontend ScopePtr getGlobalScope(); private: - ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector requireCycles, + ModulePtr check(const SourceModule& sourceModule, Mode mode, std::vector requireCycles, bool forAutocomplete = false); std::pair getSourceNode(const ModuleName& name); @@ -202,4 +202,16 @@ private: ScopePtr globalScope; }; +ModulePtr check( + const SourceModule& sourceModule, + const std::vector& requireCycles, + NotNull builtinTypes, + NotNull iceHandler, + NotNull moduleResolver, + NotNull fileResolver, + const ScopePtr& globalScope, + NotNull unifierState, + FrontendOptions options +); + } // namespace Luau diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index dd6e1146..85e27168 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -16,7 +16,6 @@ LUAU_FASTFLAGVARIABLE(LuauCompleteTableKeysBetter, false); LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInWhile, false); LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInFor, false); -LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringContent, false); static const std::unordered_set kStatementStartingKeywords = { "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; @@ -1268,9 +1267,6 @@ static bool isSimpleInterpolatedString(const AstNode* node) static std::optional getStringContents(const AstNode* node) { - if (!FFlag::LuauAutocompleteStringContent) - return std::nullopt; - if (const AstExprConstantString* string = node->as()) { return std::string(string->value.data, string->value.size); diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index c17169f4..7bb57208 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -15,8 +15,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauBuiltInMetatableNoBadSynthetic, false) - /** FIXME: Many of these type definitions are not quite completely accurate. * * Some of them require richer generics than we have. For instance, we do not yet have a way to talk @@ -558,8 +556,6 @@ static std::optional> magicFunctionSetMetaTable( if (tableName == metatableName) mtv.syntheticName = tableName; - else if (!FFlag::LuauBuiltInMetatableNoBadSynthetic) - mtv.syntheticName = "{ @metatable: " + metatableName + ", " + tableName + " }"; } TypeId mtTy = arena.addType(mtv); diff --git a/Analysis/src/ConstraintGraphBuilder.cpp b/Analysis/src/ConstraintGraphBuilder.cpp index f773863c..aa605bdf 100644 --- a/Analysis/src/ConstraintGraphBuilder.cpp +++ b/Analysis/src/ConstraintGraphBuilder.cpp @@ -11,6 +11,8 @@ #include "Luau/TypeUtils.h" #include "Luau/Type.h" +#include + LUAU_FASTINT(LuauCheckRecursionLimit); LUAU_FASTFLAG(DebugLuauLogSolverToJson); LUAU_FASTFLAG(DebugLuauMagicTypes); @@ -334,13 +336,12 @@ void ConstraintGraphBuilder::visitBlockWithoutChildScope(const ScopePtr& scope, // In order to enable mutually-recursive type aliases, we need to // populate the type bindings before we actually check any of the - // alias statements. Since we're not ready to actually resolve - // any of the annotations, we just use a fresh type for now. + // alias statements. for (AstStat* stat : block->body) { if (auto alias = stat->as()) { - if (scope->privateTypeBindings.count(alias->name.value) != 0) + if (scope->exportedTypeBindings.count(alias->name.value) || scope->privateTypeBindings.count(alias->name.value)) { auto it = aliasDefinitionLocations.find(alias->name.value); LUAU_ASSERT(it != aliasDefinitionLocations.end()); @@ -348,30 +349,28 @@ void ConstraintGraphBuilder::visitBlockWithoutChildScope(const ScopePtr& scope, continue; } - bool hasGenerics = alias->generics.size > 0 || alias->genericPacks.size > 0; + ScopePtr defnScope = childScope(alias, scope); - ScopePtr defnScope = scope; - if (hasGenerics) - { - defnScope = childScope(alias, scope); - } + TypeId initialType = arena->addType(BlockedType{}); + TypeFun initialFun{initialType}; - TypeId initialType = freshType(scope); - TypeFun initialFun = TypeFun{initialType}; - - for (const auto& [name, gen] : createGenerics(defnScope, alias->generics)) + for (const auto& [name, gen] : createGenerics(defnScope, alias->generics, /* useCache */ true)) { initialFun.typeParams.push_back(gen); defnScope->privateTypeBindings[name] = TypeFun{gen.ty}; } - for (const auto& [name, genPack] : createGenericPacks(defnScope, alias->genericPacks)) + for (const auto& [name, genPack] : createGenericPacks(defnScope, alias->genericPacks, /* useCache */ true)) { initialFun.typePackParams.push_back(genPack); defnScope->privateTypePackBindings[name] = genPack.tp; } - scope->privateTypeBindings[alias->name.value] = std::move(initialFun); + if (alias->exported) + scope->exportedTypeBindings[alias->name.value] = std::move(initialFun); + else + scope->privateTypeBindings[alias->name.value] = std::move(initialFun); + astTypeAliasDefiningScopes[alias] = defnScope; aliasDefinitionLocations[alias->name.value] = alias->location; } @@ -387,42 +386,46 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStat* stat) if (auto s = stat->as()) visit(scope, s); + else if (auto i = stat->as()) + visit(scope, i); + else if (auto s = stat->as()) + visit(scope, s); + else if (auto s = stat->as()) + visit(scope, s); + else if (stat->is() || stat->is()) + { + // Nothing + } + else if (auto r = stat->as()) + visit(scope, r); + else if (auto e = stat->as()) + checkPack(scope, e->expr); else if (auto s = stat->as()) visit(scope, s); else if (auto s = stat->as()) visit(scope, s); else if (auto s = stat->as()) visit(scope, s); - else if (auto s = stat->as()) - visit(scope, s); - else if (auto s = stat->as()) - visit(scope, s); - else if (auto f = stat->as()) - visit(scope, f); - else if (auto f = stat->as()) - visit(scope, f); - else if (auto r = stat->as()) - visit(scope, r); else if (auto a = stat->as()) visit(scope, a); else if (auto a = stat->as()) visit(scope, a); - else if (auto e = stat->as()) - checkPack(scope, e->expr); - else if (auto i = stat->as()) - visit(scope, i); + else if (auto f = stat->as()) + visit(scope, f); + else if (auto f = stat->as()) + visit(scope, f); else if (auto a = stat->as()) visit(scope, a); else if (auto s = stat->as()) visit(scope, s); - else if (auto s = stat->as()) - visit(scope, s); else if (auto s = stat->as()) visit(scope, s); + else if (auto s = stat->as()) + visit(scope, s); else if (auto s = stat->as()) visit(scope, s); else - LUAU_ASSERT(0); + LUAU_ASSERT(0 && "Internal error: Unknown AstStat type"); } void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local) @@ -482,7 +485,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local) } else { - std::vector expectedTypes; + std::vector> expectedTypes; if (hasAnnotation) expectedTypes.insert(begin(expectedTypes), begin(varTypes) + i, end(varTypes)); @@ -680,6 +683,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct TypeId generalizedType = arena->addType(BlockedType{}); + Checkpoint start = checkpoint(this); FunctionSignature sig = checkFunctionSignature(scope, function->func); if (AstExprLocal* localName = function->name->as()) @@ -724,7 +728,6 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct if (generalizedType == nullptr) ice->ice("generalizedType == nullptr", function->location); - Checkpoint start = checkpoint(this); checkFunctionBody(sig.bodyScope, function->func); Checkpoint end = checkpoint(this); @@ -745,7 +748,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatReturn* ret) // interesting in it is if the function has an explicit return annotation. // If this is the case, then we can expect that the return expression // conforms to that. - std::vector expectedTypes; + std::vector> expectedTypes; for (TypeId ty : scope->returnType) expectedTypes.push_back(ty); @@ -764,8 +767,21 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign* assign) { TypePackId varPackId = checkLValues(scope, assign->vars); - TypePack expectedTypes = extendTypePack(*arena, builtinTypes, varPackId, assign->values.size); - TypePackId valuePack = checkPack(scope, assign->values, expectedTypes.head).tp; + TypePack expectedPack = extendTypePack(*arena, builtinTypes, varPackId, assign->values.size); + + std::vector> expectedTypes; + expectedTypes.reserve(expectedPack.head.size()); + + for (TypeId ty : expectedPack.head) + { + ty = follow(ty); + if (get(ty)) + expectedTypes.push_back(std::nullopt); + else + expectedTypes.push_back(ty); + } + + TypePackId valuePack = checkPack(scope, assign->values, expectedTypes).tp; addConstraint(scope, assign->location, PackSubtypeConstraint{valuePack, varPackId}); } @@ -800,35 +816,70 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifStatement } } +static bool occursCheck(TypeId needle, TypeId haystack) +{ + LUAU_ASSERT(get(needle)); + haystack = follow(haystack); + + auto checkHaystack = [needle](TypeId haystack) { + return occursCheck(needle, haystack); + }; + + if (needle == haystack) + return true; + else if (auto ut = get(haystack)) + return std::any_of(begin(ut), end(ut), checkHaystack); + else if (auto it = get(haystack)) + return std::any_of(begin(it), end(it), checkHaystack); + + return false; +} + void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alias) { - auto bindingIt = scope->privateTypeBindings.find(alias->name.value); - ScopePtr* defnIt = astTypeAliasDefiningScopes.find(alias); + ScopePtr* defnScope = astTypeAliasDefiningScopes.find(alias); + + std::unordered_map* typeBindings; + if (alias->exported) + typeBindings = &scope->exportedTypeBindings; + else + typeBindings = &scope->privateTypeBindings; + // These will be undefined if the alias was a duplicate definition, in which // case we just skip over it. - if (bindingIt == scope->privateTypeBindings.end() || defnIt == nullptr) - { + auto bindingIt = typeBindings->find(alias->name.value); + if (bindingIt == typeBindings->end() || defnScope == nullptr) return; - } - ScopePtr resolvingScope = *defnIt; - TypeId ty = resolveType(resolvingScope, alias->type, /* inTypeArguments */ false); + TypeId ty = resolveType(*defnScope, alias->type, /* inTypeArguments */ false); - if (alias->exported) + TypeId aliasTy = bindingIt->second.type; + LUAU_ASSERT(get(aliasTy)); + + if (occursCheck(aliasTy, ty)) { - Name typeName(alias->name.value); - scope->exportedTypeBindings[typeName] = TypeFun{ty}; + asMutable(aliasTy)->ty.emplace(builtinTypes->anyType); + reportError(alias->nameLocation, OccursCheckFailed{}); } + else + asMutable(aliasTy)->ty.emplace(ty); - LUAU_ASSERT(get(bindingIt->second.type)); + std::vector typeParams; + for (auto tyParam : createGenerics(*defnScope, alias->generics, /* useCache */ true)) + typeParams.push_back(tyParam.second.ty); - // Rather than using a subtype constraint, we instead directly bind - // the free type we generated in the first pass to the resolved type. - // This prevents a case where you could cause another constraint to - // bind the free alias type to an unrelated type, causing havoc. - asMutable(bindingIt->second.type)->ty.emplace(ty); + std::vector typePackParams; + for (auto tpParam : createGenericPacks(*defnScope, alias->genericPacks, /* useCache */ true)) + typePackParams.push_back(tpParam.second.tp); - addConstraint(scope, alias->location, NameConstraint{ty, alias->name.value}); + addConstraint(scope, alias->type->location, + NameConstraint{ + ty, + alias->name.value, + /*synthetic=*/false, + std::move(typeParams), + std::move(typePackParams), + }); } void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareGlobal* global) @@ -997,7 +1048,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatError* error) check(scope, expr); } -InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray exprs, const std::vector& expectedTypes) +InferencePack ConstraintGraphBuilder::checkPack( + const ScopePtr& scope, AstArray exprs, const std::vector>& expectedTypes) { std::vector head; std::optional tail; @@ -1010,11 +1062,11 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray< std::optional expectedType; if (i < expectedTypes.size()) expectedType = expectedTypes[i]; - head.push_back(check(scope, expr).ty); + head.push_back(check(scope, expr, expectedType).ty); } else { - std::vector expectedTailTypes; + std::vector> expectedTailTypes; if (i < expectedTypes.size()) expectedTailTypes.assign(begin(expectedTypes) + i, end(expectedTypes)); tail = checkPack(scope, expr, expectedTailTypes).tp; @@ -1027,7 +1079,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray< return InferencePack{arena->addTypePack(TypePack{std::move(head), tail})}; } -InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector& expectedTypes) +InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector>& expectedTypes) { RecursionCounter counter{&recursionCount}; @@ -1040,7 +1092,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* InferencePack result; if (AstExprCall* call = expr->as()) - result = checkPack(scope, call, expectedTypes); + result = checkPack(scope, call); else if (AstExprVarargs* varargs = expr->as()) { if (scope->varargPack) @@ -1062,7 +1114,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* return result; } -InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCall* call, const std::vector& expectedTypes) +InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCall* call) { std::vector exprArgs; @@ -1164,7 +1216,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa } else { - auto [tp, refis] = checkPack(scope, arg, {}); // FIXME? not sure about expectedTypes here + auto [tp, refis] = checkPack(scope, arg, {}); argTail = tp; argumentRefinements.insert(argumentRefinements.end(), refis.begin(), refis.end()); } @@ -1209,24 +1261,13 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa if (matchAssert(*call) && !argumentRefinements.empty()) applyRefinements(scope, call->args.data[0]->location, argumentRefinements[0]); - TypeId instantiatedType = arena->addType(BlockedType{}); // TODO: How do expectedTypes play into this? Do they? TypePackId rets = arena->addTypePack(BlockedTypePack{}); TypePackId argPack = arena->addTypePack(TypePack{args, argTail}); FunctionType ftv(TypeLevel{}, scope.get(), argPack, rets); - TypeId inferredFnType = arena->addType(ftv); - - unqueuedConstraints.push_back( - std::make_unique(NotNull{scope.get()}, call->func->location, InstantiationConstraint{instantiatedType, fnType})); - NotNull ic(unqueuedConstraints.back().get()); - - unqueuedConstraints.push_back( - std::make_unique(NotNull{scope.get()}, call->func->location, SubtypeConstraint{instantiatedType, inferredFnType})); - NotNull sc(unqueuedConstraints.back().get()); NotNull fcc = addConstraint(scope, call->func->location, FunctionCallConstraint{ - {ic, sc}, fnType, argPack, rets, @@ -1276,12 +1317,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr, st else if (expr->is()) result = flattenPack(scope, expr->location, checkPack(scope, expr)); else if (auto call = expr->as()) - { - std::vector expectedTypes; - if (expectedType) - expectedTypes.push_back(*expectedType); - result = flattenPack(scope, expr->location, checkPack(scope, call, expectedTypes)); // TODO: needs predicates too - } + result = flattenPack(scope, expr->location, checkPack(scope, call)); // TODO: needs predicates too else if (auto a = expr->as()) { Checkpoint startCheckpoint = checkpoint(this); @@ -1883,6 +1919,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS } std::vector argTypes; + std::vector> argNames; TypePack expectedArgPack; const FunctionType* expectedFunction = expectedType ? get(*expectedType) : nullptr; @@ -1895,14 +1932,27 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS genericTypePacks = expectedFunction->genericPacks; } + if (fn->self) + { + TypeId selfType = freshType(signatureScope); + argTypes.push_back(selfType); + argNames.emplace_back(FunctionArgument{fn->self->name.value, fn->self->location}); + signatureScope->bindings[fn->self] = Binding{selfType, fn->self->location}; + } + for (size_t i = 0; i < fn->args.size; ++i) { AstLocal* local = fn->args.data[i]; TypeId t = freshType(signatureScope); argTypes.push_back(t); + argNames.emplace_back(FunctionArgument{local->name.value, local->location}); signatureScope->bindings[local] = Binding{t, local->location}; + auto def = dfg->getDef(local); + LUAU_ASSERT(def); + signatureScope->dcrRefinements[*def] = t; + TypeId annotationTy = t; if (local->annotation) @@ -1918,12 +1968,6 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS { addConstraint(signatureScope, local->location, SubtypeConstraint{t, expectedArgPack.head[i]}); } - - // HACK: This is the one case where the type of the definition will diverge from the type of the binding. - // We need to do this because there are cases where type refinements needs to have the information available - // at constraint generation time. - if (auto def = dfg->getDef(local)) - signatureScope->dcrRefinements[*def] = annotationTy; } TypePackId varargPack = nullptr; @@ -1978,6 +2022,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS actualFunction.hasNoGenerics = !hasGenerics; actualFunction.generics = std::move(genericTypes); actualFunction.genericPacks = std::move(genericTypePacks); + actualFunction.argNames = std::move(argNames); TypeId actualFunctionType = arena->addType(std::move(actualFunction)); LUAU_ASSERT(actualFunctionType); @@ -2085,11 +2130,6 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b } else { - std::string typeName; - if (ref->prefix) - typeName = std::string(ref->prefix->value) + "."; - typeName += ref->name.value; - result = builtinTypes->errorRecoveryType(); } } @@ -2245,6 +2285,8 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTyp else if (auto var = tp->as()) { TypeId ty = resolveType(scope, var->variadicType, inTypeArgument); + if (get(follow(ty))) + ty = freshType(scope); result = arena->addTypePack(TypePackVar{VariadicTypePack{ty}}); } else if (auto gen = tp->as()) @@ -2287,12 +2329,22 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, const return arena->addTypePack(TypePack{head, tail}); } -std::vector> ConstraintGraphBuilder::createGenerics(const ScopePtr& scope, AstArray generics) +std::vector> ConstraintGraphBuilder::createGenerics( + const ScopePtr& scope, AstArray generics, bool useCache) { std::vector> result; for (const auto& generic : generics) { - TypeId genericTy = arena->addType(GenericType{scope.get(), generic.name.value}); + TypeId genericTy = nullptr; + + if (auto it = scope->parent->typeAliasTypeParameters.find(generic.name.value); useCache && it != scope->parent->typeAliasTypeParameters.end()) + genericTy = it->second; + else + { + genericTy = arena->addType(GenericType{scope.get(), generic.name.value}); + scope->parent->typeAliasTypeParameters[generic.name.value] = genericTy; + } + std::optional defaultTy = std::nullopt; if (generic.defaultValue) @@ -2305,12 +2357,22 @@ std::vector> ConstraintGraphBuilder::crea } std::vector> ConstraintGraphBuilder::createGenericPacks( - const ScopePtr& scope, AstArray generics) + const ScopePtr& scope, AstArray generics, bool useCache) { std::vector> result; for (const auto& generic : generics) { - TypePackId genericTy = arena->addTypePack(TypePackVar{GenericTypePack{scope.get(), generic.name.value}}); + TypePackId genericTy; + + if (auto it = scope->parent->typeAliasTypePackParameters.find(generic.name.value); + useCache && it != scope->parent->typeAliasTypePackParameters.end()) + genericTy = it->second; + else + { + genericTy = arena->addTypePack(TypePackVar{GenericTypePack{scope.get(), generic.name.value}}); + scope->parent->typeAliasTypePackParameters[generic.name.value] = genericTy; + } + std::optional defaultTy = std::nullopt; if (generic.defaultValue) diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 76fd0bca..879dac39 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -217,12 +217,6 @@ void dump(ConstraintSolver* cs, ToStringOptions& opts) int blockCount = it == cs->blockedConstraints.end() ? 0 : int(it->second); printf("\t%d\t\t%s\n", blockCount, toString(*dep, opts).c_str()); } - - if (auto fcc = get(*c)) - { - for (NotNull inner : fcc->innerConstraints) - printf("\t ->\t\t%s\n", toString(*inner, opts).c_str()); - } } } @@ -531,32 +525,19 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNull mm = findMetatableEntry(builtinTypes, errors, operandType, "__unm", constraint->location)) { - const FunctionType* ftv = get(follow(*mm)); + TypeId mmTy = follow(*mm); - if (!ftv) - { - if (std::optional callMm = findMetatableEntry(builtinTypes, errors, follow(*mm), "__call", constraint->location)) - { - ftv = get(follow(*callMm)); - } - } + if (get(mmTy) && !force) + return block(mmTy, constraint); - if (!ftv) - { - asMutable(c.resultType)->ty.emplace(builtinTypes->errorRecoveryType()); - return true; - } + TypePackId argPack = arena->addTypePack(TypePack{{operandType}, {}}); + TypePackId retPack = arena->addTypePack(BlockedTypePack{}); - TypePackId argsPack = arena->addTypePack({operandType}); - unify(ftv->argTypes, argsPack, constraint->scope); + asMutable(c.resultType)->ty.emplace(constraint->scope); - TypeId result = builtinTypes->errorRecoveryType(); - if (ftv) - { - result = first(ftv->retTypes).value_or(builtinTypes->errorRecoveryType()); - } + pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{retPack, arena->addTypePack(TypePack{{c.resultType}})}); - asMutable(c.resultType)->ty.emplace(result); + pushConstraint(constraint->scope, constraint->location, FunctionCallConstraint{mmTy, argPack, retPack, nullptr}); } else { @@ -884,7 +865,11 @@ bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNullname) ttv->syntheticName = c.name; else + { ttv->name = c.name; + ttv->instantiatedTypeParams = c.typeParameters; + ttv->instantiatedTypePackParams = c.typePackParameters; + } } else if (MetatableType* mtv = getMutable(target)) mtv->syntheticName = c.name; @@ -1032,6 +1017,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul { // TODO (CLI-56761): Report an error. bindResult(errorRecoveryType()); + reportError(GenericError{"Recursive type being used with different parameters"}, constraint->location); return true; } @@ -1119,9 +1105,10 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull constraint) { TypeId fn = follow(c.fn); + TypePackId argsPack = follow(c.argsPack); TypePackId result = follow(c.result); - if (isBlocked(c.fn)) + if (isBlocked(fn)) { return block(c.fn, constraint); } @@ -1156,12 +1143,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNulladdType(BlockedType{}); - TypeId inferredFnType = arena->addType(FunctionType(TypeLevel{}, constraint->scope.get(), arena->addTypePack(TypePack{args, {}}), c.result)); - - asMutable(*c.innerConstraints.at(0)).c = InstantiationConstraint{instantiatedType, *callMm}; - asMutable(*c.innerConstraints.at(1)).c = SubtypeConstraint{inferredFnType, instantiatedType}; - + argsPack = arena->addTypePack(TypePack{args, {}}); + fn = *callMm; asMutable(c.result)->ty.emplace(constraint->scope); } else @@ -1178,19 +1161,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNulldcrMagicRefinement(MagicRefinementContext{constraint->scope, c.callSite, c.discriminantTypes}); } - if (usedMagic) - { - // There are constraints that are blocked on these constraints. If we - // are never going to even examine them, then we should not block - // anything else on them. - // - // TODO CLI-58842 -#if 0 - for (auto& c: c.innerConstraints) - unblock(c); -#endif - } - else + if (!usedMagic) asMutable(c.result)->ty.emplace(constraint->scope); } @@ -1209,22 +1180,24 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNullanyType}; } - // Alter the inner constraints. - LUAU_ASSERT(c.innerConstraints.size() == 2); + TypeId instantiatedTy = arena->addType(BlockedType{}); + TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope.get(), argsPack, c.result}); - // Anything that is blocked on this constraint must also be blocked on our inner constraints + auto ic = pushConstraint(constraint->scope, constraint->location, InstantiationConstraint{instantiatedTy, fn}); + auto sc = pushConstraint(constraint->scope, constraint->location, SubtypeConstraint{instantiatedTy, inferredTy}); + + // Anything that is blocked on this constraint must also be blocked on our + // synthesized constraints. auto blockedIt = blocked.find(constraint.get()); if (blockedIt != blocked.end()) { - for (const auto& ic : c.innerConstraints) + for (const auto& blockedConstraint : blockedIt->second) { - for (const auto& blockedConstraint : blockedIt->second) - block(ic, blockedConstraint); + block(ic, blockedConstraint); + block(sc, blockedConstraint); } } - unsolvedConstraints.insert(end(unsolvedConstraints), begin(c.innerConstraints), end(c.innerConstraints)); - unblock(c.result); return true; } @@ -1914,12 +1887,14 @@ void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, NotNull scope, const Location& location, ConstraintV cv) +NotNull ConstraintSolver::pushConstraint(NotNull scope, const Location& location, ConstraintV cv) { std::unique_ptr c = std::make_unique(scope, location, std::move(cv)); NotNull borrow = NotNull(c.get()); solverConstraints.push_back(std::move(c)); unsolvedConstraints.push_back(borrow); + + return borrow; } TypeId ConstraintSolver::resolveModule(const ModuleInfo& info, const Location& location) diff --git a/Analysis/src/DataFlowGraph.cpp b/Analysis/src/DataFlowGraph.cpp index cffd00c9..7e716603 100644 --- a/Analysis/src/DataFlowGraph.cpp +++ b/Analysis/src/DataFlowGraph.cpp @@ -372,7 +372,7 @@ ExpressionFlowGraph DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprInde ExpressionFlowGraph DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprIndexExpr* i) { visitExpr(scope, i->expr); - visitExpr(scope, i->expr); + visitExpr(scope, i->index); if (i->index->as()) { @@ -405,6 +405,13 @@ ExpressionFlowGraph DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunc ExpressionFlowGraph DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprTable* t) { + for (AstExprTable::Item item : t->items) + { + if (item.key) + visitExpr(scope, item.key); + visitExpr(scope, item.value); + } + return {}; } diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index a70d6dda..fb61b4ab 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -104,7 +104,7 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(std::string_view source, c module.root = parseResult.root; module.mode = Mode::Definition; - ModulePtr checkedModule = check(module, Mode::Definition, globalScope, {}); + ModulePtr checkedModule = check(module, Mode::Definition, {}); if (checkedModule->errors.size() > 0) return LoadDefinitionFileResult{false, parseResult, checkedModule}; @@ -517,7 +517,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional requireCycles, bool forAutocomplete) -{ +ModulePtr check( + const SourceModule& sourceModule, + const std::vector& requireCycles, + NotNull builtinTypes, + NotNull iceHandler, + NotNull moduleResolver, + NotNull fileResolver, + const ScopePtr& globalScope, + NotNull unifierState, + FrontendOptions options +) { ModulePtr result = std::make_shared(); - result->reduction = std::make_unique(NotNull{&result->internalTypes}, builtinTypes, NotNull{&iceHandler}); + result->reduction = std::make_unique(NotNull{&result->internalTypes}, builtinTypes, iceHandler); std::unique_ptr logger; if (FFlag::DebugLuauLogSolverToJson) @@ -872,20 +880,17 @@ ModulePtr Frontend::check( } } - DataFlowGraph dfg = DataFlowGraphBuilder::build(sourceModule.root, NotNull{&iceHandler}); + DataFlowGraph dfg = DataFlowGraphBuilder::build(sourceModule.root, iceHandler); - const NotNull mr{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}; - const ScopePtr& globalScope{forAutocomplete ? typeCheckerForAutocomplete.globalScope : typeChecker.globalScope}; - - Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&typeChecker.unifierState}}; + Normalizer normalizer{&result->internalTypes, builtinTypes, unifierState}; ConstraintGraphBuilder cgb{ sourceModule.name, result, &result->internalTypes, - mr, + moduleResolver, builtinTypes, - NotNull(&iceHandler), + iceHandler, globalScope, logger.get(), NotNull{&dfg}, @@ -894,7 +899,7 @@ ModulePtr Frontend::check( cgb.visit(sourceModule.root); result->errors = std::move(cgb.errors); - ConstraintSolver cs{NotNull{&normalizer}, NotNull(cgb.rootScope), borrowConstraints(cgb.constraints), sourceModule.name, NotNull(&moduleResolver), + ConstraintSolver cs{NotNull{&normalizer}, NotNull(cgb.rootScope), borrowConstraints(cgb.constraints), sourceModule.name, moduleResolver, requireCycles, logger.get()}; if (options.randomizeConstraintResolutionSeed) @@ -908,7 +913,7 @@ ModulePtr Frontend::check( result->scopes = std::move(cgb.scopes); result->type = sourceModule.type; - result->clonePublicInterface(builtinTypes, iceHandler); + result->clonePublicInterface(builtinTypes, *iceHandler); Luau::check(builtinTypes, logger.get(), sourceModule, result.get()); @@ -929,6 +934,22 @@ ModulePtr Frontend::check( return result; } +ModulePtr Frontend::check( + const SourceModule& sourceModule, Mode mode, std::vector requireCycles, bool forAutocomplete) +{ + return Luau::check( + sourceModule, + requireCycles, + builtinTypes, + NotNull{&iceHandler}, + NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, + NotNull{fileResolver}, + forAutocomplete ? typeCheckerForAutocomplete.globalScope : typeChecker.globalScope, + NotNull{&typeChecker.unifierState}, + options + ); +} + // Read AST into sourceModules if necessary. Trace require()s. Report parse errors. std::pair Frontend::getSourceNode(const ModuleName& name) { diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index c0f4405c..b51b7c9a 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -227,7 +227,10 @@ void Module::clonePublicInterface(NotNull builtinTypes, InternalEr // Copy external stuff over to Module itself this->returnType = moduleScope->returnType; - this->exportedTypeBindings = std::move(moduleScope->exportedTypeBindings); + if (FFlag::DebugLuauDeferredConstraintResolution) + this->exportedTypeBindings = moduleScope->exportedTypeBindings; + else + this->exportedTypeBindings = std::move(moduleScope->exportedTypeBindings); } bool Module::hasModuleScope() const diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index f874a0b7..2f69f698 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -24,7 +24,6 @@ LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500) LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) LUAU_FASTINT(LuauTypeInferRecursionLimit) -LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAGVARIABLE(LuauMatchReturnsOptionalString, false); @@ -358,39 +357,24 @@ bool maybeGeneric(TypeId ty) { LUAU_ASSERT(!FFlag::LuauInstantiateInSubtyping); - if (FFlag::LuauMaybeGenericIntersectionTypes) - { - ty = follow(ty); - - if (get(ty)) - return true; - - if (auto ttv = get(ty)) - { - // TODO: recurse on table types CLI-39914 - (void)ttv; - return true; - } - - if (auto itv = get(ty)) - { - return std::any_of(begin(itv), end(itv), maybeGeneric); - } - - return isGeneric(ty); - } - ty = follow(ty); + if (get(ty)) return true; - else if (auto ttv = get(ty)) + + if (auto ttv = get(ty)) { // TODO: recurse on table types CLI-39914 (void)ttv; return true; } - else - return isGeneric(ty); + + if (auto itv = get(ty)) + { + return std::any_of(begin(itv), end(itv), maybeGeneric); + } + + return isGeneric(ty); } bool maybeSingleton(TypeId ty) diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 133e324b..4322a0da 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -204,12 +204,6 @@ struct TypeChecker2 bestLocation = scopeBounds; } } - else if (scopeBounds.begin > location.end) - { - // TODO: Is this sound? This relies on the fact that scopes are inserted - // into the scope list in the order that they appear in the AST. - break; - } } return bestScope; @@ -676,18 +670,7 @@ struct TypeChecker2 void visit(AstStatTypeAlias* stat) { - for (const AstGenericType& el : stat->generics) - { - if (el.defaultValue) - visit(el.defaultValue); - } - - for (const AstGenericTypePack& el : stat->genericPacks) - { - if (el.defaultValue) - visit(el.defaultValue); - } - + visitGenerics(stat->generics, stat->genericPacks); visit(stat->type); } @@ -701,6 +684,7 @@ struct TypeChecker2 void visit(AstStatDeclareFunction* stat) { + visitGenerics(stat->generics, stat->genericPacks); visit(stat->params); visit(stat->retTypes); } @@ -973,8 +957,9 @@ struct TypeChecker2 void visit(AstExprIndexName* indexName, ValueContext context) { - TypeId leftType = lookupType(indexName->expr); + visit(indexName->expr, RValue); + TypeId leftType = lookupType(indexName->expr); const NormalizedType* norm = normalizer.normalize(leftType); if (!norm) reportError(NormalizationTooComplex{}, indexName->indexLocation); @@ -993,11 +978,18 @@ struct TypeChecker2 { auto StackPusher = pushStack(fn); + visitGenerics(fn->generics, fn->genericPacks); + TypeId inferredFnTy = lookupType(fn); const FunctionType* inferredFtv = get(inferredFnTy); LUAU_ASSERT(inferredFtv); + // There is no way to write an annotation for the self argument, so we + // cannot do anything to check it. auto argIt = begin(inferredFtv->argTypes); + if (fn->self) + ++argIt; + for (const auto& arg : fn->args) { if (argIt == end(inferredFtv->argTypes)) @@ -1037,6 +1029,7 @@ struct TypeChecker2 NotNull scope = stack.back(); TypeId operandType = lookupType(expr->expr); + TypeId resultType = lookupType(expr); if (get(operandType) || get(operandType) || get(operandType)) return; @@ -1048,9 +1041,6 @@ struct TypeChecker2 { if (const FunctionType* ftv = get(follow(*mm))) { - TypePackId expectedArgs = testArena.addTypePack({operandType}); - reportErrors(tryUnify(scope, expr->location, expectedArgs, ftv->argTypes)); - if (std::optional ret = first(ftv->retTypes)) { if (expr->op == AstExprUnary::Op::Len) @@ -1062,6 +1052,25 @@ struct TypeChecker2 { reportError(GenericError{format("Metamethod '%s' must return a value", it->second)}, expr->location); } + + std::optional firstArg = first(ftv->argTypes); + if (!firstArg) + { + reportError(GenericError{"__unm metamethod must accept one argument"}, expr->location); + return; + } + + TypePackId expectedArgs = testArena.addTypePack({operandType}); + TypePackId expectedRet = testArena.addTypePack({resultType}); + + TypeId expectedFunction = testArena.addType(FunctionType{expectedArgs, expectedRet}); + + ErrorVec errors = tryUnify(scope, expr->location, *mm, expectedFunction); + if (!errors.empty()) + { + reportError(TypeMismatch{*firstArg, operandType}, expr->location); + return; + } } return; @@ -1413,6 +1422,33 @@ struct TypeChecker2 ice.ice("flattenPack got a weird pack!"); } + void visitGenerics(AstArray generics, AstArray genericPacks) + { + DenseHashSet seen{AstName{}}; + + for (const auto& g : generics) + { + if (seen.contains(g.name)) + reportError(DuplicateGenericParameter{g.name.value}, g.location); + else + seen.insert(g.name); + + if (g.defaultValue) + visit(g.defaultValue); + } + + for (const auto& g : genericPacks) + { + if (seen.contains(g.name)) + reportError(DuplicateGenericParameter{g.name.value}, g.location); + else + seen.insert(g.name); + + if (g.defaultValue) + visit(g.defaultValue); + } + } + void visit(AstType* ty) { if (auto t = ty->as()) @@ -1579,8 +1615,7 @@ struct TypeChecker2 void visit(AstTypeFunction* ty) { - // TODO! - + visitGenerics(ty->generics, ty->genericPacks); visit(ty->argTypes); visit(ty->returnTypes); } diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index bda062af..7104f2e7 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -17,7 +17,6 @@ LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTFLAG(LuauErrorRecoveryType) -LUAU_FASTFLAGVARIABLE(LuauUnifyAnyTxnLog, false) LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false) LUAU_FASTFLAGVARIABLE(LuauScalarShapeUnifyToMtOwner2, false) LUAU_FASTFLAGVARIABLE(LuauUninhabitedSubAnything2, false) @@ -475,40 +474,23 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool return; } - if (FFlag::LuauUnifyAnyTxnLog) - { - if (log.get(superTy)) - return tryUnifyWithAny(subTy, builtinTypes->anyType); + if (log.get(superTy)) + return tryUnifyWithAny(subTy, builtinTypes->anyType); - if (log.get(superTy)) - return tryUnifyWithAny(subTy, builtinTypes->errorType); + if (log.get(superTy)) + return tryUnifyWithAny(subTy, builtinTypes->errorType); - if (log.get(superTy)) - return tryUnifyWithAny(subTy, builtinTypes->unknownType); + if (log.get(superTy)) + return tryUnifyWithAny(subTy, builtinTypes->unknownType); - if (log.get(subTy)) - return tryUnifyWithAny(superTy, builtinTypes->anyType); + if (log.get(subTy)) + return tryUnifyWithAny(superTy, builtinTypes->anyType); - if (log.get(subTy)) - return tryUnifyWithAny(superTy, builtinTypes->errorType); + if (log.get(subTy)) + return tryUnifyWithAny(superTy, builtinTypes->errorType); - if (log.get(subTy)) - return tryUnifyWithAny(superTy, builtinTypes->neverType); - } - else - { - if (get(superTy) || get(superTy) || get(superTy)) - return tryUnifyWithAny(subTy, superTy); - - if (get(subTy)) - return tryUnifyWithAny(superTy, subTy); - - if (log.get(subTy)) - return tryUnifyWithAny(superTy, subTy); - - if (log.get(subTy)) - return tryUnifyWithAny(superTy, subTy); - } + if (log.get(subTy)) + return tryUnifyWithAny(superTy, builtinTypes->neverType); auto& cache = sharedState.cachedUnify; @@ -2535,18 +2517,9 @@ void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy) { LUAU_ASSERT(get(anyTy) || get(anyTy) || get(anyTy) || get(anyTy)); - if (FFlag::LuauUnifyAnyTxnLog) - { - // These types are not visited in general loop below - if (log.get(subTy) || log.get(subTy) || log.get(subTy)) - return; - } - else - { - // These types are not visited in general loop below - if (get(subTy) || get(subTy) || get(subTy)) - return; - } + // These types are not visited in general loop below + if (log.get(subTy) || log.get(subTy) || log.get(subTy)) + return; TypePackId anyTp = types->addTypePack(TypePackVar{VariadicTypePack{anyTy}}); diff --git a/CLI/Repl.cpp b/CLI/Repl.cpp index 69a40356..63baea8b 100644 --- a/CLI/Repl.cpp +++ b/CLI/Repl.cpp @@ -319,6 +319,9 @@ std::string runCode(lua_State* L, const std::string& source) lua_insert(T, 1); lua_pcall(T, n, 0, 0); } + + lua_pop(L, 1); + return std::string(); } else { @@ -336,11 +339,9 @@ std::string runCode(lua_State* L, const std::string& source) error += "\nstack backtrace:\n"; error += lua_debugtrace(T); - fprintf(stdout, "%s", error.c_str()); + lua_pop(L, 1); + return error; } - - lua_pop(L, 1); - return std::string(); } // Replaces the top of the lua stack with the metatable __index for the value diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 28f5b29b..6a709468 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -55,7 +55,7 @@ enum class IrCmd : uint8_t // Get pointer (TValue) to table array at index // A: pointer (Table) - // B: unsigned int + // B: int GET_ARR_ADDR, // Get pointer (LuaNode) to table node element at the active cached slot index @@ -177,7 +177,7 @@ enum class IrCmd : uint8_t // A: pointer (Table) DUP_TABLE, - // Try to convert a double number into a table index or jump if it's not an integer + // Try to convert a double number into a table index (int) or jump if it's not an integer // A: double // B: block NUM_TO_INDEX, @@ -216,10 +216,10 @@ enum class IrCmd : uint8_t // B: unsigned int (import path) GET_IMPORT, - // Concatenate multiple TValues - // A: Rn (where to store the result) - // B: unsigned int (index of the first VM stack slot) - // C: unsigned int (number of stack slots to go over) + // Concatenate multiple TValues into a string + // A: Rn (value start) + // B: unsigned int (number of registers to go over) + // Note: result is stored in the register specified in 'A' CONCAT, // Load function upvalue into stack slot @@ -262,7 +262,8 @@ enum class IrCmd : uint8_t // Guard against index overflowing the table array size // A: pointer (Table) - // B: block + // B: int (index) + // C: block CHECK_ARRAY_SIZE, // Guard against cached table node slot not matching the actual table node slot for a key @@ -451,8 +452,12 @@ enum class IrCmd : uint8_t // Prepare loop variables for a generic for loop, jump to the loop backedge unconditionally // A: unsigned int (bytecode instruction index) // B: Rn (loop state, updates Rn Rn+1 Rn+2) - // B: block + // C: block FALLBACK_FORGPREP, + + // Instruction that passes value through, it is produced by constant folding and users substitute it with the value + SUBSTITUTE, + // A: operand of any type }; enum class IrConstKind : uint8_t @@ -659,6 +664,12 @@ struct IrFunction LUAU_ASSERT(value.kind == IrConstKind::Double); return value.valueDouble; } + + IrCondition conditionOp(IrOp op) + { + LUAU_ASSERT(op.kind == IrOpKind::Condition); + return IrCondition(op.index); + } }; } // namespace CodeGen diff --git a/CodeGen/include/Luau/IrUtils.h b/CodeGen/include/Luau/IrUtils.h index 1aef9a3f..3e95813b 100644 --- a/CodeGen/include/Luau/IrUtils.h +++ b/CodeGen/include/Luau/IrUtils.h @@ -10,6 +10,8 @@ namespace Luau namespace CodeGen { +struct IrBuilder; + inline bool isJumpD(LuauOpcode op) { switch (op) @@ -138,6 +140,7 @@ inline bool hasResult(IrCmd cmd) case IrCmd::DUP_TABLE: case IrCmd::NUM_TO_INDEX: case IrCmd::INT_TO_NUM: + case IrCmd::SUBSTITUTE: return true; default: break; @@ -153,6 +156,12 @@ inline bool hasSideEffects(IrCmd cmd) return !hasResult(cmd); } +inline bool isPseudo(IrCmd cmd) +{ + // Instructions that are used for internal needs and are not a part of final lowering + return cmd == IrCmd::NOP || cmd == IrCmd::SUBSTITUTE; +} + // Remove a single instruction void kill(IrFunction& function, IrInst& inst); @@ -172,5 +181,17 @@ void replace(IrFunction& function, IrOp& original, IrOp replacement); // Target instruction index instead of reference is used to handle introduction of a new block terminator void replace(IrFunction& function, uint32_t instIdx, IrInst replacement); +// Replace instruction with a different value (using IrCmd::SUBSTITUTE) +void substitute(IrFunction& function, IrInst& inst, IrOp replacement); + +// Replace instruction arguments that point to substitutions with target values +void applySubstitutions(IrFunction& function, IrOp& op); +void applySubstitutions(IrFunction& function, IrInst& inst); + +// Perform constant folding on instruction at index +// For most instructions, successful folding results in a IrCmd::SUBSTITUTE +// But it can also be successful on conditional control-flow, replacing it with an unconditional IrCmd::JUMP +void foldConstants(IrBuilder& build, IrFunction& function, uint32_t instIdx); + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index 5a23861e..918a8829 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -246,6 +246,8 @@ const char* getCmdName(IrCmd cmd) return "FALLBACK_DUPCLOSURE"; case IrCmd::FALLBACK_FORGPREP: return "FALLBACK_FORGPREP"; + case IrCmd::SUBSTITUTE: + return "SUBSTITUTE"; } LUAU_UNREACHABLE(); @@ -423,8 +425,8 @@ std::string toString(IrFunction& function, bool includeDetails) { IrInst& inst = function.instructions[index]; - // Nop is used to replace dead instructions in-place, so it's not that useful to see them - if (inst.cmd == IrCmd::NOP) + // Skip pseudo instructions unless they are still referenced + if (isPseudo(inst.cmd) && inst.useCount == 0) continue; append(ctx.result, " "); diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 03bb1814..3c816554 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -20,7 +20,7 @@ namespace Luau namespace CodeGen { -static RegisterX64 gprAlocOrder[] = {rax, rdx, rcx, rbx, rsi, rdi, r8, r9, r10, r11}; +static const RegisterX64 kGprAllocOrder[] = {rax, rdx, rcx, rbx, rsi, rdi, r8, r9, r10, r11}; IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, NativeState& data, Proto* proto, IrFunction& function) : build(build) @@ -111,7 +111,7 @@ void IrLoweringX64::lower(AssemblyOptions options) if (options.includeIr) { build.logAppend("# "); - toStringDetailed(ctx, block, uint32_t(i)); + toStringDetailed(ctx, block, blockIndex); } build.setLabel(block.label); @@ -133,9 +133,9 @@ void IrLoweringX64::lower(AssemblyOptions options) IrInst& inst = function.instructions[index]; - // Nop is used to replace dead instructions in-place - // Because it doesn't have any effects aside from output (when enabled), we skip it completely - if (inst.cmd == IrCmd::NOP) + // Skip pseudo instructions, but make sure they are not used at this stage + // This also prevents them from getting into text output when that's enabled + if (isPseudo(inst.cmd)) { LUAU_ASSERT(inst.useCount == 0); continue; @@ -263,8 +263,8 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) build.mov(inst.regX64, qword[regOp(inst.a) + offsetof(Table, array)]); - if (uintOp(inst.b) != 0) - build.lea(inst.regX64, addr[inst.regX64 + uintOp(inst.b) * sizeof(TValue)]); + if (intOp(inst.b) != 0) + build.lea(inst.regX64, addr[inst.regX64 + intOp(inst.b) * sizeof(TValue)]); } else { @@ -688,9 +688,11 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) emitInstGetImportFallback(build, inst.a.index, uintOp(inst.b)); break; case IrCmd::CONCAT: + LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg); + build.mov(rArg1, rState); - build.mov(dwordReg(rArg2), uintOp(inst.a)); - build.mov(dwordReg(rArg3), uintOp(inst.b)); + build.mov(dwordReg(rArg2), uintOp(inst.b)); + build.mov(dwordReg(rArg3), inst.a.index + uintOp(inst.b) - 1); build.call(qword[rNativeContext + offsetof(NativeContext, luaV_concat)]); emitUpdateBase(build); @@ -778,7 +780,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) if (inst.b.kind == IrOpKind::Inst) build.cmp(dword[regOp(inst.a) + offsetof(Table, sizearray)], regOp(inst.b)); else if (inst.b.kind == IrOpKind::Constant) - build.cmp(dword[regOp(inst.a) + offsetof(Table, sizearray)], uintOp(inst.b)); + build.cmp(dword[regOp(inst.a) + offsetof(Table, sizearray)], intOp(inst.b)); else LUAU_ASSERT(!"Unsupported instruction form"); @@ -897,6 +899,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) LUAU_ASSERT(inst.c.kind == IrOpKind::VmReg); emitInstNameCall(build, pc, uintOp(inst.a), proto->k, blockOp(inst.d).label, blockOp(inst.e).label); + jumpOrFallthrough(blockOp(inst.d), next); break; } case IrCmd::LOP_CALL: @@ -1133,7 +1136,7 @@ RegisterX64 IrLoweringX64::allocGprReg(SizeX64 preferredSize) LUAU_ASSERT( preferredSize == SizeX64::byte || preferredSize == SizeX64::word || preferredSize == SizeX64::dword || preferredSize == SizeX64::qword); - for (RegisterX64 reg : gprAlocOrder) + for (RegisterX64 reg : kGprAllocOrder) { if (freeGprMap[reg.index]) { diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index fdbdf667..0885c056 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -695,10 +695,10 @@ void translateInstGetTableN(IrBuilder& build, const Instruction* pc, int pcpos) IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); - build.inst(IrCmd::CHECK_ARRAY_SIZE, vb, build.constUint(c), fallback); + 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.constUint(c)); + IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constInt(c)); // TODO: per-component loads and stores might be preferable IrOp arrElTval = build.inst(IrCmd::LOAD_TVALUE, arrEl); @@ -725,11 +725,11 @@ void translateInstSetTableN(IrBuilder& build, const Instruction* pc, int pcpos) IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); - build.inst(IrCmd::CHECK_ARRAY_SIZE, vb, build.constUint(c), fallback); + build.inst(IrCmd::CHECK_ARRAY_SIZE, vb, build.constInt(c), fallback); 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.constUint(c)); + IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constInt(c)); // TODO: per-component loads and stores might be preferable IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra)); @@ -969,7 +969,7 @@ void translateInstConcat(IrBuilder& build, const Instruction* pc, int pcpos) int rc = LUAU_INSN_C(*pc); build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); - build.inst(IrCmd::CONCAT, build.constUint(rc - rb + 1), build.constUint(rc)); + build.inst(IrCmd::CONCAT, build.vmReg(rb), build.constUint(rc - rb + 1)); // TODO: per-component loads and stores might be preferable IrOp tvb = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(rb)); diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index 0c1a8966..2ff1c0d1 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -1,6 +1,14 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/IrUtils.h" +#include "Luau/IrBuilder.h" + +#include "lua.h" +#include "lnumutils.h" + +#include +#include + namespace Luau { namespace CodeGen @@ -8,16 +16,19 @@ namespace CodeGen static uint32_t getBlockEnd(IrFunction& function, uint32_t start) { + LUAU_ASSERT(start < function.instructions.size()); + uint32_t end = start; // Find previous block terminator while (!isBlockTerminator(function.instructions[end].cmd)) end++; + LUAU_ASSERT(end < function.instructions.size()); return end; } -static void addUse(IrFunction& function, IrOp op) +void addUse(IrFunction& function, IrOp op) { if (op.kind == IrOpKind::Inst) function.instructions[op.index].useCount++; @@ -25,7 +36,7 @@ static void addUse(IrFunction& function, IrOp op) function.blocks[op.index].useCount++; } -static void removeUse(IrFunction& function, IrOp op) +void removeUse(IrFunction& function, IrOp op) { if (op.kind == IrOpKind::Inst) removeUse(function, function.instructions[op.index]); @@ -44,6 +55,12 @@ void kill(IrFunction& function, IrInst& inst) removeUse(function, inst.c); removeUse(function, inst.d); removeUse(function, inst.e); + + inst.a = {}; + inst.b = {}; + inst.c = {}; + inst.d = {}; + inst.e = {}; } void kill(IrFunction& function, uint32_t start, uint32_t end) @@ -51,6 +68,7 @@ void kill(IrFunction& function, uint32_t start, uint32_t end) // Kill instructions in reverse order to avoid killing instructions that are still marked as used for (int i = int(end); i >= int(start); i--) { + LUAU_ASSERT(unsigned(i) < function.instructions.size()); IrInst& curr = function.instructions[i]; if (curr.cmd == IrCmd::NOP) @@ -102,7 +120,6 @@ void replace(IrFunction& function, IrOp& original, IrOp replacement) void replace(IrFunction& function, uint32_t instIdx, IrInst replacement) { IrInst& inst = function.instructions[instIdx]; - IrCmd prevCmd = inst.cmd; // Add uses before removing new ones if those are the last ones keeping target operand alive addUse(function, replacement.a); @@ -111,6 +128,20 @@ void replace(IrFunction& function, uint32_t instIdx, IrInst replacement) addUse(function, replacement.d); addUse(function, replacement.e); + // If we introduced an earlier terminating instruction, all following instructions become dead + if (!isBlockTerminator(inst.cmd) && isBlockTerminator(replacement.cmd)) + { + uint32_t start = instIdx + 1; + + // If we are in the process of constructing a block, replacement might happen at the last instruction + if (start < function.instructions.size()) + { + uint32_t end = getBlockEnd(function, start); + + kill(function, start, end); + } + } + removeUse(function, inst.a); removeUse(function, inst.b); removeUse(function, inst.c); @@ -118,14 +149,227 @@ void replace(IrFunction& function, uint32_t instIdx, IrInst replacement) removeUse(function, inst.e); inst = replacement; +} - // If we introduced an earlier terminating instruction, all following instructions become dead - if (!isBlockTerminator(prevCmd) && isBlockTerminator(inst.cmd)) +void substitute(IrFunction& function, IrInst& inst, IrOp replacement) +{ + LUAU_ASSERT(!isBlockTerminator(inst.cmd)); + + inst.cmd = IrCmd::SUBSTITUTE; + + removeUse(function, inst.a); + removeUse(function, inst.b); + removeUse(function, inst.c); + removeUse(function, inst.d); + removeUse(function, inst.e); + + inst.a = replacement; + inst.b = {}; + inst.c = {}; + inst.d = {}; + inst.e = {}; +} + +void applySubstitutions(IrFunction& function, IrOp& op) +{ + if (op.kind == IrOpKind::Inst) { - uint32_t start = instIdx + 1; - uint32_t end = getBlockEnd(function, start); + IrInst& src = function.instructions[op.index]; - kill(function, start, end); + if (src.cmd == IrCmd::SUBSTITUTE) + { + op.kind = src.a.kind; + op.index = src.a.index; + + // If we substitute with the result of a different instruction, update the use count + if (op.kind == IrOpKind::Inst) + { + IrInst& dst = function.instructions[op.index]; + LUAU_ASSERT(dst.cmd != IrCmd::SUBSTITUTE && "chained substitutions are not allowed"); + + dst.useCount++; + } + + LUAU_ASSERT(src.useCount > 0); + src.useCount--; + } + } +} + +void applySubstitutions(IrFunction& function, IrInst& inst) +{ + applySubstitutions(function, inst.a); + applySubstitutions(function, inst.b); + applySubstitutions(function, inst.c); + applySubstitutions(function, inst.d); + applySubstitutions(function, inst.e); +} + +static bool compare(double a, double b, IrCondition cond) +{ + switch (cond) + { + case IrCondition::Equal: + return a == b; + case IrCondition::NotEqual: + return a != b; + case IrCondition::Less: + return a < b; + case IrCondition::NotLess: + return !(a < b); + case IrCondition::LessEqual: + return a <= b; + case IrCondition::NotLessEqual: + return !(a <= b); + case IrCondition::Greater: + return a > b; + case IrCondition::NotGreater: + return !(a > b); + case IrCondition::GreaterEqual: + return a >= b; + case IrCondition::NotGreaterEqual: + return !(a >= b); + default: + LUAU_ASSERT(!"unsupported conidtion"); + } + + return false; +} + +void foldConstants(IrBuilder& build, IrFunction& function, uint32_t index) +{ + IrInst& inst = function.instructions[index]; + + switch (inst.cmd) + { + case IrCmd::ADD_INT: + if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) + { + // We need to avoid signed integer overflow, but we also have to produce a result + // So we add numbers as unsigned and use fixed-width integer types to force a two's complement evaluation + int32_t lhs = function.intOp(inst.a); + int32_t rhs = function.intOp(inst.b); + int sum = int32_t(uint32_t(lhs) + uint32_t(rhs)); + + substitute(function, inst, build.constInt(sum)); + } + break; + case IrCmd::SUB_INT: + if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) + { + // We need to avoid signed integer overflow, but we also have to produce a result + // So we subtract numbers as unsigned and use fixed-width integer types to force a two's complement evaluation + int32_t lhs = function.intOp(inst.a); + int32_t rhs = function.intOp(inst.b); + int sum = int32_t(uint32_t(lhs) - uint32_t(rhs)); + + substitute(function, inst, build.constInt(sum)); + } + break; + case IrCmd::ADD_NUM: + if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) + substitute(function, inst, build.constDouble(function.doubleOp(inst.a) + function.doubleOp(inst.b))); + break; + case IrCmd::SUB_NUM: + if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) + substitute(function, inst, build.constDouble(function.doubleOp(inst.a) - function.doubleOp(inst.b))); + break; + case IrCmd::MUL_NUM: + if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) + substitute(function, inst, build.constDouble(function.doubleOp(inst.a) * function.doubleOp(inst.b))); + break; + case IrCmd::DIV_NUM: + if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) + substitute(function, inst, build.constDouble(function.doubleOp(inst.a) / function.doubleOp(inst.b))); + break; + case IrCmd::MOD_NUM: + if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) + substitute(function, inst, build.constDouble(luai_nummod(function.doubleOp(inst.a), function.doubleOp(inst.b)))); + break; + case IrCmd::POW_NUM: + if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) + substitute(function, inst, build.constDouble(pow(function.doubleOp(inst.a), function.doubleOp(inst.b)))); + break; + case IrCmd::UNM_NUM: + if (inst.a.kind == IrOpKind::Constant) + substitute(function, inst, build.constDouble(-function.doubleOp(inst.a))); + break; + case IrCmd::NOT_ANY: + if (inst.a.kind == IrOpKind::Constant) + { + uint8_t a = function.tagOp(inst.a); + + if (a == LUA_TNIL) + substitute(function, inst, build.constInt(1)); + else if (a != LUA_TBOOLEAN) + substitute(function, inst, build.constInt(0)); + else if (inst.b.kind == IrOpKind::Constant) + substitute(function, inst, build.constInt(function.intOp(inst.b) == 1 ? 0 : 1)); + } + break; + case IrCmd::JUMP_EQ_TAG: + if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) + { + if (function.tagOp(inst.a) == function.tagOp(inst.b)) + replace(function, index, {IrCmd::JUMP, inst.c}); + else + replace(function, index, {IrCmd::JUMP, inst.d}); + } + break; + case IrCmd::JUMP_EQ_INT: + if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) + { + if (function.intOp(inst.a) == function.intOp(inst.b)) + replace(function, index, {IrCmd::JUMP, inst.c}); + else + replace(function, index, {IrCmd::JUMP, inst.d}); + } + break; + case IrCmd::JUMP_CMP_NUM: + if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) + { + if (compare(function.doubleOp(inst.a), function.doubleOp(inst.b), function.conditionOp(inst.c))) + replace(function, index, {IrCmd::JUMP, inst.d}); + else + replace(function, index, {IrCmd::JUMP, inst.e}); + } + break; + case IrCmd::NUM_TO_INDEX: + if (inst.a.kind == IrOpKind::Constant) + { + double value = function.doubleOp(inst.a); + + // To avoid undefined behavior of casting a value not representable in the target type, we check the range + if (value >= INT_MIN && value <= INT_MAX) + { + int arrIndex = int(value); + + if (double(arrIndex) == value) + substitute(function, inst, build.constInt(arrIndex)); + else + replace(function, index, {IrCmd::JUMP, inst.b}); + } + else + { + replace(function, index, {IrCmd::JUMP, inst.b}); + } + } + break; + case IrCmd::INT_TO_NUM: + if (inst.a.kind == IrOpKind::Constant) + substitute(function, inst, build.constDouble(double(function.intOp(inst.a)))); + break; + case IrCmd::CHECK_TAG: + if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) + { + if (function.tagOp(inst.a) == function.tagOp(inst.b)) + kill(function, inst); + else + replace(function, index, {IrCmd::JUMP, inst.c}); // Shows a conflict in assumptions on this path + } + break; + default: + break; } } diff --git a/Common/include/Luau/ExperimentalFlags.h b/Common/include/Luau/ExperimentalFlags.h index a14cc1e6..afd36401 100644 --- a/Common/include/Luau/ExperimentalFlags.h +++ b/Common/include/Luau/ExperimentalFlags.h @@ -10,7 +10,7 @@ inline bool isFlagExperimental(const char* flag) { // Flags in this list are disabled by default in various command-line tools. They may have behavior that is not fully final, // or critical bugs that are found after the code has been submitted. - static const char* kList[] = { + static const char* const kList[] = { "LuauInstantiateInSubtyping", // requires some fixes to lua-apps code "LuauTryhardAnd", // waiting for a fix in graphql-lua -> apollo-client-lia -> lua-apps "LuauTypecheckTypeguards", // requires some fixes to lua-apps code (CLI-67030) diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 3fd0e9cd..d238e9ec 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -3442,8 +3442,6 @@ TEST_CASE_FIXTURE(ACFixture, "type_reduction_is_hooked_up_to_autocomplete") TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback") { - ScopedFastFlag luauAutocompleteStringContent{"LuauAutocompleteStringContent", true}; - loadDefinition(R"( declare function require(path: string): any )"); diff --git a/tests/ClassFixture.h b/tests/ClassFixture.h index 66aec764..c46697a2 100644 --- a/tests/ClassFixture.h +++ b/tests/ClassFixture.h @@ -1,4 +1,5 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once #include "Fixture.h" diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index 9d21973d..f245ca93 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -176,7 +176,22 @@ AstStatBlock* Fixture::parse(const std::string& source, const ParseOptions& pars { frontend.lint(*sourceModule); - typeChecker.check(*sourceModule, sourceModule->mode.value_or(Luau::Mode::Nonstrict)); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + Luau::check( + *sourceModule, + {}, + frontend.builtinTypes, + NotNull{&ice}, + NotNull{&moduleResolver}, + NotNull{&fileResolver}, + typeChecker.globalScope, + NotNull{&typeChecker.unifierState}, + frontend.options + ); + } + else + typeChecker.check(*sourceModule, sourceModule->mode.value_or(Luau::Mode::Nonstrict)); } throw ParseErrors(result.errors); diff --git a/tests/IrBuilder.test.cpp b/tests/IrBuilder.test.cpp index 4ed87286..4bb638e8 100644 --- a/tests/IrBuilder.test.cpp +++ b/tests/IrBuilder.test.cpp @@ -2,16 +2,83 @@ #include "Luau/IrBuilder.h" #include "Luau/IrAnalysis.h" #include "Luau/IrDump.h" +#include "Luau/IrUtils.h" #include "Luau/OptimizeFinalX64.h" #include "doctest.h" +#include + using namespace Luau::CodeGen; class IrBuilderFixture { public: + void constantFold() + { + for (size_t i = 0; i < build.function.instructions.size(); i++) + { + IrInst& inst = build.function.instructions[i]; + + applySubstitutions(build.function, inst); + foldConstants(build, build.function, uint32_t(i)); + } + } + + template + void withOneBlock(F&& f) + { + IrOp main = build.block(IrBlockKind::Internal); + IrOp a = build.block(IrBlockKind::Internal); + + build.beginBlock(main); + f(a); + + build.beginBlock(a); + build.inst(IrCmd::LOP_RETURN, build.constUint(1)); + }; + + template + void withTwoBlocks(F&& f) + { + IrOp main = build.block(IrBlockKind::Internal); + IrOp a = build.block(IrBlockKind::Internal); + IrOp b = build.block(IrBlockKind::Internal); + + build.beginBlock(main); + f(a, b); + + build.beginBlock(a); + build.inst(IrCmd::LOP_RETURN, build.constUint(1)); + + build.beginBlock(b); + build.inst(IrCmd::LOP_RETURN, build.constUint(2)); + }; + + void checkEq(IrOp lhs, IrOp rhs) + { + CHECK_EQ(lhs.kind, rhs.kind); + LUAU_ASSERT(lhs.kind != IrOpKind::Constant && "can't compare constants, each ref is unique"); + CHECK_EQ(lhs.index, rhs.index); + } + + void checkEq(IrOp instOp, const IrInst& inst) + { + const IrInst& target = build.function.instOp(instOp); + CHECK(target.cmd == inst.cmd); + checkEq(target.a, inst.a); + checkEq(target.b, inst.b); + checkEq(target.c, inst.c); + checkEq(target.d, inst.d); + checkEq(target.e, inst.e); + } + IrBuilder build; + + // Luau.VM headers are not accessible + static const int tnil = 0; + static const int tboolean = 1; + static const int tnumber = 3; }; TEST_SUITE_BEGIN("Optimization"); @@ -153,7 +220,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag3") build.beginBlock(block); IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1)); - IrOp arrElem = build.inst(IrCmd::GET_ARR_ADDR, table, build.constUint(0)); + IrOp arrElem = build.inst(IrCmd::GET_ARR_ADDR, table, build.constInt(0)); IrOp opA = build.inst(IrCmd::LOAD_TAG, arrElem); build.inst(IrCmd::JUMP_EQ_TAG, opA, build.constTag(0), trueBlock, falseBlock); build.inst(IrCmd::LOP_RETURN, build.constUint(0)); @@ -171,7 +238,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag3") CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"( bb_0: %0 = LOAD_POINTER R1 - %1 = GET_ARR_ADDR %0, 0u + %1 = GET_ARR_ADDR %0, 0i %2 = LOAD_TAG %1 JUMP_EQ_TAG %2, tnil, bb_1, bb_2 @@ -221,3 +288,247 @@ bb_2: } TEST_SUITE_END(); + +TEST_SUITE_BEGIN("ConstantFolding"); + +TEST_CASE_FIXTURE(IrBuilderFixture, "Numeric") +{ + IrOp block = build.block(IrBlockKind::Internal); + + build.beginBlock(block); + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::ADD_INT, build.constInt(10), build.constInt(20))); + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::ADD_INT, build.constInt(INT_MAX), build.constInt(1))); + + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::SUB_INT, build.constInt(10), build.constInt(20))); + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::SUB_INT, build.constInt(INT_MIN), build.constInt(1))); + + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::ADD_NUM, build.constDouble(2), build.constDouble(5))); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::SUB_NUM, build.constDouble(2), build.constDouble(5))); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::MUL_NUM, build.constDouble(2), build.constDouble(5))); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::DIV_NUM, build.constDouble(2), build.constDouble(5))); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::MOD_NUM, build.constDouble(5), build.constDouble(2))); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::POW_NUM, build.constDouble(5), build.constDouble(2))); + + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::UNM_NUM, build.constDouble(5))); + + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::NOT_ANY, build.constTag(tnil), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1)))); + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::NOT_ANY, build.constTag(tnumber), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1)))); + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::NOT_ANY, build.constTag(tboolean), build.constInt(0))); + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::NOT_ANY, build.constTag(tboolean), build.constInt(1))); + + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::INT_TO_NUM, build.constInt(8))); + + build.inst(IrCmd::LOP_RETURN, build.constUint(0)); + + updateUseCounts(build.function); + constantFold(); + + CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"( +bb_0: + STORE_INT R0, 30i + STORE_INT R0, -2147483648i + STORE_INT R0, -10i + STORE_INT R0, 2147483647i + STORE_DOUBLE R0, 7 + STORE_DOUBLE R0, -3 + STORE_DOUBLE R0, 10 + STORE_DOUBLE R0, 0.40000000000000002 + STORE_DOUBLE R0, 1 + STORE_DOUBLE R0, 25 + STORE_DOUBLE R0, -5 + STORE_INT R0, 1i + STORE_INT R0, 0i + STORE_INT R0, 1i + STORE_INT R0, 0i + STORE_DOUBLE R0, 8 + LOP_RETURN 0u + +)"); +} + +TEST_CASE_FIXTURE(IrBuilderFixture, "ControlFlowEq") +{ + withTwoBlocks([this](IrOp a, IrOp b) { + build.inst(IrCmd::JUMP_EQ_TAG, build.constTag(tnil), build.constTag(tnil), a, b); + }); + + withTwoBlocks([this](IrOp a, IrOp b) { + build.inst(IrCmd::JUMP_EQ_TAG, build.constTag(tnil), build.constTag(tnumber), a, b); + }); + + withTwoBlocks([this](IrOp a, IrOp b) { + build.inst(IrCmd::JUMP_EQ_INT, build.constInt(0), build.constInt(0), a, b); + }); + + withTwoBlocks([this](IrOp a, IrOp b) { + build.inst(IrCmd::JUMP_EQ_INT, build.constInt(0), build.constInt(1), a, b); + }); + + updateUseCounts(build.function); + constantFold(); + + CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"( +bb_0: + JUMP bb_1 + +bb_1: + LOP_RETURN 1u + +bb_3: + JUMP bb_5 + +bb_5: + LOP_RETURN 2u + +bb_6: + JUMP bb_7 + +bb_7: + LOP_RETURN 1u + +bb_9: + JUMP bb_11 + +bb_11: + LOP_RETURN 2u + +)"); +} + +TEST_CASE_FIXTURE(IrBuilderFixture, "NumToIndex") +{ + withOneBlock([this](IrOp a) { + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::NUM_TO_INDEX, build.constDouble(4), a)); + build.inst(IrCmd::LOP_RETURN, build.constUint(0)); + }); + + withOneBlock([this](IrOp a) { + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::NUM_TO_INDEX, build.constDouble(1.2), a)); + build.inst(IrCmd::LOP_RETURN, build.constUint(0)); + }); + + withOneBlock([this](IrOp a) { + IrOp nan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0)); + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::NUM_TO_INDEX, nan, a)); + build.inst(IrCmd::LOP_RETURN, build.constUint(0)); + }); + + updateUseCounts(build.function); + constantFold(); + + CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"( +bb_0: + STORE_INT R0, 4i + LOP_RETURN 0u + +bb_2: + JUMP bb_3 + +bb_3: + LOP_RETURN 1u + +bb_4: + JUMP bb_5 + +bb_5: + LOP_RETURN 1u + +)"); +} + +TEST_CASE_FIXTURE(IrBuilderFixture, "Guards") +{ + withOneBlock([this](IrOp a) { + build.inst(IrCmd::CHECK_TAG, build.constTag(tnumber), build.constTag(tnumber), a); + build.inst(IrCmd::LOP_RETURN, build.constUint(0)); + }); + + withOneBlock([this](IrOp a) { + build.inst(IrCmd::CHECK_TAG, build.constTag(tnil), build.constTag(tnumber), a); + build.inst(IrCmd::LOP_RETURN, build.constUint(0)); + }); + + updateUseCounts(build.function); + constantFold(); + + CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"( +bb_0: + LOP_RETURN 0u + +bb_2: + JUMP bb_3 + +bb_3: + LOP_RETURN 1u + +)"); +} + +TEST_CASE_FIXTURE(IrBuilderFixture, "ControlFlowCmpNum") +{ + IrOp nan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0)); + + auto compareFold = [this](IrOp lhs, IrOp rhs, IrCondition cond, bool result) { + IrOp instOp; + IrInst instExpected; + + withTwoBlocks([&](IrOp a, IrOp b) { + instOp = build.inst(IrCmd::JUMP_CMP_NUM, lhs, rhs, build.cond(cond), a, b); + instExpected = IrInst{IrCmd::JUMP, result ? a : b}; + }); + + updateUseCounts(build.function); + constantFold(); + checkEq(instOp, instExpected); + }; + + compareFold(build.constDouble(1), build.constDouble(1), IrCondition::Equal, true); + compareFold(build.constDouble(1), build.constDouble(2), IrCondition::Equal, false); + compareFold(nan, nan, IrCondition::Equal, false); + + compareFold(build.constDouble(1), build.constDouble(1), IrCondition::NotEqual, false); + compareFold(build.constDouble(1), build.constDouble(2), IrCondition::NotEqual, true); + compareFold(nan, nan, IrCondition::NotEqual, true); + + compareFold(build.constDouble(1), build.constDouble(1), IrCondition::Less, false); + compareFold(build.constDouble(1), build.constDouble(2), IrCondition::Less, true); + compareFold(build.constDouble(2), build.constDouble(1), IrCondition::Less, false); + compareFold(build.constDouble(1), nan, IrCondition::Less, false); + + compareFold(build.constDouble(1), build.constDouble(1), IrCondition::NotLess, true); + compareFold(build.constDouble(1), build.constDouble(2), IrCondition::NotLess, false); + compareFold(build.constDouble(2), build.constDouble(1), IrCondition::NotLess, true); + compareFold(build.constDouble(1), nan, IrCondition::NotLess, true); + + compareFold(build.constDouble(1), build.constDouble(1), IrCondition::LessEqual, true); + compareFold(build.constDouble(1), build.constDouble(2), IrCondition::LessEqual, true); + compareFold(build.constDouble(2), build.constDouble(1), IrCondition::LessEqual, false); + compareFold(build.constDouble(1), nan, IrCondition::LessEqual, false); + + compareFold(build.constDouble(1), build.constDouble(1), IrCondition::NotLessEqual, false); + compareFold(build.constDouble(1), build.constDouble(2), IrCondition::NotLessEqual, false); + compareFold(build.constDouble(2), build.constDouble(1), IrCondition::NotLessEqual, true); + compareFold(build.constDouble(1), nan, IrCondition::NotLessEqual, true); + + compareFold(build.constDouble(1), build.constDouble(1), IrCondition::Greater, false); + compareFold(build.constDouble(1), build.constDouble(2), IrCondition::Greater, false); + compareFold(build.constDouble(2), build.constDouble(1), IrCondition::Greater, true); + compareFold(build.constDouble(1), nan, IrCondition::Greater, false); + + compareFold(build.constDouble(1), build.constDouble(1), IrCondition::NotGreater, true); + compareFold(build.constDouble(1), build.constDouble(2), IrCondition::NotGreater, true); + compareFold(build.constDouble(2), build.constDouble(1), IrCondition::NotGreater, false); + compareFold(build.constDouble(1), nan, IrCondition::NotGreater, true); + + compareFold(build.constDouble(1), build.constDouble(1), IrCondition::GreaterEqual, true); + compareFold(build.constDouble(1), build.constDouble(2), IrCondition::GreaterEqual, false); + compareFold(build.constDouble(2), build.constDouble(1), IrCondition::GreaterEqual, true); + compareFold(build.constDouble(1), nan, IrCondition::GreaterEqual, false); + + compareFold(build.constDouble(1), build.constDouble(1), IrCondition::NotGreaterEqual, false); + compareFold(build.constDouble(1), build.constDouble(2), IrCondition::NotGreaterEqual, true); + compareFold(build.constDouble(2), build.constDouble(1), IrCondition::NotGreaterEqual, false); + compareFold(build.constDouble(1), nan, IrCondition::NotGreaterEqual, true); +} + +TEST_SUITE_END(); diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index 1eaec909..c70ef522 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -226,7 +226,7 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases") LUAU_REQUIRE_ERROR_COUNT(1, result); - const char* expectedError; + std::string expectedError; if (FFlag::LuauTypeMismatchInvarianceInError) expectedError = "Type 'bad' could not be converted into 'U'\n" "caused by:\n" diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 683469a8..a267419e 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -95,6 +95,26 @@ TEST_CASE_FIXTURE(Fixture, "infer_that_function_does_not_return_a_table") CHECK_EQ(result.errors[0], (TypeError{Location{Position{5, 8}, Position{5, 24}}, NotATable{typeChecker.numberType}})); } +TEST_CASE_FIXTURE(Fixture, "generalize_table_property") +{ + CheckResult result = check(R"( + local T = {} + + T.foo = function(x) + return x + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + TypeId t = requireType("T"); + const TableType* tt = get(follow(t)); + REQUIRE(tt); + + TypeId fooTy = tt->props.at("foo").type; + CHECK("(a) -> a" == toString(fooTy)); +} + TEST_CASE_FIXTURE(Fixture, "vararg_functions_should_allow_calls_of_any_types_and_size") { CheckResult result = check(R"( diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index d7b0bdb4..0ba889c8 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -303,13 +303,8 @@ TEST_CASE_FIXTURE(Fixture, "calling_self_generic_methods") end )"); - if (FFlag::DebugLuauDeferredConstraintResolution) - LUAU_REQUIRE_NO_ERRORS(result); - else - { - // TODO: Should typecheck but currently errors CLI-39916 - LUAU_REQUIRE_ERRORS(result); - } + // TODO: Should typecheck but currently errors CLI-54277 + LUAU_REQUIRE_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "infer_generic_property") @@ -1053,8 +1048,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument") )"); LUAU_REQUIRE_NO_ERRORS(result); +} - result = check(R"( +TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument_2") +{ + CheckResult result = check(R"( local function map(arr: {a}, f: (a) -> b) local r = {} for i,v in ipairs(arr) do @@ -1068,8 +1066,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument") LUAU_REQUIRE_NO_ERRORS(result); REQUIRE_EQ("{boolean}", toString(requireType("r"))); +} - check(R"( +TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument_3") +{ + CheckResult result = check(R"( local function foldl(arr: {a}, init: b, f: (b, a) -> b) local r = init for i,v in ipairs(arr) do @@ -1214,10 +1215,6 @@ TEST_CASE_FIXTURE(Fixture, "quantify_functions_even_if_they_have_an_explicit_gen TEST_CASE_FIXTURE(Fixture, "do_not_always_instantiate_generic_intersection_types") { - ScopedFastFlag sff[] = { - {"LuauMaybeGenericIntersectionTypes", true}, - }; - CheckResult result = check(R"( --!strict type Array = { [number]: T } diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index feb04c29..d75f00a2 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -464,7 +464,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus") local foo local mt = {} - mt.__unm = function(val: typeof(foo)): string + mt.__unm = function(val): string return tostring(val.value) .. "test" end diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index fb44ec4d..50056290 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -1478,8 +1478,6 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_take_the_length") { - ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true}; - CheckResult result = check(R"( local function f(x: unknown) if typeof(x) == "table" then @@ -1488,8 +1486,16 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_take_the_length end )"); - LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("table", toString(requireTypeAtPosition({3, 29}))); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_NO_ERRORS(result); + CHECK_EQ("a & table", toString(requireTypeAtPosition({3, 29}))); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ("unknown", toString(requireTypeAtPosition({3, 29}))); + } } TEST_CASE_FIXTURE(RefinementClassFixture, "refine_a_param_that_got_resolved_during_constraint_solving_stage") diff --git a/tests/TypeInfer.tryUnify.test.cpp b/tests/TypeInfer.tryUnify.test.cpp index 8a55c5cf..a22149c7 100644 --- a/tests/TypeInfer.tryUnify.test.cpp +++ b/tests/TypeInfer.tryUnify.test.cpp @@ -362,8 +362,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "fuzz_tail_unification_issue") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_unify_any_should_check_log") { - ScopedFastFlag luauUnifyAnyTxnLog{"LuauUnifyAnyTxnLog", true}; - CheckResult result = check(R"( repeat _._,_ = nil diff --git a/tools/faillist.txt b/tools/faillist.txt index 565982cf..0a09f3f6 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -1,8 +1,4 @@ -AnnotationTests.corecursive_types_error_on_tight_loop -AnnotationTests.duplicate_type_param_name -AnnotationTests.generic_aliases_are_cloned_properly -AnnotationTests.occurs_check_on_cyclic_intersection_type -AnnotationTests.occurs_check_on_cyclic_union_type +AnnotationTests.instantiate_type_fun_should_not_trip_rbxassert AnnotationTests.too_many_type_params AnnotationTests.two_type_params AstQuery.last_argument_function_call_type @@ -14,9 +10,6 @@ AutocompleteTest.autocomplete_oop_implicit_self AutocompleteTest.autocomplete_string_singleton_equality AutocompleteTest.do_compatible_self_calls AutocompleteTest.do_wrong_compatible_self_calls -AutocompleteTest.type_correct_expected_argument_type_pack_suggestion -AutocompleteTest.type_correct_expected_argument_type_suggestion_self -AutocompleteTest.type_correct_expected_return_type_pack_suggestion AutocompleteTest.type_correct_expected_return_type_suggestion AutocompleteTest.type_correct_suggestion_for_overloads BuiltinTests.aliased_string_format @@ -51,41 +44,32 @@ BuiltinTests.table_pack_variadic DefinitionTests.class_definition_overload_metamethods DefinitionTests.class_definition_string_props DefinitionTests.definition_file_classes -DefinitionTests.definitions_symbols_are_generated_for_recursively_referenced_types -DefinitionTests.single_class_type_identity_in_global_types FrontendTest.environments FrontendTest.nocheck_cycle_used_by_checked -FrontendTest.reexport_cyclic_type -GenericsTests.apply_type_function_nested_generics1 GenericsTests.apply_type_function_nested_generics2 GenericsTests.better_mismatch_error_messages +GenericsTests.bound_tables_do_not_clone_original_fields GenericsTests.check_mutual_generic_functions GenericsTests.correctly_instantiate_polymorphic_member_functions GenericsTests.do_not_infer_generic_functions -GenericsTests.duplicate_generic_type_packs -GenericsTests.duplicate_generic_types GenericsTests.generic_argument_count_too_few GenericsTests.generic_argument_count_too_many GenericsTests.generic_functions_should_be_memory_safe -GenericsTests.generic_table_method GenericsTests.generic_type_pack_parentheses GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments -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_property GenericsTests.instantiated_function_argument_names GenericsTests.instantiation_sharing_types GenericsTests.no_stack_overflow_from_quantifying -GenericsTests.reject_clashing_generic_and_pack_names GenericsTests.self_recursive_instantiated_param -IntersectionTypes.no_stack_overflow_from_flattenintersection IntersectionTypes.select_correct_union_fn IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions IntersectionTypes.table_intersection_write_sealed IntersectionTypes.table_intersection_write_sealed_indirect IntersectionTypes.table_write_sealed_indirect -ModuleTests.any_persistance_does_not_leak ModuleTests.clone_self_property ModuleTests.deepClone_cyclic_table NonstrictModeTests.for_in_iterator_variables_are_any @@ -102,10 +86,6 @@ NonstrictModeTests.parameters_having_type_any_are_optional NonstrictModeTests.table_dot_insert_and_recursive_calls NonstrictModeTests.table_props_are_any Normalize.cyclic_table_normalizes_sensibly -ParseErrorRecovery.generic_type_list_recovery -ParseErrorRecovery.recovery_of_parenthesized_expressions -ParserTests.parse_nesting_based_end_detection_failsafe_earlier -ParserTests.parse_nesting_based_end_detection_local_function ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal ProvisionalTests.bail_early_if_unification_is_too_complicated ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack @@ -114,7 +94,6 @@ ProvisionalTests.free_options_cannot_be_unified_together ProvisionalTests.generic_type_leak_to_module_interface_variadic ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns ProvisionalTests.pcall_returns_at_least_two_value_but_function_returns_nothing -ProvisionalTests.refine_unknown_to_table_then_clone_it ProvisionalTests.setmetatable_constrains_free_type_into_free_table ProvisionalTests.specialization_binds_with_prototypes_too_early ProvisionalTests.table_insert_with_a_singleton_argument @@ -122,10 +101,11 @@ ProvisionalTests.typeguard_inference_incomplete ProvisionalTests.weirditer_should_not_loop_forever RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string RefinementTest.discriminate_tag -RefinementTest.else_with_no_explicit_expression_should_also_refine_the_tagged_union RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil RefinementTest.narrow_property_of_a_bounded_variable RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true +RefinementTest.refine_a_param_that_got_resolved_during_constraint_solving_stage +RefinementTest.refine_unknowns RefinementTest.type_guard_can_filter_for_intersection_of_tables RefinementTest.type_narrow_for_all_the_userdata RefinementTest.type_narrow_to_vector @@ -157,7 +137,6 @@ TableTests.found_like_key_in_table_property_access TableTests.found_multiple_like_keys TableTests.function_calls_produces_sealed_table_given_unsealed_table TableTests.fuzz_table_unify_instantiated_table -TableTests.fuzz_table_unify_instantiated_table_with_prop_realloc TableTests.generic_table_instantiation_potential_regression TableTests.give_up_after_one_metatable_index_look_up TableTests.indexer_on_sealed_table_must_unify_with_free_table @@ -198,39 +177,31 @@ TableTests.table_simple_call TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors TableTests.table_unification_4 -TableTests.tc_member_function_2 TableTests.unifying_tables_shouldnt_uaf2 TableTests.used_colon_instead_of_dot TableTests.used_dot_instead_of_colon ToString.exhaustive_toString_of_cyclic_table -ToString.function_type_with_argument_names_generic ToString.named_metatable_toStringNamedFunction ToString.toStringDetailed2 ToString.toStringErrorPack ToString.toStringNamedFunction_generic_pack ToString.toStringNamedFunction_hide_self_param -ToString.toStringNamedFunction_hide_type_params -ToString.toStringNamedFunction_id ToString.toStringNamedFunction_include_self_param ToString.toStringNamedFunction_map -ToString.toStringNamedFunction_variadics TryUnifyTests.cli_41095_concat_log_in_sealed_table_unification 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.variadics_should_use_reversed_properly TypeAliases.cannot_create_cyclic_type_with_unknown_module -TypeAliases.corecursive_types_generic TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any TypeAliases.generic_param_remap TypeAliases.mismatched_generic_type_param -TypeAliases.mutually_recursive_types_errors 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_type_alias_of_recursive_template_table_type TypeAliases.type_alias_local_mutation TypeAliases.type_alias_local_rename TypeAliases.type_alias_of_an_imported_recursive_generic_type @@ -298,7 +269,7 @@ TypeInferLoops.loop_iter_trailing_nil TypeInferLoops.properly_infer_iteratee_is_a_free_table TypeInferLoops.unreachable_code_after_infinite_loop TypeInferModules.custom_require_global -TypeInferModules.do_not_modify_imported_types_4 +TypeInferModules.do_not_modify_imported_types_5 TypeInferModules.module_type_conflict TypeInferModules.module_type_conflict_instantiated TypeInferModules.type_error_of_unknown_qualified_type @@ -312,6 +283,7 @@ TypeInferOperators.cannot_compare_tables_that_do_not_have_the_same_metatable TypeInferOperators.cannot_indirectly_compare_types_that_do_not_have_a_metatable TypeInferOperators.cannot_indirectly_compare_types_that_do_not_offer_overloaded_ordering_operators TypeInferOperators.cli_38355_recursive_union +TypeInferOperators.compound_assign_metatable TypeInferOperators.compound_assign_mismatch_metatable TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops TypeInferOperators.in_nonstrict_mode_strip_nil_from_intersections_when_considering_relational_operators @@ -319,6 +291,7 @@ TypeInferOperators.infer_any_in_all_modes_when_lhs_is_unknown TypeInferOperators.operator_eq_completely_incompatible TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs +TypeInferOperators.typecheck_unary_len_error TypeInferOperators.UnknownGlobalCompoundAssign TypeInferOperators.unrelated_classes_cannot_be_compared TypeInferOperators.unrelated_primitives_cannot_be_compared @@ -341,11 +314,8 @@ TypePackTests.type_alias_defaults_confusing_types TypePackTests.type_alias_defaults_recursive_type TypePackTests.type_alias_type_pack_multi TypePackTests.type_alias_type_pack_variadic -TypePackTests.type_alias_type_packs TypePackTests.type_alias_type_packs_errors -TypePackTests.type_alias_type_packs_import TypePackTests.type_alias_type_packs_nested -TypePackTests.type_pack_type_parameters TypePackTests.unify_variadic_tails_in_arguments TypePackTests.unify_variadic_tails_in_arguments_free TypePackTests.variadic_packs @@ -360,7 +330,6 @@ TypeSingletons.table_properties_type_error_escapes TypeSingletons.taking_the_length_of_union_of_string_singleton TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton TypeSingletons.widening_happens_almost_everywhere -TypeSingletons.widening_happens_almost_everywhere_except_for_tables UnionTypes.index_on_a_union_type_with_missing_property UnionTypes.optional_assignment_errors UnionTypes.optional_call_error