Sync to upstream/release/564 (#841)

This week we only have updates to new type solver and JIT. Both projects
are still in the process of being built out. Neither are ready for
general use yet.

In the new solver, we fixed issues with recursive type aliases.
Duplicated type parameters are once again reported, exported types are
being recorder and function argument names are placed inside function
types.
We also made improvements to restore parts of bidirectional type
tracking.

On native code generation side, namecall instruction lowering was fixed,
we fixed inconsistencies in IR command definitions and added utility
function to help with constant folding.
This commit is contained in:
vegorov-rbx 2023-02-18 01:41:51 +02:00 committed by GitHub
parent 2e5f95ca58
commit b570ff0a37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 1044 additions and 380 deletions

View file

@ -90,6 +90,8 @@ struct NameConstraint
TypeId namedType; TypeId namedType;
std::string name; std::string name;
bool synthetic = false; bool synthetic = false;
std::vector<TypeId> typeParameters;
std::vector<TypePackId> typePackParameters;
}; };
// target ~ inst target // target ~ inst target
@ -101,7 +103,6 @@ struct TypeAliasExpansionConstraint
struct FunctionCallConstraint struct FunctionCallConstraint
{ {
std::vector<NotNull<const struct Constraint>> innerConstraints;
TypeId fn; TypeId fn;
TypePackId argsPack; TypePackId argsPack;
TypePackId result; TypePackId result;

View file

@ -162,10 +162,10 @@ struct ConstraintGraphBuilder
void visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction); void visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction);
void visit(const ScopePtr& scope, AstStatError* error); void visit(const ScopePtr& scope, AstStatError* error);
InferencePack checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<TypeId>& expectedTypes = {}); InferencePack checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<std::optional<TypeId>>& expectedTypes = {});
InferencePack checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector<TypeId>& expectedTypes = {}); InferencePack checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector<std::optional<TypeId>>& expectedTypes = {});
InferencePack checkPack(const ScopePtr& scope, AstExprCall* call, const std::vector<TypeId>& expectedTypes); InferencePack checkPack(const ScopePtr& scope, AstExprCall* call);
/** /**
* Checks an expression that is expected to evaluate to one type. * 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); TypePackId resolveTypePack(const ScopePtr& scope, const AstTypeList& list, bool inTypeArguments);
std::vector<std::pair<Name, GenericTypeDefinition>> createGenerics(const ScopePtr& scope, AstArray<AstGenericType> generics); std::vector<std::pair<Name, GenericTypeDefinition>> createGenerics(
std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks(const ScopePtr& scope, AstArray<AstGenericTypePack> packs); const ScopePtr& scope, AstArray<AstGenericType> generics, bool useCache = false);
std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks(
const ScopePtr& scope, AstArray<AstGenericTypePack> packs, bool useCache = false);
Inference flattenPack(const ScopePtr& scope, Location location, InferencePack pack); Inference flattenPack(const ScopePtr& scope, Location location, InferencePack pack);

View file

@ -183,7 +183,7 @@ struct ConstraintSolver
/** Pushes a new solver constraint to the solver. /** Pushes a new solver constraint to the solver.
* @param cv the body of the constraint. * @param cv the body of the constraint.
**/ **/
void pushConstraint(NotNull<Scope> scope, const Location& location, ConstraintV cv); NotNull<Constraint> pushConstraint(NotNull<Scope> scope, const Location& location, ConstraintV cv);
/** /**
* Attempts to resolve a module from its module information. Returns the * Attempts to resolve a module from its module information. Returns the

View file

@ -162,7 +162,7 @@ struct Frontend
ScopePtr getGlobalScope(); ScopePtr getGlobalScope();
private: private:
ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector<RequireCycle> requireCycles, ModulePtr check(const SourceModule& sourceModule, Mode mode, std::vector<RequireCycle> requireCycles,
bool forAutocomplete = false); bool forAutocomplete = false);
std::pair<SourceNode*, SourceModule*> getSourceNode(const ModuleName& name); std::pair<SourceNode*, SourceModule*> getSourceNode(const ModuleName& name);
@ -202,4 +202,16 @@ private:
ScopePtr globalScope; ScopePtr globalScope;
}; };
ModulePtr check(
const SourceModule& sourceModule,
const std::vector<RequireCycle>& requireCycles,
NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> iceHandler,
NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver,
const ScopePtr& globalScope,
NotNull<UnifierSharedState> unifierState,
FrontendOptions options
);
} // namespace Luau } // namespace Luau

View file

@ -16,7 +16,6 @@
LUAU_FASTFLAGVARIABLE(LuauCompleteTableKeysBetter, false); LUAU_FASTFLAGVARIABLE(LuauCompleteTableKeysBetter, false);
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInWhile, false); LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInWhile, false);
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInFor, false); LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInFor, false);
LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringContent, false);
static const std::unordered_set<std::string> kStatementStartingKeywords = { static const std::unordered_set<std::string> kStatementStartingKeywords = {
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; "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<std::string> getStringContents(const AstNode* node) static std::optional<std::string> getStringContents(const AstNode* node)
{ {
if (!FFlag::LuauAutocompleteStringContent)
return std::nullopt;
if (const AstExprConstantString* string = node->as<AstExprConstantString>()) if (const AstExprConstantString* string = node->as<AstExprConstantString>())
{ {
return std::string(string->value.data, string->value.size); return std::string(string->value.data, string->value.size);

View file

@ -15,8 +15,6 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAGVARIABLE(LuauBuiltInMetatableNoBadSynthetic, false)
/** FIXME: Many of these type definitions are not quite completely accurate. /** 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 * 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<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
if (tableName == metatableName) if (tableName == metatableName)
mtv.syntheticName = tableName; mtv.syntheticName = tableName;
else if (!FFlag::LuauBuiltInMetatableNoBadSynthetic)
mtv.syntheticName = "{ @metatable: " + metatableName + ", " + tableName + " }";
} }
TypeId mtTy = arena.addType(mtv); TypeId mtTy = arena.addType(mtv);

View file

@ -11,6 +11,8 @@
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include <algorithm>
LUAU_FASTINT(LuauCheckRecursionLimit); LUAU_FASTINT(LuauCheckRecursionLimit);
LUAU_FASTFLAG(DebugLuauLogSolverToJson); LUAU_FASTFLAG(DebugLuauLogSolverToJson);
LUAU_FASTFLAG(DebugLuauMagicTypes); LUAU_FASTFLAG(DebugLuauMagicTypes);
@ -334,13 +336,12 @@ void ConstraintGraphBuilder::visitBlockWithoutChildScope(const ScopePtr& scope,
// In order to enable mutually-recursive type aliases, we need to // In order to enable mutually-recursive type aliases, we need to
// populate the type bindings before we actually check any of the // populate the type bindings before we actually check any of the
// alias statements. Since we're not ready to actually resolve // alias statements.
// any of the annotations, we just use a fresh type for now.
for (AstStat* stat : block->body) for (AstStat* stat : block->body)
{ {
if (auto alias = stat->as<AstStatTypeAlias>()) if (auto alias = stat->as<AstStatTypeAlias>())
{ {
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); auto it = aliasDefinitionLocations.find(alias->name.value);
LUAU_ASSERT(it != aliasDefinitionLocations.end()); LUAU_ASSERT(it != aliasDefinitionLocations.end());
@ -348,30 +349,28 @@ void ConstraintGraphBuilder::visitBlockWithoutChildScope(const ScopePtr& scope,
continue; continue;
} }
bool hasGenerics = alias->generics.size > 0 || alias->genericPacks.size > 0; ScopePtr defnScope = childScope(alias, scope);
ScopePtr defnScope = scope; TypeId initialType = arena->addType(BlockedType{});
if (hasGenerics) TypeFun initialFun{initialType};
{
defnScope = childScope(alias, scope);
}
TypeId initialType = freshType(scope); for (const auto& [name, gen] : createGenerics(defnScope, alias->generics, /* useCache */ true))
TypeFun initialFun = TypeFun{initialType};
for (const auto& [name, gen] : createGenerics(defnScope, alias->generics))
{ {
initialFun.typeParams.push_back(gen); initialFun.typeParams.push_back(gen);
defnScope->privateTypeBindings[name] = TypeFun{gen.ty}; 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); initialFun.typePackParams.push_back(genPack);
defnScope->privateTypePackBindings[name] = genPack.tp; 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; astTypeAliasDefiningScopes[alias] = defnScope;
aliasDefinitionLocations[alias->name.value] = alias->location; aliasDefinitionLocations[alias->name.value] = alias->location;
} }
@ -387,42 +386,46 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStat* stat)
if (auto s = stat->as<AstStatBlock>()) if (auto s = stat->as<AstStatBlock>())
visit(scope, s); visit(scope, s);
else if (auto i = stat->as<AstStatIf>())
visit(scope, i);
else if (auto s = stat->as<AstStatWhile>())
visit(scope, s);
else if (auto s = stat->as<AstStatRepeat>())
visit(scope, s);
else if (stat->is<AstStatBreak>() || stat->is<AstStatContinue>())
{
// Nothing
}
else if (auto r = stat->as<AstStatReturn>())
visit(scope, r);
else if (auto e = stat->as<AstStatExpr>())
checkPack(scope, e->expr);
else if (auto s = stat->as<AstStatLocal>()) else if (auto s = stat->as<AstStatLocal>())
visit(scope, s); visit(scope, s);
else if (auto s = stat->as<AstStatFor>()) else if (auto s = stat->as<AstStatFor>())
visit(scope, s); visit(scope, s);
else if (auto s = stat->as<AstStatForIn>()) else if (auto s = stat->as<AstStatForIn>())
visit(scope, s); visit(scope, s);
else if (auto s = stat->as<AstStatWhile>())
visit(scope, s);
else if (auto s = stat->as<AstStatRepeat>())
visit(scope, s);
else if (auto f = stat->as<AstStatFunction>())
visit(scope, f);
else if (auto f = stat->as<AstStatLocalFunction>())
visit(scope, f);
else if (auto r = stat->as<AstStatReturn>())
visit(scope, r);
else if (auto a = stat->as<AstStatAssign>()) else if (auto a = stat->as<AstStatAssign>())
visit(scope, a); visit(scope, a);
else if (auto a = stat->as<AstStatCompoundAssign>()) else if (auto a = stat->as<AstStatCompoundAssign>())
visit(scope, a); visit(scope, a);
else if (auto e = stat->as<AstStatExpr>()) else if (auto f = stat->as<AstStatFunction>())
checkPack(scope, e->expr); visit(scope, f);
else if (auto i = stat->as<AstStatIf>()) else if (auto f = stat->as<AstStatLocalFunction>())
visit(scope, i); visit(scope, f);
else if (auto a = stat->as<AstStatTypeAlias>()) else if (auto a = stat->as<AstStatTypeAlias>())
visit(scope, a); visit(scope, a);
else if (auto s = stat->as<AstStatDeclareGlobal>()) else if (auto s = stat->as<AstStatDeclareGlobal>())
visit(scope, s); visit(scope, s);
else if (auto s = stat->as<AstStatDeclareClass>())
visit(scope, s);
else if (auto s = stat->as<AstStatDeclareFunction>()) else if (auto s = stat->as<AstStatDeclareFunction>())
visit(scope, s); visit(scope, s);
else if (auto s = stat->as<AstStatDeclareClass>())
visit(scope, s);
else if (auto s = stat->as<AstStatError>()) else if (auto s = stat->as<AstStatError>())
visit(scope, s); visit(scope, s);
else else
LUAU_ASSERT(0); LUAU_ASSERT(0 && "Internal error: Unknown AstStat type");
} }
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local) void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
@ -482,7 +485,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
} }
else else
{ {
std::vector<TypeId> expectedTypes; std::vector<std::optional<TypeId>> expectedTypes;
if (hasAnnotation) if (hasAnnotation)
expectedTypes.insert(begin(expectedTypes), begin(varTypes) + i, end(varTypes)); 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{}); TypeId generalizedType = arena->addType(BlockedType{});
Checkpoint start = checkpoint(this);
FunctionSignature sig = checkFunctionSignature(scope, function->func); FunctionSignature sig = checkFunctionSignature(scope, function->func);
if (AstExprLocal* localName = function->name->as<AstExprLocal>()) if (AstExprLocal* localName = function->name->as<AstExprLocal>())
@ -724,7 +728,6 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
if (generalizedType == nullptr) if (generalizedType == nullptr)
ice->ice("generalizedType == nullptr", function->location); ice->ice("generalizedType == nullptr", function->location);
Checkpoint start = checkpoint(this);
checkFunctionBody(sig.bodyScope, function->func); checkFunctionBody(sig.bodyScope, function->func);
Checkpoint end = checkpoint(this); 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. // 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 // If this is the case, then we can expect that the return expression
// conforms to that. // conforms to that.
std::vector<TypeId> expectedTypes; std::vector<std::optional<TypeId>> expectedTypes;
for (TypeId ty : scope->returnType) for (TypeId ty : scope->returnType)
expectedTypes.push_back(ty); expectedTypes.push_back(ty);
@ -764,8 +767,21 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign* assign)
{ {
TypePackId varPackId = checkLValues(scope, assign->vars); TypePackId varPackId = checkLValues(scope, assign->vars);
TypePack expectedTypes = extendTypePack(*arena, builtinTypes, varPackId, assign->values.size); TypePack expectedPack = extendTypePack(*arena, builtinTypes, varPackId, assign->values.size);
TypePackId valuePack = checkPack(scope, assign->values, expectedTypes.head).tp;
std::vector<std::optional<TypeId>> expectedTypes;
expectedTypes.reserve(expectedPack.head.size());
for (TypeId ty : expectedPack.head)
{
ty = follow(ty);
if (get<FreeType>(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}); 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<BlockedType>(needle));
haystack = follow(haystack);
auto checkHaystack = [needle](TypeId haystack) {
return occursCheck(needle, haystack);
};
if (needle == haystack)
return true;
else if (auto ut = get<UnionType>(haystack))
return std::any_of(begin(ut), end(ut), checkHaystack);
else if (auto it = get<IntersectionType>(haystack))
return std::any_of(begin(it), end(it), checkHaystack);
return false;
}
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alias) void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alias)
{ {
auto bindingIt = scope->privateTypeBindings.find(alias->name.value); ScopePtr* defnScope = astTypeAliasDefiningScopes.find(alias);
ScopePtr* defnIt = astTypeAliasDefiningScopes.find(alias);
std::unordered_map<Name, TypeFun>* typeBindings;
if (alias->exported)
typeBindings = &scope->exportedTypeBindings;
else
typeBindings = &scope->privateTypeBindings;
// These will be undefined if the alias was a duplicate definition, in which // These will be undefined if the alias was a duplicate definition, in which
// case we just skip over it. // 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; return;
}
ScopePtr resolvingScope = *defnIt; TypeId ty = resolveType(*defnScope, alias->type, /* inTypeArguments */ false);
TypeId ty = resolveType(resolvingScope, alias->type, /* inTypeArguments */ false);
if (alias->exported) TypeId aliasTy = bindingIt->second.type;
LUAU_ASSERT(get<BlockedType>(aliasTy));
if (occursCheck(aliasTy, ty))
{ {
Name typeName(alias->name.value); asMutable(aliasTy)->ty.emplace<BoundType>(builtinTypes->anyType);
scope->exportedTypeBindings[typeName] = TypeFun{ty}; reportError(alias->nameLocation, OccursCheckFailed{});
} }
else
asMutable(aliasTy)->ty.emplace<BoundType>(ty);
LUAU_ASSERT(get<FreeType>(bindingIt->second.type)); std::vector<TypeId> typeParams;
for (auto tyParam : createGenerics(*defnScope, alias->generics, /* useCache */ true))
typeParams.push_back(tyParam.second.ty);
// Rather than using a subtype constraint, we instead directly bind std::vector<TypePackId> typePackParams;
// the free type we generated in the first pass to the resolved type. for (auto tpParam : createGenericPacks(*defnScope, alias->genericPacks, /* useCache */ true))
// This prevents a case where you could cause another constraint to typePackParams.push_back(tpParam.second.tp);
// bind the free alias type to an unrelated type, causing havoc.
asMutable(bindingIt->second.type)->ty.emplace<BoundType>(ty);
addConstraint(scope, alias->location, NameConstraint{ty, alias->name.value}); addConstraint(scope, alias->type->location,
NameConstraint{
ty,
alias->name.value,
/*synthetic=*/false,
std::move(typeParams),
std::move(typePackParams),
});
} }
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareGlobal* global) void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareGlobal* global)
@ -997,7 +1048,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatError* error)
check(scope, expr); check(scope, expr);
} }
InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<TypeId>& expectedTypes) InferencePack ConstraintGraphBuilder::checkPack(
const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<std::optional<TypeId>>& expectedTypes)
{ {
std::vector<TypeId> head; std::vector<TypeId> head;
std::optional<TypePackId> tail; std::optional<TypePackId> tail;
@ -1010,11 +1062,11 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<
std::optional<TypeId> expectedType; std::optional<TypeId> expectedType;
if (i < expectedTypes.size()) if (i < expectedTypes.size())
expectedType = expectedTypes[i]; expectedType = expectedTypes[i];
head.push_back(check(scope, expr).ty); head.push_back(check(scope, expr, expectedType).ty);
} }
else else
{ {
std::vector<TypeId> expectedTailTypes; std::vector<std::optional<TypeId>> expectedTailTypes;
if (i < expectedTypes.size()) if (i < expectedTypes.size())
expectedTailTypes.assign(begin(expectedTypes) + i, end(expectedTypes)); expectedTailTypes.assign(begin(expectedTypes) + i, end(expectedTypes));
tail = checkPack(scope, expr, expectedTailTypes).tp; 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})}; return InferencePack{arena->addTypePack(TypePack{std::move(head), tail})};
} }
InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector<TypeId>& expectedTypes) InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector<std::optional<TypeId>>& expectedTypes)
{ {
RecursionCounter counter{&recursionCount}; RecursionCounter counter{&recursionCount};
@ -1040,7 +1092,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr*
InferencePack result; InferencePack result;
if (AstExprCall* call = expr->as<AstExprCall>()) if (AstExprCall* call = expr->as<AstExprCall>())
result = checkPack(scope, call, expectedTypes); result = checkPack(scope, call);
else if (AstExprVarargs* varargs = expr->as<AstExprVarargs>()) else if (AstExprVarargs* varargs = expr->as<AstExprVarargs>())
{ {
if (scope->varargPack) if (scope->varargPack)
@ -1062,7 +1114,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr*
return result; return result;
} }
InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCall* call, const std::vector<TypeId>& expectedTypes) InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCall* call)
{ {
std::vector<AstExpr*> exprArgs; std::vector<AstExpr*> exprArgs;
@ -1164,7 +1216,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
} }
else else
{ {
auto [tp, refis] = checkPack(scope, arg, {}); // FIXME? not sure about expectedTypes here auto [tp, refis] = checkPack(scope, arg, {});
argTail = tp; argTail = tp;
argumentRefinements.insert(argumentRefinements.end(), refis.begin(), refis.end()); argumentRefinements.insert(argumentRefinements.end(), refis.begin(), refis.end());
} }
@ -1209,24 +1261,13 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
if (matchAssert(*call) && !argumentRefinements.empty()) if (matchAssert(*call) && !argumentRefinements.empty())
applyRefinements(scope, call->args.data[0]->location, argumentRefinements[0]); applyRefinements(scope, call->args.data[0]->location, argumentRefinements[0]);
TypeId instantiatedType = arena->addType(BlockedType{});
// TODO: How do expectedTypes play into this? Do they? // TODO: How do expectedTypes play into this? Do they?
TypePackId rets = arena->addTypePack(BlockedTypePack{}); TypePackId rets = arena->addTypePack(BlockedTypePack{});
TypePackId argPack = arena->addTypePack(TypePack{args, argTail}); TypePackId argPack = arena->addTypePack(TypePack{args, argTail});
FunctionType ftv(TypeLevel{}, scope.get(), argPack, rets); FunctionType ftv(TypeLevel{}, scope.get(), argPack, rets);
TypeId inferredFnType = arena->addType(ftv);
unqueuedConstraints.push_back(
std::make_unique<Constraint>(NotNull{scope.get()}, call->func->location, InstantiationConstraint{instantiatedType, fnType}));
NotNull<const Constraint> ic(unqueuedConstraints.back().get());
unqueuedConstraints.push_back(
std::make_unique<Constraint>(NotNull{scope.get()}, call->func->location, SubtypeConstraint{instantiatedType, inferredFnType}));
NotNull<Constraint> sc(unqueuedConstraints.back().get());
NotNull<Constraint> fcc = addConstraint(scope, call->func->location, NotNull<Constraint> fcc = addConstraint(scope, call->func->location,
FunctionCallConstraint{ FunctionCallConstraint{
{ic, sc},
fnType, fnType,
argPack, argPack,
rets, rets,
@ -1276,12 +1317,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr, st
else if (expr->is<AstExprVarargs>()) else if (expr->is<AstExprVarargs>())
result = flattenPack(scope, expr->location, checkPack(scope, expr)); result = flattenPack(scope, expr->location, checkPack(scope, expr));
else if (auto call = expr->as<AstExprCall>()) else if (auto call = expr->as<AstExprCall>())
{ result = flattenPack(scope, expr->location, checkPack(scope, call)); // TODO: needs predicates too
std::vector<TypeId> expectedTypes;
if (expectedType)
expectedTypes.push_back(*expectedType);
result = flattenPack(scope, expr->location, checkPack(scope, call, expectedTypes)); // TODO: needs predicates too
}
else if (auto a = expr->as<AstExprFunction>()) else if (auto a = expr->as<AstExprFunction>())
{ {
Checkpoint startCheckpoint = checkpoint(this); Checkpoint startCheckpoint = checkpoint(this);
@ -1883,6 +1919,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
} }
std::vector<TypeId> argTypes; std::vector<TypeId> argTypes;
std::vector<std::optional<FunctionArgument>> argNames;
TypePack expectedArgPack; TypePack expectedArgPack;
const FunctionType* expectedFunction = expectedType ? get<FunctionType>(*expectedType) : nullptr; const FunctionType* expectedFunction = expectedType ? get<FunctionType>(*expectedType) : nullptr;
@ -1895,14 +1932,27 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
genericTypePacks = expectedFunction->genericPacks; 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) for (size_t i = 0; i < fn->args.size; ++i)
{ {
AstLocal* local = fn->args.data[i]; AstLocal* local = fn->args.data[i];
TypeId t = freshType(signatureScope); TypeId t = freshType(signatureScope);
argTypes.push_back(t); argTypes.push_back(t);
argNames.emplace_back(FunctionArgument{local->name.value, local->location});
signatureScope->bindings[local] = Binding{t, local->location}; signatureScope->bindings[local] = Binding{t, local->location};
auto def = dfg->getDef(local);
LUAU_ASSERT(def);
signatureScope->dcrRefinements[*def] = t;
TypeId annotationTy = t; TypeId annotationTy = t;
if (local->annotation) if (local->annotation)
@ -1918,12 +1968,6 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
{ {
addConstraint(signatureScope, local->location, SubtypeConstraint{t, expectedArgPack.head[i]}); 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; TypePackId varargPack = nullptr;
@ -1978,6 +2022,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
actualFunction.hasNoGenerics = !hasGenerics; actualFunction.hasNoGenerics = !hasGenerics;
actualFunction.generics = std::move(genericTypes); actualFunction.generics = std::move(genericTypes);
actualFunction.genericPacks = std::move(genericTypePacks); actualFunction.genericPacks = std::move(genericTypePacks);
actualFunction.argNames = std::move(argNames);
TypeId actualFunctionType = arena->addType(std::move(actualFunction)); TypeId actualFunctionType = arena->addType(std::move(actualFunction));
LUAU_ASSERT(actualFunctionType); LUAU_ASSERT(actualFunctionType);
@ -2085,11 +2130,6 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
} }
else else
{ {
std::string typeName;
if (ref->prefix)
typeName = std::string(ref->prefix->value) + ".";
typeName += ref->name.value;
result = builtinTypes->errorRecoveryType(); result = builtinTypes->errorRecoveryType();
} }
} }
@ -2245,6 +2285,8 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTyp
else if (auto var = tp->as<AstTypePackVariadic>()) else if (auto var = tp->as<AstTypePackVariadic>())
{ {
TypeId ty = resolveType(scope, var->variadicType, inTypeArgument); TypeId ty = resolveType(scope, var->variadicType, inTypeArgument);
if (get<ErrorType>(follow(ty)))
ty = freshType(scope);
result = arena->addTypePack(TypePackVar{VariadicTypePack{ty}}); result = arena->addTypePack(TypePackVar{VariadicTypePack{ty}});
} }
else if (auto gen = tp->as<AstTypePackGeneric>()) else if (auto gen = tp->as<AstTypePackGeneric>())
@ -2287,12 +2329,22 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, const
return arena->addTypePack(TypePack{head, tail}); return arena->addTypePack(TypePack{head, tail});
} }
std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGraphBuilder::createGenerics(const ScopePtr& scope, AstArray<AstGenericType> generics) std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGraphBuilder::createGenerics(
const ScopePtr& scope, AstArray<AstGenericType> generics, bool useCache)
{ {
std::vector<std::pair<Name, GenericTypeDefinition>> result; std::vector<std::pair<Name, GenericTypeDefinition>> result;
for (const auto& generic : generics) 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<TypeId> defaultTy = std::nullopt; std::optional<TypeId> defaultTy = std::nullopt;
if (generic.defaultValue) if (generic.defaultValue)
@ -2305,12 +2357,22 @@ std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGraphBuilder::crea
} }
std::vector<std::pair<Name, GenericTypePackDefinition>> ConstraintGraphBuilder::createGenericPacks( std::vector<std::pair<Name, GenericTypePackDefinition>> ConstraintGraphBuilder::createGenericPacks(
const ScopePtr& scope, AstArray<AstGenericTypePack> generics) const ScopePtr& scope, AstArray<AstGenericTypePack> generics, bool useCache)
{ {
std::vector<std::pair<Name, GenericTypePackDefinition>> result; std::vector<std::pair<Name, GenericTypePackDefinition>> result;
for (const auto& generic : generics) 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<TypePackId> defaultTy = std::nullopt; std::optional<TypePackId> defaultTy = std::nullopt;
if (generic.defaultValue) if (generic.defaultValue)

View file

@ -217,12 +217,6 @@ void dump(ConstraintSolver* cs, ToStringOptions& opts)
int blockCount = it == cs->blockedConstraints.end() ? 0 : int(it->second); int blockCount = it == cs->blockedConstraints.end() ? 0 : int(it->second);
printf("\t%d\t\t%s\n", blockCount, toString(*dep, opts).c_str()); printf("\t%d\t\t%s\n", blockCount, toString(*dep, opts).c_str());
} }
if (auto fcc = get<FunctionCallConstraint>(*c))
{
for (NotNull<const Constraint> 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<const Const
} }
else if (std::optional<TypeId> mm = findMetatableEntry(builtinTypes, errors, operandType, "__unm", constraint->location)) else if (std::optional<TypeId> mm = findMetatableEntry(builtinTypes, errors, operandType, "__unm", constraint->location))
{ {
const FunctionType* ftv = get<FunctionType>(follow(*mm)); TypeId mmTy = follow(*mm);
if (!ftv) if (get<FreeType>(mmTy) && !force)
{ return block(mmTy, constraint);
if (std::optional<TypeId> callMm = findMetatableEntry(builtinTypes, errors, follow(*mm), "__call", constraint->location))
{
ftv = get<FunctionType>(follow(*callMm));
}
}
if (!ftv) TypePackId argPack = arena->addTypePack(TypePack{{operandType}, {}});
{ TypePackId retPack = arena->addTypePack(BlockedTypePack{});
asMutable(c.resultType)->ty.emplace<BoundType>(builtinTypes->errorRecoveryType());
return true;
}
TypePackId argsPack = arena->addTypePack({operandType}); asMutable(c.resultType)->ty.emplace<FreeType>(constraint->scope);
unify(ftv->argTypes, argsPack, constraint->scope);
TypeId result = builtinTypes->errorRecoveryType(); pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{retPack, arena->addTypePack(TypePack{{c.resultType}})});
if (ftv)
{
result = first(ftv->retTypes).value_or(builtinTypes->errorRecoveryType());
}
asMutable(c.resultType)->ty.emplace<BoundType>(result); pushConstraint(constraint->scope, constraint->location, FunctionCallConstraint{mmTy, argPack, retPack, nullptr});
} }
else else
{ {
@ -884,7 +865,11 @@ bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNull<const Constr
if (c.synthetic && !ttv->name) if (c.synthetic && !ttv->name)
ttv->syntheticName = c.name; ttv->syntheticName = c.name;
else else
{
ttv->name = c.name; ttv->name = c.name;
ttv->instantiatedTypeParams = c.typeParameters;
ttv->instantiatedTypePackParams = c.typePackParameters;
}
} }
else if (MetatableType* mtv = getMutable<MetatableType>(target)) else if (MetatableType* mtv = getMutable<MetatableType>(target))
mtv->syntheticName = c.name; mtv->syntheticName = c.name;
@ -1032,6 +1017,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
{ {
// TODO (CLI-56761): Report an error. // TODO (CLI-56761): Report an error.
bindResult(errorRecoveryType()); bindResult(errorRecoveryType());
reportError(GenericError{"Recursive type being used with different parameters"}, constraint->location);
return true; return true;
} }
@ -1119,9 +1105,10 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint) bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint)
{ {
TypeId fn = follow(c.fn); TypeId fn = follow(c.fn);
TypePackId argsPack = follow(c.argsPack);
TypePackId result = follow(c.result); TypePackId result = follow(c.result);
if (isBlocked(c.fn)) if (isBlocked(fn))
{ {
return block(c.fn, constraint); return block(c.fn, constraint);
} }
@ -1156,12 +1143,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
for (TypeId arg : c.argsPack) for (TypeId arg : c.argsPack)
args.push_back(arg); args.push_back(arg);
TypeId instantiatedType = arena->addType(BlockedType{}); argsPack = arena->addTypePack(TypePack{args, {}});
TypeId inferredFnType = arena->addType(FunctionType(TypeLevel{}, constraint->scope.get(), arena->addTypePack(TypePack{args, {}}), c.result)); fn = *callMm;
asMutable(*c.innerConstraints.at(0)).c = InstantiationConstraint{instantiatedType, *callMm};
asMutable(*c.innerConstraints.at(1)).c = SubtypeConstraint{inferredFnType, instantiatedType};
asMutable(c.result)->ty.emplace<FreeTypePack>(constraint->scope); asMutable(c.result)->ty.emplace<FreeTypePack>(constraint->scope);
} }
else else
@ -1178,19 +1161,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
ftv->dcrMagicRefinement(MagicRefinementContext{constraint->scope, c.callSite, c.discriminantTypes}); ftv->dcrMagicRefinement(MagicRefinementContext{constraint->scope, c.callSite, c.discriminantTypes});
} }
if (usedMagic) 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
asMutable(c.result)->ty.emplace<FreeTypePack>(constraint->scope); asMutable(c.result)->ty.emplace<FreeTypePack>(constraint->scope);
} }
@ -1209,22 +1180,24 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
*asMutable(follow(*ty)) = BoundType{builtinTypes->anyType}; *asMutable(follow(*ty)) = BoundType{builtinTypes->anyType};
} }
// Alter the inner constraints. TypeId instantiatedTy = arena->addType(BlockedType{});
LUAU_ASSERT(c.innerConstraints.size() == 2); TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope.get(), argsPack, c.result});
// Anything that is blocked on this constraint must also be blocked on our inner constraints auto ic = pushConstraint(constraint->scope, constraint->location, InstantiationConstraint{instantiatedTy, fn});
auto sc = pushConstraint(constraint->scope, constraint->location, SubtypeConstraint{instantiatedTy, inferredTy});
// Anything that is blocked on this constraint must also be blocked on our
// synthesized constraints.
auto blockedIt = blocked.find(constraint.get()); auto blockedIt = blocked.find(constraint.get());
if (blockedIt != blocked.end()) 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); unblock(c.result);
return true; return true;
} }
@ -1914,12 +1887,14 @@ void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, NotNull<S
unblock(changedPacks); unblock(changedPacks);
} }
void ConstraintSolver::pushConstraint(NotNull<Scope> scope, const Location& location, ConstraintV cv) NotNull<Constraint> ConstraintSolver::pushConstraint(NotNull<Scope> scope, const Location& location, ConstraintV cv)
{ {
std::unique_ptr<Constraint> c = std::make_unique<Constraint>(scope, location, std::move(cv)); std::unique_ptr<Constraint> c = std::make_unique<Constraint>(scope, location, std::move(cv));
NotNull<Constraint> borrow = NotNull(c.get()); NotNull<Constraint> borrow = NotNull(c.get());
solverConstraints.push_back(std::move(c)); solverConstraints.push_back(std::move(c));
unsolvedConstraints.push_back(borrow); unsolvedConstraints.push_back(borrow);
return borrow;
} }
TypeId ConstraintSolver::resolveModule(const ModuleInfo& info, const Location& location) TypeId ConstraintSolver::resolveModule(const ModuleInfo& info, const Location& location)

View file

@ -372,7 +372,7 @@ ExpressionFlowGraph DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprInde
ExpressionFlowGraph DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprIndexExpr* i) ExpressionFlowGraph DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprIndexExpr* i)
{ {
visitExpr(scope, i->expr); visitExpr(scope, i->expr);
visitExpr(scope, i->expr); visitExpr(scope, i->index);
if (i->index->as<AstExprConstantString>()) if (i->index->as<AstExprConstantString>())
{ {
@ -405,6 +405,13 @@ ExpressionFlowGraph DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunc
ExpressionFlowGraph DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprTable* t) 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 {}; return {};
} }

View file

@ -104,7 +104,7 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(std::string_view source, c
module.root = parseResult.root; module.root = parseResult.root;
module.mode = Mode::Definition; module.mode = Mode::Definition;
ModulePtr checkedModule = check(module, Mode::Definition, globalScope, {}); ModulePtr checkedModule = check(module, Mode::Definition, {});
if (checkedModule->errors.size() > 0) if (checkedModule->errors.size() > 0)
return LoadDefinitionFileResult{false, parseResult, checkedModule}; return LoadDefinitionFileResult{false, parseResult, checkedModule};
@ -517,7 +517,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
typeCheckerForAutocomplete.unifierIterationLimit = std::nullopt; typeCheckerForAutocomplete.unifierIterationLimit = std::nullopt;
ModulePtr moduleForAutocomplete = FFlag::DebugLuauDeferredConstraintResolution ModulePtr moduleForAutocomplete = FFlag::DebugLuauDeferredConstraintResolution
? check(sourceModule, mode, environmentScope, requireCycles, /*forAutocomplete*/ true) ? check(sourceModule, mode, requireCycles, /*forAutocomplete*/ true)
: typeCheckerForAutocomplete.check(sourceModule, Mode::Strict, environmentScope); : typeCheckerForAutocomplete.check(sourceModule, Mode::Strict, environmentScope);
moduleResolverForAutocomplete.modules[moduleName] = moduleForAutocomplete; moduleResolverForAutocomplete.modules[moduleName] = moduleForAutocomplete;
@ -544,7 +544,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
typeChecker.requireCycles = requireCycles; typeChecker.requireCycles = requireCycles;
ModulePtr module = FFlag::DebugLuauDeferredConstraintResolution ? check(sourceModule, mode, environmentScope, requireCycles) ModulePtr module = FFlag::DebugLuauDeferredConstraintResolution ? check(sourceModule, mode, requireCycles)
: typeChecker.check(sourceModule, mode, environmentScope); : typeChecker.check(sourceModule, mode, environmentScope);
stats.timeCheck += getTimestamp() - timestamp; stats.timeCheck += getTimestamp() - timestamp;
@ -855,11 +855,19 @@ ScopePtr Frontend::getGlobalScope()
return globalScope; return globalScope;
} }
ModulePtr Frontend::check( ModulePtr check(
const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector<RequireCycle> requireCycles, bool forAutocomplete) const SourceModule& sourceModule,
{ const std::vector<RequireCycle>& requireCycles,
NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> iceHandler,
NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver,
const ScopePtr& globalScope,
NotNull<UnifierSharedState> unifierState,
FrontendOptions options
) {
ModulePtr result = std::make_shared<Module>(); ModulePtr result = std::make_shared<Module>();
result->reduction = std::make_unique<TypeReduction>(NotNull{&result->internalTypes}, builtinTypes, NotNull{&iceHandler}); result->reduction = std::make_unique<TypeReduction>(NotNull{&result->internalTypes}, builtinTypes, iceHandler);
std::unique_ptr<DcrLogger> logger; std::unique_ptr<DcrLogger> logger;
if (FFlag::DebugLuauLogSolverToJson) 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<ModuleResolver> mr{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}; Normalizer normalizer{&result->internalTypes, builtinTypes, unifierState};
const ScopePtr& globalScope{forAutocomplete ? typeCheckerForAutocomplete.globalScope : typeChecker.globalScope};
Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&typeChecker.unifierState}};
ConstraintGraphBuilder cgb{ ConstraintGraphBuilder cgb{
sourceModule.name, sourceModule.name,
result, result,
&result->internalTypes, &result->internalTypes,
mr, moduleResolver,
builtinTypes, builtinTypes,
NotNull(&iceHandler), iceHandler,
globalScope, globalScope,
logger.get(), logger.get(),
NotNull{&dfg}, NotNull{&dfg},
@ -894,7 +899,7 @@ ModulePtr Frontend::check(
cgb.visit(sourceModule.root); cgb.visit(sourceModule.root);
result->errors = std::move(cgb.errors); 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()}; requireCycles, logger.get()};
if (options.randomizeConstraintResolutionSeed) if (options.randomizeConstraintResolutionSeed)
@ -908,7 +913,7 @@ ModulePtr Frontend::check(
result->scopes = std::move(cgb.scopes); result->scopes = std::move(cgb.scopes);
result->type = sourceModule.type; result->type = sourceModule.type;
result->clonePublicInterface(builtinTypes, iceHandler); result->clonePublicInterface(builtinTypes, *iceHandler);
Luau::check(builtinTypes, logger.get(), sourceModule, result.get()); Luau::check(builtinTypes, logger.get(), sourceModule, result.get());
@ -929,6 +934,22 @@ ModulePtr Frontend::check(
return result; return result;
} }
ModulePtr Frontend::check(
const SourceModule& sourceModule, Mode mode, std::vector<RequireCycle> 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. // Read AST into sourceModules if necessary. Trace require()s. Report parse errors.
std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(const ModuleName& name) std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(const ModuleName& name)
{ {

View file

@ -227,7 +227,10 @@ void Module::clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalEr
// Copy external stuff over to Module itself // Copy external stuff over to Module itself
this->returnType = moduleScope->returnType; 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 bool Module::hasModuleScope() const

View file

@ -24,7 +24,6 @@ LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500) LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAGVARIABLE(LuauMatchReturnsOptionalString, false); LUAU_FASTFLAGVARIABLE(LuauMatchReturnsOptionalString, false);
@ -358,39 +357,24 @@ bool maybeGeneric(TypeId ty)
{ {
LUAU_ASSERT(!FFlag::LuauInstantiateInSubtyping); LUAU_ASSERT(!FFlag::LuauInstantiateInSubtyping);
if (FFlag::LuauMaybeGenericIntersectionTypes)
{
ty = follow(ty);
if (get<FreeType>(ty))
return true;
if (auto ttv = get<TableType>(ty))
{
// TODO: recurse on table types CLI-39914
(void)ttv;
return true;
}
if (auto itv = get<IntersectionType>(ty))
{
return std::any_of(begin(itv), end(itv), maybeGeneric);
}
return isGeneric(ty);
}
ty = follow(ty); ty = follow(ty);
if (get<FreeType>(ty)) if (get<FreeType>(ty))
return true; return true;
else if (auto ttv = get<TableType>(ty))
if (auto ttv = get<TableType>(ty))
{ {
// TODO: recurse on table types CLI-39914 // TODO: recurse on table types CLI-39914
(void)ttv; (void)ttv;
return true; return true;
} }
else
return isGeneric(ty); if (auto itv = get<IntersectionType>(ty))
{
return std::any_of(begin(itv), end(itv), maybeGeneric);
}
return isGeneric(ty);
} }
bool maybeSingleton(TypeId ty) bool maybeSingleton(TypeId ty)

View file

@ -204,12 +204,6 @@ struct TypeChecker2
bestLocation = scopeBounds; 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; return bestScope;
@ -676,18 +670,7 @@ struct TypeChecker2
void visit(AstStatTypeAlias* stat) void visit(AstStatTypeAlias* stat)
{ {
for (const AstGenericType& el : stat->generics) visitGenerics(stat->generics, stat->genericPacks);
{
if (el.defaultValue)
visit(el.defaultValue);
}
for (const AstGenericTypePack& el : stat->genericPacks)
{
if (el.defaultValue)
visit(el.defaultValue);
}
visit(stat->type); visit(stat->type);
} }
@ -701,6 +684,7 @@ struct TypeChecker2
void visit(AstStatDeclareFunction* stat) void visit(AstStatDeclareFunction* stat)
{ {
visitGenerics(stat->generics, stat->genericPacks);
visit(stat->params); visit(stat->params);
visit(stat->retTypes); visit(stat->retTypes);
} }
@ -973,8 +957,9 @@ struct TypeChecker2
void visit(AstExprIndexName* indexName, ValueContext context) 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); const NormalizedType* norm = normalizer.normalize(leftType);
if (!norm) if (!norm)
reportError(NormalizationTooComplex{}, indexName->indexLocation); reportError(NormalizationTooComplex{}, indexName->indexLocation);
@ -993,11 +978,18 @@ struct TypeChecker2
{ {
auto StackPusher = pushStack(fn); auto StackPusher = pushStack(fn);
visitGenerics(fn->generics, fn->genericPacks);
TypeId inferredFnTy = lookupType(fn); TypeId inferredFnTy = lookupType(fn);
const FunctionType* inferredFtv = get<FunctionType>(inferredFnTy); const FunctionType* inferredFtv = get<FunctionType>(inferredFnTy);
LUAU_ASSERT(inferredFtv); 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); auto argIt = begin(inferredFtv->argTypes);
if (fn->self)
++argIt;
for (const auto& arg : fn->args) for (const auto& arg : fn->args)
{ {
if (argIt == end(inferredFtv->argTypes)) if (argIt == end(inferredFtv->argTypes))
@ -1037,6 +1029,7 @@ struct TypeChecker2
NotNull<Scope> scope = stack.back(); NotNull<Scope> scope = stack.back();
TypeId operandType = lookupType(expr->expr); TypeId operandType = lookupType(expr->expr);
TypeId resultType = lookupType(expr);
if (get<AnyType>(operandType) || get<ErrorType>(operandType) || get<NeverType>(operandType)) if (get<AnyType>(operandType) || get<ErrorType>(operandType) || get<NeverType>(operandType))
return; return;
@ -1048,9 +1041,6 @@ struct TypeChecker2
{ {
if (const FunctionType* ftv = get<FunctionType>(follow(*mm))) if (const FunctionType* ftv = get<FunctionType>(follow(*mm)))
{ {
TypePackId expectedArgs = testArena.addTypePack({operandType});
reportErrors(tryUnify(scope, expr->location, expectedArgs, ftv->argTypes));
if (std::optional<TypeId> ret = first(ftv->retTypes)) if (std::optional<TypeId> ret = first(ftv->retTypes))
{ {
if (expr->op == AstExprUnary::Op::Len) 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); reportError(GenericError{format("Metamethod '%s' must return a value", it->second)}, expr->location);
} }
std::optional<TypeId> 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; return;
@ -1413,6 +1422,33 @@ struct TypeChecker2
ice.ice("flattenPack got a weird pack!"); ice.ice("flattenPack got a weird pack!");
} }
void visitGenerics(AstArray<AstGenericType> generics, AstArray<AstGenericTypePack> genericPacks)
{
DenseHashSet<AstName> 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) void visit(AstType* ty)
{ {
if (auto t = ty->as<AstTypeReference>()) if (auto t = ty->as<AstTypeReference>())
@ -1579,8 +1615,7 @@ struct TypeChecker2
void visit(AstTypeFunction* ty) void visit(AstTypeFunction* ty)
{ {
// TODO! visitGenerics(ty->generics, ty->genericPacks);
visit(ty->argTypes); visit(ty->argTypes);
visit(ty->returnTypes); visit(ty->returnTypes);
} }

View file

@ -17,7 +17,6 @@
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
LUAU_FASTFLAG(LuauErrorRecoveryType) LUAU_FASTFLAG(LuauErrorRecoveryType)
LUAU_FASTFLAGVARIABLE(LuauUnifyAnyTxnLog, false)
LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false) LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
LUAU_FASTFLAGVARIABLE(LuauScalarShapeUnifyToMtOwner2, false) LUAU_FASTFLAGVARIABLE(LuauScalarShapeUnifyToMtOwner2, false)
LUAU_FASTFLAGVARIABLE(LuauUninhabitedSubAnything2, false) LUAU_FASTFLAGVARIABLE(LuauUninhabitedSubAnything2, false)
@ -475,40 +474,23 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
return; return;
} }
if (FFlag::LuauUnifyAnyTxnLog) if (log.get<AnyType>(superTy))
{ return tryUnifyWithAny(subTy, builtinTypes->anyType);
if (log.get<AnyType>(superTy))
return tryUnifyWithAny(subTy, builtinTypes->anyType);
if (log.get<ErrorType>(superTy)) if (log.get<ErrorType>(superTy))
return tryUnifyWithAny(subTy, builtinTypes->errorType); return tryUnifyWithAny(subTy, builtinTypes->errorType);
if (log.get<UnknownType>(superTy)) if (log.get<UnknownType>(superTy))
return tryUnifyWithAny(subTy, builtinTypes->unknownType); return tryUnifyWithAny(subTy, builtinTypes->unknownType);
if (log.get<AnyType>(subTy)) if (log.get<AnyType>(subTy))
return tryUnifyWithAny(superTy, builtinTypes->anyType); return tryUnifyWithAny(superTy, builtinTypes->anyType);
if (log.get<ErrorType>(subTy)) if (log.get<ErrorType>(subTy))
return tryUnifyWithAny(superTy, builtinTypes->errorType); return tryUnifyWithAny(superTy, builtinTypes->errorType);
if (log.get<NeverType>(subTy)) if (log.get<NeverType>(subTy))
return tryUnifyWithAny(superTy, builtinTypes->neverType); return tryUnifyWithAny(superTy, builtinTypes->neverType);
}
else
{
if (get<ErrorType>(superTy) || get<AnyType>(superTy) || get<UnknownType>(superTy))
return tryUnifyWithAny(subTy, superTy);
if (get<AnyType>(subTy))
return tryUnifyWithAny(superTy, subTy);
if (log.get<ErrorType>(subTy))
return tryUnifyWithAny(superTy, subTy);
if (log.get<NeverType>(subTy))
return tryUnifyWithAny(superTy, subTy);
}
auto& cache = sharedState.cachedUnify; auto& cache = sharedState.cachedUnify;
@ -2535,18 +2517,9 @@ void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy)
{ {
LUAU_ASSERT(get<AnyType>(anyTy) || get<ErrorType>(anyTy) || get<UnknownType>(anyTy) || get<NeverType>(anyTy)); LUAU_ASSERT(get<AnyType>(anyTy) || get<ErrorType>(anyTy) || get<UnknownType>(anyTy) || get<NeverType>(anyTy));
if (FFlag::LuauUnifyAnyTxnLog) // These types are not visited in general loop below
{ if (log.get<PrimitiveType>(subTy) || log.get<AnyType>(subTy) || log.get<ClassType>(subTy))
// These types are not visited in general loop below return;
if (log.get<PrimitiveType>(subTy) || log.get<AnyType>(subTy) || log.get<ClassType>(subTy))
return;
}
else
{
// These types are not visited in general loop below
if (get<PrimitiveType>(subTy) || get<AnyType>(subTy) || get<ClassType>(subTy))
return;
}
TypePackId anyTp = types->addTypePack(TypePackVar{VariadicTypePack{anyTy}}); TypePackId anyTp = types->addTypePack(TypePackVar{VariadicTypePack{anyTy}});

View file

@ -319,6 +319,9 @@ std::string runCode(lua_State* L, const std::string& source)
lua_insert(T, 1); lua_insert(T, 1);
lua_pcall(T, n, 0, 0); lua_pcall(T, n, 0, 0);
} }
lua_pop(L, 1);
return std::string();
} }
else else
{ {
@ -336,11 +339,9 @@ std::string runCode(lua_State* L, const std::string& source)
error += "\nstack backtrace:\n"; error += "\nstack backtrace:\n";
error += lua_debugtrace(T); 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 // Replaces the top of the lua stack with the metatable __index for the value

View file

@ -55,7 +55,7 @@ enum class IrCmd : uint8_t
// Get pointer (TValue) to table array at index // Get pointer (TValue) to table array at index
// A: pointer (Table) // A: pointer (Table)
// B: unsigned int // B: int
GET_ARR_ADDR, GET_ARR_ADDR,
// Get pointer (LuaNode) to table node element at the active cached slot index // 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) // A: pointer (Table)
DUP_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 // A: double
// B: block // B: block
NUM_TO_INDEX, NUM_TO_INDEX,
@ -216,10 +216,10 @@ enum class IrCmd : uint8_t
// B: unsigned int (import path) // B: unsigned int (import path)
GET_IMPORT, GET_IMPORT,
// Concatenate multiple TValues // Concatenate multiple TValues into a string
// A: Rn (where to store the result) // A: Rn (value start)
// B: unsigned int (index of the first VM stack slot) // B: unsigned int (number of registers to go over)
// C: unsigned int (number of stack slots to go over) // Note: result is stored in the register specified in 'A'
CONCAT, CONCAT,
// Load function upvalue into stack slot // Load function upvalue into stack slot
@ -262,7 +262,8 @@ enum class IrCmd : uint8_t
// Guard against index overflowing the table array size // Guard against index overflowing the table array size
// A: pointer (Table) // A: pointer (Table)
// B: block // B: int (index)
// C: block
CHECK_ARRAY_SIZE, CHECK_ARRAY_SIZE,
// Guard against cached table node slot not matching the actual table node slot for a key // 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 // Prepare loop variables for a generic for loop, jump to the loop backedge unconditionally
// A: unsigned int (bytecode instruction index) // A: unsigned int (bytecode instruction index)
// B: Rn (loop state, updates Rn Rn+1 Rn+2) // B: Rn (loop state, updates Rn Rn+1 Rn+2)
// B: block // C: block
FALLBACK_FORGPREP, 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 enum class IrConstKind : uint8_t
@ -659,6 +664,12 @@ struct IrFunction
LUAU_ASSERT(value.kind == IrConstKind::Double); LUAU_ASSERT(value.kind == IrConstKind::Double);
return value.valueDouble; return value.valueDouble;
} }
IrCondition conditionOp(IrOp op)
{
LUAU_ASSERT(op.kind == IrOpKind::Condition);
return IrCondition(op.index);
}
}; };
} // namespace CodeGen } // namespace CodeGen

View file

@ -10,6 +10,8 @@ namespace Luau
namespace CodeGen namespace CodeGen
{ {
struct IrBuilder;
inline bool isJumpD(LuauOpcode op) inline bool isJumpD(LuauOpcode op)
{ {
switch (op) switch (op)
@ -138,6 +140,7 @@ inline bool hasResult(IrCmd cmd)
case IrCmd::DUP_TABLE: case IrCmd::DUP_TABLE:
case IrCmd::NUM_TO_INDEX: case IrCmd::NUM_TO_INDEX:
case IrCmd::INT_TO_NUM: case IrCmd::INT_TO_NUM:
case IrCmd::SUBSTITUTE:
return true; return true;
default: default:
break; break;
@ -153,6 +156,12 @@ inline bool hasSideEffects(IrCmd cmd)
return !hasResult(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 // Remove a single instruction
void kill(IrFunction& function, IrInst& inst); 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 // 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); 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 CodeGen
} // namespace Luau } // namespace Luau

View file

@ -246,6 +246,8 @@ const char* getCmdName(IrCmd cmd)
return "FALLBACK_DUPCLOSURE"; return "FALLBACK_DUPCLOSURE";
case IrCmd::FALLBACK_FORGPREP: case IrCmd::FALLBACK_FORGPREP:
return "FALLBACK_FORGPREP"; return "FALLBACK_FORGPREP";
case IrCmd::SUBSTITUTE:
return "SUBSTITUTE";
} }
LUAU_UNREACHABLE(); LUAU_UNREACHABLE();
@ -423,8 +425,8 @@ std::string toString(IrFunction& function, bool includeDetails)
{ {
IrInst& inst = function.instructions[index]; IrInst& inst = function.instructions[index];
// Nop is used to replace dead instructions in-place, so it's not that useful to see them // Skip pseudo instructions unless they are still referenced
if (inst.cmd == IrCmd::NOP) if (isPseudo(inst.cmd) && inst.useCount == 0)
continue; continue;
append(ctx.result, " "); append(ctx.result, " ");

View file

@ -20,7 +20,7 @@ namespace Luau
namespace CodeGen 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) IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, NativeState& data, Proto* proto, IrFunction& function)
: build(build) : build(build)
@ -111,7 +111,7 @@ void IrLoweringX64::lower(AssemblyOptions options)
if (options.includeIr) if (options.includeIr)
{ {
build.logAppend("# "); build.logAppend("# ");
toStringDetailed(ctx, block, uint32_t(i)); toStringDetailed(ctx, block, blockIndex);
} }
build.setLabel(block.label); build.setLabel(block.label);
@ -133,9 +133,9 @@ void IrLoweringX64::lower(AssemblyOptions options)
IrInst& inst = function.instructions[index]; IrInst& inst = function.instructions[index];
// Nop is used to replace dead instructions in-place // Skip pseudo instructions, but make sure they are not used at this stage
// Because it doesn't have any effects aside from output (when enabled), we skip it completely // This also prevents them from getting into text output when that's enabled
if (inst.cmd == IrCmd::NOP) if (isPseudo(inst.cmd))
{ {
LUAU_ASSERT(inst.useCount == 0); LUAU_ASSERT(inst.useCount == 0);
continue; 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)]); build.mov(inst.regX64, qword[regOp(inst.a) + offsetof(Table, array)]);
if (uintOp(inst.b) != 0) if (intOp(inst.b) != 0)
build.lea(inst.regX64, addr[inst.regX64 + uintOp(inst.b) * sizeof(TValue)]); build.lea(inst.regX64, addr[inst.regX64 + intOp(inst.b) * sizeof(TValue)]);
} }
else else
{ {
@ -688,9 +688,11 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
emitInstGetImportFallback(build, inst.a.index, uintOp(inst.b)); emitInstGetImportFallback(build, inst.a.index, uintOp(inst.b));
break; break;
case IrCmd::CONCAT: case IrCmd::CONCAT:
LUAU_ASSERT(inst.a.kind == IrOpKind::VmReg);
build.mov(rArg1, rState); build.mov(rArg1, rState);
build.mov(dwordReg(rArg2), uintOp(inst.a)); build.mov(dwordReg(rArg2), uintOp(inst.b));
build.mov(dwordReg(rArg3), uintOp(inst.b)); build.mov(dwordReg(rArg3), inst.a.index + uintOp(inst.b) - 1);
build.call(qword[rNativeContext + offsetof(NativeContext, luaV_concat)]); build.call(qword[rNativeContext + offsetof(NativeContext, luaV_concat)]);
emitUpdateBase(build); emitUpdateBase(build);
@ -778,7 +780,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
if (inst.b.kind == IrOpKind::Inst) if (inst.b.kind == IrOpKind::Inst)
build.cmp(dword[regOp(inst.a) + offsetof(Table, sizearray)], regOp(inst.b)); build.cmp(dword[regOp(inst.a) + offsetof(Table, sizearray)], regOp(inst.b));
else if (inst.b.kind == IrOpKind::Constant) 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 else
LUAU_ASSERT(!"Unsupported instruction form"); 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); LUAU_ASSERT(inst.c.kind == IrOpKind::VmReg);
emitInstNameCall(build, pc, uintOp(inst.a), proto->k, blockOp(inst.d).label, blockOp(inst.e).label); emitInstNameCall(build, pc, uintOp(inst.a), proto->k, blockOp(inst.d).label, blockOp(inst.e).label);
jumpOrFallthrough(blockOp(inst.d), next);
break; break;
} }
case IrCmd::LOP_CALL: case IrCmd::LOP_CALL:
@ -1133,7 +1136,7 @@ RegisterX64 IrLoweringX64::allocGprReg(SizeX64 preferredSize)
LUAU_ASSERT( LUAU_ASSERT(
preferredSize == SizeX64::byte || preferredSize == SizeX64::word || preferredSize == SizeX64::dword || preferredSize == SizeX64::qword); preferredSize == SizeX64::byte || preferredSize == SizeX64::word || preferredSize == SizeX64::dword || preferredSize == SizeX64::qword);
for (RegisterX64 reg : gprAlocOrder) for (RegisterX64 reg : kGprAllocOrder)
{ {
if (freeGprMap[reg.index]) if (freeGprMap[reg.index])
{ {

View file

@ -695,10 +695,10 @@ void translateInstGetTableN(IrBuilder& build, const Instruction* pc, int pcpos)
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); 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_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 // TODO: per-component loads and stores might be preferable
IrOp arrElTval = build.inst(IrCmd::LOAD_TVALUE, arrEl); 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)); 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_NO_METATABLE, vb, fallback);
build.inst(IrCmd::CHECK_READONLY, 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 // TODO: per-component loads and stores might be preferable
IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra)); 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); int rc = LUAU_INSN_C(*pc);
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); 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 // TODO: per-component loads and stores might be preferable
IrOp tvb = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(rb)); IrOp tvb = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(rb));

View file

@ -1,6 +1,14 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // 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/IrUtils.h"
#include "Luau/IrBuilder.h"
#include "lua.h"
#include "lnumutils.h"
#include <limits.h>
#include <math.h>
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
@ -8,16 +16,19 @@ namespace CodeGen
static uint32_t getBlockEnd(IrFunction& function, uint32_t start) static uint32_t getBlockEnd(IrFunction& function, uint32_t start)
{ {
LUAU_ASSERT(start < function.instructions.size());
uint32_t end = start; uint32_t end = start;
// Find previous block terminator // Find previous block terminator
while (!isBlockTerminator(function.instructions[end].cmd)) while (!isBlockTerminator(function.instructions[end].cmd))
end++; end++;
LUAU_ASSERT(end < function.instructions.size());
return end; return end;
} }
static void addUse(IrFunction& function, IrOp op) void addUse(IrFunction& function, IrOp op)
{ {
if (op.kind == IrOpKind::Inst) if (op.kind == IrOpKind::Inst)
function.instructions[op.index].useCount++; function.instructions[op.index].useCount++;
@ -25,7 +36,7 @@ static void addUse(IrFunction& function, IrOp op)
function.blocks[op.index].useCount++; function.blocks[op.index].useCount++;
} }
static void removeUse(IrFunction& function, IrOp op) void removeUse(IrFunction& function, IrOp op)
{ {
if (op.kind == IrOpKind::Inst) if (op.kind == IrOpKind::Inst)
removeUse(function, function.instructions[op.index]); removeUse(function, function.instructions[op.index]);
@ -44,6 +55,12 @@ void kill(IrFunction& function, IrInst& inst)
removeUse(function, inst.c); removeUse(function, inst.c);
removeUse(function, inst.d); removeUse(function, inst.d);
removeUse(function, inst.e); removeUse(function, inst.e);
inst.a = {};
inst.b = {};
inst.c = {};
inst.d = {};
inst.e = {};
} }
void kill(IrFunction& function, uint32_t start, uint32_t end) 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 // Kill instructions in reverse order to avoid killing instructions that are still marked as used
for (int i = int(end); i >= int(start); i--) for (int i = int(end); i >= int(start); i--)
{ {
LUAU_ASSERT(unsigned(i) < function.instructions.size());
IrInst& curr = function.instructions[i]; IrInst& curr = function.instructions[i];
if (curr.cmd == IrCmd::NOP) 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) void replace(IrFunction& function, uint32_t instIdx, IrInst replacement)
{ {
IrInst& inst = function.instructions[instIdx]; 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 // Add uses before removing new ones if those are the last ones keeping target operand alive
addUse(function, replacement.a); addUse(function, replacement.a);
@ -111,6 +128,20 @@ void replace(IrFunction& function, uint32_t instIdx, IrInst replacement)
addUse(function, replacement.d); addUse(function, replacement.d);
addUse(function, replacement.e); 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.a);
removeUse(function, inst.b); removeUse(function, inst.b);
removeUse(function, inst.c); removeUse(function, inst.c);
@ -118,14 +149,227 @@ void replace(IrFunction& function, uint32_t instIdx, IrInst replacement)
removeUse(function, inst.e); removeUse(function, inst.e);
inst = replacement; inst = replacement;
}
// If we introduced an earlier terminating instruction, all following instructions become dead void substitute(IrFunction& function, IrInst& inst, IrOp replacement)
if (!isBlockTerminator(prevCmd) && isBlockTerminator(inst.cmd)) {
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; IrInst& src = function.instructions[op.index];
uint32_t end = getBlockEnd(function, start);
kill(function, start, end); if (src.cmd == IrCmd::SUBSTITUTE)
{
op.kind = src.a.kind;
op.index = src.a.index;
// If we substitute with the result of a different instruction, update the use count
if (op.kind == IrOpKind::Inst)
{
IrInst& dst = function.instructions[op.index];
LUAU_ASSERT(dst.cmd != IrCmd::SUBSTITUTE && "chained substitutions are not allowed");
dst.useCount++;
}
LUAU_ASSERT(src.useCount > 0);
src.useCount--;
}
}
}
void applySubstitutions(IrFunction& function, IrInst& inst)
{
applySubstitutions(function, inst.a);
applySubstitutions(function, inst.b);
applySubstitutions(function, inst.c);
applySubstitutions(function, inst.d);
applySubstitutions(function, inst.e);
}
static bool compare(double a, double b, IrCondition cond)
{
switch (cond)
{
case IrCondition::Equal:
return a == b;
case IrCondition::NotEqual:
return a != b;
case IrCondition::Less:
return a < b;
case IrCondition::NotLess:
return !(a < b);
case IrCondition::LessEqual:
return a <= b;
case IrCondition::NotLessEqual:
return !(a <= b);
case IrCondition::Greater:
return a > b;
case IrCondition::NotGreater:
return !(a > b);
case IrCondition::GreaterEqual:
return a >= b;
case IrCondition::NotGreaterEqual:
return !(a >= b);
default:
LUAU_ASSERT(!"unsupported conidtion");
}
return false;
}
void foldConstants(IrBuilder& build, IrFunction& function, uint32_t index)
{
IrInst& inst = function.instructions[index];
switch (inst.cmd)
{
case IrCmd::ADD_INT:
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
{
// We need to avoid signed integer overflow, but we also have to produce a result
// So we add numbers as unsigned and use fixed-width integer types to force a two's complement evaluation
int32_t lhs = function.intOp(inst.a);
int32_t rhs = function.intOp(inst.b);
int sum = int32_t(uint32_t(lhs) + uint32_t(rhs));
substitute(function, inst, build.constInt(sum));
}
break;
case IrCmd::SUB_INT:
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
{
// We need to avoid signed integer overflow, but we also have to produce a result
// So we subtract numbers as unsigned and use fixed-width integer types to force a two's complement evaluation
int32_t lhs = function.intOp(inst.a);
int32_t rhs = function.intOp(inst.b);
int sum = int32_t(uint32_t(lhs) - uint32_t(rhs));
substitute(function, inst, build.constInt(sum));
}
break;
case IrCmd::ADD_NUM:
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
substitute(function, inst, build.constDouble(function.doubleOp(inst.a) + function.doubleOp(inst.b)));
break;
case IrCmd::SUB_NUM:
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
substitute(function, inst, build.constDouble(function.doubleOp(inst.a) - function.doubleOp(inst.b)));
break;
case IrCmd::MUL_NUM:
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
substitute(function, inst, build.constDouble(function.doubleOp(inst.a) * function.doubleOp(inst.b)));
break;
case IrCmd::DIV_NUM:
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
substitute(function, inst, build.constDouble(function.doubleOp(inst.a) / function.doubleOp(inst.b)));
break;
case IrCmd::MOD_NUM:
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
substitute(function, inst, build.constDouble(luai_nummod(function.doubleOp(inst.a), function.doubleOp(inst.b))));
break;
case IrCmd::POW_NUM:
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
substitute(function, inst, build.constDouble(pow(function.doubleOp(inst.a), function.doubleOp(inst.b))));
break;
case IrCmd::UNM_NUM:
if (inst.a.kind == IrOpKind::Constant)
substitute(function, inst, build.constDouble(-function.doubleOp(inst.a)));
break;
case IrCmd::NOT_ANY:
if (inst.a.kind == IrOpKind::Constant)
{
uint8_t a = function.tagOp(inst.a);
if (a == LUA_TNIL)
substitute(function, inst, build.constInt(1));
else if (a != LUA_TBOOLEAN)
substitute(function, inst, build.constInt(0));
else if (inst.b.kind == IrOpKind::Constant)
substitute(function, inst, build.constInt(function.intOp(inst.b) == 1 ? 0 : 1));
}
break;
case IrCmd::JUMP_EQ_TAG:
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
{
if (function.tagOp(inst.a) == function.tagOp(inst.b))
replace(function, index, {IrCmd::JUMP, inst.c});
else
replace(function, index, {IrCmd::JUMP, inst.d});
}
break;
case IrCmd::JUMP_EQ_INT:
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
{
if (function.intOp(inst.a) == function.intOp(inst.b))
replace(function, index, {IrCmd::JUMP, inst.c});
else
replace(function, index, {IrCmd::JUMP, inst.d});
}
break;
case IrCmd::JUMP_CMP_NUM:
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
{
if (compare(function.doubleOp(inst.a), function.doubleOp(inst.b), function.conditionOp(inst.c)))
replace(function, index, {IrCmd::JUMP, inst.d});
else
replace(function, index, {IrCmd::JUMP, inst.e});
}
break;
case IrCmd::NUM_TO_INDEX:
if (inst.a.kind == IrOpKind::Constant)
{
double value = function.doubleOp(inst.a);
// To avoid undefined behavior of casting a value not representable in the target type, we check the range
if (value >= INT_MIN && value <= INT_MAX)
{
int arrIndex = int(value);
if (double(arrIndex) == value)
substitute(function, inst, build.constInt(arrIndex));
else
replace(function, index, {IrCmd::JUMP, inst.b});
}
else
{
replace(function, index, {IrCmd::JUMP, inst.b});
}
}
break;
case IrCmd::INT_TO_NUM:
if (inst.a.kind == IrOpKind::Constant)
substitute(function, inst, build.constDouble(double(function.intOp(inst.a))));
break;
case IrCmd::CHECK_TAG:
if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant)
{
if (function.tagOp(inst.a) == function.tagOp(inst.b))
kill(function, inst);
else
replace(function, index, {IrCmd::JUMP, inst.c}); // Shows a conflict in assumptions on this path
}
break;
default:
break;
} }
} }

View file

@ -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, // 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. // 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 "LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
"LuauTryhardAnd", // waiting for a fix in graphql-lua -> apollo-client-lia -> lua-apps "LuauTryhardAnd", // waiting for a fix in graphql-lua -> apollo-client-lia -> lua-apps
"LuauTypecheckTypeguards", // requires some fixes to lua-apps code (CLI-67030) "LuauTypecheckTypeguards", // requires some fixes to lua-apps code (CLI-67030)

View file

@ -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") TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback")
{ {
ScopedFastFlag luauAutocompleteStringContent{"LuauAutocompleteStringContent", true};
loadDefinition(R"( loadDefinition(R"(
declare function require(path: string): any declare function require(path: string): any
)"); )");

View file

@ -1,4 +1,5 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // 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" #include "Fixture.h"

View file

@ -176,7 +176,22 @@ AstStatBlock* Fixture::parse(const std::string& source, const ParseOptions& pars
{ {
frontend.lint(*sourceModule); 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); throw ParseErrors(result.errors);

View file

@ -2,16 +2,83 @@
#include "Luau/IrBuilder.h" #include "Luau/IrBuilder.h"
#include "Luau/IrAnalysis.h" #include "Luau/IrAnalysis.h"
#include "Luau/IrDump.h" #include "Luau/IrDump.h"
#include "Luau/IrUtils.h"
#include "Luau/OptimizeFinalX64.h" #include "Luau/OptimizeFinalX64.h"
#include "doctest.h" #include "doctest.h"
#include <limits.h>
using namespace Luau::CodeGen; using namespace Luau::CodeGen;
class IrBuilderFixture class IrBuilderFixture
{ {
public: 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<typename F>
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<typename F>
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; 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"); TEST_SUITE_BEGIN("Optimization");
@ -153,7 +220,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag3")
build.beginBlock(block); build.beginBlock(block);
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1)); 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); IrOp opA = build.inst(IrCmd::LOAD_TAG, arrElem);
build.inst(IrCmd::JUMP_EQ_TAG, opA, build.constTag(0), trueBlock, falseBlock); build.inst(IrCmd::JUMP_EQ_TAG, opA, build.constTag(0), trueBlock, falseBlock);
build.inst(IrCmd::LOP_RETURN, build.constUint(0)); 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"( CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
bb_0: bb_0:
%0 = LOAD_POINTER R1 %0 = LOAD_POINTER R1
%1 = GET_ARR_ADDR %0, 0u %1 = GET_ARR_ADDR %0, 0i
%2 = LOAD_TAG %1 %2 = LOAD_TAG %1
JUMP_EQ_TAG %2, tnil, bb_1, bb_2 JUMP_EQ_TAG %2, tnil, bb_1, bb_2
@ -221,3 +288,247 @@ bb_2:
} }
TEST_SUITE_END(); 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();

View file

@ -226,7 +226,7 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const char* expectedError; std::string expectedError;
if (FFlag::LuauTypeMismatchInvarianceInError) if (FFlag::LuauTypeMismatchInvarianceInError)
expectedError = "Type 'bad' could not be converted into 'U<number>'\n" expectedError = "Type 'bad' could not be converted into 'U<number>'\n"
"caused by:\n" "caused by:\n"

View file

@ -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}})); 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<TableType>(follow(t));
REQUIRE(tt);
TypeId fooTy = tt->props.at("foo").type;
CHECK("<a>(a) -> a" == toString(fooTy));
}
TEST_CASE_FIXTURE(Fixture, "vararg_functions_should_allow_calls_of_any_types_and_size") TEST_CASE_FIXTURE(Fixture, "vararg_functions_should_allow_calls_of_any_types_and_size")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(

View file

@ -303,13 +303,8 @@ TEST_CASE_FIXTURE(Fixture, "calling_self_generic_methods")
end end
)"); )");
if (FFlag::DebugLuauDeferredConstraintResolution) // TODO: Should typecheck but currently errors CLI-54277
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_ERRORS(result);
else
{
// TODO: Should typecheck but currently errors CLI-39916
LUAU_REQUIRE_ERRORS(result);
}
} }
TEST_CASE_FIXTURE(Fixture, "infer_generic_property") 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); 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<a, b>(arr: {a}, f: (a) -> b) local function map<a, b>(arr: {a}, f: (a) -> b)
local r = {} local r = {}
for i,v in ipairs(arr) do 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); LUAU_REQUIRE_NO_ERRORS(result);
REQUIRE_EQ("{boolean}", toString(requireType("r"))); 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<a, b>(arr: {a}, init: b, f: (b, a) -> b) local function foldl<a, b>(arr: {a}, init: b, f: (b, a) -> b)
local r = init local r = init
for i,v in ipairs(arr) do 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") TEST_CASE_FIXTURE(Fixture, "do_not_always_instantiate_generic_intersection_types")
{ {
ScopedFastFlag sff[] = {
{"LuauMaybeGenericIntersectionTypes", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
type Array<T> = { [number]: T } type Array<T> = { [number]: T }

View file

@ -464,7 +464,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus")
local foo local foo
local mt = {} local mt = {}
mt.__unm = function(val: typeof(foo)): string mt.__unm = function(val): string
return tostring(val.value) .. "test" return tostring(val.value) .. "test"
end end

View file

@ -1478,8 +1478,6 @@ end
TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_take_the_length") TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_take_the_length")
{ {
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function f(x: unknown) local function f(x: unknown)
if typeof(x) == "table" then if typeof(x) == "table" then
@ -1488,8 +1486,16 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_take_the_length
end end
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("table", toString(requireTypeAtPosition({3, 29}))); {
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") TEST_CASE_FIXTURE(RefinementClassFixture, "refine_a_param_that_got_resolved_during_constraint_solving_stage")

View file

@ -362,8 +362,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "fuzz_tail_unification_issue")
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_unify_any_should_check_log") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_unify_any_should_check_log")
{ {
ScopedFastFlag luauUnifyAnyTxnLog{"LuauUnifyAnyTxnLog", true};
CheckResult result = check(R"( CheckResult result = check(R"(
repeat repeat
_._,_ = nil _._,_ = nil

View file

@ -1,8 +1,4 @@
AnnotationTests.corecursive_types_error_on_tight_loop AnnotationTests.instantiate_type_fun_should_not_trip_rbxassert
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.too_many_type_params AnnotationTests.too_many_type_params
AnnotationTests.two_type_params AnnotationTests.two_type_params
AstQuery.last_argument_function_call_type AstQuery.last_argument_function_call_type
@ -14,9 +10,6 @@ AutocompleteTest.autocomplete_oop_implicit_self
AutocompleteTest.autocomplete_string_singleton_equality AutocompleteTest.autocomplete_string_singleton_equality
AutocompleteTest.do_compatible_self_calls AutocompleteTest.do_compatible_self_calls
AutocompleteTest.do_wrong_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_expected_return_type_suggestion
AutocompleteTest.type_correct_suggestion_for_overloads AutocompleteTest.type_correct_suggestion_for_overloads
BuiltinTests.aliased_string_format BuiltinTests.aliased_string_format
@ -51,41 +44,32 @@ BuiltinTests.table_pack_variadic
DefinitionTests.class_definition_overload_metamethods DefinitionTests.class_definition_overload_metamethods
DefinitionTests.class_definition_string_props DefinitionTests.class_definition_string_props
DefinitionTests.definition_file_classes DefinitionTests.definition_file_classes
DefinitionTests.definitions_symbols_are_generated_for_recursively_referenced_types
DefinitionTests.single_class_type_identity_in_global_types
FrontendTest.environments FrontendTest.environments
FrontendTest.nocheck_cycle_used_by_checked FrontendTest.nocheck_cycle_used_by_checked
FrontendTest.reexport_cyclic_type
GenericsTests.apply_type_function_nested_generics1
GenericsTests.apply_type_function_nested_generics2 GenericsTests.apply_type_function_nested_generics2
GenericsTests.better_mismatch_error_messages GenericsTests.better_mismatch_error_messages
GenericsTests.bound_tables_do_not_clone_original_fields
GenericsTests.check_mutual_generic_functions GenericsTests.check_mutual_generic_functions
GenericsTests.correctly_instantiate_polymorphic_member_functions GenericsTests.correctly_instantiate_polymorphic_member_functions
GenericsTests.do_not_infer_generic_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_few
GenericsTests.generic_argument_count_too_many GenericsTests.generic_argument_count_too_many
GenericsTests.generic_functions_should_be_memory_safe GenericsTests.generic_functions_should_be_memory_safe
GenericsTests.generic_table_method
GenericsTests.generic_type_pack_parentheses GenericsTests.generic_type_pack_parentheses
GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments 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_function_function_argument_overloaded
GenericsTests.infer_generic_lib_function_function_argument GenericsTests.infer_generic_lib_function_function_argument
GenericsTests.infer_generic_property
GenericsTests.instantiated_function_argument_names GenericsTests.instantiated_function_argument_names
GenericsTests.instantiation_sharing_types GenericsTests.instantiation_sharing_types
GenericsTests.no_stack_overflow_from_quantifying GenericsTests.no_stack_overflow_from_quantifying
GenericsTests.reject_clashing_generic_and_pack_names
GenericsTests.self_recursive_instantiated_param GenericsTests.self_recursive_instantiated_param
IntersectionTypes.no_stack_overflow_from_flattenintersection
IntersectionTypes.select_correct_union_fn IntersectionTypes.select_correct_union_fn
IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions
IntersectionTypes.table_intersection_write_sealed IntersectionTypes.table_intersection_write_sealed
IntersectionTypes.table_intersection_write_sealed_indirect IntersectionTypes.table_intersection_write_sealed_indirect
IntersectionTypes.table_write_sealed_indirect IntersectionTypes.table_write_sealed_indirect
ModuleTests.any_persistance_does_not_leak
ModuleTests.clone_self_property ModuleTests.clone_self_property
ModuleTests.deepClone_cyclic_table ModuleTests.deepClone_cyclic_table
NonstrictModeTests.for_in_iterator_variables_are_any 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_dot_insert_and_recursive_calls
NonstrictModeTests.table_props_are_any NonstrictModeTests.table_props_are_any
Normalize.cyclic_table_normalizes_sensibly 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.assign_table_with_refined_property_with_a_similar_type_is_illegal
ProvisionalTests.bail_early_if_unification_is_too_complicated ProvisionalTests.bail_early_if_unification_is_too_complicated
ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack 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.generic_type_leak_to_module_interface_variadic
ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns
ProvisionalTests.pcall_returns_at_least_two_value_but_function_returns_nothing 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.setmetatable_constrains_free_type_into_free_table
ProvisionalTests.specialization_binds_with_prototypes_too_early ProvisionalTests.specialization_binds_with_prototypes_too_early
ProvisionalTests.table_insert_with_a_singleton_argument ProvisionalTests.table_insert_with_a_singleton_argument
@ -122,10 +101,11 @@ ProvisionalTests.typeguard_inference_incomplete
ProvisionalTests.weirditer_should_not_loop_forever ProvisionalTests.weirditer_should_not_loop_forever
RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string
RefinementTest.discriminate_tag RefinementTest.discriminate_tag
RefinementTest.else_with_no_explicit_expression_should_also_refine_the_tagged_union
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
RefinementTest.narrow_property_of_a_bounded_variable RefinementTest.narrow_property_of_a_bounded_variable
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true 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_guard_can_filter_for_intersection_of_tables
RefinementTest.type_narrow_for_all_the_userdata RefinementTest.type_narrow_for_all_the_userdata
RefinementTest.type_narrow_to_vector RefinementTest.type_narrow_to_vector
@ -157,7 +137,6 @@ TableTests.found_like_key_in_table_property_access
TableTests.found_multiple_like_keys TableTests.found_multiple_like_keys
TableTests.function_calls_produces_sealed_table_given_unsealed_table TableTests.function_calls_produces_sealed_table_given_unsealed_table
TableTests.fuzz_table_unify_instantiated_table TableTests.fuzz_table_unify_instantiated_table
TableTests.fuzz_table_unify_instantiated_table_with_prop_realloc
TableTests.generic_table_instantiation_potential_regression TableTests.generic_table_instantiation_potential_regression
TableTests.give_up_after_one_metatable_index_look_up TableTests.give_up_after_one_metatable_index_look_up
TableTests.indexer_on_sealed_table_must_unify_with_free_table 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_extra_props_dont_report_multiple_errors
TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors
TableTests.table_unification_4 TableTests.table_unification_4
TableTests.tc_member_function_2
TableTests.unifying_tables_shouldnt_uaf2 TableTests.unifying_tables_shouldnt_uaf2
TableTests.used_colon_instead_of_dot TableTests.used_colon_instead_of_dot
TableTests.used_dot_instead_of_colon TableTests.used_dot_instead_of_colon
ToString.exhaustive_toString_of_cyclic_table ToString.exhaustive_toString_of_cyclic_table
ToString.function_type_with_argument_names_generic
ToString.named_metatable_toStringNamedFunction ToString.named_metatable_toStringNamedFunction
ToString.toStringDetailed2 ToString.toStringDetailed2
ToString.toStringErrorPack ToString.toStringErrorPack
ToString.toStringNamedFunction_generic_pack ToString.toStringNamedFunction_generic_pack
ToString.toStringNamedFunction_hide_self_param ToString.toStringNamedFunction_hide_self_param
ToString.toStringNamedFunction_hide_type_params
ToString.toStringNamedFunction_id
ToString.toStringNamedFunction_include_self_param ToString.toStringNamedFunction_include_self_param
ToString.toStringNamedFunction_map ToString.toStringNamedFunction_map
ToString.toStringNamedFunction_variadics
TryUnifyTests.cli_41095_concat_log_in_sealed_table_unification TryUnifyTests.cli_41095_concat_log_in_sealed_table_unification
TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
TryUnifyTests.result_of_failed_typepack_unification_is_constrained TryUnifyTests.result_of_failed_typepack_unification_is_constrained
TryUnifyTests.typepack_unification_should_trim_free_tails TryUnifyTests.typepack_unification_should_trim_free_tails
TryUnifyTests.variadics_should_use_reversed_properly TryUnifyTests.variadics_should_use_reversed_properly
TypeAliases.cannot_create_cyclic_type_with_unknown_module 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.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any
TypeAliases.generic_param_remap TypeAliases.generic_param_remap
TypeAliases.mismatched_generic_type_param 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_1
TypeAliases.mutually_recursive_types_restriction_not_ok_2 TypeAliases.mutually_recursive_types_restriction_not_ok_2
TypeAliases.mutually_recursive_types_swapsies_not_ok TypeAliases.mutually_recursive_types_swapsies_not_ok
TypeAliases.recursive_types_restriction_not_ok TypeAliases.recursive_types_restriction_not_ok
TypeAliases.report_shadowed_aliases TypeAliases.report_shadowed_aliases
TypeAliases.stringify_type_alias_of_recursive_template_table_type
TypeAliases.type_alias_local_mutation TypeAliases.type_alias_local_mutation
TypeAliases.type_alias_local_rename TypeAliases.type_alias_local_rename
TypeAliases.type_alias_of_an_imported_recursive_generic_type 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.properly_infer_iteratee_is_a_free_table
TypeInferLoops.unreachable_code_after_infinite_loop TypeInferLoops.unreachable_code_after_infinite_loop
TypeInferModules.custom_require_global 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
TypeInferModules.module_type_conflict_instantiated TypeInferModules.module_type_conflict_instantiated
TypeInferModules.type_error_of_unknown_qualified_type 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_have_a_metatable
TypeInferOperators.cannot_indirectly_compare_types_that_do_not_offer_overloaded_ordering_operators TypeInferOperators.cannot_indirectly_compare_types_that_do_not_offer_overloaded_ordering_operators
TypeInferOperators.cli_38355_recursive_union TypeInferOperators.cli_38355_recursive_union
TypeInferOperators.compound_assign_metatable
TypeInferOperators.compound_assign_mismatch_metatable TypeInferOperators.compound_assign_mismatch_metatable
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
TypeInferOperators.in_nonstrict_mode_strip_nil_from_intersections_when_considering_relational_operators 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.operator_eq_completely_incompatible
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs
TypeInferOperators.typecheck_unary_len_error
TypeInferOperators.UnknownGlobalCompoundAssign TypeInferOperators.UnknownGlobalCompoundAssign
TypeInferOperators.unrelated_classes_cannot_be_compared TypeInferOperators.unrelated_classes_cannot_be_compared
TypeInferOperators.unrelated_primitives_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_defaults_recursive_type
TypePackTests.type_alias_type_pack_multi TypePackTests.type_alias_type_pack_multi
TypePackTests.type_alias_type_pack_variadic TypePackTests.type_alias_type_pack_variadic
TypePackTests.type_alias_type_packs
TypePackTests.type_alias_type_packs_errors TypePackTests.type_alias_type_packs_errors
TypePackTests.type_alias_type_packs_import
TypePackTests.type_alias_type_packs_nested TypePackTests.type_alias_type_packs_nested
TypePackTests.type_pack_type_parameters
TypePackTests.unify_variadic_tails_in_arguments TypePackTests.unify_variadic_tails_in_arguments
TypePackTests.unify_variadic_tails_in_arguments_free TypePackTests.unify_variadic_tails_in_arguments_free
TypePackTests.variadic_packs TypePackTests.variadic_packs
@ -360,7 +330,6 @@ TypeSingletons.table_properties_type_error_escapes
TypeSingletons.taking_the_length_of_union_of_string_singleton TypeSingletons.taking_the_length_of_union_of_string_singleton
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
TypeSingletons.widening_happens_almost_everywhere TypeSingletons.widening_happens_almost_everywhere
TypeSingletons.widening_happens_almost_everywhere_except_for_tables
UnionTypes.index_on_a_union_type_with_missing_property UnionTypes.index_on_a_union_type_with_missing_property
UnionTypes.optional_assignment_errors UnionTypes.optional_assignment_errors
UnionTypes.optional_call_error UnionTypes.optional_call_error