diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index 18ff30921..65599e498 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 29afabf38..085b67328 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 66d3e8f3f..de7b3a044 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 dfb35cbdb..403551f67 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 @@ struct Frontend 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 dd6e11468..85e27168a 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 c17169f45..7bb57208c 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 f773863c9..aa605bdf0 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 = scope; - if (hasGenerics) - { - defnScope = childScope(alias, scope); - } + ScopePtr defnScope = childScope(alias, scope); - TypeId initialType = freshType(scope); - TypeFun initialFun = TypeFun{initialType}; + TypeId initialType = arena->addType(BlockedType{}); + TypeFun initialFun{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 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 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 (auto f = stat->as()) - visit(scope, f); - else if (auto f = stat->as()) - visit(scope, f); + 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 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{}); } - - LUAU_ASSERT(get(bindingIt->second.type)); - - // 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); - - addConstraint(scope, alias->location, NameConstraint{ty, alias->name.value}); + else + asMutable(aliasTy)->ty.emplace(ty); + + std::vector typeParams; + for (auto tyParam : createGenerics(*defnScope, alias->generics, /* useCache */ true)) + typeParams.push_back(tyParam.second.ty); + + std::vector typePackParams; + for (auto tpParam : createGenericPacks(*defnScope, alias->genericPacks, /* useCache */ true)) + typePackParams.push_back(tpParam.second.tp); + + 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 76fd0bca8..879dac39b 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}); + + 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 inner constraints + // 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 cffd00c91..7e7166037 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 a70d6dda7..fb61b4ab3 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 c0f4405c6..b51b7c9a6 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 f874a0b7b..2f69f6980 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 133e324b2..4322a0daa 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 bda062af8..7104f2e73 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 69a40356b..63baea8b7 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 28f5b29bd..6a7094684 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 1aef9a3fc..3e95813bb 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 5a23861e4..918a8829a 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 03bb18146..3c816554a 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 fdbdf6670..0885c0562 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 0c1a89668..2ff1c0d14 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]; + + 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); - kill(function, start, end); + 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 a14cc1e65..afd364018 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 3fd0e9cd9..d238e9eca 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 66aec7646..c46697a26 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 9d21973dc..f245ca933 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 4ed872862..4bb638e83 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 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptJumpCmpNum") } 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 1eaec909c..c70ef5226 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 683469a82..a267419e0 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 d7b0bdb4e..0ba889c89 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 feb04c29b..d75f00a2d 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 fb44ec4d4..50056290b 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 8a55c5cf1..a22149c71 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 565982cf2..0a09f3f64 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