Sync to upstream/release/560 (#810)

* For autocomplete, additional information is included in Scope for type
alias name locations and names of imported modules
* Improved autocomplete suggestions in 'for' and 'while' loop headers
* String match functions return types are now optional strings and
numbers because match is not guaranteed at runtime
* Fixed build issue on gcc 11 and up (Fixes
https://github.com/Roblox/luau/issues/806)
This commit is contained in:
vegorov-rbx 2023-01-20 12:27:03 -08:00 committed by GitHub
parent 729bc44729
commit 4a2e8013c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
68 changed files with 3107 additions and 1260 deletions

View File

@ -64,7 +64,7 @@ private:
};
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(const SourceModule& source, Position pos);
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos);
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos, bool includeTypes = false);
AstNode* findNodeAtPosition(const SourceModule& source, Position pos);
AstExpr* findExprAtPosition(const SourceModule& source, Position pos);
ScopePtr findScopeAtPosition(const Module& module, Position pos);

View File

@ -240,20 +240,28 @@ struct ConstraintGraphBuilder
* Resolves a type from its AST annotation.
* @param scope the scope that the type annotation appears within.
* @param ty the AST annotation to resolve.
* @param topLevel whether the annotation is a "top-level" annotation.
* @param inTypeArguments whether we are resolving a type that's contained within type arguments, `<...>`.
* @return the type of the AST annotation.
**/
TypeId resolveType(const ScopePtr& scope, AstType* ty, bool topLevel = false);
TypeId resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments);
/**
* Resolves a type pack from its AST annotation.
* @param scope the scope that the type annotation appears within.
* @param tp the AST annotation to resolve.
* @param inTypeArguments whether we are resolving a type that's contained within type arguments, `<...>`.
* @return the type pack of the AST annotation.
**/
TypePackId resolveTypePack(const ScopePtr& scope, AstTypePack* tp);
TypePackId resolveTypePack(const ScopePtr& scope, AstTypePack* tp, bool inTypeArguments);
TypePackId resolveTypePack(const ScopePtr& scope, const AstTypeList& list);
/**
* Resolves a type pack from its AST annotation.
* @param scope the scope that the type annotation appears within.
* @param list the AST annotation to resolve.
* @param inTypeArguments whether we are resolving a type that's contained within type arguments, `<...>`.
* @return the type pack of the AST annotation.
**/
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, GenericTypePackDefinition>> createGenericPacks(const ScopePtr& scope, AstArray<AstGenericTypePack> packs);

View File

@ -111,7 +111,7 @@ struct ConstraintSolver
bool tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint);
// for a, ... in some_table do

View File

@ -43,6 +43,8 @@ struct Scope
std::unordered_map<Name, TypeFun> exportedTypeBindings;
std::unordered_map<Name, TypeFun> privateTypeBindings;
std::unordered_map<Name, Location> typeAliasLocations;
std::unordered_map<Name, Location> typeAliasNameLocations;
std::unordered_map<Name, ModuleName> importedModules; // Mapping from the name in the require statement to the internal moduleName.
std::unordered_map<Name, std::unordered_map<Name, TypeFun>> importedTypeBindings;
DenseHashSet<Name> builtinTypeNames{""};

View File

@ -132,7 +132,9 @@ std::optional<std::string> getFunctionNameAsString(const AstExpr& expr);
// It could be useful to see the text representation of a type during a debugging session instead of exploring the content of the class
// These functions will dump the type to stdout and can be evaluated in Watch/Immediate windows or as gdb/lldb expression
std::string dump(TypeId ty);
std::string dump(const std::optional<TypeId>& ty);
std::string dump(TypePackId ty);
std::string dump(const std::optional<TypePackId>& ty);
std::string dump(const Constraint& c);
std::string dump(const std::shared_ptr<Scope>& scope, const char* name);

View File

@ -657,8 +657,11 @@ public:
const TypeId unknownType;
const TypeId neverType;
const TypeId errorType;
const TypeId falsyType; // No type binding!
const TypeId truthyType; // No type binding!
const TypeId falsyType;
const TypeId truthyType;
const TypeId optionalNumberType;
const TypeId optionalStringType;
const TypePackId anyTypePack;
const TypePackId neverTypePack;

View File

@ -9,11 +9,28 @@
namespace Luau
{
/// If it's desirable to allocate into a different arena than the TypeReduction instance you have, you will need
/// to create a temporary TypeReduction in that case. This is because TypeReduction caches the reduced type.
namespace detail
{
template<typename T>
struct ReductionContext
{
T type = nullptr;
bool irreducible = false;
};
} // namespace detail
struct TypeReductionOptions
{
/// If it's desirable for type reduction to allocate into a different arena than the TypeReduction instance you have, you will need
/// to create a temporary TypeReduction in that case, and set [`TypeReductionOptions::allowTypeReductionsFromOtherArenas`] to true.
/// This is because TypeReduction caches the reduced type.
bool allowTypeReductionsFromOtherArenas = false;
};
struct TypeReduction
{
explicit TypeReduction(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> handle);
explicit TypeReduction(
NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> handle, const TypeReductionOptions& opts = {});
std::optional<TypeId> reduce(TypeId ty);
std::optional<TypePackId> reduce(TypePackId tp);
@ -23,12 +40,10 @@ private:
NotNull<TypeArena> arena;
NotNull<BuiltinTypes> builtinTypes;
NotNull<struct InternalErrorReporter> handle;
TypeReductionOptions options;
DenseHashMap<TypeId, TypeId> cachedTypes{nullptr};
DenseHashMap<TypePackId, TypePackId> cachedTypePacks{nullptr};
std::pair<std::optional<TypeId>, bool> reduceImpl(TypeId ty);
std::pair<std::optional<TypePackId>, bool> reduceImpl(TypePackId tp);
DenseHashMap<TypeId, detail::ReductionContext<TypeId>> memoizedTypes{nullptr};
DenseHashMap<TypePackId, detail::ReductionContext<TypePackId>> memoizedTypePacks{nullptr};
// Computes an *estimated length* of the cartesian product of the given type.
size_t cartesianProductSize(TypeId ty) const;

View File

@ -318,7 +318,10 @@ struct GenericTypeVisitor
}
}
else if (auto ntv = get<NegationType>(ty))
visit(ty, *ntv);
{
if (visit(ty, *ntv))
traverse(ntv->ty);
}
else if (!FFlag::LuauCompleteVisitor)
return visit_detail::unsee(seen, ty);
else

View File

@ -12,6 +12,7 @@
#include <algorithm>
LUAU_FASTFLAG(LuauCompleteTableKeysBetter);
LUAU_FASTFLAGVARIABLE(SupportTypeAliasGoToDeclaration, false);
namespace Luau
{
@ -183,14 +184,31 @@ struct FindFullAncestry final : public AstVisitor
std::vector<AstNode*> nodes;
Position pos;
Position documentEnd;
bool includeTypes = false;
explicit FindFullAncestry(Position pos, Position documentEnd)
explicit FindFullAncestry(Position pos, Position documentEnd, bool includeTypes = false)
: pos(pos)
, documentEnd(documentEnd)
, includeTypes(includeTypes)
{
}
bool visit(AstNode* node)
bool visit(AstType* type) override
{
if (FFlag::SupportTypeAliasGoToDeclaration)
{
if (includeTypes)
return visit(static_cast<AstNode*>(type));
else
return false;
}
else
{
return AstVisitor::visit(type);
}
}
bool visit(AstNode* node) override
{
if (node->location.contains(pos))
{
@ -220,13 +238,13 @@ std::vector<AstNode*> findAncestryAtPositionForAutocomplete(const SourceModule&
return finder.ancestry;
}
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos)
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos, bool includeTypes)
{
const Position end = source.root->location.end;
if (pos > end)
pos = end;
FindFullAncestry finder(pos, end);
FindFullAncestry finder(pos, end, includeTypes);
source.root->visit(&finder);
return finder.nodes;
}

View File

@ -14,6 +14,9 @@
LUAU_FASTFLAGVARIABLE(LuauCompleteTableKeysBetter, false);
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInIf, false);
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInWhile, false);
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInFor, false);
LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringContent, false);
static const std::unordered_set<std::string> kStatementStartingKeywords = {
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
@ -1265,6 +1268,9 @@ static bool isSimpleInterpolatedString(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>())
{
return std::string(string->value.data, string->value.size);
@ -1314,8 +1320,7 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(const Source
std::optional<std::string> candidateString = getStringContents(nodes.back());
auto performCallback = [&](const FunctionType* funcType) -> std::optional<AutocompleteEntryMap>
{
auto performCallback = [&](const FunctionType* funcType) -> std::optional<AutocompleteEntryMap> {
for (const std::string& tag : funcType->tags)
{
if (std::optional<AutocompleteEntryMap> ret = callback(tag, getMethodContainingClass(module, candidate->func), candidateString))
@ -1349,6 +1354,15 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(const Source
return std::nullopt;
}
static AutocompleteResult autocompleteWhileLoopKeywords(std::vector<AstNode*> ancestry)
{
AutocompleteEntryMap ret;
ret["do"] = {AutocompleteEntryKind::Keyword};
ret["and"] = {AutocompleteEntryKind::Keyword};
ret["or"] = {AutocompleteEntryKind::Keyword};
return {std::move(ret), std::move(ancestry), AutocompleteContext::Keyword};
}
static AutocompleteResult autocomplete(const SourceModule& sourceModule, const ModulePtr& module, NotNull<BuiltinTypes> builtinTypes,
TypeArena* typeArena, Scope* globalScope, Position position, StringCompletionCallback callback)
{
@ -1407,13 +1421,24 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
{
if (!statFor->hasDo || position < statFor->doLocation.begin)
{
if (!statFor->from->is<AstExprError>() && !statFor->to->is<AstExprError>() && (!statFor->step || !statFor->step->is<AstExprError>()))
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
if (FFlag::LuauFixAutocompleteInFor)
{
if (statFor->from->location.containsClosed(position) || statFor->to->location.containsClosed(position) ||
(statFor->step && statFor->step->location.containsClosed(position)))
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
if (statFor->from->location.containsClosed(position) || statFor->to->location.containsClosed(position) ||
(statFor->step && statFor->step->location.containsClosed(position)))
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
if (!statFor->from->is<AstExprError>() && !statFor->to->is<AstExprError>() && (!statFor->step || !statFor->step->is<AstExprError>()))
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
}
else
{
if (!statFor->from->is<AstExprError>() && !statFor->to->is<AstExprError>() && (!statFor->step || !statFor->step->is<AstExprError>()))
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
if (statFor->from->location.containsClosed(position) || statFor->to->location.containsClosed(position) ||
(statFor->step && statFor->step->location.containsClosed(position)))
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
}
return {};
}
@ -1463,7 +1488,16 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
else if (AstStatWhile* statWhile = parent->as<AstStatWhile>(); node->is<AstStatBlock>() && statWhile)
{
if (!statWhile->hasDo && !statWhile->condition->is<AstStatError>() && position > statWhile->condition->location.end)
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
{
if (FFlag::LuauFixAutocompleteInWhile)
{
return autocompleteWhileLoopKeywords(ancestry);
}
else
{
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
}
}
if (!statWhile->hasDo || position < statWhile->doLocation.begin)
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
@ -1472,9 +1506,20 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
}
else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(ancestry); statWhile && !statWhile->hasDo)
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(ancestry);
FFlag::LuauFixAutocompleteInWhile ? (statWhile && (!statWhile->hasDo || statWhile->doLocation.containsClosed(position)) &&
statWhile->condition && !statWhile->condition->location.containsClosed(position))
: (statWhile && !statWhile->hasDo))
{
if (FFlag::LuauFixAutocompleteInWhile)
{
return autocompleteWhileLoopKeywords(ancestry);
}
else
{
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
}
}
else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->elseLocation.has_value())
{
return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}},
@ -1488,7 +1533,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
}
else if (AstStatIf* statIf = extractStat<AstStatIf>(ancestry);
statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position)) &&
statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position)) &&
(!FFlag::LuauFixAutocompleteInIf || (statIf->condition && !statIf->condition->location.containsClosed(position))))
{
if (FFlag::LuauFixAutocompleteInIf)

View File

@ -15,7 +15,6 @@
#include <algorithm>
LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false)
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauBuiltInMetatableNoBadSynthetic, false)
LUAU_FASTFLAG(LuauReportShadowedTypeAlias)
@ -583,7 +582,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
TypeId mtTy = arena.addType(mtv);
if (FFlag::LuauSetMetaTableArgsCheck && expr.args.size < 1)
if (expr.args.size < 1)
{
if (FFlag::LuauUnknownAndNeverType)
return std::nullopt;
@ -591,7 +590,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
return WithPredicate<TypePackId>{};
}
if (!FFlag::LuauSetMetaTableArgsCheck || !expr.self)
if (!expr.self)
{
AstExpr* targetExpr = expr.args.data[0];
if (AstExprLocal* targetLocal = targetExpr->as<AstExprLocal>())

View File

@ -16,6 +16,7 @@ LUAU_FASTFLAG(DebugLuauLogSolverToJson);
LUAU_FASTFLAG(DebugLuauMagicTypes);
LUAU_FASTFLAG(LuauNegatedClassTypes);
LUAU_FASTFLAG(LuauScopelessModule);
LUAU_FASTFLAG(SupportTypeAliasGoToDeclaration);
namespace Luau
{
@ -418,7 +419,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
TypeId ty = nullptr;
if (local->annotation)
ty = resolveType(scope, local->annotation, /* topLevel */ true);
ty = resolveType(scope, local->annotation, /* inTypeArguments */ false);
varTypes.push_back(ty);
}
@ -521,8 +522,12 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
const Name name{local->vars.data[i]->name.value};
if (ModulePtr module = moduleResolver->getModule(moduleInfo->name))
{
scope->importedTypeBindings[name] =
FFlag::LuauScopelessModule ? module->exportedTypeBindings : module->getModuleScope()->exportedTypeBindings;
if (FFlag::SupportTypeAliasGoToDeclaration)
scope->importedModules[name] = moduleName;
}
}
}
}
@ -775,7 +780,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alia
}
ScopePtr resolvingScope = *defnIt;
TypeId ty = resolveType(resolvingScope, alias->type, /* topLevel */ true);
TypeId ty = resolveType(resolvingScope, alias->type, /* inTypeArguments */ false);
if (alias->exported)
{
@ -798,7 +803,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareGlobal*
{
LUAU_ASSERT(global->type);
TypeId globalTy = resolveType(scope, global->type);
TypeId globalTy = resolveType(scope, global->type, /* inTypeArguments */ false);
Name globalName(global->name.value);
module->declaredGlobals[globalName] = globalTy;
@ -854,7 +859,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareClass* d
for (const AstDeclaredClassProp& prop : declaredClass->props)
{
Name propName(prop.name.value);
TypeId propTy = resolveType(scope, prop.ty);
TypeId propTy = resolveType(scope, prop.ty, /* inTypeArguments */ false);
bool assignToMetatable = isMetamethod(propName);
@ -937,8 +942,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareFunction
if (!generics.empty() || !genericPacks.empty())
funScope = childScope(global, scope);
TypePackId paramPack = resolveTypePack(funScope, global->params);
TypePackId retPack = resolveTypePack(funScope, global->retTypes);
TypePackId paramPack = resolveTypePack(funScope, global->params, /* inTypeArguments */ false);
TypePackId retPack = resolveTypePack(funScope, global->retTypes, /* inTypeArguments */ false);
TypeId fnType = arena->addType(FunctionType{TypeLevel{}, funScope.get(), std::move(genericTys), std::move(genericTps), paramPack, retPack});
FunctionType* ftv = getMutable<FunctionType>(fnType);
@ -1501,7 +1506,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* if
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert)
{
check(scope, typeAssert->expr, std::nullopt);
return Inference{resolveType(scope, typeAssert->annotation)};
return Inference{resolveType(scope, typeAssert->annotation, /* inTypeArguments */ false)};
}
std::tuple<TypeId, TypeId, ConnectiveId> ConstraintGraphBuilder::checkBinary(
@ -1563,7 +1568,7 @@ std::tuple<TypeId, TypeId, ConnectiveId> ConstraintGraphBuilder::checkBinary(
TypeId ty = follow(typeFun->type);
// We're only interested in the root class of any classes.
if (auto ctv = get<ClassType>(ty); !ctv || !ctv->parent)
if (auto ctv = get<ClassType>(ty); !ctv || (FFlag::LuauNegatedClassTypes ? (ctv->parent == builtinTypes->classType) : !ctv->parent))
discriminantTy = ty;
}
@ -1618,39 +1623,6 @@ TypePackId ConstraintGraphBuilder::checkLValues(const ScopePtr& scope, AstArray<
return arena->addTypePack(std::move(types));
}
/**
* If the expr is a dotted set of names, and if the root symbol refers to an
* unsealed table, return that table type, plus the indeces that follow as a
* vector.
*/
static std::optional<std::pair<Symbol, std::vector<const char*>>> extractDottedName(AstExpr* expr)
{
std::vector<const char*> names;
while (expr)
{
if (auto global = expr->as<AstExprGlobal>())
{
std::reverse(begin(names), end(names));
return std::pair{global->name, std::move(names)};
}
else if (auto local = expr->as<AstExprLocal>())
{
std::reverse(begin(names), end(names));
return std::pair{local->local, std::move(names)};
}
else if (auto indexName = expr->as<AstExprIndexName>())
{
names.push_back(indexName->index.value);
expr = indexName->expr;
}
else
return std::nullopt;
}
return std::nullopt;
}
/**
* This function is mostly about identifying properties that are being inserted into unsealed tables.
*
@ -1671,13 +1643,38 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
else if (!expr->is<AstExprIndexName>())
return check(scope, expr).ty;
auto dottedPath = extractDottedName(expr);
if (!dottedPath)
return check(scope, expr).ty;
const auto [sym, segments] = std::move(*dottedPath);
Symbol sym;
std::vector<std::string> segments;
std::vector<AstExpr*> exprs;
AstExpr* e = expr;
while (e)
{
if (auto global = e->as<AstExprGlobal>())
{
sym = global->name;
break;
}
else if (auto local = e->as<AstExprLocal>())
{
sym = local->local;
break;
}
else if (auto indexName = e->as<AstExprIndexName>())
{
segments.push_back(indexName->index.value);
exprs.push_back(e);
e = indexName->expr;
}
else
return check(scope, expr).ty;
}
LUAU_ASSERT(!segments.empty());
std::reverse(begin(segments), end(segments));
std::reverse(begin(exprs), end(exprs));
auto lookupResult = scope->lookupEx(sym);
if (!lookupResult)
return check(scope, expr).ty;
@ -1695,7 +1692,18 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
symbolScope->bindings[sym].typeId = updatedType;
symbolScope->dcrRefinements[*def] = updatedType;
astTypes[expr] = propTy;
TypeId prevSegmentTy = updatedType;
for (size_t i = 0; i < segments.size(); ++i)
{
TypeId segmentTy = arena->addType(BlockedType{});
astTypes[exprs[i]] = segmentTy;
addConstraint(scope, expr->location, HasPropConstraint{segmentTy, prevSegmentTy, segments[i]});
prevSegmentTy = segmentTy;
}
astTypes[expr] = prevSegmentTy;
astTypes[e] = updatedType;
// astTypes[expr] = propTy;
return propTy;
}
@ -1845,7 +1853,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
if (local->annotation)
{
annotationTy = resolveType(signatureScope, local->annotation, /* topLevel */ true);
annotationTy = resolveType(signatureScope, local->annotation, /* inTypeArguments */ false);
addConstraint(signatureScope, local->annotation->location, SubtypeConstraint{t, annotationTy});
}
else if (i < expectedArgPack.head.size())
@ -1866,7 +1874,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
{
if (fn->varargAnnotation)
{
TypePackId annotationType = resolveTypePack(signatureScope, fn->varargAnnotation);
TypePackId annotationType = resolveTypePack(signatureScope, fn->varargAnnotation, /* inTypeArguments */ false);
varargPack = annotationType;
}
else if (expectedArgPack.tail && get<VariadicTypePack>(*expectedArgPack.tail))
@ -1893,7 +1901,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
// Type checking will sort out any discrepancies later.
if (fn->returnAnnotation)
{
TypePackId annotatedRetType = resolveTypePack(signatureScope, *fn->returnAnnotation);
TypePackId annotatedRetType = resolveTypePack(signatureScope, *fn->returnAnnotation, /* inTypeArguments */ false);
// We bind the annotated type directly here so that, when we need to
// generate constraints for return types, we have a guarantee that we
@ -1942,7 +1950,7 @@ void ConstraintGraphBuilder::checkFunctionBody(const ScopePtr& scope, AstExprFun
}
}
TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, bool topLevel)
TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments)
{
TypeId result = nullptr;
@ -1960,7 +1968,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
return builtinTypes->errorRecoveryType();
}
else
return resolveType(scope, ref->parameters.data[0].type, topLevel);
return resolveType(scope, ref->parameters.data[0].type, inTypeArguments);
}
}
@ -1994,11 +2002,11 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
// that is done in the parser.
if (p.type)
{
parameters.push_back(resolveType(scope, p.type));
parameters.push_back(resolveType(scope, p.type, /* inTypeArguments */ true));
}
else if (p.typePack)
{
packParameters.push_back(resolveTypePack(scope, p.typePack));
packParameters.push_back(resolveTypePack(scope, p.typePack, /* inTypeArguments */ true));
}
else
{
@ -2010,10 +2018,11 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
result = arena->addType(PendingExpansionType{ref->prefix, ref->name, parameters, packParameters});
if (topLevel)
{
// If we're not in a type argument context, we need to create a constraint that expands this.
// The dispatching of the above constraint will queue up additional constraints for nested
// type function applications.
if (!inTypeArguments)
addConstraint(scope, ty->location, TypeAliasExpansionConstraint{/* target */ result});
}
}
}
else
@ -2035,7 +2044,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
{
std::string name = prop.name.value;
// TODO: Recursion limit.
TypeId propTy = resolveType(scope, prop.type);
TypeId propTy = resolveType(scope, prop.type, inTypeArguments);
// TODO: Fill in location.
props[name] = {propTy};
}
@ -2044,8 +2053,8 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
{
// TODO: Recursion limit.
indexer = TableIndexer{
resolveType(scope, tab->indexer->indexType),
resolveType(scope, tab->indexer->resultType),
resolveType(scope, tab->indexer->indexType, inTypeArguments),
resolveType(scope, tab->indexer->resultType, inTypeArguments),
};
}
@ -2089,8 +2098,8 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
signatureScope = scope;
}
TypePackId argTypes = resolveTypePack(signatureScope, fn->argTypes);
TypePackId returnTypes = resolveTypePack(signatureScope, fn->returnTypes);
TypePackId argTypes = resolveTypePack(signatureScope, fn->argTypes, inTypeArguments);
TypePackId returnTypes = resolveTypePack(signatureScope, fn->returnTypes, inTypeArguments);
// TODO: FunctionType needs a pointer to the scope so that we know
// how to quantify/instantiate it.
@ -2130,7 +2139,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
for (AstType* part : unionAnnotation->types)
{
// TODO: Recursion limit.
parts.push_back(resolveType(scope, part, topLevel));
parts.push_back(resolveType(scope, part, inTypeArguments));
}
result = arena->addType(UnionType{parts});
@ -2141,7 +2150,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
for (AstType* part : intersectionAnnotation->types)
{
// TODO: Recursion limit.
parts.push_back(resolveType(scope, part, topLevel));
parts.push_back(resolveType(scope, part, inTypeArguments));
}
result = arena->addType(IntersectionType{parts});
@ -2168,16 +2177,16 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
return result;
}
TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTypePack* tp)
TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTypePack* tp, bool inTypeArgument)
{
TypePackId result;
if (auto expl = tp->as<AstTypePackExplicit>())
{
result = resolveTypePack(scope, expl->typeList);
result = resolveTypePack(scope, expl->typeList, inTypeArgument);
}
else if (auto var = tp->as<AstTypePackVariadic>())
{
TypeId ty = resolveType(scope, var->variadicType);
TypeId ty = resolveType(scope, var->variadicType, inTypeArgument);
result = arena->addTypePack(TypePackVar{VariadicTypePack{ty}});
}
else if (auto gen = tp->as<AstTypePackGeneric>())
@ -2202,19 +2211,19 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTyp
return result;
}
TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, const AstTypeList& list)
TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, const AstTypeList& list, bool inTypeArguments)
{
std::vector<TypeId> head;
for (AstType* headTy : list.types)
{
head.push_back(resolveType(scope, headTy));
head.push_back(resolveType(scope, headTy, inTypeArguments));
}
std::optional<TypePackId> tail = std::nullopt;
if (list.tailType)
{
tail = resolveTypePack(scope, list.tailType);
tail = resolveTypePack(scope, list.tailType, inTypeArguments);
}
return arena->addTypePack(TypePack{head, tail});
@ -2229,7 +2238,7 @@ std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGraphBuilder::crea
std::optional<TypeId> defaultTy = std::nullopt;
if (generic.defaultValue)
defaultTy = resolveType(scope, generic.defaultValue);
defaultTy = resolveType(scope, generic.defaultValue, /* inTypeArguments */ false);
result.push_back({generic.name.value, GenericTypeDefinition{genericTy, defaultTy}});
}
@ -2247,7 +2256,7 @@ std::vector<std::pair<Name, GenericTypePackDefinition>> ConstraintGraphBuilder::
std::optional<TypePackId> defaultTy = std::nullopt;
if (generic.defaultValue)
defaultTy = resolveTypePack(scope, generic.defaultValue);
defaultTy = resolveTypePack(scope, generic.defaultValue, /* inTypeArguments */ false);
result.push_back({generic.name.value, GenericTypePackDefinition{genericTy, defaultTy}});
}

View File

@ -417,7 +417,7 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
else if (auto hpc = get<HasPropConstraint>(*constraint))
success = tryDispatch(*hpc, constraint);
else if (auto spc = get<SetPropConstraint>(*constraint))
success = tryDispatch(*spc, constraint);
success = tryDispatch(*spc, constraint, force);
else if (auto sottc = get<SingletonOrTopTypeConstraint>(*constraint))
success = tryDispatch(*sottc, constraint);
else
@ -933,13 +933,11 @@ struct InfiniteTypeFinder : TypeOnceVisitor
struct InstantiationQueuer : TypeOnceVisitor
{
ConstraintSolver* solver;
const InstantiationSignature& signature;
NotNull<Scope> scope;
Location location;
explicit InstantiationQueuer(NotNull<Scope> scope, const Location& location, ConstraintSolver* solver, const InstantiationSignature& signature)
explicit InstantiationQueuer(NotNull<Scope> scope, const Location& location, ConstraintSolver* solver)
: solver(solver)
, signature(signature)
, scope(scope)
, location(location)
{
@ -1061,8 +1059,17 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
TypeId instantiated = *maybeInstantiated;
TypeId target = follow(instantiated);
// The application is not recursive, so we need to queue up application of
// any child type function instantiations within the result in order for it
// to be complete.
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
queuer.traverse(target);
if (target->persistent)
{
bindResult(target);
return true;
}
// Type function application will happily give us the exact same type if
// there are e.g. generic saturatedTypeArguments that go unused.
@ -1102,12 +1109,6 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
bindResult(target);
// The application is not recursive, so we need to queue up application of
// any child type function instantiations within the result in order for it
// to be complete.
InstantiationQueuer queuer{constraint->scope, constraint->location, this, signature};
queuer.traverse(target);
instantiatedAliases[signature] = target;
return true;
@ -1326,13 +1327,16 @@ static std::optional<TypeId> updateTheTableType(NotNull<TypeArena> arena, TypeId
return res;
}
bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint)
bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint, bool force)
{
TypeId subjectType = follow(c.subjectType);
if (isBlocked(subjectType))
return block(subjectType, constraint);
if (!force && get<FreeType>(subjectType))
return block(subjectType, constraint);
std::optional<TypeId> existingPropType = subjectType;
for (const std::string& segment : c.path)
{
@ -1399,6 +1403,13 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
return true;
}
}
else if (get<ClassType>(subjectType))
{
// Classes never change shape as a result of property assignments.
// The result is always the subject.
bind(c.resultType, subjectType);
return true;
}
else if (get<AnyType>(subjectType) || get<ErrorType>(subjectType))
{
bind(c.resultType, subjectType);

View File

@ -13,7 +13,6 @@
#include <limits.h>
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
LUAU_FASTFLAGVARIABLE(LuauLintGlobalNeverReadBeforeWritten, false)
namespace Luau
{
@ -331,8 +330,7 @@ private:
"Global '%s' is only used in the enclosing function defined at line %d; consider changing it to local",
g.firstRef->name.value, top->location.begin.line + 1);
}
else if (FFlag::LuauLintGlobalNeverReadBeforeWritten && g.assigned && !g.readBeforeWritten && !g.definedInModuleScope &&
g.firstRef->name != context->placeholder)
else if (g.assigned && !g.readBeforeWritten && !g.definedInModuleScope && g.firstRef->name != context->placeholder)
{
emitWarning(*context, LintWarning::Code_GlobalUsedAsLocal, g.firstRef->location,
"Global '%s' is never read before being written. Consider changing it to local", g.firstRef->name.value);
@ -353,7 +351,7 @@ private:
bool visit(AstExprGlobal* node) override
{
if (FFlag::LuauLintGlobalNeverReadBeforeWritten && !functionStack.empty() && !functionStack.back().dominatedGlobals.contains(node->name))
if (!functionStack.empty() && !functionStack.back().dominatedGlobals.contains(node->name))
{
Global& g = globals[node->name];
g.readBeforeWritten = true;
@ -386,18 +384,15 @@ private:
{
Global& g = globals[gv->name];
if (FFlag::LuauLintGlobalNeverReadBeforeWritten)
if (functionStack.empty())
{
if (functionStack.empty())
g.definedInModuleScope = true;
}
else
{
if (!functionStack.back().conditionalExecution)
{
g.definedInModuleScope = true;
}
else
{
if (!functionStack.back().conditionalExecution)
{
functionStack.back().dominatedGlobals.insert(gv->name);
}
functionStack.back().dominatedGlobals.insert(gv->name);
}
}
@ -437,11 +432,8 @@ private:
else
{
g.assigned = true;
if (FFlag::LuauLintGlobalNeverReadBeforeWritten)
{
g.definedAsFunction = true;
g.definedInModuleScope = functionStack.empty();
}
g.definedAsFunction = true;
g.definedInModuleScope = functionStack.empty();
}
trackGlobalRef(gv);
@ -475,9 +467,6 @@ private:
bool visit(AstStatIf* node) override
{
if (!FFlag::LuauLintGlobalNeverReadBeforeWritten)
return true;
HoldConditionalExecution ce(*this);
node->condition->visit(this);
node->thenbody->visit(this);
@ -489,9 +478,6 @@ private:
bool visit(AstStatWhile* node) override
{
if (!FFlag::LuauLintGlobalNeverReadBeforeWritten)
return true;
HoldConditionalExecution ce(*this);
node->condition->visit(this);
node->body->visit(this);
@ -501,9 +487,6 @@ private:
bool visit(AstStatRepeat* node) override
{
if (!FFlag::LuauLintGlobalNeverReadBeforeWritten)
return true;
HoldConditionalExecution ce(*this);
node->condition->visit(this);
node->body->visit(this);
@ -513,9 +496,6 @@ private:
bool visit(AstStatFor* node) override
{
if (!FFlag::LuauLintGlobalNeverReadBeforeWritten)
return true;
HoldConditionalExecution ce(*this);
node->from->visit(this);
node->to->visit(this);
@ -530,9 +510,6 @@ private:
bool visit(AstStatForIn* node) override
{
if (!FFlag::LuauLintGlobalNeverReadBeforeWritten)
return true;
HoldConditionalExecution ce(*this);
for (AstExpr* expr : node->values)
expr->visit(this);

View File

@ -17,13 +17,10 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
// This could theoretically be 2000 on amd64, but x86 requires this.
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false);
LUAU_FASTFLAGVARIABLE(LuauTypeNormalization2, false);
LUAU_FASTFLAGVARIABLE(LuauNegatedClassTypes, false);
LUAU_FASTFLAGVARIABLE(LuauNegatedFunctionTypes, false);
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauOverloadedFunctionSubtypingPerf);
LUAU_FASTFLAG(LuauUninhabitedSubAnything2)
namespace Luau
@ -2165,7 +2162,7 @@ std::optional<TypeId> Normalizer::intersectionOfFunctions(TypeId here, TypeId th
argTypes = *argTypesOpt;
retTypes = hftv->retTypes;
}
else if (FFlag::LuauOverloadedFunctionSubtypingPerf && hftv->argTypes == tftv->argTypes)
else if (hftv->argTypes == tftv->argTypes)
{
std::optional<TypePackId> retTypesOpt = intersectionOfTypePacks(hftv->argTypes, tftv->argTypes);
if (!retTypesOpt)

View File

@ -157,6 +157,8 @@ struct PureQuantifier : Substitution
Scope* scope;
std::vector<TypeId> insertedGenerics;
std::vector<TypePackId> insertedGenericPacks;
bool seenMutableType = false;
bool seenGenericType = false;
PureQuantifier(TypeArena* arena, Scope* scope)
: Substitution(TxnLog::empty(), arena)
@ -170,11 +172,18 @@ struct PureQuantifier : Substitution
if (auto ftv = get<FreeType>(ty))
{
return subsumes(scope, ftv->scope);
bool result = subsumes(scope, ftv->scope);
seenMutableType |= result;
return result;
}
else if (auto ttv = get<TableType>(ty))
{
return ttv->state == TableState::Free && subsumes(scope, ttv->scope);
if (ttv->state == TableState::Free)
seenMutableType = true;
else if (ttv->state == TableState::Generic)
seenGenericType = true;
return ttv->state == TableState::Unsealed || (ttv->state == TableState::Free && subsumes(scope, ttv->scope));
}
return false;
@ -207,7 +216,11 @@ struct PureQuantifier : Substitution
*resultTable = *ttv;
resultTable->level = TypeLevel{};
resultTable->scope = scope;
resultTable->state = TableState::Generic;
if (ttv->state == TableState::Free)
resultTable->state = TableState::Generic;
else if (ttv->state == TableState::Unsealed)
resultTable->state = TableState::Sealed;
return result;
}
@ -251,7 +264,7 @@ TypeId quantify(TypeArena* arena, TypeId ty, Scope* scope)
ftv->scope = scope;
ftv->generics.insert(ftv->generics.end(), quantifier.insertedGenerics.begin(), quantifier.insertedGenerics.end());
ftv->genericPacks.insert(ftv->genericPacks.end(), quantifier.insertedGenericPacks.begin(), quantifier.insertedGenericPacks.end());
ftv->hasNoGenerics = ftv->generics.empty() && ftv->genericPacks.empty();
ftv->hasNoGenerics = ftv->generics.empty() && ftv->genericPacks.empty() && !quantifier.seenGenericType && !quantifier.seenMutableType;
return *result;
}

View File

@ -16,7 +16,6 @@
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauFunctionReturnStringificationFixup, false)
LUAU_FASTFLAGVARIABLE(LuauUnseeArrayTtv, false)
/*
* Prefix generic typenames with gen-
@ -311,8 +310,7 @@ struct TypeStringifier
}
Luau::visit(
[this, tv](auto&& t)
{
[this, tv](auto&& t) {
return (*this)(tv, t);
},
tv->ty);
@ -607,9 +605,7 @@ struct TypeStringifier
stringify(ttv.indexer->indexResultType);
state.emit("}");
if (FFlag::LuauUnseeArrayTtv)
state.unsee(&ttv);
state.unsee(&ttv);
return;
}
@ -910,8 +906,7 @@ struct TypePackStringifier
}
Luau::visit(
[this, tp](auto&& t)
{
[this, tp](auto&& t) {
return (*this)(tp, t);
},
tp->ty);
@ -1061,11 +1056,9 @@ static void assignCycleNames(const std::set<TypeId>& cycles, const std::set<Type
if (auto ttv = get<TableType>(follow(cycleTy)); !exhaustive && ttv && (ttv->syntheticName || ttv->name))
{
// If we have a cycle type in type parameters, assign a cycle name for this named table
if (std::find_if(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(),
[&](auto&& el)
{
return cycles.count(follow(el));
}) != ttv->instantiatedTypeParams.end())
if (std::find_if(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(), [&](auto&& el) {
return cycles.count(follow(el));
}) != ttv->instantiatedTypeParams.end())
cycleNames[cycleTy] = ttv->name ? *ttv->name : *ttv->syntheticName;
continue;
@ -1160,11 +1153,9 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts)
state.exhaustive = true;
std::vector<std::pair<TypeId, std::string>> sortedCycleNames{state.cycleNames.begin(), state.cycleNames.end()};
std::sort(sortedCycleNames.begin(), sortedCycleNames.end(),
[](const auto& a, const auto& b)
{
return a.second < b.second;
});
std::sort(sortedCycleNames.begin(), sortedCycleNames.end(), [](const auto& a, const auto& b) {
return a.second < b.second;
});
bool semi = false;
for (const auto& [cycleTy, name] : sortedCycleNames)
@ -1175,8 +1166,7 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts)
state.emit(name);
state.emit(" = ");
Luau::visit(
[&tvs, cycleTy = cycleTy](auto&& t)
{
[&tvs, cycleTy = cycleTy](auto&& t) {
return tvs(cycleTy, t);
},
cycleTy->ty);
@ -1257,11 +1247,9 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts)
state.exhaustive = true;
std::vector<std::pair<TypeId, std::string>> sortedCycleNames{state.cycleNames.begin(), state.cycleNames.end()};
std::sort(sortedCycleNames.begin(), sortedCycleNames.end(),
[](const auto& a, const auto& b)
{
return a.second < b.second;
});
std::sort(sortedCycleNames.begin(), sortedCycleNames.end(), [](const auto& a, const auto& b) {
return a.second < b.second;
});
bool semi = false;
for (const auto& [cycleTy, name] : sortedCycleNames)
@ -1272,8 +1260,7 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts)
state.emit(name);
state.emit(" = ");
Luau::visit(
[&tvs, cycleTy = cycleTy](auto t)
{
[&tvs, cycleTy = cycleTy](auto t) {
return tvs(cycleTy, t);
},
cycleTy->ty);
@ -1413,6 +1400,15 @@ std::string dump(TypeId ty)
return s;
}
std::string dump(const std::optional<TypeId>& ty)
{
if (ty)
return dump(*ty);
printf("nullopt\n");
return "nullopt";
}
std::string dump(TypePackId ty)
{
std::string s = toString(ty, dumpOptions());
@ -1420,6 +1416,15 @@ std::string dump(TypePackId ty)
return s;
}
std::string dump(const std::optional<TypePackId>& ty)
{
if (ty)
return dump(*ty);
printf("nullopt\n");
return "nullopt";
}
std::string dump(const ScopePtr& scope, const char* name)
{
auto binding = scope->linearSearchForBinding(name);

View File

@ -27,6 +27,7 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAGVARIABLE(LuauMatchReturnsOptionalString, false);
namespace Luau
{
@ -768,15 +769,16 @@ BuiltinTypes::BuiltinTypes()
, errorType(arena->addType(Type{ErrorType{}, /*persistent*/ true}))
, falsyType(arena->addType(Type{UnionType{{falseType, nilType}}, /*persistent*/ true}))
, truthyType(arena->addType(Type{NegationType{falsyType}, /*persistent*/ true}))
, optionalNumberType(arena->addType(Type{UnionType{{numberType, nilType}}, /*persistent*/ true}))
, optionalStringType(arena->addType(Type{UnionType{{stringType, nilType}}, /*persistent*/ true}))
, anyTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{anyType}, /*persistent*/ true}))
, neverTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{neverType}, /*persistent*/ true}))
, uninhabitableTypePack(arena->addTypePack({neverType}, neverTypePack))
, uninhabitableTypePack(arena->addTypePack(TypePackVar{TypePack{{neverType}, neverTypePack}, /*persistent*/ true}))
, errorTypePack(arena->addTypePack(TypePackVar{Unifiable::Error{}, /*persistent*/ true}))
{
TypeId stringMetatable = makeStringMetatable();
asMutable(stringType)->ty = PrimitiveType{PrimitiveType::String, stringMetatable};
persist(stringMetatable);
persist(uninhabitableTypePack);
freeze(*arena);
}
@ -1231,12 +1233,12 @@ static std::vector<TypeId> parsePatternString(NotNull<BuiltinTypes> builtinTypes
if (i + 1 < size && data[i + 1] == ')')
{
i++;
result.push_back(builtinTypes->numberType);
result.push_back(FFlag::LuauMatchReturnsOptionalString ? builtinTypes->optionalNumberType : builtinTypes->numberType);
continue;
}
++depth;
result.push_back(builtinTypes->stringType);
result.push_back(FFlag::LuauMatchReturnsOptionalString ? builtinTypes->optionalStringType : builtinTypes->stringType);
}
else if (data[i] == ')')
{
@ -1254,7 +1256,7 @@ static std::vector<TypeId> parsePatternString(NotNull<BuiltinTypes> builtinTypes
return std::vector<TypeId>();
if (result.empty())
result.push_back(builtinTypes->stringType);
result.push_back(FFlag::LuauMatchReturnsOptionalString ? builtinTypes->optionalStringType : builtinTypes->stringType);
return result;
}

View File

@ -4,6 +4,7 @@
#include "Luau/Ast.h"
#include "Luau/AstQuery.h"
#include "Luau/Clone.h"
#include "Luau/Error.h"
#include "Luau/Instantiation.h"
#include "Luau/Metamethods.h"
#include "Luau/Normalize.h"
@ -212,6 +213,12 @@ struct TypeChecker2
return bestScope;
}
enum ValueContext
{
LValue,
RValue
};
void visit(AstStat* stat)
{
auto pusher = pushStack(stat);
@ -273,7 +280,7 @@ struct TypeChecker2
void visit(AstStatIf* ifStatement)
{
visit(ifStatement->condition);
visit(ifStatement->condition, RValue);
visit(ifStatement->thenbody);
if (ifStatement->elsebody)
visit(ifStatement->elsebody);
@ -281,14 +288,14 @@ struct TypeChecker2
void visit(AstStatWhile* whileStatement)
{
visit(whileStatement->condition);
visit(whileStatement->condition, RValue);
visit(whileStatement->body);
}
void visit(AstStatRepeat* repeatStatement)
{
visit(repeatStatement->body);
visit(repeatStatement->condition);
visit(repeatStatement->condition, RValue);
}
void visit(AstStatBreak*) {}
@ -315,12 +322,12 @@ struct TypeChecker2
}
for (AstExpr* expr : ret->list)
visit(expr);
visit(expr, RValue);
}
void visit(AstStatExpr* expr)
{
visit(expr->expr);
visit(expr->expr, RValue);
}
void visit(AstStatLocal* local)
@ -331,7 +338,7 @@ struct TypeChecker2
AstExpr* value = i < local->values.size ? local->values.data[i] : nullptr;
if (value)
visit(value);
visit(value, RValue);
TypeId* maybeValueType = value ? module->astTypes.find(value) : nullptr;
if (i != local->values.size - 1 || maybeValueType)
@ -387,10 +394,10 @@ struct TypeChecker2
if (forStatement->var->annotation)
visit(forStatement->var->annotation);
visit(forStatement->from);
visit(forStatement->to);
visit(forStatement->from, RValue);
visit(forStatement->to, RValue);
if (forStatement->step)
visit(forStatement->step);
visit(forStatement->step, RValue);
visit(forStatement->body);
}
@ -403,7 +410,7 @@ struct TypeChecker2
}
for (AstExpr* expr : forInStatement->values)
visit(expr);
visit(expr, RValue);
visit(forInStatement->body);
@ -610,11 +617,11 @@ struct TypeChecker2
for (size_t i = 0; i < count; ++i)
{
AstExpr* lhs = assign->vars.data[i];
visit(lhs);
visit(lhs, LValue);
TypeId lhsType = lookupType(lhs);
AstExpr* rhs = assign->values.data[i];
visit(rhs);
visit(rhs, RValue);
TypeId rhsType = lookupType(rhs);
if (!isSubtype(rhsType, lhsType, stack.back()))
@ -635,7 +642,7 @@ struct TypeChecker2
void visit(AstStatFunction* stat)
{
visit(stat->name);
visit(stat->name, LValue);
visit(stat->func);
}
@ -698,13 +705,13 @@ struct TypeChecker2
void visit(AstStatError* stat)
{
for (AstExpr* expr : stat->expressions)
visit(expr);
visit(expr, RValue);
for (AstStat* s : stat->statements)
visit(s);
}
void visit(AstExpr* expr)
void visit(AstExpr* expr, ValueContext context)
{
auto StackPusher = pushStack(expr);
@ -712,7 +719,7 @@ struct TypeChecker2
{
}
else if (auto e = expr->as<AstExprGroup>())
return visit(e);
return visit(e, context);
else if (auto e = expr->as<AstExprConstantNil>())
return visit(e);
else if (auto e = expr->as<AstExprConstantBool>())
@ -730,9 +737,9 @@ struct TypeChecker2
else if (auto e = expr->as<AstExprCall>())
return visit(e);
else if (auto e = expr->as<AstExprIndexName>())
return visit(e);
return visit(e, context);
else if (auto e = expr->as<AstExprIndexExpr>())
return visit(e);
return visit(e, context);
else if (auto e = expr->as<AstExprFunction>())
return visit(e);
else if (auto e = expr->as<AstExprTable>())
@ -754,9 +761,9 @@ struct TypeChecker2
LUAU_ASSERT(!"TypeChecker2 encountered an unknown expression type");
}
void visit(AstExprGroup* expr)
void visit(AstExprGroup* expr, ValueContext context)
{
visit(expr->expr);
visit(expr->expr, context);
}
void visit(AstExprConstantNil* expr)
@ -808,10 +815,10 @@ struct TypeChecker2
void visit(AstExprCall* call)
{
visit(call->func);
visit(call->func, RValue);
for (AstExpr* arg : call->args)
visit(arg);
visit(arg, RValue);
TypeArena* arena = &testArena;
Instantiation instantiation{TxnLog::empty(), arena, TypeLevel{}, stack.back()};
@ -820,6 +827,8 @@ struct TypeChecker2
TypeId functionType = lookupType(call->func);
TypeId testFunctionType = functionType;
TypePack args;
std::vector<Location> argLocs;
argLocs.reserve(call->args.size + 1);
if (get<AnyType>(functionType) || get<ErrorType>(functionType))
return;
@ -830,6 +839,7 @@ struct TypeChecker2
if (std::optional<TypeId> instantiatedCallMm = instantiation.substitute(*callMm))
{
args.head.push_back(functionType);
argLocs.push_back(call->func->location);
testFunctionType = follow(*instantiatedCallMm);
}
else
@ -899,11 +909,13 @@ struct TypeChecker2
ice.ice("method call expression has no 'self'");
args.head.push_back(lookupType(indexExpr->expr));
argLocs.push_back(indexExpr->expr->location);
}
for (size_t i = 0; i < call->args.size; ++i)
{
AstExpr* arg = call->args.data[i];
argLocs.push_back(arg->location);
TypeId* argTy = module->astTypes.find(arg);
if (argTy)
args.head.push_back(*argTy);
@ -919,19 +931,34 @@ struct TypeChecker2
args.head.push_back(builtinTypes->anyType);
}
TypePackId argsTp = arena->addTypePack(args);
FunctionType ftv{argsTp, expectedRetType};
TypeId expectedType = arena->addType(ftv);
TypePackId expectedArgTypes = arena->addTypePack(args);
if (!isSubtype(testFunctionType, expectedType, stack.back()))
const FunctionType* inferredFunctionType = get<FunctionType>(testFunctionType);
LUAU_ASSERT(inferredFunctionType); // testFunctionType should always be a FunctionType here
size_t argIndex = 0;
auto inferredArgIt = begin(inferredFunctionType->argTypes);
auto expectedArgIt = begin(expectedArgTypes);
while (inferredArgIt != end(inferredFunctionType->argTypes) && expectedArgIt != end(expectedArgTypes))
{
CloneState cloneState;
expectedType = clone(expectedType, testArena, cloneState);
reportError(TypeMismatch{expectedType, functionType}, call->location);
Location argLoc = (argIndex >= argLocs.size()) ? argLocs.back() : argLocs[argIndex];
reportErrors(tryUnify(stack.back(), argLoc, *expectedArgIt, *inferredArgIt));
++argIndex;
++inferredArgIt;
++expectedArgIt;
}
// piggyback on the unifier for arity checking, but we can't do this for checking the actual arguments since the locations would be bad
ErrorVec errors = tryUnify(stack.back(), call->location, expectedArgTypes, inferredFunctionType->argTypes);
for (TypeError e : errors)
if (get<CountMismatch>(e) != nullptr)
reportError(std::move(e));
reportErrors(tryUnify(stack.back(), call->location, inferredFunctionType->retTypes, expectedRetType, CountMismatch::FunctionResult));
}
void visit(AstExprIndexName* indexName)
void visit(AstExprIndexName* indexName, ValueContext context)
{
TypeId leftType = lookupType(indexName->expr);
@ -939,14 +966,14 @@ struct TypeChecker2
if (!norm)
reportError(NormalizationTooComplex{}, indexName->indexLocation);
checkIndexTypeFromType(leftType, *norm, indexName->index.value, indexName->location);
checkIndexTypeFromType(leftType, *norm, indexName->index.value, indexName->location, context);
}
void visit(AstExprIndexExpr* indexExpr)
void visit(AstExprIndexExpr* indexExpr, ValueContext context)
{
// TODO!
visit(indexExpr->expr);
visit(indexExpr->index);
visit(indexExpr->expr, LValue);
visit(indexExpr->index, RValue);
}
void visit(AstExprFunction* fn)
@ -986,14 +1013,14 @@ struct TypeChecker2
for (const AstExprTable::Item& item : expr->items)
{
if (item.key)
visit(item.key);
visit(item.value);
visit(item.key, LValue);
visit(item.value, RValue);
}
}
void visit(AstExprUnary* expr)
{
visit(expr->expr);
visit(expr->expr, RValue);
NotNull<Scope> scope = stack.back();
TypeId operandType = lookupType(expr->expr);
@ -1053,8 +1080,8 @@ struct TypeChecker2
TypeId visit(AstExprBinary* expr, void* overrideKey = nullptr)
{
visit(expr->left);
visit(expr->right);
visit(expr->left, LValue);
visit(expr->right, LValue);
NotNull<Scope> scope = stack.back();
@ -1307,7 +1334,7 @@ struct TypeChecker2
void visit(AstExprTypeAssertion* expr)
{
visit(expr->expr);
visit(expr->expr, RValue);
visit(expr->annotation);
TypeId annotationType = lookupAnnotation(expr->annotation);
@ -1326,16 +1353,16 @@ struct TypeChecker2
void visit(AstExprIfElse* expr)
{
// TODO!
visit(expr->condition);
visit(expr->trueExpr);
visit(expr->falseExpr);
visit(expr->condition, RValue);
visit(expr->trueExpr, RValue);
visit(expr->falseExpr, RValue);
}
void visit(AstExprError* expr)
{
// TODO!
for (AstExpr* e : expr->expressions)
visit(e);
visit(e, RValue);
}
/** Extract a TypeId for the first type of the provided pack.
@ -1550,7 +1577,7 @@ struct TypeChecker2
void visit(AstTypeTypeof* ty)
{
visit(ty->expr);
visit(ty->expr, RValue);
}
void visit(AstTypeUnion* ty)
@ -1630,9 +1657,10 @@ struct TypeChecker2
}
template<typename TID>
ErrorVec tryUnify(NotNull<Scope> scope, const Location& location, TID subTy, TID superTy)
ErrorVec tryUnify(NotNull<Scope> scope, const Location& location, TID subTy, TID superTy, CountMismatch::Context context = CountMismatch::Arg)
{
Unifier u{NotNull{&normalizer}, Mode::Strict, scope, location, Covariant};
u.ctx = context;
u.useScopes = true;
u.tryUnify(subTy, superTy);
@ -1658,7 +1686,7 @@ struct TypeChecker2
reportError(std::move(e));
}
void checkIndexTypeFromType(TypeId denormalizedTy, const NormalizedType& norm, const std::string& prop, const Location& location)
void checkIndexTypeFromType(TypeId tableTy, const NormalizedType& norm, const std::string& prop, const Location& location, ValueContext context)
{
bool foundOneProp = false;
std::vector<TypeId> typesMissingTheProp;
@ -1723,9 +1751,11 @@ struct TypeChecker2
if (!typesMissingTheProp.empty())
{
if (foundOneProp)
reportError(TypeError{location, MissingUnionProperty{denormalizedTy, typesMissingTheProp, prop}});
reportError(MissingUnionProperty{tableTy, typesMissingTheProp, prop}, location);
else if (context == LValue)
reportError(CannotExtendTable{tableTy, CannotExtendTable::Property, prop}, location);
else
reportError(TypeError{location, UnknownProperty{denormalizedTy, prop}});
reportError(UnknownProperty{tableTy, prop}, location);
}
}

View File

@ -32,16 +32,13 @@ LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000)
LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300)
LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauTypeNormalization2)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
LUAU_FASTFLAGVARIABLE(LuauUnknownAndNeverType, false)
LUAU_FASTFLAGVARIABLE(LuauTypeInferMissingFollows, false)
LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false)
LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false)
LUAU_FASTFLAGVARIABLE(LuauScopelessModule, false)
LUAU_FASTFLAGVARIABLE(LuauFollowInLvalueIndexCheck, false)
LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false)
LUAU_FASTFLAGVARIABLE(LuauTryhardAnd, false)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
@ -52,9 +49,8 @@ LUAU_FASTFLAGVARIABLE(LuauIntersectionTestForEquality, false)
LUAU_FASTFLAGVARIABLE(LuauImplicitElseRefinement, false)
LUAU_FASTFLAG(LuauNegatedClassTypes)
LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false)
LUAU_FASTFLAGVARIABLE(LuauDeclareClassPrototype, false)
LUAU_FASTFLAG(LuauUninhabitedSubAnything2)
LUAU_FASTFLAGVARIABLE(LuauCallableClasses, false)
LUAU_FASTFLAG(SupportTypeAliasGoToDeclaration)
namespace Luau
{
@ -333,12 +329,9 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
prepareErrorsForDisplay(currentModule->errors);
if (FFlag::LuauTypeNormalization2)
{
// Clear the normalizer caches, since they contain types from the internal type surface
normalizer.clearCaches();
normalizer.arena = nullptr;
}
// Clear the normalizer caches, since they contain types from the internal type surface
normalizer.clearCaches();
normalizer.arena = nullptr;
currentModule->clonePublicInterface(builtinTypes, *iceHandler);
@ -512,7 +505,7 @@ void TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, const A
prototype(scope, *typealias, subLevel);
++subLevel;
}
else if (const auto& declaredClass = stat->as<AstStatDeclareClass>(); FFlag::LuauDeclareClassPrototype && declaredClass)
else if (const auto& declaredClass = stat->as<AstStatDeclareClass>())
{
prototype(scope, *declaredClass);
}
@ -1137,8 +1130,12 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatLocal& local)
const Name name{local.vars.data[i]->name.value};
if (ModulePtr module = resolver->getModule(moduleInfo->name))
{
scope->importedTypeBindings[name] =
FFlag::LuauScopelessModule ? module->exportedTypeBindings : module->getModuleScope()->exportedTypeBindings;
if (FFlag::SupportTypeAliasGoToDeclaration)
scope->importedModules[name] = moduleInfo->name;
}
// In non-strict mode we force the module type on the variable, in strict mode it is already unified
if (isNonstrictMode())
@ -1622,6 +1619,8 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
scope->typeAliasLocations[name] = typealias.location;
if (FFlag::SupportTypeAliasGoToDeclaration)
scope->typeAliasNameLocations[name] = typealias.nameLocation;
}
}
else
@ -1640,12 +1639,13 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
scope->typeAliasLocations[name] = typealias.location;
if (FFlag::SupportTypeAliasGoToDeclaration)
scope->typeAliasNameLocations[name] = typealias.nameLocation;
}
}
void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
{
LUAU_ASSERT(FFlag::LuauDeclareClassPrototype);
std::optional<TypeId> superTy = FFlag::LuauNegatedClassTypes ? std::make_optional(builtinTypes->classType) : std::nullopt;
if (declaredClass.superName)
{
@ -1684,166 +1684,74 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& de
void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
{
if (FFlag::LuauDeclareClassPrototype)
Name className(declaredClass.name.value);
// Don't bother checking if the class definition was incorrect
if (incorrectClassDefinitions.find(&declaredClass))
return;
std::optional<TypeFun> binding;
if (auto it = scope->exportedTypeBindings.find(className); it != scope->exportedTypeBindings.end())
binding = it->second;
// This class definition must have been `prototype()`d first.
if (!binding)
ice("Class not predeclared");
TypeId classTy = binding->type;
ClassType* ctv = getMutable<ClassType>(classTy);
if (!ctv->metatable)
ice("No metatable for declared class");
TableType* metatable = getMutable<TableType>(*ctv->metatable);
for (const AstDeclaredClassProp& prop : declaredClass.props)
{
Name className(declaredClass.name.value);
Name propName(prop.name.value);
TypeId propTy = resolveType(scope, *prop.ty);
// Don't bother checking if the class definition was incorrect
if (incorrectClassDefinitions.find(&declaredClass))
return;
bool assignToMetatable = isMetamethod(propName);
Luau::ClassType::Props& assignTo = assignToMetatable ? metatable->props : ctv->props;
std::optional<TypeFun> binding;
if (auto it = scope->exportedTypeBindings.find(className); it != scope->exportedTypeBindings.end())
binding = it->second;
// This class definition must have been `prototype()`d first.
if (!binding)
ice("Class not predeclared");
TypeId classTy = binding->type;
ClassType* ctv = getMutable<ClassType>(classTy);
if (!ctv->metatable)
ice("No metatable for declared class");
TableType* metatable = getMutable<TableType>(*ctv->metatable);
for (const AstDeclaredClassProp& prop : declaredClass.props)
// Function types always take 'self', but this isn't reflected in the
// parsed annotation. Add it here.
if (prop.isMethod)
{
Name propName(prop.name.value);
TypeId propTy = resolveType(scope, *prop.ty);
bool assignToMetatable = isMetamethod(propName);
Luau::ClassType::Props& assignTo = assignToMetatable ? metatable->props : ctv->props;
// Function types always take 'self', but this isn't reflected in the
// parsed annotation. Add it here.
if (prop.isMethod)
if (FunctionType* ftv = getMutable<FunctionType>(propTy))
{
if (FunctionType* ftv = getMutable<FunctionType>(propTy))
{
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes});
ftv->hasSelf = true;
}
}
if (assignTo.count(propName) == 0)
{
assignTo[propName] = {propTy};
}
else
{
TypeId currentTy = assignTo[propName].type;
// We special-case this logic to keep the intersection flat; otherwise we
// would create a ton of nested intersection types.
if (const IntersectionType* itv = get<IntersectionType>(currentTy))
{
std::vector<TypeId> options = itv->parts;
options.push_back(propTy);
TypeId newItv = addType(IntersectionType{std::move(options)});
assignTo[propName] = {newItv};
}
else if (get<FunctionType>(currentTy))
{
TypeId intersection = addType(IntersectionType{{currentTy, propTy}});
assignTo[propName] = {intersection};
}
else
{
reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
}
}
}
}
else
{
std::optional<TypeId> superTy = FFlag::LuauNegatedClassTypes ? std::make_optional(builtinTypes->classType) : std::nullopt;
if (declaredClass.superName)
{
Name superName = Name(declaredClass.superName->value);
std::optional<TypeFun> lookupType = scope->lookupType(superName);
if (!lookupType)
{
reportError(declaredClass.location, UnknownSymbol{superName, UnknownSymbol::Type});
return;
}
// We don't have generic classes, so this assertion _should_ never be hit.
LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
superTy = lookupType->type;
if (!get<ClassType>(follow(*superTy)))
{
reportError(declaredClass.location, GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'",
superName.c_str(), declaredClass.name.value)});
return;
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes});
ftv->hasSelf = true;
}
}
Name className(declaredClass.name.value);
TypeId classTy = addType(ClassType(className, {}, superTy, std::nullopt, {}, {}, currentModuleName));
ClassType* ctv = getMutable<ClassType>(classTy);
TypeId metaTy = addType(TableType{TableState::Sealed, scope->level});
TableType* metatable = getMutable<TableType>(metaTy);
ctv->metatable = metaTy;
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
for (const AstDeclaredClassProp& prop : declaredClass.props)
if (assignTo.count(propName) == 0)
{
Name propName(prop.name.value);
TypeId propTy = resolveType(scope, *prop.ty);
assignTo[propName] = {propTy};
}
else
{
TypeId currentTy = assignTo[propName].type;
bool assignToMetatable = isMetamethod(propName);
Luau::ClassType::Props& assignTo = assignToMetatable ? metatable->props : ctv->props;
// Function types always take 'self', but this isn't reflected in the
// parsed annotation. Add it here.
if (prop.isMethod)
// We special-case this logic to keep the intersection flat; otherwise we
// would create a ton of nested intersection types.
if (const IntersectionType* itv = get<IntersectionType>(currentTy))
{
if (FunctionType* ftv = getMutable<FunctionType>(propTy))
{
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes});
ftv->hasSelf = true;
}
std::vector<TypeId> options = itv->parts;
options.push_back(propTy);
TypeId newItv = addType(IntersectionType{std::move(options)});
assignTo[propName] = {newItv};
}
if (assignTo.count(propName) == 0)
else if (get<FunctionType>(currentTy))
{
assignTo[propName] = {propTy};
TypeId intersection = addType(IntersectionType{{currentTy, propTy}});
assignTo[propName] = {intersection};
}
else
{
TypeId currentTy = assignTo[propName].type;
// We special-case this logic to keep the intersection flat; otherwise we
// would create a ton of nested intersection types.
if (const IntersectionType* itv = get<IntersectionType>(currentTy))
{
std::vector<TypeId> options = itv->parts;
options.push_back(propTy);
TypeId newItv = addType(IntersectionType{std::move(options)});
assignTo[propName] = {newItv};
}
else if (get<FunctionType>(currentTy))
{
TypeId intersection = addType(IntersectionType{{currentTy, propTy}});
assignTo[propName] = {intersection};
}
else
{
reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
}
reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
}
}
}
@ -3364,8 +3272,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
TypeId indexType = checkExpr(scope, *expr.index).type;
if (FFlag::LuauFollowInLvalueIndexCheck)
exprType = follow(exprType);
exprType = follow(exprType);
if (get<AnyType>(exprType) || get<ErrorType>(exprType))
return exprType;
@ -4282,7 +4189,7 @@ std::optional<WithPredicate<TypePackId>> TypeChecker::checkCallOverload(const Sc
{
callTy = getIndexTypeFromType(scope, mttv->metatable, "__call", expr.func->location, /* addErrors= */ false);
}
else if (const ClassType* ctv = get<ClassType>(fn); FFlag::LuauCallableClasses && ctv && ctv->metatable)
else if (const ClassType* ctv = get<ClassType>(fn); ctv && ctv->metatable)
{
callTy = getIndexTypeFromType(scope, *ctv->metatable, "__call", expr.func->location, /* addErrors= */ false);
}
@ -4479,7 +4386,7 @@ void TypeChecker::reportOverloadResolutionError(const ScopePtr& scope, const Ast
std::string s;
for (size_t i = 0; i < overloadTypes.size(); ++i)
{
TypeId overload = FFlag::LuauTypeInferMissingFollows ? follow(overloadTypes[i]) : overloadTypes[i];
TypeId overload = follow(overloadTypes[i]);
Unifier state = mkUnifier(scope, expr.location);
// Unify return types
@ -4861,7 +4768,7 @@ TypePackId TypeChecker::anyifyModuleReturnTypePackGenerics(TypePackId tp)
if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
{
TypeId ty = FFlag::LuauTypeInferMissingFollows ? follow(vtp->ty) : vtp->ty;
TypeId ty = follow(vtp->ty);
return get<GenericType>(ty) ? anyTypePack : tp;
}
@ -6105,11 +6012,11 @@ void TypeChecker::resolve(const EqPredicate& eqP, RefinementMap& refis, const Sc
if (optionIsSubtype && !targetIsSubtype)
return option;
else if (!optionIsSubtype && targetIsSubtype)
return FFlag::LuauTypeInferMissingFollows ? follow(eqP.type) : eqP.type;
return follow(eqP.type);
else if (!optionIsSubtype && !targetIsSubtype)
return nope;
else if (optionIsSubtype && targetIsSubtype)
return FFlag::LuauTypeInferMissingFollows ? follow(eqP.type) : eqP.type;
return follow(eqP.type);
}
else
{

View File

@ -6,8 +6,6 @@
#include <stdexcept>
LUAU_FASTFLAGVARIABLE(LuauTxnLogTypePackIterator, false)
namespace Luau
{
@ -62,8 +60,8 @@ TypePackIterator::TypePackIterator(TypePackId typePack)
}
TypePackIterator::TypePackIterator(TypePackId typePack, const TxnLog* log)
: currentTypePack(FFlag::LuauTxnLogTypePackIterator ? log->follow(typePack) : follow(typePack))
, tp(FFlag::LuauTxnLogTypePackIterator ? log->get<TypePack>(currentTypePack) : get<TypePack>(currentTypePack))
: currentTypePack(log->follow(typePack))
, tp(log->get<TypePack>(currentTypePack))
, currentIndex(0)
, log(log)
{

View File

@ -4,6 +4,7 @@
#include "Luau/Common.h"
#include "Luau/Error.h"
#include "Luau/RecursionCounter.h"
#include "Luau/VisitType.h"
#include <numeric>
#include <deque>
@ -18,24 +19,7 @@ namespace Luau
namespace
{
struct RecursionGuard : RecursionLimiter
{
std::deque<const void*>* seen;
RecursionGuard(int* count, int limit, std::deque<const void*>* seen)
: RecursionLimiter(count, limit)
, seen(seen)
{
// count has been incremented, which should imply that seen has already had an element pushed in.
LUAU_ASSERT(size_t(*count) == seen->size());
}
~RecursionGuard()
{
LUAU_ASSERT(!seen->empty()); // It is UB to pop_back() on an empty deque.
seen->pop_back();
}
};
using detail::ReductionContext;
template<typename A, typename B, typename Thing>
std::pair<const A*, const B*> get2(const Thing& one, const Thing& two)
@ -51,15 +35,11 @@ struct TypeReducer
NotNull<BuiltinTypes> builtinTypes;
NotNull<InternalErrorReporter> handle;
std::unordered_map<TypeId, TypeId> copies;
std::deque<const void*> seen;
int depth = 0;
DenseHashMap<TypeId, ReductionContext<TypeId>>* memoizedTypes;
DenseHashMap<TypePackId, ReductionContext<TypePackId>>* memoizedTypePacks;
DenseHashSet<TypeId>* cyclicTypes;
// When we encounter _any type_ that which is usually mutated in-place, we need to not cache the result.
// e.g. `'a & {} T` may have an upper bound constraint `{}` placed upon `'a`, but this constraint was not
// known when we decided to reduce this intersection type. By not caching, we'll always be forced to perform
// the reduction calculus over again.
bool cacheOk = true;
int depth = 0;
TypeId reduce(TypeId ty);
TypePackId reduce(TypePackId tp);
@ -70,62 +50,73 @@ struct TypeReducer
TypeId functionType(TypeId ty);
TypeId negationType(TypeId ty);
RecursionGuard guard(TypeId ty);
RecursionGuard guard(TypePackId tp);
bool isIrreducible(TypeId ty);
bool isIrreducible(TypePackId tp);
void checkCacheable(TypeId ty);
void checkCacheable(TypePackId tp);
TypeId memoize(TypeId ty, TypeId reducedTy);
TypePackId memoize(TypePackId tp, TypePackId reducedTp);
// It's either cyclic with no memoized result, so we should terminate, or
// there is a memoized result but one that's being reduced top-down, so
// we need to return the root of that memoized result to tighten up things.
TypeId memoizedOr(TypeId ty) const;
TypePackId memoizedOr(TypePackId tp) const;
using BinaryFold = std::optional<TypeId> (TypeReducer::*)(TypeId, TypeId);
using UnaryFold = TypeId (TypeReducer::*)(TypeId);
template<typename T>
LUAU_NOINLINE std::pair<TypeId, T*> copy(TypeId ty, const T* t)
{
if (auto it = copies.find(ty); it != copies.end())
return {it->second, getMutable<T>(it->second)};
ty = follow(ty);
if (auto ctx = memoizedTypes->find(ty))
return {ctx->type, getMutable<T>(ctx->type)};
TypeId copiedTy = arena->addType(*t);
copies[ty] = copiedTy;
(*memoizedTypes)[ty] = {copiedTy, false};
(*memoizedTypes)[copiedTy] = {copiedTy, false};
return {copiedTy, getMutable<T>(copiedTy)};
}
using Folder = std::optional<TypeId> (TypeReducer::*)(TypeId, TypeId);
template<typename T, typename Iter>
void foldl_impl(Iter it, Iter endIt, Folder f, NotNull<std::vector<TypeId>> result)
void foldl_impl(Iter it, Iter endIt, BinaryFold f, std::vector<TypeId>* result, bool* didReduce)
{
RecursionLimiter rl{&depth, FInt::LuauTypeReductionRecursionLimit};
while (it != endIt)
{
bool replaced = false;
TypeId currentTy = reduce(*it);
RecursionGuard rg = guard(*it);
TypeId right = reduce(*it);
*didReduce |= right != follow(*it);
// We're hitting a case where the `currentTy` returned a type that's the same as `T`.
// e.g. `(string?) & ~(false | nil)` became `(string?) & (~false & ~nil)` but the current iterator we're consuming doesn't know this.
// We will need to recurse and traverse that first.
if (auto t = get<T>(currentTy))
if (auto t = get<T>(right))
{
foldl_impl<T>(begin(t), end(t), f, result);
foldl_impl<T>(begin(t), end(t), f, result, didReduce);
++it;
continue;
}
bool replaced = false;
auto resultIt = result->begin();
while (resultIt != result->end())
{
TypeId& ty = *resultIt;
std::optional<TypeId> reduced = (this->*f)(ty, currentTy);
if (reduced && replaced)
TypeId left = *resultIt;
if (left == right)
{
// We want to erase any other elements that occurs after the first replacement too.
// e.g. `"a" | "b" | string` where `"a"` and `"b"` is in the `result` vector, then `string` replaces both `"a"` and `"b"`.
// If we don't erase redundant elements, `"b"` may be kept or be replaced by `string`, leaving us with `string | string`.
resultIt = result->erase(resultIt);
replaced = true;
++resultIt;
continue;
}
else if (reduced && !replaced)
std::optional<TypeId> reduced = (this->*f)(left, right);
if (reduced)
{
*resultIt = *reduced;
++resultIt;
replaced = true;
ty = *reduced;
}
else
{
@ -135,21 +126,65 @@ struct TypeReducer
}
if (!replaced)
result->push_back(currentTy);
result->push_back(right);
*didReduce |= replaced;
++it;
}
}
template<typename T>
TypeId flatten(std::vector<TypeId>&& types)
{
if (types.size() == 1)
return types[0];
else
return arena->addType(T{std::move(types)});
}
template<typename T, typename Iter>
TypeId foldl(Iter it, Iter endIt, Folder f)
TypeId foldl(Iter it, Iter endIt, std::optional<TypeId> ty, BinaryFold f)
{
std::vector<TypeId> result;
foldl_impl<T>(it, endIt, f, NotNull{&result});
if (result.size() == 1)
return result[0];
bool didReduce = false;
foldl_impl<T>(it, endIt, f, &result, &didReduce);
if (!didReduce && ty)
return *ty;
else
return arena->addType(T{std::move(result)});
{
// If we've done any reduction, then we'll need to reduce it again, e.g.
// `"a" | "b" | string` is reduced into `string | string`, which is then reduced into `string`.
return reduce(flatten<T>(std::move(result)));
}
}
template<typename T>
TypeId apply(BinaryFold f, TypeId left, TypeId right)
{
left = follow(left);
right = follow(right);
if (get<T>(left) || get<T>(right))
{
std::vector<TypeId> types{left, right};
return foldl<T>(begin(types), end(types), std::nullopt, f);
}
else if (auto reduced = (this->*f)(left, right))
return *reduced;
else
return arena->addType(T{{left, right}});
}
template<typename Into, typename Over>
TypeId distribute(TypeIterator<Over> it, TypeIterator<Over> endIt, BinaryFold f, TypeId ty)
{
std::vector<TypeId> result;
while (it != endIt)
{
result.push_back(apply<Into>(f, *it, ty));
++it;
}
return flatten<Over>(std::move(result));
}
};
@ -157,42 +192,48 @@ TypeId TypeReducer::reduce(TypeId ty)
{
ty = follow(ty);
if (std::find(seen.begin(), seen.end(), ty) != seen.end())
return ty;
if (auto ctx = memoizedTypes->find(ty); ctx && ctx->irreducible)
return ctx->type;
else if (auto cyclicTy = cyclicTypes->find(ty))
return *cyclicTy;
RecursionGuard rg = guard(ty);
checkCacheable(ty);
RecursionLimiter rl{&depth, FInt::LuauTypeReductionRecursionLimit};
TypeId result = nullptr;
if (auto i = get<IntersectionType>(ty))
return foldl<IntersectionType>(begin(i), end(i), &TypeReducer::intersectionType);
result = foldl<IntersectionType>(begin(i), end(i), ty, &TypeReducer::intersectionType);
else if (auto u = get<UnionType>(ty))
return foldl<UnionType>(begin(u), end(u), &TypeReducer::unionType);
result = foldl<UnionType>(begin(u), end(u), ty, &TypeReducer::unionType);
else if (get<TableType>(ty) || get<MetatableType>(ty))
return tableType(ty);
result = tableType(ty);
else if (get<FunctionType>(ty))
return functionType(ty);
else if (auto n = get<NegationType>(ty))
return negationType(follow(n->ty));
result = functionType(ty);
else if (get<NegationType>(ty))
result = negationType(ty);
else
return ty;
result = ty;
return memoize(ty, result);
}
TypePackId TypeReducer::reduce(TypePackId tp)
{
tp = follow(tp);
if (std::find(seen.begin(), seen.end(), tp) != seen.end())
return tp;
if (auto ctx = memoizedTypePacks->find(tp); ctx && ctx->irreducible)
return ctx->type;
RecursionGuard rg = guard(tp);
checkCacheable(tp);
RecursionLimiter rl{&depth, FInt::LuauTypeReductionRecursionLimit};
bool didReduce = false;
TypePackIterator it = begin(tp);
std::vector<TypeId> head;
while (it != end(tp))
{
head.push_back(reduce(*it));
TypeId reducedTy = reduce(*it);
head.push_back(reducedTy);
didReduce |= follow(*it) != follow(reducedTy);
++it;
}
@ -200,10 +241,22 @@ TypePackId TypeReducer::reduce(TypePackId tp)
if (tail)
{
if (auto vtp = get<VariadicTypePack>(follow(*it.tail())))
tail = arena->addTypePack(VariadicTypePack{reduce(vtp->ty), vtp->hidden});
{
TypeId reducedTy = reduce(vtp->ty);
if (follow(vtp->ty) != follow(reducedTy))
{
tail = arena->addTypePack(VariadicTypePack{reducedTy, vtp->hidden});
didReduce = true;
}
}
}
return arena->addTypePack(TypePack{std::move(head), tail});
if (!didReduce)
return memoize(tp, tp);
else if (head.empty() && tail)
return memoize(tp, *tail);
else
return memoize(tp, arena->addTypePack(TypePack{std::move(head), tail}));
}
std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
@ -236,18 +289,7 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
else if (get<ErrorType>(right))
return std::nullopt; // T & error ~ T & error
else if (auto ut = get<UnionType>(left))
{
std::vector<TypeId> options;
for (TypeId option : ut)
{
if (auto result = intersectionType(option, right))
options.push_back(*result);
else
options.push_back(arena->addType(IntersectionType{{option, right}}));
}
return foldl<UnionType>(begin(options), end(options), &TypeReducer::unionType); // (A | B) & T ~ (A & T) | (B & T)
}
return reduce(distribute<IntersectionType>(begin(ut), end(ut), &TypeReducer::intersectionType, right)); // (A | B) & T ~ (A & T) | (B & T)
else if (get<UnionType>(right))
return intersectionType(right, left); // T & (A | B) ~ (A | B) & T
else if (auto [p1, p2] = get2<PrimitiveType, PrimitiveType>(left, right); p1 && p2)
@ -294,14 +336,7 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
return builtinTypes->neverType; // Base & Unrelated ~ never
}
else if (auto [f1, f2] = get2<FunctionType, FunctionType>(left, right); f1 && f2)
{
if (std::find(seen.begin(), seen.end(), left) != seen.end())
return std::nullopt;
else if (std::find(seen.begin(), seen.end(), right) != seen.end())
return std::nullopt;
return std::nullopt; // TODO
}
else if (auto [t1, t2] = get2<TableType, TableType>(left, right); t1 && t2)
{
if (t1->state == TableState::Free || t2->state == TableState::Free)
@ -309,10 +344,10 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
else if (t1->state == TableState::Generic || t2->state == TableState::Generic)
return std::nullopt; // '{ x: T } & { x: U } ~ '{ x: T } & { x: U }
if (std::find(seen.begin(), seen.end(), left) != seen.end())
return std::nullopt;
else if (std::find(seen.begin(), seen.end(), right) != seen.end())
return std::nullopt;
if (cyclicTypes->find(left))
return std::nullopt; // (t1 where t1 = { p: t1 }) & {} ~ t1 & {}
else if (cyclicTypes->find(right))
return std::nullopt; // {} & (t1 where t1 = { p: t1 }) ~ {} & t1
TypeId resultTy = arena->addType(TableType{});
TableType* table = getMutable<TableType>(resultTy);
@ -324,8 +359,7 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
// even if we have the corresponding property in the other one.
if (auto other = t2->props.find(name); other != t2->props.end())
{
std::vector<TypeId> parts{prop.type, other->second.type};
TypeId propTy = foldl<IntersectionType>(begin(parts), end(parts), &TypeReducer::intersectionType);
TypeId propTy = apply<IntersectionType>(&TypeReducer::intersectionType, prop.type, other->second.type);
if (get<NeverType>(propTy))
return builtinTypes->neverType; // { p : string } & { p : number } ~ { p : string & number } ~ { p : never } ~ never
else
@ -340,27 +374,33 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
// TODO: And vice versa, t2 properties against t1 indexer if it exists,
// even if we have the corresponding property in the other one.
if (!t1->props.count(name))
table->props[name] = prop; // {} & { p : string } ~ { p : string }
table->props[name] = {reduce(prop.type)}; // {} & { p : string & string } ~ { p : string }
}
if (t1->indexer && t2->indexer)
{
std::vector<TypeId> keyParts{t1->indexer->indexType, t2->indexer->indexType};
TypeId keyTy = foldl<IntersectionType>(begin(keyParts), end(keyParts), &TypeReducer::intersectionType);
TypeId keyTy = apply<IntersectionType>(&TypeReducer::intersectionType, t1->indexer->indexType, t2->indexer->indexType);
if (get<NeverType>(keyTy))
return builtinTypes->neverType; // { [string]: _ } & { [number]: _ } ~ { [string & number]: _ } ~ { [never]: _ } ~ never
return std::nullopt; // { [string]: _ } & { [number]: _ } ~ { [string]: _ } & { [number]: _ }
std::vector<TypeId> valueParts{t1->indexer->indexResultType, t2->indexer->indexResultType};
TypeId valueTy = foldl<IntersectionType>(begin(valueParts), end(valueParts), &TypeReducer::intersectionType);
TypeId valueTy = apply<IntersectionType>(&TypeReducer::intersectionType, t1->indexer->indexResultType, t2->indexer->indexResultType);
if (get<NeverType>(valueTy))
return builtinTypes->neverType; // { [_]: string } & { [_]: number } ~ { [_]: string & number } ~ { [_]: never } ~ never
table->indexer = TableIndexer{keyTy, valueTy};
}
else if (t1->indexer)
table->indexer = t1->indexer; // { [number]: boolean } & { p : string } ~ { p : string, [number]: boolean }
{
TypeId keyTy = reduce(t1->indexer->indexType);
TypeId valueTy = reduce(t1->indexer->indexResultType);
table->indexer = TableIndexer{keyTy, valueTy}; // { [number]: boolean } & { p : string } ~ { p : string, [number]: boolean }
}
else if (t2->indexer)
table->indexer = t2->indexer; // { p : string } & { [number]: boolean } ~ { p : string, [number]: boolean }
{
TypeId keyTy = reduce(t2->indexer->indexType);
TypeId valueTy = reduce(t2->indexer->indexResultType);
table->indexer = TableIndexer{keyTy, valueTy}; // { p : string } & { [number]: boolean } ~ { p : string, [number]: boolean }
}
return resultTy;
}
@ -506,22 +546,7 @@ std::optional<TypeId> TypeReducer::unionType(TypeId left, TypeId right)
return std::nullopt; // Base | Unrelated ~ Base | Unrelated
}
else if (auto [nt, it] = get2<NegationType, IntersectionType>(left, right); nt && it)
{
std::vector<TypeId> parts;
for (TypeId option : it)
{
if (auto result = unionType(left, option))
parts.push_back(*result);
else
{
// TODO: does there exist a reduced form such that `~T | A` hasn't already reduced it, if `A & B` is irreducible?
// I want to say yes, but I can't generate a case that hits this code path.
parts.push_back(arena->addType(UnionType{{left, option}}));
}
}
return foldl<IntersectionType>(begin(parts), end(parts), &TypeReducer::intersectionType); // ~T | (A & B) ~ (~T | A) & (~T | B)
}
return reduce(distribute<UnionType>(begin(it), end(it), &TypeReducer::unionType, left)); // ~T | (A & B) ~ (~T | A) & (~T | B)
else if (auto [it, nt] = get2<IntersectionType, NegationType>(left, right); it && nt)
return unionType(right, left); // (A & B) | ~T ~ ~T | (A & B)
else if (auto [nl, nr] = get2<NegationType, NegationType>(left, right); nl && nr)
@ -628,8 +653,6 @@ std::optional<TypeId> TypeReducer::unionType(TypeId left, TypeId right)
TypeId TypeReducer::tableType(TypeId ty)
{
RecursionGuard rg = guard(ty);
if (auto mt = get<MetatableType>(ty))
{
auto [copiedTy, copied] = copy(ty, mt);
@ -639,15 +662,30 @@ TypeId TypeReducer::tableType(TypeId ty)
}
else if (auto tt = get<TableType>(ty))
{
// Because of `typeof()`, we need to preserve pointer identity of free/unsealed tables so that
// all mutations that occurs on this will be applied without leaking the implementation details.
// As a result, we'll just use the type instead of cloning it if it's free/unsealed.
//
// We could choose to do in-place reductions here, but to be on the safer side, I propose that we do not.
if (tt->state == TableState::Free || tt->state == TableState::Unsealed)
return ty;
auto [copiedTy, copied] = copy(ty, tt);
for (auto& [name, prop] : copied->props)
prop.type = reduce(prop.type);
if (auto& indexer = copied->indexer)
{
indexer->indexType = reduce(indexer->indexType);
indexer->indexResultType = reduce(indexer->indexResultType);
TypeId propTy = reduce(prop.type);
if (get<NeverType>(propTy))
return builtinTypes->neverType;
else
prop.type = propTy;
}
if (copied->indexer)
{
TypeId keyTy = reduce(copied->indexer->indexType);
TypeId valueTy = reduce(copied->indexer->indexResultType);
copied->indexer = TableIndexer{keyTy, valueTy};
}
for (TypeId& ty : copied->instantiatedTypeParams)
@ -659,16 +697,14 @@ TypeId TypeReducer::tableType(TypeId ty)
return copiedTy;
}
else
handle->ice("Unexpected type in TypeReducer::tableType");
handle->ice("TypeReducer::tableType expects a TableType or MetatableType");
}
TypeId TypeReducer::functionType(TypeId ty)
{
RecursionGuard rg = guard(ty);
const FunctionType* f = get<FunctionType>(ty);
if (!f)
handle->ice("TypeReducer::reduce expects a FunctionType");
handle->ice("TypeReducer::functionType expects a FunctionType");
// TODO: once we have bounded quantification, we need to be able to reduce the generic bounds.
auto [copiedTy, copied] = copy(ty, f);
@ -679,140 +715,238 @@ TypeId TypeReducer::functionType(TypeId ty)
TypeId TypeReducer::negationType(TypeId ty)
{
RecursionGuard rg = guard(ty);
const NegationType* n = get<NegationType>(ty);
if (!n)
return arena->addType(NegationType{ty});
if (auto nn = get<NegationType>(ty))
if (auto nn = get<NegationType>(n->ty))
return nn->ty; // ~~T ~ T
else if (get<NeverType>(ty))
else if (get<NeverType>(n->ty))
return builtinTypes->unknownType; // ~never ~ unknown
else if (get<UnknownType>(ty))
else if (get<UnknownType>(n->ty))
return builtinTypes->neverType; // ~unknown ~ never
else if (get<AnyType>(ty))
else if (get<AnyType>(n->ty))
return builtinTypes->anyType; // ~any ~ any
else if (auto ni = get<IntersectionType>(ty))
else if (auto ni = get<IntersectionType>(n->ty))
{
std::vector<TypeId> options;
for (TypeId part : ni)
options.push_back(negationType(part));
return foldl<UnionType>(begin(options), end(options), &TypeReducer::unionType); // ~(T & U) ~ (~T | ~U)
options.push_back(negationType(arena->addType(NegationType{part})));
return reduce(flatten<UnionType>(std::move(options))); // ~(T & U) ~ (~T | ~U)
}
else if (auto nu = get<UnionType>(ty))
else if (auto nu = get<UnionType>(n->ty))
{
std::vector<TypeId> parts;
for (TypeId option : nu)
parts.push_back(negationType(option));
return foldl<IntersectionType>(begin(parts), end(parts), &TypeReducer::intersectionType); // ~(T | U) ~ (~T & ~U)
parts.push_back(negationType(arena->addType(NegationType{option})));
return reduce(flatten<IntersectionType>(std::move(parts))); // ~(T | U) ~ (~T & ~U)
}
else
return arena->addType(NegationType{ty}); // for all T except the ones handled above, ~T ~ ~T
return ty; // for all T except the ones handled above, ~T ~ ~T
}
RecursionGuard TypeReducer::guard(TypeId ty)
bool TypeReducer::isIrreducible(TypeId ty)
{
seen.push_back(ty);
return RecursionGuard{&depth, FInt::LuauTypeReductionRecursionLimit, &seen};
}
RecursionGuard TypeReducer::guard(TypePackId tp)
{
seen.push_back(tp);
return RecursionGuard{&depth, FInt::LuauTypeReductionRecursionLimit, &seen};
}
void TypeReducer::checkCacheable(TypeId ty)
{
if (!cacheOk)
return;
ty = follow(ty);
// Only does shallow check, the TypeReducer itself already does deep traversal.
if (get<FreeType>(ty) || get<BlockedType>(ty) || get<PendingExpansionType>(ty))
cacheOk = false;
if (auto ctx = memoizedTypes->find(ty); ctx && ctx->irreducible)
return true;
else if (get<FreeType>(ty) || get<BlockedType>(ty) || get<PendingExpansionType>(ty))
return false;
else if (auto tt = get<TableType>(ty); tt && (tt->state == TableState::Free || tt->state == TableState::Unsealed))
cacheOk = false;
return false;
else
return true;
}
void TypeReducer::checkCacheable(TypePackId tp)
bool TypeReducer::isIrreducible(TypePackId tp)
{
if (!cacheOk)
return;
tp = follow(tp);
// Only does shallow check, the TypeReducer itself already does deep traversal.
if (get<FreeTypePack>(tp) || get<BlockedTypePack>(tp))
cacheOk = false;
if (auto ctx = memoizedTypePacks->find(tp); ctx && ctx->irreducible)
return true;
else if (get<FreeTypePack>(tp) || get<BlockedTypePack>(tp))
return false;
else if (auto vtp = get<VariadicTypePack>(tp))
return isIrreducible(vtp->ty);
else
return true;
}
TypeId TypeReducer::memoize(TypeId ty, TypeId reducedTy)
{
ty = follow(ty);
reducedTy = follow(reducedTy);
// The irreducibility of this [`reducedTy`] depends on whether its contents are themselves irreducible.
// We don't need to recurse much further than that, because we already record the irreducibility from
// the bottom up.
bool irreducible = isIrreducible(reducedTy);
if (auto it = get<IntersectionType>(reducedTy))
{
for (TypeId part : it)
irreducible &= isIrreducible(part);
}
else if (auto ut = get<UnionType>(reducedTy))
{
for (TypeId option : ut)
irreducible &= isIrreducible(option);
}
else if (auto tt = get<TableType>(reducedTy))
{
for (auto& [k, p] : tt->props)
irreducible &= isIrreducible(p.type);
if (tt->indexer)
{
irreducible &= isIrreducible(tt->indexer->indexType);
irreducible &= isIrreducible(tt->indexer->indexResultType);
}
for (auto ta : tt->instantiatedTypeParams)
irreducible &= isIrreducible(ta);
for (auto tpa : tt->instantiatedTypePackParams)
irreducible &= isIrreducible(tpa);
}
else if (auto mt = get<MetatableType>(reducedTy))
{
irreducible &= isIrreducible(mt->table);
irreducible &= isIrreducible(mt->metatable);
}
else if (auto ft = get<FunctionType>(reducedTy))
{
irreducible &= isIrreducible(ft->argTypes);
irreducible &= isIrreducible(ft->retTypes);
}
else if (auto nt = get<NegationType>(reducedTy))
irreducible &= isIrreducible(nt->ty);
(*memoizedTypes)[ty] = {reducedTy, irreducible};
(*memoizedTypes)[reducedTy] = {reducedTy, irreducible};
return reducedTy;
}
TypePackId TypeReducer::memoize(TypePackId tp, TypePackId reducedTp)
{
tp = follow(tp);
reducedTp = follow(reducedTp);
bool irreducible = isIrreducible(reducedTp);
TypePackIterator it = begin(tp);
while (it != end(tp))
{
irreducible &= isIrreducible(*it);
++it;
}
if (it.tail())
irreducible &= isIrreducible(*it.tail());
(*memoizedTypePacks)[tp] = {reducedTp, irreducible};
(*memoizedTypePacks)[reducedTp] = {reducedTp, irreducible};
return reducedTp;
}
TypeId TypeReducer::memoizedOr(TypeId ty) const
{
ty = follow(ty);
if (auto ctx = memoizedTypes->find(ty))
return ctx->type;
else
return ty;
};
TypePackId TypeReducer::memoizedOr(TypePackId tp) const
{
tp = follow(tp);
if (auto ctx = memoizedTypePacks->find(tp))
return ctx->type;
else
return tp;
};
struct MarkCycles : TypeVisitor
{
DenseHashSet<TypeId> cyclicTypes{nullptr};
void cycle(TypeId ty) override
{
cyclicTypes.insert(ty);
}
bool visit(TypeId ty) override
{
return !cyclicTypes.find(ty);
}
};
} // namespace
TypeReduction::TypeReduction(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> handle)
TypeReduction::TypeReduction(
NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> handle, const TypeReductionOptions& opts)
: arena(arena)
, builtinTypes(builtinTypes)
, handle(handle)
, options(opts)
{
}
std::optional<TypeId> TypeReduction::reduce(TypeId ty)
{
if (auto found = cachedTypes.find(ty))
return *found;
ty = follow(ty);
auto [reducedTy, cacheOk] = reduceImpl(ty);
if (cacheOk)
cachedTypes[ty] = *reducedTy;
if (FFlag::DebugLuauDontReduceTypes)
return ty;
else if (!options.allowTypeReductionsFromOtherArenas && ty->owningArena != arena)
return ty;
else if (auto ctx = memoizedTypes.find(ty); ctx && ctx->irreducible)
return ctx->type;
else if (hasExceededCartesianProductLimit(ty))
return std::nullopt;
return reducedTy;
try
{
MarkCycles finder;
finder.traverse(ty);
TypeReducer reducer{arena, builtinTypes, handle, &memoizedTypes, &memoizedTypePacks, &finder.cyclicTypes};
return reducer.reduce(ty);
}
catch (const RecursionLimitException&)
{
return std::nullopt;
}
}
std::optional<TypePackId> TypeReduction::reduce(TypePackId tp)
{
if (auto found = cachedTypePacks.find(tp))
return *found;
tp = follow(tp);
auto [reducedTp, cacheOk] = reduceImpl(tp);
if (cacheOk)
cachedTypePacks[tp] = *reducedTp;
return reducedTp;
}
std::pair<std::optional<TypeId>, bool> TypeReduction::reduceImpl(TypeId ty)
{
if (FFlag::DebugLuauDontReduceTypes)
return {ty, false};
if (hasExceededCartesianProductLimit(ty))
return {std::nullopt, false};
return tp;
else if (!options.allowTypeReductionsFromOtherArenas && tp->owningArena != arena)
return tp;
else if (auto ctx = memoizedTypePacks.find(tp); ctx && ctx->irreducible)
return ctx->type;
else if (hasExceededCartesianProductLimit(tp))
return std::nullopt;
try
{
TypeReducer reducer{arena, builtinTypes, handle};
return {reducer.reduce(ty), reducer.cacheOk};
MarkCycles finder;
finder.traverse(tp);
TypeReducer reducer{arena, builtinTypes, handle, &memoizedTypes, &memoizedTypePacks, &finder.cyclicTypes};
return reducer.reduce(tp);
}
catch (const RecursionLimitException&)
{
return {std::nullopt, false};
}
}
std::pair<std::optional<TypePackId>, bool> TypeReduction::reduceImpl(TypePackId tp)
{
if (FFlag::DebugLuauDontReduceTypes)
return {tp, false};
if (hasExceededCartesianProductLimit(tp))
return {std::nullopt, false};
try
{
TypeReducer reducer{arena, builtinTypes, handle};
return {reducer.reduce(tp), reducer.cacheOk};
}
catch (const RecursionLimitException&)
{
return {std::nullopt, false};
return std::nullopt;
}
}

View File

@ -1,8 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Unifiable.h"
LUAU_FASTFLAG(LuauTypeNormalization2);
namespace Luau
{
namespace Unifiable
@ -11,19 +9,19 @@ namespace Unifiable
static int nextIndex = 0;
Free::Free(TypeLevel level)
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
: index(++nextIndex)
, level(level)
{
}
Free::Free(Scope* scope)
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
: index(++nextIndex)
, scope(scope)
{
}
Free::Free(Scope* scope, TypeLevel level)
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
: index(++nextIndex)
, level(level)
, scope(scope)
{
@ -32,33 +30,33 @@ Free::Free(Scope* scope, TypeLevel level)
int Free::DEPRECATED_nextIndex = 0;
Generic::Generic()
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
: index(++nextIndex)
, name("g" + std::to_string(index))
{
}
Generic::Generic(TypeLevel level)
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
: index(++nextIndex)
, level(level)
, name("g" + std::to_string(index))
{
}
Generic::Generic(const Name& name)
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
: index(++nextIndex)
, name(name)
, explicitName(true)
{
}
Generic::Generic(Scope* scope)
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
: index(++nextIndex)
, scope(scope)
{
}
Generic::Generic(TypeLevel level, const Name& name)
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
: index(++nextIndex)
, level(level)
, name(name)
, explicitName(true)
@ -66,7 +64,7 @@ Generic::Generic(TypeLevel level, const Name& name)
}
Generic::Generic(Scope* scope, const Name& name)
: index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex)
: index(++nextIndex)
, scope(scope)
, name(name)
, explicitName(true)

View File

@ -18,15 +18,13 @@
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
LUAU_FASTFLAG(LuauErrorRecoveryType);
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauReportTypeMismatchForTypePackUnificationFailure, false)
LUAU_FASTFLAGVARIABLE(LuauSubtypeNormalizer, false);
LUAU_FASTFLAGVARIABLE(LuauScalarShapeSubtyping, false)
LUAU_FASTFLAGVARIABLE(LuauUnifyAnyTxnLog, false)
LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
LUAU_FASTFLAGVARIABLE(LuauOverloadedFunctionSubtypingPerf, false);
LUAU_FASTFLAGVARIABLE(LuauScalarShapeUnifyToMtOwner2, false)
LUAU_FASTFLAGVARIABLE(LuauUninhabitedSubAnything2, false)
LUAU_FASTFLAGVARIABLE(LuauMaintainScopesInUnifier, false)
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
LUAU_FASTFLAG(LuauTxnLogTypePackIterator)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauNegatedFunctionTypes)
LUAU_FASTFLAG(LuauNegatedClassTypes)
@ -378,7 +376,7 @@ Unifier::Unifier(NotNull<Normalizer> normalizer, Mode mode, NotNull<Scope> scope
, variance(variance)
, sharedState(*normalizer->sharedState)
{
normalize = FFlag::LuauSubtypeNormalizer;
normalize = true;
LUAU_ASSERT(sharedState.iceHandler);
}
@ -480,17 +478,40 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
return;
}
if (get<ErrorType>(superTy) || get<AnyType>(superTy) || get<UnknownType>(superTy))
return tryUnifyWithAny(subTy, superTy);
if (FFlag::LuauUnifyAnyTxnLog)
{
if (log.get<AnyType>(superTy))
return tryUnifyWithAny(subTy, builtinTypes->anyType);
if (get<AnyType>(subTy))
return tryUnifyWithAny(superTy, subTy);
if (log.get<ErrorType>(superTy))
return tryUnifyWithAny(subTy, builtinTypes->errorType);
if (log.get<ErrorType>(subTy))
return tryUnifyWithAny(superTy, subTy);
if (log.get<UnknownType>(superTy))
return tryUnifyWithAny(subTy, builtinTypes->unknownType);
if (log.get<NeverType>(subTy))
return tryUnifyWithAny(superTy, subTy);
if (log.get<AnyType>(subTy))
return tryUnifyWithAny(superTy, builtinTypes->anyType);
if (log.get<ErrorType>(subTy))
return tryUnifyWithAny(superTy, builtinTypes->errorType);
if (log.get<NeverType>(subTy))
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;
@ -524,10 +545,6 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
{
tryUnifyUnionWithType(subTy, subUnion, superTy);
}
else if (const UnionType* uv = (FFlag::LuauSubtypeNormalizer ? nullptr : log.getMutable<UnionType>(superTy)))
{
tryUnifyTypeWithUnion(subTy, superTy, uv, cacheEnabled, isFunctionCall);
}
else if (const IntersectionType* uv = log.getMutable<IntersectionType>(superTy))
{
tryUnifyTypeWithIntersection(subTy, superTy, uv);
@ -915,8 +932,6 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType*
void Unifier::tryUnifyNormalizedTypes(
TypeId subTy, TypeId superTy, const NormalizedType& subNorm, const NormalizedType& superNorm, std::string reason, std::optional<TypeError> error)
{
LUAU_ASSERT(FFlag::LuauSubtypeNormalizer);
if (get<UnknownType>(superNorm.tops) || get<AnyType>(superNorm.tops) || get<AnyType>(subNorm.tops))
return;
else if (get<UnknownType>(subNorm.tops))
@ -1096,12 +1111,9 @@ TypePackId Unifier::tryApplyOverloadedFunction(TypeId function, const Normalized
log.concat(std::move(innerState.log));
if (result)
{
if (FFlag::LuauOverloadedFunctionSubtypingPerf)
{
innerState.log.clear();
innerState.tryUnify_(*result, ftv->retTypes);
}
if (FFlag::LuauOverloadedFunctionSubtypingPerf && innerState.errors.empty())
innerState.log.clear();
innerState.tryUnify_(*result, ftv->retTypes);
if (innerState.errors.empty())
log.concat(std::move(innerState.log));
// Annoyingly, since we don't support intersection of generic type packs,
// the intersection may fail. We rather arbitrarily use the first matching overload
@ -1250,8 +1262,11 @@ struct WeirdIter
LUAU_ASSERT(canGrow());
LUAU_ASSERT(log.getMutable<TypePack>(newTail));
level = log.getMutable<Unifiable::Free>(packId)->level;
scope = log.getMutable<Unifiable::Free>(packId)->scope;
auto freePack = log.getMutable<Unifiable::Free>(packId);
level = freePack->level;
if (FFlag::LuauMaintainScopesInUnifier && freePack->scope != nullptr)
scope = freePack->scope;
log.replace(packId, BoundTypePack(newTail));
packId = newTail;
pack = log.getMutable<TypePack>(newTail);
@ -1380,6 +1395,12 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
auto superIter = WeirdIter(superTp, log);
auto subIter = WeirdIter(subTp, log);
if (FFlag::LuauMaintainScopesInUnifier)
{
superIter.scope = scope.get();
subIter.scope = scope.get();
}
auto mkFreshType = [this](Scope* scope, TypeLevel level) {
return types->freshType(scope, level);
};
@ -1420,15 +1441,9 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
// If both are at the end, we're done
if (!superIter.good() && !subIter.good())
{
if (!FFlag::LuauTxnLogTypePackIterator && subTpv->tail && superTpv->tail)
{
tryUnify_(*subTpv->tail, *superTpv->tail);
break;
}
const bool lFreeTail = superTpv->tail && log.getMutable<FreeTypePack>(log.follow(*superTpv->tail)) != nullptr;
const bool rFreeTail = subTpv->tail && log.getMutable<FreeTypePack>(log.follow(*subTpv->tail)) != nullptr;
if (FFlag::LuauTxnLogTypePackIterator && lFreeTail && rFreeTail)
if (lFreeTail && rFreeTail)
{
tryUnify_(*subTpv->tail, *superTpv->tail);
}
@ -1440,7 +1455,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
{
tryUnify_(emptyTp, *subTpv->tail);
}
else if (FFlag::LuauTxnLogTypePackIterator && subTpv->tail && superTpv->tail)
else if (subTpv->tail && superTpv->tail)
{
if (log.getMutable<VariadicTypePack>(superIter.packId))
tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index));
@ -1523,10 +1538,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
}
else
{
if (FFlag::LuauReportTypeMismatchForTypePackUnificationFailure)
reportError(location, TypePackMismatch{subTp, superTp});
else
reportError(location, GenericError{"Failed to unify type packs"});
reportError(location, TypePackMismatch{subTp, superTp});
}
}
@ -2356,11 +2368,11 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever
if (!superVariadic)
ice("passed non-variadic pack to tryUnifyVariadics");
if (const VariadicTypePack* subVariadic = FFlag::LuauTxnLogTypePackIterator ? log.get<VariadicTypePack>(subTp) : get<VariadicTypePack>(subTp))
if (const VariadicTypePack* subVariadic = log.get<VariadicTypePack>(subTp))
{
tryUnify_(reversed ? superVariadic->ty : subVariadic->ty, reversed ? subVariadic->ty : superVariadic->ty);
}
else if (FFlag::LuauTxnLogTypePackIterator ? log.get<TypePack>(subTp) : get<TypePack>(subTp))
else if (log.get<TypePack>(subTp))
{
TypePackIterator subIter = begin(subTp, &log);
TypePackIterator subEnd = end(subTp);
@ -2465,9 +2477,18 @@ void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy)
{
LUAU_ASSERT(get<AnyType>(anyTy) || get<ErrorType>(anyTy) || get<UnknownType>(anyTy) || get<NeverType>(anyTy));
// These types are not visited in general loop below
if (get<PrimitiveType>(subTy) || get<AnyType>(subTy) || get<ClassType>(subTy))
return;
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))
return;
}
else
{
// These types are not visited in general loop below
if (get<PrimitiveType>(subTy) || get<AnyType>(subTy) || get<ClassType>(subTy))
return;
}
TypePackId anyTp;
if (FFlag::LuauUnknownAndNeverType)

View File

@ -983,12 +983,13 @@ class AstStatTypeAlias : public AstStat
public:
LUAU_RTTI(AstStatTypeAlias)
AstStatTypeAlias(const Location& location, const AstName& name, const AstArray<AstGenericType>& generics,
AstStatTypeAlias(const Location& location, const AstName& name, const Location& nameLocation, const AstArray<AstGenericType>& generics,
const AstArray<AstGenericTypePack>& genericPacks, AstType* type, bool exported);
void visit(AstVisitor* visitor) override;
AstName name;
Location nameLocation;
AstArray<AstGenericType> generics;
AstArray<AstGenericTypePack> genericPacks;
AstType* type;

View File

@ -647,10 +647,11 @@ void AstStatLocalFunction::visit(AstVisitor* visitor)
func->visit(visitor);
}
AstStatTypeAlias::AstStatTypeAlias(const Location& location, const AstName& name, const AstArray<AstGenericType>& generics,
const AstArray<AstGenericTypePack>& genericPacks, AstType* type, bool exported)
AstStatTypeAlias::AstStatTypeAlias(const Location& location, const AstName& name, const Location& nameLocation,
const AstArray<AstGenericType>& generics, const AstArray<AstGenericTypePack>& genericPacks, AstType* type, bool exported)
: AstStat(ClassIndex(), location)
, name(name)
, nameLocation(nameLocation)
, generics(generics)
, genericPacks(genericPacks)
, type(type)

View File

@ -768,7 +768,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported)
AstType* type = parseTypeAnnotation();
return allocator.alloc<AstStatTypeAlias>(Location(start, type->location), name->name, generics, genericPacks, type, exported);
return allocator.alloc<AstStatTypeAlias>(Location(start, type->location), name->name, name->location, generics, genericPacks, type, exported);
}
AstDeclaredClassProp Parser::parseDeclaredClassMethod()

View File

@ -244,7 +244,7 @@ static int emitInst(AssemblyBuilderX64& build, NativeState& data, ModuleHelpers&
emitInstForNPrep(build, pc, i, labelarr[i + 1 + LUAU_INSN_D(*pc)]);
break;
case LOP_FORNLOOP:
emitInstForNLoop(build, pc, i, labelarr[i + 1 + LUAU_INSN_D(*pc)]);
emitInstForNLoop(build, pc, i, labelarr[i + 1 + LUAU_INSN_D(*pc)], next);
break;
case LOP_FORGLOOP:
emitinstForGLoop(build, pc, i, labelarr[i + 1 + LUAU_INSN_D(*pc)], next, fallback);

View File

@ -251,7 +251,7 @@ void callGetFastTmOrFallback(AssemblyBuilderX64& build, RegisterX64 table, TMS t
// rArg1 is already prepared
build.mov(rArg2, tm);
build.mov(rax, qword[rState + offsetof(lua_State, global)]);
build.mov(rArg3, qword[rax + offsetof(global_State, tmname[tm])]);
build.mov(rArg3, qword[rax + offsetof(global_State, tmname) + tm * sizeof(TString*)]);
build.call(qword[rNativeContext + offsetof(NativeContext, luaT_gettm)]);
}

View File

@ -1010,17 +1010,15 @@ static int emitInstFastCallN(
if (nparams == LUA_MULTRET)
{
// TODO: for SystemV ABI we can compute the result directly into rArg6
// L->top - (ra + 1)
build.mov(rcx, qword[rState + offsetof(lua_State, top)]);
RegisterX64 reg = (build.abi == ABIX64::Windows) ? rcx : rArg6;
build.mov(reg, qword[rState + offsetof(lua_State, top)]);
build.lea(rdx, addr[rBase + (ra + 1) * sizeof(TValue)]);
build.sub(rcx, rdx);
build.shr(rcx, kTValueSizeLog2);
build.sub(reg, rdx);
build.shr(reg, kTValueSizeLog2);
if (build.abi == ABIX64::Windows)
build.mov(sArg6, rcx);
else
build.mov(rArg6, rcx);
build.mov(sArg6, reg);
}
else
{
@ -1126,7 +1124,7 @@ void emitInstForNPrep(AssemblyBuilderX64& build, const Instruction* pc, int pcpo
build.setLabel(exit);
}
void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat)
void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat, Label& loopExit)
{
emitInterrupt(build, pcpos);
@ -1144,20 +1142,18 @@ void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpo
build.vaddsd(idx, idx, step);
build.vmovsd(luauRegValue(ra + 2), idx);
Label reverse, exit;
Label reverse;
// step <= 0
jumpOnNumberCmp(build, noreg, step, zero, ConditionX64::LessEqual, reverse);
// false: idx <= limit
jumpOnNumberCmp(build, noreg, idx, limit, ConditionX64::LessEqual, loopRepeat);
build.jmp(exit);
build.jmp(loopExit);
// true: limit <= idx
build.setLabel(reverse);
jumpOnNumberCmp(build, noreg, limit, idx, ConditionX64::LessEqual, loopRepeat);
build.setLabel(exit);
}
void emitinstForGLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat, Label& loopExit, Label& fallback)

View File

@ -61,7 +61,7 @@ int emitInstFastCall2(AssemblyBuilderX64& build, const Instruction* pc, int pcpo
int emitInstFastCall2K(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& fallback);
int emitInstFastCall(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& fallback);
void emitInstForNPrep(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopExit);
void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat);
void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat, Label& loopExit);
void emitinstForGLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat, Label& loopExit, Label& fallback);
void emitinstForGLoopFallback(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat);
void emitInstForGPrepNext(AssemblyBuilderX64& build, const Instruction* pc, Label& target, Label& fallback);

563
CodeGen/src/IrBuilder.cpp Normal file
View File

@ -0,0 +1,563 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "IrBuilder.h"
#include "Luau/Common.h"
#include "CustomExecUtils.h"
#include "IrTranslation.h"
#include "IrUtils.h"
#include "lapi.h"
namespace Luau
{
namespace CodeGen
{
constexpr unsigned kNoAssociatedBlockIndex = ~0u;
void IrBuilder::buildFunctionIr(Proto* proto)
{
function.proto = proto;
// Rebuild original control flow blocks
rebuildBytecodeBasicBlocks(proto);
function.bcMapping.resize(proto->sizecode, {~0u, 0});
// Translate all instructions to IR inside blocks
for (int i = 0; i < proto->sizecode;)
{
const Instruction* pc = &proto->code[i];
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(*pc));
int nexti = i + getOpLength(op);
LUAU_ASSERT(nexti <= proto->sizecode);
function.bcMapping[i] = {uint32_t(function.instructions.size()), 0};
// Begin new block at this instruction if it was in the bytecode or requested during translation
if (instIndexToBlock[i] != kNoAssociatedBlockIndex)
beginBlock(blockAtInst(i));
translateInst(op, pc, i);
i = nexti;
LUAU_ASSERT(i <= proto->sizecode);
// If we are going into a new block at the next instruction and it's a fallthrough, jump has to be placed to mark block termination
if (i < int(instIndexToBlock.size()) && instIndexToBlock[i] != kNoAssociatedBlockIndex)
{
if (!isBlockTerminator(function.instructions.back().cmd))
inst(IrCmd::JUMP, blockAtInst(i));
}
}
}
void IrBuilder::rebuildBytecodeBasicBlocks(Proto* proto)
{
instIndexToBlock.resize(proto->sizecode, kNoAssociatedBlockIndex);
// Mark jump targets
std::vector<uint8_t> jumpTargets(proto->sizecode, 0);
for (int i = 0; i < proto->sizecode;)
{
const Instruction* pc = &proto->code[i];
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(*pc));
int target = getJumpTarget(*pc, uint32_t(i));
if (target >= 0 && !isFastCall(op))
jumpTargets[target] = true;
i += getOpLength(op);
LUAU_ASSERT(i <= proto->sizecode);
}
// Bytecode blocks are created at bytecode jump targets and the start of a function
jumpTargets[0] = true;
for (int i = 0; i < proto->sizecode; i++)
{
if (jumpTargets[i])
{
IrOp b = block(IrBlockKind::Bytecode);
instIndexToBlock[i] = b.index;
}
}
}
void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
{
switch (op)
{
case LOP_NOP:
break;
case LOP_LOADNIL:
translateInstLoadNil(*this, pc);
break;
case LOP_LOADB:
translateInstLoadB(*this, pc, i);
break;
case LOP_LOADN:
translateInstLoadN(*this, pc);
break;
case LOP_LOADK:
translateInstLoadK(*this, pc);
break;
case LOP_LOADKX:
translateInstLoadKX(*this, pc);
break;
case LOP_MOVE:
translateInstMove(*this, pc);
break;
case LOP_GETGLOBAL:
translateInstGetGlobal(*this, pc, i);
break;
case LOP_SETGLOBAL:
translateInstSetGlobal(*this, pc, i);
break;
case LOP_CALL:
inst(IrCmd::LOP_CALL, constUint(i));
if (activeFastcallFallback)
{
inst(IrCmd::JUMP, fastcallFallbackReturn);
beginBlock(fastcallFallbackReturn);
activeFastcallFallback = false;
}
break;
case LOP_RETURN:
inst(IrCmd::LOP_RETURN, constUint(i));
break;
case LOP_GETTABLE:
translateInstGetTable(*this, pc, i);
break;
case LOP_SETTABLE:
translateInstSetTable(*this, pc, i);
break;
case LOP_GETTABLEKS:
translateInstGetTableKS(*this, pc, i);
break;
case LOP_SETTABLEKS:
translateInstSetTableKS(*this, pc, i);
break;
case LOP_GETTABLEN:
translateInstGetTableN(*this, pc, i);
break;
case LOP_SETTABLEN:
translateInstSetTableN(*this, pc, i);
break;
case LOP_JUMP:
translateInstJump(*this, pc, i);
break;
case LOP_JUMPBACK:
translateInstJumpBack(*this, pc, i);
break;
case LOP_JUMPIF:
translateInstJumpIf(*this, pc, i, /* not_ */ false);
break;
case LOP_JUMPIFNOT:
translateInstJumpIf(*this, pc, i, /* not_ */ true);
break;
case LOP_JUMPIFEQ:
translateInstJumpIfEq(*this, pc, i, /* not_ */ false);
break;
case LOP_JUMPIFLE:
translateInstJumpIfCond(*this, pc, i, IrCondition::LessEqual);
break;
case LOP_JUMPIFLT:
translateInstJumpIfCond(*this, pc, i, IrCondition::Less);
break;
case LOP_JUMPIFNOTEQ:
translateInstJumpIfEq(*this, pc, i, /* not_ */ true);
break;
case LOP_JUMPIFNOTLE:
translateInstJumpIfCond(*this, pc, i, IrCondition::NotLessEqual);
break;
case LOP_JUMPIFNOTLT:
translateInstJumpIfCond(*this, pc, i, IrCondition::NotLess);
break;
case LOP_JUMPX:
translateInstJumpX(*this, pc, i);
break;
case LOP_JUMPXEQKNIL:
translateInstJumpxEqNil(*this, pc, i);
break;
case LOP_JUMPXEQKB:
translateInstJumpxEqB(*this, pc, i);
break;
case LOP_JUMPXEQKN:
translateInstJumpxEqN(*this, pc, i);
break;
case LOP_JUMPXEQKS:
translateInstJumpxEqS(*this, pc, i);
break;
case LOP_ADD:
translateInstBinary(*this, pc, i, TM_ADD);
break;
case LOP_SUB:
translateInstBinary(*this, pc, i, TM_SUB);
break;
case LOP_MUL:
translateInstBinary(*this, pc, i, TM_MUL);
break;
case LOP_DIV:
translateInstBinary(*this, pc, i, TM_DIV);
break;
case LOP_MOD:
translateInstBinary(*this, pc, i, TM_MOD);
break;
case LOP_POW:
translateInstBinary(*this, pc, i, TM_POW);
break;
case LOP_ADDK:
translateInstBinaryK(*this, pc, i, TM_ADD);
break;
case LOP_SUBK:
translateInstBinaryK(*this, pc, i, TM_SUB);
break;
case LOP_MULK:
translateInstBinaryK(*this, pc, i, TM_MUL);
break;
case LOP_DIVK:
translateInstBinaryK(*this, pc, i, TM_DIV);
break;
case LOP_MODK:
translateInstBinaryK(*this, pc, i, TM_MOD);
break;
case LOP_POWK:
translateInstBinaryK(*this, pc, i, TM_POW);
break;
case LOP_NOT:
translateInstNot(*this, pc);
break;
case LOP_MINUS:
translateInstMinus(*this, pc, i);
break;
case LOP_LENGTH:
translateInstLength(*this, pc, i);
break;
case LOP_NEWTABLE:
translateInstNewTable(*this, pc, i);
break;
case LOP_DUPTABLE:
translateInstDupTable(*this, pc, i);
break;
case LOP_SETLIST:
inst(IrCmd::LOP_SETLIST, constUint(i));
break;
case LOP_GETUPVAL:
translateInstGetUpval(*this, pc, i);
break;
case LOP_SETUPVAL:
translateInstSetUpval(*this, pc, i);
break;
case LOP_CLOSEUPVALS:
translateInstCloseUpvals(*this, pc);
break;
case LOP_FASTCALL:
{
IrOp fallback = block(IrBlockKind::Fallback);
IrOp next = blockAtInst(i + LUAU_INSN_C(*pc) + 2);
inst(IrCmd::LOP_FASTCALL, constUint(i), fallback);
inst(IrCmd::JUMP, next);
beginBlock(fallback);
activeFastcallFallback = true;
fastcallFallbackReturn = next;
break;
}
case LOP_FASTCALL1:
{
IrOp fallback = block(IrBlockKind::Fallback);
IrOp next = blockAtInst(i + LUAU_INSN_C(*pc) + 2);
inst(IrCmd::LOP_FASTCALL1, constUint(i), fallback);
inst(IrCmd::JUMP, next);
beginBlock(fallback);
activeFastcallFallback = true;
fastcallFallbackReturn = next;
break;
}
case LOP_FASTCALL2:
{
IrOp fallback = block(IrBlockKind::Fallback);
IrOp next = blockAtInst(i + LUAU_INSN_C(*pc) + 2);
inst(IrCmd::LOP_FASTCALL2, constUint(i), fallback);
inst(IrCmd::JUMP, next);
beginBlock(fallback);
activeFastcallFallback = true;
fastcallFallbackReturn = next;
break;
}
case LOP_FASTCALL2K:
{
IrOp fallback = block(IrBlockKind::Fallback);
IrOp next = blockAtInst(i + LUAU_INSN_C(*pc) + 2);
inst(IrCmd::LOP_FASTCALL2K, constUint(i), fallback);
inst(IrCmd::JUMP, next);
beginBlock(fallback);
activeFastcallFallback = true;
fastcallFallbackReturn = next;
break;
}
case LOP_FORNPREP:
{
IrOp loopExit = blockAtInst(i + 1 + LUAU_INSN_D(*pc));
inst(IrCmd::LOP_FORNPREP, constUint(i), loopExit);
break;
}
case LOP_FORNLOOP:
{
IrOp loopRepeat = blockAtInst(i + 1 + LUAU_INSN_D(*pc));
IrOp loopExit = blockAtInst(i + getOpLength(LOP_FORNLOOP));
inst(IrCmd::LOP_FORNLOOP, constUint(i), loopRepeat, loopExit);
beginBlock(loopExit);
break;
}
case LOP_FORGLOOP:
{
IrOp loopRepeat = blockAtInst(i + 1 + LUAU_INSN_D(*pc));
IrOp loopExit = blockAtInst(i + getOpLength(LOP_FORGLOOP));
IrOp fallback = block(IrBlockKind::Fallback);
inst(IrCmd::LOP_FORGLOOP, constUint(i), loopRepeat, loopExit, fallback);
beginBlock(fallback);
inst(IrCmd::LOP_FORGLOOP_FALLBACK, constUint(i), loopRepeat, loopExit);
beginBlock(loopExit);
break;
}
case LOP_FORGPREP_NEXT:
{
IrOp target = blockAtInst(i + 1 + LUAU_INSN_D(*pc));
IrOp fallback = block(IrBlockKind::Fallback);
inst(IrCmd::LOP_FORGPREP_NEXT, constUint(i), target, fallback);
beginBlock(fallback);
inst(IrCmd::LOP_FORGPREP_XNEXT_FALLBACK, constUint(i), target);
break;
}
case LOP_FORGPREP_INEXT:
{
IrOp target = blockAtInst(i + 1 + LUAU_INSN_D(*pc));
IrOp fallback = block(IrBlockKind::Fallback);
inst(IrCmd::LOP_FORGPREP_INEXT, constUint(i), target, fallback);
beginBlock(fallback);
inst(IrCmd::LOP_FORGPREP_XNEXT_FALLBACK, constUint(i), target);
break;
}
case LOP_AND:
inst(IrCmd::LOP_AND, constUint(i));
break;
case LOP_ANDK:
inst(IrCmd::LOP_ANDK, constUint(i));
break;
case LOP_OR:
inst(IrCmd::LOP_OR, constUint(i));
break;
case LOP_ORK:
inst(IrCmd::LOP_ORK, constUint(i));
break;
case LOP_COVERAGE:
inst(IrCmd::LOP_COVERAGE, constUint(i));
break;
case LOP_GETIMPORT:
translateInstGetImport(*this, pc, i);
break;
case LOP_CONCAT:
translateInstConcat(*this, pc, i);
break;
case LOP_CAPTURE:
translateInstCapture(*this, pc, i);
break;
case LOP_NAMECALL:
{
IrOp next = blockAtInst(i + getOpLength(LOP_NAMECALL));
IrOp fallback = block(IrBlockKind::Fallback);
inst(IrCmd::LOP_NAMECALL, constUint(i), next, fallback);
beginBlock(fallback);
inst(IrCmd::FALLBACK_NAMECALL, constUint(i));
inst(IrCmd::JUMP, next);
beginBlock(next);
break;
}
case LOP_PREPVARARGS:
inst(IrCmd::FALLBACK_PREPVARARGS, constUint(i));
break;
case LOP_GETVARARGS:
inst(IrCmd::FALLBACK_GETVARARGS, constUint(i));
break;
case LOP_NEWCLOSURE:
inst(IrCmd::FALLBACK_NEWCLOSURE, constUint(i));
break;
case LOP_DUPCLOSURE:
inst(IrCmd::FALLBACK_DUPCLOSURE, constUint(i));
break;
case LOP_FORGPREP:
inst(IrCmd::FALLBACK_FORGPREP, constUint(i));
break;
default:
LUAU_ASSERT(!"unknown instruction");
break;
}
}
bool IrBuilder::isInternalBlock(IrOp block)
{
IrBlock& target = function.blocks[block.index];
return target.kind == IrBlockKind::Internal;
}
void IrBuilder::beginBlock(IrOp block)
{
function.blocks[block.index].start = uint32_t(function.instructions.size());
}
IrOp IrBuilder::constBool(bool value)
{
IrConst constant;
constant.kind = IrConstKind::Bool;
constant.valueBool = value;
return constAny(constant);
}
IrOp IrBuilder::constInt(int value)
{
IrConst constant;
constant.kind = IrConstKind::Int;
constant.valueInt = value;
return constAny(constant);
}
IrOp IrBuilder::constUint(unsigned value)
{
IrConst constant;
constant.kind = IrConstKind::Uint;
constant.valueUint = value;
return constAny(constant);
}
IrOp IrBuilder::constDouble(double value)
{
IrConst constant;
constant.kind = IrConstKind::Double;
constant.valueDouble = value;
return constAny(constant);
}
IrOp IrBuilder::constTag(uint8_t value)
{
IrConst constant;
constant.kind = IrConstKind::Tag;
constant.valueTag = value;
return constAny(constant);
}
IrOp IrBuilder::constAny(IrConst constant)
{
uint32_t index = uint32_t(function.constants.size());
function.constants.push_back(constant);
return {IrOpKind::Constant, index};
}
IrOp IrBuilder::cond(IrCondition cond)
{
return {IrOpKind::Condition, uint32_t(cond)};
}
IrOp IrBuilder::inst(IrCmd cmd)
{
return inst(cmd, {}, {}, {}, {}, {});
}
IrOp IrBuilder::inst(IrCmd cmd, IrOp a)
{
return inst(cmd, a, {}, {}, {}, {});
}
IrOp IrBuilder::inst(IrCmd cmd, IrOp a, IrOp b)
{
return inst(cmd, a, b, {}, {}, {});
}
IrOp IrBuilder::inst(IrCmd cmd, IrOp a, IrOp b, IrOp c)
{
return inst(cmd, a, b, c, {}, {});
}
IrOp IrBuilder::inst(IrCmd cmd, IrOp a, IrOp b, IrOp c, IrOp d)
{
return inst(cmd, a, b, c, d, {});
}
IrOp IrBuilder::inst(IrCmd cmd, IrOp a, IrOp b, IrOp c, IrOp d, IrOp e)
{
uint32_t index = uint32_t(function.instructions.size());
function.instructions.push_back({cmd, a, b, c, d, e});
return {IrOpKind::Inst, index};
}
IrOp IrBuilder::block(IrBlockKind kind)
{
if (kind == IrBlockKind::Internal && activeFastcallFallback)
kind = IrBlockKind::Fallback;
uint32_t index = uint32_t(function.blocks.size());
function.blocks.push_back(IrBlock{kind, ~0u});
return IrOp{IrOpKind::Block, index};
}
IrOp IrBuilder::blockAtInst(uint32_t index)
{
uint32_t blockIndex = instIndexToBlock[index];
if (blockIndex != kNoAssociatedBlockIndex)
return IrOp{IrOpKind::Block, blockIndex};
return block(IrBlockKind::Internal);
}
IrOp IrBuilder::vmReg(uint8_t index)
{
return {IrOpKind::VmReg, index};
}
IrOp IrBuilder::vmConst(uint32_t index)
{
return {IrOpKind::VmConst, index};
}
IrOp IrBuilder::vmUpvalue(uint8_t index)
{
return {IrOpKind::VmUpvalue, index};
}
} // namespace CodeGen
} // namespace Luau

63
CodeGen/src/IrBuilder.h Normal file
View File

@ -0,0 +1,63 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Common.h"
#include "Luau/Bytecode.h"
#include "IrData.h"
#include <vector>
struct Proto;
typedef uint32_t Instruction;
namespace Luau
{
namespace CodeGen
{
struct AssemblyOptions;
struct IrBuilder
{
void buildFunctionIr(Proto* proto);
void rebuildBytecodeBasicBlocks(Proto* proto);
void translateInst(LuauOpcode op, const Instruction* pc, int i);
bool isInternalBlock(IrOp block);
void beginBlock(IrOp block);
IrOp constBool(bool value);
IrOp constInt(int value);
IrOp constUint(unsigned value);
IrOp constDouble(double value);
IrOp constTag(uint8_t value);
IrOp constAny(IrConst constant);
IrOp cond(IrCondition cond);
IrOp inst(IrCmd cmd);
IrOp inst(IrCmd cmd, IrOp a);
IrOp inst(IrCmd cmd, IrOp a, IrOp b);
IrOp inst(IrCmd cmd, IrOp a, IrOp b, IrOp c);
IrOp inst(IrCmd cmd, IrOp a, IrOp b, IrOp c, IrOp d);
IrOp inst(IrCmd cmd, IrOp a, IrOp b, IrOp c, IrOp d, IrOp e);
IrOp block(IrBlockKind kind); // Requested kind can be ignored if we are in an outlined sequence
IrOp blockAtInst(uint32_t index);
IrOp vmReg(uint8_t index);
IrOp vmConst(uint32_t index);
IrOp vmUpvalue(uint8_t index);
bool activeFastcallFallback = false;
IrOp fastcallFallbackReturn;
IrFunction function;
std::vector<uint32_t> instIndexToBlock; // Block index at the bytecode instruction
};
} // namespace CodeGen
} // namespace Luau

View File

@ -9,6 +9,8 @@
#include <stdint.h>
struct Proto;
namespace Luau
{
namespace CodeGen
@ -99,6 +101,7 @@ enum class IrCmd : uint8_t
// Operations that don't have an IR representation yet
LOP_SETLIST,
LOP_NAMECALL,
LOP_CALL,
LOP_RETURN,
LOP_FASTCALL,
@ -116,21 +119,21 @@ enum class IrCmd : uint8_t
LOP_ANDK,
LOP_OR,
LOP_ORK,
LOP_COVERAGE,
// Operations that have a translation, but use a full instruction fallback
FALLBACK_GETGLOBAL,
FALLBACK_SETGLOBAL,
FALLBACK_GETTABLEKS,
FALLBACK_SETTABLEKS,
FALLBACK_NAMECALL,
// Operations that don't have assembly lowering at all
FALLBACK_NAMECALL,
FALLBACK_PREPVARARGS,
FALLBACK_GETVARARGS,
FALLBACK_NEWCLOSURE,
FALLBACK_DUPCLOSURE,
FALLBACK_FORGPREP,
FALLBACK_COVERAGE,
};
enum class IrConstKind : uint8_t
@ -274,6 +277,8 @@ struct IrFunction
std::vector<IrConst> constants;
std::vector<BytecodeMapping> bcMapping;
Proto* proto = nullptr;
};
} // namespace CodeGen

View File

@ -186,6 +186,8 @@ const char* getCmdName(IrCmd cmd)
return "CAPTURE";
case IrCmd::LOP_SETLIST:
return "LOP_SETLIST";
case IrCmd::LOP_NAMECALL:
return "LOP_NAMECALL";
case IrCmd::LOP_CALL:
return "LOP_CALL";
case IrCmd::LOP_RETURN:
@ -220,6 +222,8 @@ const char* getCmdName(IrCmd cmd)
return "LOP_OR";
case IrCmd::LOP_ORK:
return "LOP_ORK";
case IrCmd::LOP_COVERAGE:
return "LOP_COVERAGE";
case IrCmd::FALLBACK_GETGLOBAL:
return "FALLBACK_GETGLOBAL";
case IrCmd::FALLBACK_SETGLOBAL:
@ -240,8 +244,6 @@ const char* getCmdName(IrCmd cmd)
return "FALLBACK_DUPCLOSURE";
case IrCmd::FALLBACK_FORGPREP:
return "FALLBACK_FORGPREP";
case IrCmd::FALLBACK_COVERAGE:
return "FALLBACK_COVERAGE";
}
LUAU_UNREACHABLE();
@ -375,5 +377,48 @@ void toStringDetailed(IrToStringContext& ctx, IrInst inst, uint32_t index)
append(ctx.result, "; useCount: %d, lastUse: %%%u\n", inst.useCount, inst.lastUse);
}
std::string dump(IrFunction& function)
{
std::string result;
IrToStringContext ctx{result, function.blocks, function.constants};
for (size_t i = 0; i < function.blocks.size(); i++)
{
IrBlock& block = function.blocks[i];
append(ctx.result, "%s_%u:\n", getBlockKindName(block.kind), unsigned(i));
if (block.start == ~0u)
{
append(ctx.result, " *empty*\n\n");
continue;
}
for (uint32_t index = block.start; true; index++)
{
LUAU_ASSERT(index < function.instructions.size());
IrInst& inst = function.instructions[index];
// Nop is used to replace dead instructions in-place, so it's not that useful to see them
if (inst.cmd == IrCmd::NOP)
continue;
append(ctx.result, " ");
toStringDetailed(ctx, inst, index);
if (isBlockTerminator(inst.cmd))
{
append(ctx.result, "\n");
break;
}
}
}
printf("%s\n", result.c_str());
return result;
}
} // namespace CodeGen
} // namespace Luau

View File

@ -28,5 +28,7 @@ void toString(std::string& result, IrConst constant);
void toStringDetailed(IrToStringContext& ctx, IrInst inst, uint32_t index);
std::string dump(IrFunction& function);
} // namespace CodeGen
} // namespace Luau

View File

@ -0,0 +1,780 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "IrTranslation.h"
#include "Luau/Bytecode.h"
#include "IrBuilder.h"
#include "lobject.h"
#include "ltm.h"
namespace Luau
{
namespace CodeGen
{
// Helper to consistently define a switch to instruction fallback code
struct FallbackStreamScope
{
FallbackStreamScope(IrBuilder& build, IrOp fallback, IrOp next)
: build(build)
, next(next)
{
LUAU_ASSERT(fallback.kind == IrOpKind::Block);
LUAU_ASSERT(next.kind == IrOpKind::Block);
build.inst(IrCmd::JUMP, next);
build.beginBlock(fallback);
}
~FallbackStreamScope()
{
build.beginBlock(next);
}
IrBuilder& build;
IrOp next;
};
void translateInstLoadNil(IrBuilder& build, const Instruction* pc)
{
int ra = LUAU_INSN_A(*pc);
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNIL));
}
void translateInstLoadB(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
build.inst(IrCmd::STORE_INT, build.vmReg(ra), build.constInt(LUAU_INSN_B(*pc)));
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TBOOLEAN));
if (int target = LUAU_INSN_C(*pc))
build.inst(IrCmd::JUMP, build.blockAtInst(pcpos + 1 + target));
}
void translateInstLoadN(IrBuilder& build, const Instruction* pc)
{
int ra = LUAU_INSN_A(*pc);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), build.constDouble(double(LUAU_INSN_D(*pc))));
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
}
void translateInstLoadK(IrBuilder& build, const Instruction* pc)
{
int ra = LUAU_INSN_A(*pc);
// TODO: per-component loads and stores might be preferable
IrOp load = build.inst(IrCmd::LOAD_TVALUE, build.vmConst(LUAU_INSN_D(*pc)));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), load);
}
void translateInstLoadKX(IrBuilder& build, const Instruction* pc)
{
int ra = LUAU_INSN_A(*pc);
uint32_t aux = pc[1];
// TODO: per-component loads and stores might be preferable
IrOp load = build.inst(IrCmd::LOAD_TVALUE, build.vmConst(aux));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), load);
}
void translateInstMove(IrBuilder& build, const Instruction* pc)
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
// TODO: per-component loads and stores might be preferable
IrOp load = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(rb));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), load);
}
void translateInstJump(IrBuilder& build, const Instruction* pc, int pcpos)
{
build.inst(IrCmd::JUMP, build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc)));
}
void translateInstJumpBack(IrBuilder& build, const Instruction* pc, int pcpos)
{
build.inst(IrCmd::INTERRUPT, build.constUint(pcpos));
build.inst(IrCmd::JUMP, build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc)));
}
void translateInstJumpIf(IrBuilder& build, const Instruction* pc, int pcpos, bool not_)
{
int ra = LUAU_INSN_A(*pc);
IrOp target = build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc));
IrOp next = build.blockAtInst(pcpos + 1);
// TODO: falsy/truthy conditions should be deconstructed into more primitive operations
if (not_)
build.inst(IrCmd::JUMP_IF_FALSY, build.vmReg(ra), target, next);
else
build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(ra), target, next);
// Fallthrough in original bytecode is implicit, so we start next internal block here
if (build.isInternalBlock(next))
build.beginBlock(next);
}
void translateInstJumpIfEq(IrBuilder& build, const Instruction* pc, int pcpos, bool not_)
{
int ra = LUAU_INSN_A(*pc);
int rb = pc[1];
IrOp target = build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc));
IrOp next = build.blockAtInst(pcpos + 2);
IrOp numberCheck = build.block(IrBlockKind::Internal);
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp ta = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra));
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::JUMP_EQ_TAG, ta, tb, numberCheck, not_ ? target : next);
build.beginBlock(numberCheck);
// fast-path: number
build.inst(IrCmd::CHECK_TAG, ta, build.constTag(LUA_TNUMBER), fallback);
IrOp va = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra));
IrOp vb = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rb));
build.inst(IrCmd::JUMP_CMP_NUM, va, vb, build.cond(IrCondition::NotEqual), not_ ? target : next, not_ ? next : target);
FallbackStreamScope scope(build, fallback, next);
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
build.inst(IrCmd::JUMP_CMP_ANY, build.vmReg(ra), build.vmReg(rb), build.cond(not_ ? IrCondition::NotEqual : IrCondition::Equal), target, next);
}
void translateInstJumpIfCond(IrBuilder& build, const Instruction* pc, int pcpos, IrCondition cond)
{
int ra = LUAU_INSN_A(*pc);
int rb = pc[1];
IrOp target = build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc));
IrOp next = build.blockAtInst(pcpos + 2);
IrOp fallback = build.block(IrBlockKind::Fallback);
// fast-path: number
IrOp ta = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra));
build.inst(IrCmd::CHECK_TAG, ta, build.constTag(LUA_TNUMBER), fallback);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TNUMBER), fallback);
IrOp va = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra));
IrOp vb = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rb));
build.inst(IrCmd::JUMP_CMP_NUM, va, vb, build.cond(cond), target, next);
FallbackStreamScope scope(build, fallback, next);
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
build.inst(IrCmd::JUMP_CMP_ANY, build.vmReg(ra), build.vmReg(rb), build.cond(cond), target, next);
}
void translateInstJumpX(IrBuilder& build, const Instruction* pc, int pcpos)
{
build.inst(IrCmd::INTERRUPT, build.constUint(pcpos));
build.inst(IrCmd::JUMP, build.blockAtInst(pcpos + 1 + LUAU_INSN_E(*pc)));
}
void translateInstJumpxEqNil(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
bool not_ = (pc[1] & 0x80000000) != 0;
IrOp target = build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc));
IrOp next = build.blockAtInst(pcpos + 2);
IrOp ta = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra));
build.inst(IrCmd::JUMP_EQ_TAG, ta, build.constTag(LUA_TNIL), not_ ? next : target, not_ ? target : next);
// Fallthrough in original bytecode is implicit, so we start next internal block here
if (build.isInternalBlock(next))
build.beginBlock(next);
}
void translateInstJumpxEqB(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
uint32_t aux = pc[1];
bool not_ = (aux & 0x80000000) != 0;
IrOp target = build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc));
IrOp next = build.blockAtInst(pcpos + 2);
IrOp checkValue = build.block(IrBlockKind::Internal);
IrOp ta = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra));
build.inst(IrCmd::JUMP_EQ_TAG, ta, build.constTag(LUA_TBOOLEAN), checkValue, not_ ? target : next);
build.beginBlock(checkValue);
IrOp va = build.inst(IrCmd::LOAD_INT, build.vmReg(ra));
build.inst(IrCmd::JUMP_EQ_BOOLEAN, va, build.constBool(aux & 0x1), not_ ? next : target, not_ ? target : next);
// Fallthrough in original bytecode is implicit, so we start next internal block here
if (build.isInternalBlock(next))
build.beginBlock(next);
}
void translateInstJumpxEqN(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
uint32_t aux = pc[1];
bool not_ = (aux & 0x80000000) != 0;
IrOp target = build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc));
IrOp next = build.blockAtInst(pcpos + 2);
IrOp checkValue = build.block(IrBlockKind::Internal);
IrOp ta = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra));
build.inst(IrCmd::JUMP_EQ_TAG, ta, build.constTag(LUA_TNUMBER), checkValue, not_ ? target : next);
build.beginBlock(checkValue);
IrOp va = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra));
IrOp vb = build.inst(IrCmd::LOAD_DOUBLE, build.vmConst(aux & 0xffffff));
build.inst(IrCmd::JUMP_CMP_NUM, va, vb, build.cond(IrCondition::NotEqual), not_ ? target : next, not_ ? next : target);
// Fallthrough in original bytecode is implicit, so we start next internal block here
if (build.isInternalBlock(next))
build.beginBlock(next);
}
void translateInstJumpxEqS(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
uint32_t aux = pc[1];
bool not_ = (aux & 0x80000000) != 0;
IrOp target = build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc));
IrOp next = build.blockAtInst(pcpos + 2);
IrOp checkValue = build.block(IrBlockKind::Internal);
IrOp ta = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra));
build.inst(IrCmd::JUMP_EQ_TAG, ta, build.constTag(LUA_TSTRING), checkValue, not_ ? target : next);
build.beginBlock(checkValue);
IrOp va = build.inst(IrCmd::LOAD_POINTER, build.vmReg(ra));
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmConst(aux & 0xffffff));
build.inst(IrCmd::JUMP_EQ_POINTER, va, vb, not_ ? next : target, not_ ? target : next);
// Fallthrough in original bytecode is implicit, so we start next internal block here
if (build.isInternalBlock(next))
build.beginBlock(next);
}
static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc, IrOp opc, int pcpos, TMS tm)
{
IrOp fallback = build.block(IrBlockKind::Fallback);
// fast-path: number
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TNUMBER), fallback);
if (rc != -1 && rc != rb) // TODO: optimization should handle second check, but we'll test it later
{
IrOp tc = build.inst(IrCmd::LOAD_TAG, build.vmReg(rc));
build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER), fallback);
}
IrOp vb = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rb));
IrOp vc = build.inst(IrCmd::LOAD_DOUBLE, opc);
IrOp va;
switch (tm)
{
case TM_ADD:
va = build.inst(IrCmd::ADD_NUM, vb, vc);
break;
case TM_SUB:
va = build.inst(IrCmd::SUB_NUM, vb, vc);
break;
case TM_MUL:
va = build.inst(IrCmd::MUL_NUM, vb, vc);
break;
case TM_DIV:
va = build.inst(IrCmd::DIV_NUM, vb, vc);
break;
case TM_MOD:
va = build.inst(IrCmd::MOD_NUM, vb, vc);
break;
case TM_POW:
va = build.inst(IrCmd::POW_NUM, vb, vc);
break;
default:
LUAU_ASSERT(!"unsupported binary op");
}
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), va);
if (ra != rb && ra != rc) // TODO: optimization should handle second check, but we'll test this later
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
IrOp next = build.blockAtInst(pcpos + 1);
FallbackStreamScope scope(build, fallback, next);
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
build.inst(IrCmd::DO_ARITH, build.vmReg(ra), build.vmReg(rb), opc, build.constInt(tm));
build.inst(IrCmd::JUMP, next);
}
void translateInstBinary(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm)
{
translateInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), build.vmReg(LUAU_INSN_C(*pc)), pcpos, tm);
}
void translateInstBinaryK(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm)
{
translateInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, build.vmConst(LUAU_INSN_C(*pc)), pcpos, tm);
}
void translateInstNot(IrBuilder& build, const Instruction* pc)
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
IrOp vb = build.inst(IrCmd::LOAD_INT, build.vmReg(rb));
IrOp va = build.inst(IrCmd::NOT_ANY, tb, vb);
build.inst(IrCmd::STORE_INT, build.vmReg(ra), va);
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TBOOLEAN));
}
void translateInstMinus(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TNUMBER), fallback);
// fast-path: number
IrOp vb = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rb));
IrOp va = build.inst(IrCmd::UNM_NUM, vb);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), va);
if (ra != rb)
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
IrOp next = build.blockAtInst(pcpos + 1);
FallbackStreamScope scope(build, fallback, next);
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
build.inst(IrCmd::DO_ARITH, build.vmReg(LUAU_INSN_A(*pc)), build.vmReg(LUAU_INSN_B(*pc)), build.vmReg(LUAU_INSN_B(*pc)), build.constInt(TM_UNM));
build.inst(IrCmd::JUMP, next);
}
void translateInstLength(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
// fast-path: table without __len
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
build.inst(IrCmd::CHECK_NO_METATABLE, vb, fallback);
IrOp va = build.inst(IrCmd::TABLE_LEN, vb);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), va);
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
IrOp next = build.blockAtInst(pcpos + 1);
FallbackStreamScope scope(build, fallback, next);
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
build.inst(IrCmd::DO_LEN, build.vmReg(LUAU_INSN_A(*pc)), build.vmReg(LUAU_INSN_B(*pc)));
build.inst(IrCmd::JUMP, next);
}
void translateInstNewTable(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
int b = LUAU_INSN_B(*pc);
uint32_t aux = pc[1];
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
IrOp va = build.inst(IrCmd::NEW_TABLE, build.constUint(aux), build.constUint(b == 0 ? 0 : 1 << (b - 1)));
build.inst(IrCmd::STORE_POINTER, build.vmReg(ra), va);
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TTABLE));
build.inst(IrCmd::CHECK_GC);
}
void translateInstDupTable(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
int k = LUAU_INSN_D(*pc);
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmConst(k));
IrOp va = build.inst(IrCmd::DUP_TABLE, table);
build.inst(IrCmd::STORE_POINTER, build.vmReg(ra), va);
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TTABLE));
build.inst(IrCmd::CHECK_GC);
}
void translateInstGetUpval(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
int up = LUAU_INSN_B(*pc);
build.inst(IrCmd::GET_UPVALUE, build.vmReg(ra), build.vmUpvalue(up));
}
void translateInstSetUpval(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
int up = LUAU_INSN_B(*pc);
build.inst(IrCmd::SET_UPVALUE, build.vmUpvalue(up), build.vmReg(ra));
}
void translateInstCloseUpvals(IrBuilder& build, const Instruction* pc)
{
int ra = LUAU_INSN_A(*pc);
build.inst(IrCmd::CLOSE_UPVALS, build.vmReg(ra));
}
void translateInstGetTableN(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
int c = LUAU_INSN_C(*pc);
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
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_NO_METATABLE, vb, fallback);
IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constUint(c));
// TODO: per-component loads and stores might be preferable
IrOp arrElTval = build.inst(IrCmd::LOAD_TVALUE, arrEl);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), arrElTval);
IrOp next = build.blockAtInst(pcpos + 1);
FallbackStreamScope scope(build, fallback, next);
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
build.inst(IrCmd::GET_TABLE, build.vmReg(ra), build.vmReg(rb), build.constUint(c + 1));
build.inst(IrCmd::JUMP, next);
}
void translateInstSetTableN(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
int c = LUAU_INSN_C(*pc);
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
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_NO_METATABLE, vb, fallback);
build.inst(IrCmd::CHECK_READONLY, vb, fallback);
IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constUint(c));
// TODO: per-component loads and stores might be preferable
IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra));
build.inst(IrCmd::STORE_TVALUE, arrEl, tva);
build.inst(IrCmd::BARRIER_TABLE_FORWARD, vb, build.vmReg(ra));
IrOp next = build.blockAtInst(pcpos + 1);
FallbackStreamScope scope(build, fallback, next);
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
build.inst(IrCmd::SET_TABLE, build.vmReg(ra), build.vmReg(rb), build.constUint(c + 1));
build.inst(IrCmd::JUMP, next);
}
void translateInstGetTable(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
int rc = LUAU_INSN_C(*pc);
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
IrOp tc = build.inst(IrCmd::LOAD_TAG, build.vmReg(rc));
build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER), fallback);
// fast-path: table with a number index
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
IrOp vc = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rc));
IrOp index = build.inst(IrCmd::NUM_TO_INDEX, vc, fallback);
index = build.inst(IrCmd::SUB_INT, index, build.constInt(1));
build.inst(IrCmd::CHECK_ARRAY_SIZE, vb, index, fallback);
build.inst(IrCmd::CHECK_NO_METATABLE, vb, fallback);
IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, index);
// TODO: per-component loads and stores might be preferable
IrOp arrElTval = build.inst(IrCmd::LOAD_TVALUE, arrEl);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), arrElTval);
IrOp next = build.blockAtInst(pcpos + 1);
FallbackStreamScope scope(build, fallback, next);
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
build.inst(IrCmd::GET_TABLE, build.vmReg(ra), build.vmReg(rb), build.vmReg(rc));
build.inst(IrCmd::JUMP, next);
}
void translateInstSetTable(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
int rc = LUAU_INSN_C(*pc);
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
IrOp tc = build.inst(IrCmd::LOAD_TAG, build.vmReg(rc));
build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER), fallback);
// fast-path: table with a number index
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
IrOp vc = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rc));
IrOp index = build.inst(IrCmd::NUM_TO_INDEX, vc, fallback);
index = build.inst(IrCmd::SUB_INT, index, build.constInt(1));
build.inst(IrCmd::CHECK_ARRAY_SIZE, vb, index, fallback);
build.inst(IrCmd::CHECK_NO_METATABLE, vb, fallback);
build.inst(IrCmd::CHECK_READONLY, vb, fallback);
IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, index);
// TODO: per-component loads and stores might be preferable
IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra));
build.inst(IrCmd::STORE_TVALUE, arrEl, tva);
build.inst(IrCmd::BARRIER_TABLE_FORWARD, vb, build.vmReg(ra));
IrOp next = build.blockAtInst(pcpos + 1);
FallbackStreamScope scope(build, fallback, next);
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
build.inst(IrCmd::SET_TABLE, build.vmReg(ra), build.vmReg(rb), build.vmReg(rc));
build.inst(IrCmd::JUMP, next);
}
void translateInstGetImport(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
int k = LUAU_INSN_D(*pc);
uint32_t aux = pc[1];
IrOp fastPath = build.block(IrBlockKind::Internal);
IrOp fallback = build.block(IrBlockKind::Fallback);
build.inst(IrCmd::CHECK_SAFE_ENV, fallback);
// note: if import failed, k[] is nil; we could check this during codegen, but we instead use runtime fallback
// this allows us to handle ahead-of-time codegen smoothly when an import fails to resolve at runtime
IrOp tk = build.inst(IrCmd::LOAD_TAG, build.vmConst(k));
build.inst(IrCmd::JUMP_EQ_TAG, tk, build.constTag(LUA_TNIL), fallback, fastPath);
build.beginBlock(fastPath);
// TODO: per-component loads and stores might be preferable
IrOp tvk = build.inst(IrCmd::LOAD_TVALUE, build.vmConst(k));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), tvk);
IrOp next = build.blockAtInst(pcpos + 2);
FallbackStreamScope scope(build, fallback, next);
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
build.inst(IrCmd::GET_IMPORT, build.vmReg(ra), build.constUint(aux));
build.inst(IrCmd::JUMP, next);
}
void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
uint32_t aux = pc[1];
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, vb, build.constUint(pcpos));
build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback);
// TODO: per-component loads and stores might be preferable
IrOp tvn = build.inst(IrCmd::LOAD_NODE_VALUE_TV, addrSlotEl);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), tvn);
IrOp next = build.blockAtInst(pcpos + 2);
FallbackStreamScope scope(build, fallback, next);
build.inst(IrCmd::FALLBACK_GETTABLEKS, build.constUint(pcpos));
build.inst(IrCmd::JUMP, next);
}
void translateInstSetTableKS(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
uint32_t aux = pc[1];
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, vb, build.constUint(pcpos));
build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback);
build.inst(IrCmd::CHECK_READONLY, vb, fallback);
// TODO: per-component loads and stores might be preferable
IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra));
build.inst(IrCmd::STORE_NODE_VALUE_TV, addrSlotEl, tva);
build.inst(IrCmd::BARRIER_TABLE_FORWARD, vb, build.vmReg(ra));
IrOp next = build.blockAtInst(pcpos + 2);
FallbackStreamScope scope(build, fallback, next);
build.inst(IrCmd::FALLBACK_SETTABLEKS, build.constUint(pcpos));
build.inst(IrCmd::JUMP, next);
}
void translateInstGetGlobal(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
uint32_t aux = pc[1];
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp env = build.inst(IrCmd::LOAD_ENV);
IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, env, build.constUint(pcpos));
build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback);
// TODO: per-component loads and stores might be preferable
IrOp tvn = build.inst(IrCmd::LOAD_NODE_VALUE_TV, addrSlotEl);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), tvn);
IrOp next = build.blockAtInst(pcpos + 2);
FallbackStreamScope scope(build, fallback, next);
build.inst(IrCmd::FALLBACK_GETGLOBAL, build.constUint(pcpos));
build.inst(IrCmd::JUMP, next);
}
void translateInstSetGlobal(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
uint32_t aux = pc[1];
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp env = build.inst(IrCmd::LOAD_ENV);
IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, env, build.constUint(pcpos));
build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback);
build.inst(IrCmd::CHECK_READONLY, env, fallback);
// TODO: per-component loads and stores might be preferable
IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra));
build.inst(IrCmd::STORE_NODE_VALUE_TV, addrSlotEl, tva);
build.inst(IrCmd::BARRIER_TABLE_FORWARD, env, build.vmReg(ra));
IrOp next = build.blockAtInst(pcpos + 2);
FallbackStreamScope scope(build, fallback, next);
build.inst(IrCmd::FALLBACK_SETGLOBAL, build.constUint(pcpos));
build.inst(IrCmd::JUMP, next);
}
void translateInstConcat(IrBuilder& build, const Instruction* pc, int pcpos)
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
int rc = LUAU_INSN_C(*pc);
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
build.inst(IrCmd::CONCAT, build.constUint(rc - rb + 1), build.constUint(rc));
// TODO: per-component loads and stores might be preferable
IrOp tvb = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(rb));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), tvb);
build.inst(IrCmd::CHECK_GC);
}
void translateInstCapture(IrBuilder& build, const Instruction* pc, int pcpos)
{
int type = LUAU_INSN_A(*pc);
int index = LUAU_INSN_B(*pc);
switch (type)
{
case LCT_VAL:
build.inst(IrCmd::CAPTURE, build.vmReg(index), build.constBool(false));
break;
case LCT_REF:
build.inst(IrCmd::CAPTURE, build.vmReg(index), build.constBool(true));
break;
case LCT_UPVAL:
build.inst(IrCmd::CAPTURE, build.vmUpvalue(index), build.constBool(false));
break;
default:
LUAU_ASSERT(!"Unknown upvalue capture type");
}
}
} // namespace CodeGen
} // namespace Luau

View File

@ -0,0 +1,58 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <stdint.h>
#include "ltm.h"
typedef uint32_t Instruction;
namespace Luau
{
namespace CodeGen
{
enum class IrCondition : uint8_t;
struct IrOp;
struct IrBuilder;
void translateInstLoadNil(IrBuilder& build, const Instruction* pc);
void translateInstLoadB(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstLoadN(IrBuilder& build, const Instruction* pc);
void translateInstLoadK(IrBuilder& build, const Instruction* pc);
void translateInstLoadKX(IrBuilder& build, const Instruction* pc);
void translateInstMove(IrBuilder& build, const Instruction* pc);
void translateInstJump(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstJumpBack(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstJumpIf(IrBuilder& build, const Instruction* pc, int pcpos, bool not_);
void translateInstJumpIfEq(IrBuilder& build, const Instruction* pc, int pcpos, bool not_);
void translateInstJumpIfCond(IrBuilder& build, const Instruction* pc, int pcpos, IrCondition cond);
void translateInstJumpX(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstJumpxEqNil(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstJumpxEqB(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstJumpxEqN(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstJumpxEqS(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstBinary(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm);
void translateInstBinaryK(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm);
void translateInstNot(IrBuilder& build, const Instruction* pc);
void translateInstMinus(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstLength(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstNewTable(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstDupTable(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstGetUpval(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstSetUpval(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstCloseUpvals(IrBuilder& build, const Instruction* pc);
void translateInstGetTableN(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstSetTableN(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstGetTable(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstSetTable(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstGetImport(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstSetTableKS(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstGetGlobal(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstSetGlobal(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstConcat(IrBuilder& build, const Instruction* pc, int pcpos);
void translateInstCapture(IrBuilder& build, const Instruction* pc, int pcpos);
} // namespace CodeGen
} // namespace Luau

View File

@ -98,6 +98,7 @@ inline bool isBlockTerminator(IrCmd cmd)
case IrCmd::JUMP_CMP_NUM:
case IrCmd::JUMP_CMP_STR:
case IrCmd::JUMP_CMP_ANY:
case IrCmd::LOP_NAMECALL:
case IrCmd::LOP_RETURN:
case IrCmd::LOP_FORNPREP:
case IrCmd::LOP_FORNLOOP:

View File

@ -35,7 +35,10 @@ inline AssertHandler& assertHandler()
return handler;
}
inline int assertCallHandler(const char* expression, const char* file, int line, const char* function)
// We want 'inline' to correctly link this function declared in the header
// But we also want to prevent compiler from inlining this function when optimization and assertions are enabled together
// Reason for that is that compilation times can increase significantly in such a configuration
LUAU_NOINLINE inline int assertCallHandler(const char* expression, const char* file, int line, const char* function)
{
if (AssertHandler handler = assertHandler())
return handler(expression, file, line, function);

View File

@ -4,6 +4,7 @@ if(NOT ${CMAKE_VERSION} VERSION_LESS "3.19")
target_sources(Luau.Common PRIVATE
Common/include/Luau/Common.h
Common/include/Luau/Bytecode.h
Common/include/Luau/DenseHash.h
Common/include/Luau/ExperimentalFlags.h
)
endif()
@ -12,7 +13,6 @@ endif()
target_sources(Luau.Ast PRIVATE
Ast/include/Luau/Ast.h
Ast/include/Luau/Confusables.h
Ast/include/Luau/DenseHash.h
Ast/include/Luau/Lexer.h
Ast/include/Luau/Location.h
Ast/include/Luau/ParseOptions.h
@ -82,7 +82,9 @@ target_sources(Luau.CodeGen PRIVATE
CodeGen/src/EmitCommonX64.cpp
CodeGen/src/EmitInstructionX64.cpp
CodeGen/src/Fallbacks.cpp
CodeGen/src/IrBuilder.cpp
CodeGen/src/IrDump.cpp
CodeGen/src/IrTranslation.cpp
CodeGen/src/NativeState.cpp
CodeGen/src/UnwindBuilderDwarf2.cpp
CodeGen/src/UnwindBuilderWin.cpp
@ -96,8 +98,10 @@ target_sources(Luau.CodeGen PRIVATE
CodeGen/src/EmitInstructionX64.h
CodeGen/src/Fallbacks.h
CodeGen/src/FallbacksProlog.h
CodeGen/src/IrBuilder.h
CodeGen/src/IrDump.h
CodeGen/src/IrData.h
CodeGen/src/IrTranslation.h
CodeGen/src/IrUtils.h
CodeGen/src/NativeState.h
)

View File

@ -20,6 +20,7 @@ message Expr {
ExprUnary unary = 14;
ExprBinary binary = 15;
ExprIfElse ifelse = 16;
ExprInterpString interpstring = 17;
}
}
@ -161,6 +162,10 @@ message ExprIfElse {
}
}
message ExprInterpString {
repeated Expr parts = 1;
}
message LValue {
oneof lvalue_oneof {
ExprLocal local = 1;

View File

@ -282,6 +282,8 @@ struct ProtoToLuau
print(expr.binary());
else if (expr.has_ifelse())
print(expr.ifelse());
else if (expr.has_interpstring())
print(expr.interpstring());
else
source += "_";
}
@ -538,6 +540,28 @@ struct ProtoToLuau
}
}
void print(const luau::ExprInterpString& expr)
{
source += "`";
for (int i = 0; i < expr.parts_size(); ++i)
{
if (expr.parts(i).has_string())
{
// String literal is added surrounded with "", but that's ok
print(expr.parts(i));
}
else
{
source += "{";
print(expr.parts(i));
source += "}";
}
}
source += "`";
}
void print(const luau::LValue& expr)
{
if (expr.has_local())

View File

@ -16,6 +16,8 @@
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTFLAG(LuauFixAutocompleteInIf)
LUAU_FASTFLAG(LuauFixAutocompleteInWhile)
LUAU_FASTFLAG(LuauFixAutocompleteInFor)
using namespace Luau;
@ -380,7 +382,7 @@ TEST_CASE_FIXTURE(ACFixture, "table_intersection")
{
check(R"(
type t1 = { a1 : string, b2 : number }
type t2 = { b2 : string, c3 : string }
type t2 = { b2 : number, c3 : string }
function func(abc : t1 & t2)
abc. @1
end
@ -629,9 +631,19 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_for_middle_keywords")
)");
auto ac5 = autocomplete('1');
CHECK_EQ(ac5.entryMap.count("do"), 1);
CHECK_EQ(ac5.entryMap.count("end"), 0);
CHECK_EQ(ac5.context, AutocompleteContext::Keyword);
if (FFlag::LuauFixAutocompleteInFor)
{
CHECK_EQ(ac5.entryMap.count("math"), 1);
CHECK_EQ(ac5.entryMap.count("do"), 0);
CHECK_EQ(ac5.entryMap.count("end"), 0);
CHECK_EQ(ac5.context, AutocompleteContext::Expression);
}
else
{
CHECK_EQ(ac5.entryMap.count("do"), 1);
CHECK_EQ(ac5.entryMap.count("end"), 0);
CHECK_EQ(ac5.context, AutocompleteContext::Keyword);
}
check(R"(
for x = 1, 2, 5 f@1
@ -649,6 +661,31 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_for_middle_keywords")
auto ac7 = autocomplete('1');
CHECK_EQ(ac7.entryMap.count("end"), 1);
CHECK_EQ(ac7.context, AutocompleteContext::Statement);
if (FFlag::LuauFixAutocompleteInFor)
{
check(R"(local Foo = 1
for x = @11, @22, @35
)");
for (int i = 0; i < 3; ++i)
{
auto ac8 = autocomplete('1' + i);
CHECK_EQ(ac8.entryMap.count("Foo"), 1);
CHECK_EQ(ac8.entryMap.count("do"), 0);
}
check(R"(local Foo = 1
for x = @11, @22
)");
for (int i = 0; i < 2; ++i)
{
auto ac9 = autocomplete('1' + i);
CHECK_EQ(ac9.entryMap.count("Foo"), 1);
CHECK_EQ(ac9.entryMap.count("do"), 0);
}
}
}
TEST_CASE_FIXTURE(ACFixture, "autocomplete_for_in_middle_keywords")
@ -740,8 +777,18 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_while_middle_keywords")
)");
auto ac2 = autocomplete('1');
CHECK_EQ(1, ac2.entryMap.size());
CHECK_EQ(ac2.entryMap.count("do"), 1);
if (FFlag::LuauFixAutocompleteInWhile)
{
CHECK_EQ(3, ac2.entryMap.size());
CHECK_EQ(ac2.entryMap.count("do"), 1);
CHECK_EQ(ac2.entryMap.count("and"), 1);
CHECK_EQ(ac2.entryMap.count("or"), 1);
}
else
{
CHECK_EQ(1, ac2.entryMap.size());
CHECK_EQ(ac2.entryMap.count("do"), 1);
}
CHECK_EQ(ac2.context, AutocompleteContext::Keyword);
check(R"(
@ -757,9 +804,31 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_while_middle_keywords")
)");
auto ac4 = autocomplete('1');
CHECK_EQ(1, ac4.entryMap.size());
CHECK_EQ(ac4.entryMap.count("do"), 1);
if (FFlag::LuauFixAutocompleteInWhile)
{
CHECK_EQ(3, ac4.entryMap.size());
CHECK_EQ(ac4.entryMap.count("do"), 1);
CHECK_EQ(ac4.entryMap.count("and"), 1);
CHECK_EQ(ac4.entryMap.count("or"), 1);
}
else
{
CHECK_EQ(1, ac4.entryMap.size());
CHECK_EQ(ac4.entryMap.count("do"), 1);
}
CHECK_EQ(ac4.context, AutocompleteContext::Keyword);
if (FFlag::LuauFixAutocompleteInWhile)
{
check(R"(
while t@1
)");
auto ac5 = autocomplete('1');
CHECK_EQ(ac5.entryMap.count("do"), 0);
CHECK_EQ(ac5.entryMap.count("true"), 1);
CHECK_EQ(ac5.entryMap.count("false"), 1);
}
}
TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords")
@ -856,7 +925,7 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords")
CHECK_EQ(ac5.entryMap.count("elseif"), 0);
CHECK_EQ(ac5.entryMap.count("end"), 0);
CHECK_EQ(ac5.context, AutocompleteContext::Statement);
if (FFlag::LuauFixAutocompleteInIf)
{
check(R"(
@ -3399,6 +3468,8 @@ TEST_CASE_FIXTURE(ACFixture, "type_reduction_is_hooked_up_to_autocomplete")
TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback")
{
ScopedFastFlag luauAutocompleteStringContent{"LuauAutocompleteStringContent", true};
loadDefinition(R"(
declare function require(path: string): any
)");
@ -3414,10 +3485,9 @@ TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback")
)");
bool isCorrect = false;
auto ac1 = autocomplete('1',
[&isCorrect](std::string, std::optional<const ClassType*>, std::optional<std::string> contents) -> std::optional<AutocompleteEntryMap>
{
isCorrect = contents.has_value() && contents.value() == "testing/";
auto ac1 = autocomplete(
'1', [&isCorrect](std::string, std::optional<const ClassType*>, std::optional<std::string> contents) -> std::optional<AutocompleteEntryMap> {
isCorrect = contents && *contents == "testing/";
return std::nullopt;
});

View File

@ -701,8 +701,7 @@ TEST_CASE("NDebugGetUpValue")
copts.optimizationLevel = 0;
runConformance(
"ndebug_upvalues.lua",
nullptr,
"ndebug_upvalues.lua", nullptr,
[](lua_State* L) {
lua_checkstack(L, LUA_MINSTACK);

View File

@ -606,12 +606,14 @@ void createSomeClasses(Frontend* frontend)
TypeId childType = arena.addType(ClassType{"Child", {}, parentType, std::nullopt, {}, nullptr, "Test"});
ClassType* childClass = getMutable<ClassType>(childType);
childClass->props["virtual_method"] = {makeFunction(arena, childType, {}, {})};
addGlobalBinding(*frontend, "Child", {childType});
moduleScope->exportedTypeBindings["Child"] = TypeFun{{}, childType};
TypeId anotherChildType = arena.addType(ClassType{"AnotherChild", {}, parentType, std::nullopt, {}, nullptr, "Test"});
addGlobalBinding(*frontend, "AnotherChild", {anotherChildType});
moduleScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildType};
TypeId unrelatedType = arena.addType(ClassType{"Unrelated", {}, frontend->builtinTypes->classType, std::nullopt, {}, nullptr, "Test"});
addGlobalBinding(*frontend, "Unrelated", {unrelatedType});

View File

@ -171,7 +171,6 @@ return bar()
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMultiFx")
{
ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true};
LintResult result = lint(R"(
function bar()
foo = 6
@ -192,7 +191,6 @@ return bar() + baz()
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMultiFxWithRead")
{
ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true};
LintResult result = lint(R"(
function bar()
foo = 6
@ -216,7 +214,6 @@ return bar() + baz() + read()
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalWithConditional")
{
ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true};
LintResult result = lint(R"(
function bar()
if true then foo = 6 end
@ -236,7 +233,6 @@ return bar() + baz()
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocal3WithConditionalRead")
{
ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true};
LintResult result = lint(R"(
function bar()
foo = 6
@ -260,7 +256,6 @@ return bar() + baz() + read()
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalInnerRead")
{
ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true};
LintResult result = lint(R"(
function foo()
local f = function() return bar end

View File

@ -174,11 +174,6 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "table_with_any_prop")
TEST_CASE_FIXTURE(IsSubtypeFixture, "intersection")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
check(R"(
local a: number & string
local b: number

View File

@ -9,6 +9,8 @@
using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
struct ToDotClassFixture : Fixture
{
ToDotClassFixture()
@ -109,7 +111,27 @@ local function f(a, ...: string) return a end
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(R"(digraph graphname {
n1 [label="FunctionType 1"];
n1 -> n2 [label="arg"];
n2 [label="TypePack 2"];
n2 -> n3;
n3 [label="GenericType 3"];
n2 -> n4 [label="tail"];
n4 [label="VariadicTypePack 4"];
n4 -> n5;
n5 [label="string"];
n1 -> n6 [label="ret"];
n6 [label="TypePack 6"];
n6 -> n3;
})",
toDot(requireType("f"), opts));
}
else
{
CHECK_EQ(R"(digraph graphname {
n1 [label="FunctionType 1"];
n1 -> n2 [label="arg"];
n2 [label="TypePack 2"];
@ -125,7 +147,8 @@ n6 -> n7;
n7 [label="TypePack 7"];
n7 -> n3;
})",
toDot(requireType("f"), opts));
toDot(requireType("f"), opts));
}
}
TEST_CASE_FIXTURE(Fixture, "union")
@ -176,7 +199,35 @@ local a: A<number, ...string>
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(R"(digraph graphname {
n1 [label="TableType A"];
n1 -> n2 [label="x"];
n2 [label="number"];
n1 -> n3 [label="y"];
n3 [label="FunctionType 3"];
n3 -> n4 [label="arg"];
n4 [label="TypePack 4"];
n4 -> n5 [label="tail"];
n5 [label="VariadicTypePack 5"];
n5 -> n6;
n6 [label="string"];
n3 -> n7 [label="ret"];
n7 [label="TypePack 7"];
n1 -> n8 [label="[index]"];
n8 [label="string"];
n1 -> n9 [label="[value]"];
n9 [label="any"];
n1 -> n10 [label="typeParam"];
n10 [label="number"];
n1 -> n5 [label="typePackParam"];
})",
toDot(requireType("a"), opts));
}
else
{
CHECK_EQ(R"(digraph graphname {
n1 [label="TableType A"];
n1 -> n2 [label="x"];
n2 [label="number"];
@ -196,7 +247,8 @@ n1 -> n9 [label="typeParam"];
n9 [label="number"];
n1 -> n4 [label="typePackParam"];
})",
toDot(requireType("a"), opts));
toDot(requireType("a"), opts));
}
// Extra coverage with pointers (unstable values)
(void)toDot(requireType("a"));
@ -357,14 +409,31 @@ b = a
ToDotOptions opts;
opts.showPointers = false;
CHECK_EQ(R"(digraph graphname {
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(R"(digraph graphname {
n1 [label="BoundType 1"];
n1 -> n2;
n2 [label="TableType 2"];
n2 -> n3 [label="boundTo"];
n3 [label="TableType 3"];
n3 -> n4 [label="x"];
n4 [label="number"];
})",
toDot(*ty, opts));
}
else
{
CHECK_EQ(R"(digraph graphname {
n1 [label="TableType 1"];
n1 -> n2 [label="boundTo"];
n2 [label="TableType a"];
n2 -> n3 [label="x"];
n3 [label="number"];
})",
toDot(*ty, opts));
toDot(*ty, opts));
}
}
TEST_CASE_FIXTURE(Fixture, "builtintypes")

View File

@ -814,8 +814,6 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_self_param")
TEST_CASE_FIXTURE(Fixture, "tostring_unsee_ttv_if_array")
{
ScopedFastFlag sff("LuauUnseeArrayTtv", true);
CheckResult result = check(R"(
local x: {string}
-- This code is constructed very specifically to use the same (by pointer

View File

@ -8,7 +8,8 @@
using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauMatchReturnsOptionalString);
TEST_SUITE_BEGIN("BuiltinTests");
@ -174,7 +175,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "math_max_checks_for_numbers")
local n = math.max(1,2,"3")
)");
CHECK(!result.errors.empty());
LUAU_REQUIRE_ERRORS(result);
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
}
@ -1004,7 +1005,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_is_generic")
TEST_CASE_FIXTURE(BuiltinsFixture, "set_metatable_needs_arguments")
{
ScopedFastFlag sff{"LuauSetMetaTableArgsCheck", true};
CheckResult result = check(R"(
local a = {b=setmetatable}
a.b()
@ -1055,6 +1055,20 @@ end
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "string_match")
{
CheckResult result = check(R"(
local s:string
local p = s:match("foo")
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauMatchReturnsOptionalString)
CHECK_EQ(toString(requireType("p")), "string?");
else
CHECK_EQ(toString(requireType("p")), "string");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types")
{
CheckResult result = check(R"END(
@ -1063,12 +1077,21 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types")
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "number");
CHECK_EQ(toString(requireType("c")), "string");
if (FFlag::LuauMatchReturnsOptionalString)
{
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "number?");
CHECK_EQ(toString(requireType("c")), "string?");
}
else
{
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "number");
CHECK_EQ(toString(requireType("c")), "string");
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types2")
TEST_CASE_FIXTURE(Fixture, "gmatch_capture_types2")
{
CheckResult result = check(R"END(
local a, b, c = ("This is a string"):gmatch("(.()(%a+))")()
@ -1076,9 +1099,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types2")
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "number");
CHECK_EQ(toString(requireType("c")), "string");
if (FFlag::LuauMatchReturnsOptionalString)
{
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "number?");
CHECK_EQ(toString(requireType("c")), "string?");
}
else
{
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "number");
CHECK_EQ(toString(requireType("c")), "string");
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture")
@ -1095,7 +1127,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture")
CHECK_EQ(acm->expected, 1);
CHECK_EQ(acm->actual, 4);
CHECK_EQ(toString(requireType("a")), "string");
if (FFlag::LuauMatchReturnsOptionalString)
CHECK_EQ(toString(requireType("a")), "string?");
else
CHECK_EQ(toString(requireType("a")), "string");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens")
@ -1112,9 +1147,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens
CHECK_EQ(acm->expected, 3);
CHECK_EQ(acm->actual, 4);
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "string");
CHECK_EQ(toString(requireType("c")), "number");
if (FFlag::LuauMatchReturnsOptionalString)
{
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "string?");
CHECK_EQ(toString(requireType("c")), "number?");
}
else
{
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "string");
CHECK_EQ(toString(requireType("c")), "number");
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_parens_in_sets_are_ignored")
@ -1131,8 +1175,16 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_parens_in_sets_are_igno
CHECK_EQ(acm->expected, 2);
CHECK_EQ(acm->actual, 3);
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "number");
if (FFlag::LuauMatchReturnsOptionalString)
{
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "number?");
}
else
{
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "number");
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_set_containing_lbracket")
@ -1143,8 +1195,16 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_set_containing_lbracket
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a")), "number");
CHECK_EQ(toString(requireType("b")), "string");
if (FFlag::LuauMatchReturnsOptionalString)
{
CHECK_EQ(toString(requireType("a")), "number?");
CHECK_EQ(toString(requireType("b")), "string?");
}
else
{
CHECK_EQ(toString(requireType("a")), "number");
CHECK_EQ(toString(requireType("b")), "string");
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_leading_end_bracket_is_part_of_set")
@ -1192,9 +1252,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types")
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "number");
CHECK_EQ(toString(requireType("c")), "string");
if (FFlag::LuauMatchReturnsOptionalString)
{
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "number?");
CHECK_EQ(toString(requireType("c")), "string?");
}
else
{
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "number");
CHECK_EQ(toString(requireType("c")), "string");
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types2")
@ -1210,9 +1279,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types2")
CHECK_EQ(toString(tm->wantedType), "number?");
CHECK_EQ(toString(tm->givenType), "string");
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "number");
CHECK_EQ(toString(requireType("c")), "string");
if (FFlag::LuauMatchReturnsOptionalString)
{
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "number?");
CHECK_EQ(toString(requireType("c")), "string?");
}
else
{
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "number");
CHECK_EQ(toString(requireType("c")), "string");
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types")
@ -1223,9 +1301,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types")
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "number");
CHECK_EQ(toString(requireType("c")), "string");
if (FFlag::LuauMatchReturnsOptionalString)
{
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "number?");
CHECK_EQ(toString(requireType("c")), "string?");
}
else
{
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "number");
CHECK_EQ(toString(requireType("c")), "string");
}
CHECK_EQ(toString(requireType("d")), "number?");
CHECK_EQ(toString(requireType("e")), "number?");
}
@ -1243,9 +1330,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types2")
CHECK_EQ(toString(tm->wantedType), "number?");
CHECK_EQ(toString(tm->givenType), "string");
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "number");
CHECK_EQ(toString(requireType("c")), "string");
if (FFlag::LuauMatchReturnsOptionalString)
{
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "number?");
CHECK_EQ(toString(requireType("c")), "string?");
}
else
{
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "number");
CHECK_EQ(toString(requireType("c")), "string");
}
CHECK_EQ(toString(requireType("d")), "number?");
CHECK_EQ(toString(requireType("e")), "number?");
}
@ -1263,9 +1359,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3")
CHECK_EQ(toString(tm->wantedType), "boolean?");
CHECK_EQ(toString(tm->givenType), "string");
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "number");
CHECK_EQ(toString(requireType("c")), "string");
if (FFlag::LuauMatchReturnsOptionalString)
{
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "number?");
CHECK_EQ(toString(requireType("c")), "string?");
}
else
{
CHECK_EQ(toString(requireType("a")), "string");
CHECK_EQ(toString(requireType("b")), "number");
CHECK_EQ(toString(requireType("c")), "string");
}
CHECK_EQ(toString(requireType("d")), "number?");
CHECK_EQ(toString(requireType("e")), "number?");
}

View File

@ -398,11 +398,6 @@ local a: ChildClass = i
TEST_CASE_FIXTURE(ClassFixture, "intersections_of_unions_of_classes")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : (BaseClass | Vector2) & (ChildClass | AnotherChild)
local y : (ChildClass | AnotherChild)
@ -415,11 +410,6 @@ TEST_CASE_FIXTURE(ClassFixture, "intersections_of_unions_of_classes")
TEST_CASE_FIXTURE(ClassFixture, "unions_of_intersections_of_classes")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : (BaseClass & ChildClass) | (BaseClass & AnotherChild) | (BaseClass & Vector2)
local y : (ChildClass | AnotherChild)
@ -482,8 +472,6 @@ caused by:
TEST_CASE_FIXTURE(ClassFixture, "callable_classes")
{
ScopedFastFlag luauCallableClasses{"LuauCallableClasses", true};
CheckResult result = check(R"(
local x : CallableClass
local y = x("testing")

View File

@ -396,8 +396,6 @@ TEST_CASE_FIXTURE(Fixture, "class_definition_string_props")
TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
{
ScopedFastFlag LuauDeclareClassPrototype("LuauDeclareClassPrototype", true);
unfreeze(typeChecker.globalTypes);
LoadDefinitionFileResult result = loadDefinitionFile(typeChecker, typeChecker.globalScope, R"(
declare class Channel

View File

@ -1726,12 +1726,6 @@ foo(string.find("hello", "e"))
TEST_CASE_FIXTURE(Fixture, "luau_subtyping_is_np_hard")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
{"LuauOverloadedFunctionSubtypingPerf", true},
};
CheckResult result = check(R"(
--!strict
@ -1834,8 +1828,6 @@ TEST_CASE_FIXTURE(Fixture, "other_things_are_not_related_to_function")
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_must_follow_in_overload_resolution")
{
ScopedFastFlag luauTypeInferMissingFollows{"LuauTypeInferMissingFollows", true};
CheckResult result = check(R"(
for _ in function<t0>():(t0)&((()->())&(()->()))
end do

View File

@ -463,11 +463,6 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : ((number?) -> number?) & ((string?) -> string?)
local y : (nil) -> nil = x -- OK
@ -481,11 +476,6 @@ TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : ((number) -> number) & ((string) -> string)
local y : ((number | string) -> (number | string)) = x -- OK
@ -499,11 +489,6 @@ TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : { p : number?, q : string? } & { p : number?, q : number?, r : number? }
local y : { p : number?, q : nil, r : number? } = x -- OK
@ -531,8 +516,6 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
{"LuauUninhabitedSubAnything2", true},
};
@ -547,11 +530,6 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties")
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : ((number?) -> ({ p : number } & { q : number })) & ((string?) -> ({ p : number } & { r : number }))
local y : (nil) -> { p : number, q : number, r : number} = x -- OK
@ -566,11 +544,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f<a>()
local x : ((number?) -> (a | number)) & ((string?) -> (a | string))
@ -586,11 +559,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f<a,b,c>()
local x : ((a?) -> (a | b)) & ((c?) -> (b | c))
@ -606,11 +574,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f<a...,b...>()
local x : ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))
@ -626,11 +589,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f<a...,b...>()
local x : ((number) -> number) & ((nil) -> unknown)
@ -646,11 +604,6 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f<a...,b...>()
local x : ((number) -> number?) & ((unknown) -> string?)
@ -666,11 +619,6 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f<a...,b...>()
local x : ((number) -> number) & ((nil) -> never)
@ -686,11 +634,6 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f<a...,b...>()
local x : ((number) -> number?) & ((never) -> string?)
@ -779,11 +722,6 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local a : string? = nil
local b : number? = nil
@ -807,11 +745,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables")
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_subtypes")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x = setmetatable({ a = 5 }, { p = 5 });
local y = setmetatable({ b = "hi" }, { p = 5, q = "hi" });
@ -833,11 +766,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_subtypes")
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables_with_properties")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x = setmetatable({ a = 5 }, { p = 5 });
local y = setmetatable({ b = "hi" }, { q = "hi" });
@ -856,11 +784,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables_with_properties")
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_with_table")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x = setmetatable({ a = 5 }, { p = 5 });
local z = setmetatable({ a = 5, b = "hi" }, { p = 5 });
@ -881,11 +804,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_with_table")
TEST_CASE_FIXTURE(Fixture, "CLI-44817")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
type X = {x: number}
type Y = {y: number}

View File

@ -475,8 +475,6 @@ return l0
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_anyify_variadic_return_must_follow")
{
ScopedFastFlag luauTypeInferMissingFollows{"LuauTypeInferMissingFollows", true};
CheckResult result = check(R"(
return unpack(l0[_])
)");

View File

@ -14,9 +14,6 @@ namespace
struct NegationFixture : Fixture
{
TypeArena arena;
ScopedFastFlag sff[1]{
{"LuauSubtypeNormalizer", true},
};
NegationFixture()
{

View File

@ -72,17 +72,6 @@ TEST_CASE_FIXTURE(Fixture, "string_function_indirect")
CHECK_EQ(*requireType("p"), *typeChecker.stringType);
}
TEST_CASE_FIXTURE(Fixture, "string_function_other")
{
CheckResult result = check(R"(
local s:string
local p = s:match("foo")
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("p")), "string");
}
TEST_CASE_FIXTURE(Fixture, "CheckMethodsOfNumber")
{
CheckResult result = check(R"(

View File

@ -800,7 +800,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_can_filter_for_intersection_of_ta
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("{| x: number |} & {| y: number |}", toString(requireTypeAtPosition({4, 28})));
ToStringOptions opts;
opts.exhaustive = true;
CHECK_EQ("{| x: number |} & {| y: number |}", toString(requireTypeAtPosition({4, 28}), opts));
CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28})));
}
@ -1436,6 +1438,32 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "type_narrow_for_all_the_userdata")
CHECK_EQ("number | string", toString(requireTypeAtPosition({5, 28})));
}
TEST_CASE_FIXTURE(RefinementClassFixture, "type_narrow_but_the_discriminant_type_isnt_a_class")
{
CheckResult result = check(R"(
local function f(x: string | number | Instance | Vector3)
if type(x) == "any" then
local foo = x
else
local foo = x
end
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(Instance | Vector3 | number | string) & never", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("(Instance | Vector3 | number | string) & ~never", toString(requireTypeAtPosition({5, 28})));
}
else
{
CHECK_EQ("*error-type*", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("*error-type*", toString(requireTypeAtPosition({5, 28})));
}
}
TEST_CASE_FIXTURE(RefinementClassFixture, "eliminate_subclasses_of_instance")
{
CheckResult result = check(R"(
@ -1721,8 +1749,6 @@ TEST_CASE_FIXTURE(Fixture, "else_with_no_explicit_expression_should_also_refine_
TEST_CASE_FIXTURE(Fixture, "fuzz_filtered_refined_types_are_followed")
{
ScopedFastFlag luauTypeInferMissingFollows{"LuauTypeInferMissingFollows", true};
CheckResult result = check(R"(
local _
do

View File

@ -55,7 +55,10 @@ TEST_CASE_FIXTURE(Fixture, "augment_table")
TEST_CASE_FIXTURE(Fixture, "augment_nested_table")
{
CheckResult result = check("local t = { p = {} } t.p.foo = 'bar'");
CheckResult result = check(R"(
local t = { p = {} }
t.p.foo = 'bar'
)");
LUAU_REQUIRE_NO_ERRORS(result);
TableType* tType = getMutable<TableType>(requireType("t"));
@ -70,19 +73,28 @@ TEST_CASE_FIXTURE(Fixture, "augment_nested_table")
TEST_CASE_FIXTURE(Fixture, "cannot_augment_sealed_table")
{
CheckResult result = check("function mkt() return {prop=999} end local t = mkt() t.foo = 'bar'");
CheckResult result = check(R"(
function mkt()
return {prop=999}
end
local t = mkt()
t.foo = 'bar'
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
TypeError& err = result.errors[0];
CHECK(err.location == Location{Position{6, 8}, Position{6, 13}});
CannotExtendTable* error = get<CannotExtendTable>(err);
REQUIRE(error != nullptr);
REQUIRE_MESSAGE(error != nullptr, "Expected CannotExtendTable but got: " << toString(err));
// TODO: better, more robust comparison of type vars
auto s = toString(error->tableType, ToStringOptions{/*exhaustive*/ true});
CHECK_EQ(s, "{| prop: number |}");
CHECK_EQ(error->prop, "foo");
CHECK_EQ(error->context, CannotExtendTable::Property);
CHECK_EQ(err.location, (Location{Position{0, 59}, Position{0, 64}}));
}
TEST_CASE_FIXTURE(Fixture, "dont_seal_an_unsealed_table_by_passing_it_to_a_function_that_takes_a_sealed_table")

View File

@ -1029,10 +1029,6 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_no_ice")
TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
{
ScopedFastInt sfi("LuauTypeInferRecursionLimit", 10);
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f<a,b,c,d,e,f,g,h,i,j>()
@ -1048,10 +1044,6 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
TEST_CASE_FIXTURE(Fixture, "type_infer_cache_limit_normalizer")
{
ScopedFastInt sfi("LuauNormalizeCacheLimit", 10);
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : ((number) -> number) & ((string) -> string) & ((nil) -> nil) & (({}) -> {})
@ -1161,8 +1153,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "it_is_ok_to_have_inconsistent_number_of_retu
TEST_CASE_FIXTURE(Fixture, "fuzz_free_table_type_change_during_index_check")
{
ScopedFastFlag luauFollowInLvalueIndexCheck{"LuauFollowInLvalueIndexCheck", true};
CheckResult result = check(R"(
local _ = nil
while _["" >= _] do

View File

@ -112,11 +112,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_tables_are_preserved")
TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_never")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f(arg : string & number) : never
return arg
@ -127,11 +122,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_never")
TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_anything")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f(arg : string & number) : boolean
return arg
@ -143,8 +133,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_anything")
TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_never")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
{"LuauUninhabitedSubAnything2", true},
};
@ -159,8 +147,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_never")
TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_anything")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
{"LuauUninhabitedSubAnything2", true},
};
@ -363,8 +349,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table
TEST_CASE_FIXTURE(TryUnifyFixture, "fuzz_tail_unification_issue")
{
ScopedFastFlag luauTxnLogTypePackIterator{"LuauTxnLogTypePackIterator", true};
TypePackVar variadicAny{VariadicTypePack{typeChecker.anyType}};
TypePackVar packTmp{TypePack{{typeChecker.anyType}, &variadicAny}};
TypePackVar packSub{TypePack{{typeChecker.anyType, typeChecker.anyType}, &packTmp}};
@ -376,4 +360,18 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "fuzz_tail_unification_issue")
state.tryUnify(&packSub, &packSuper);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_unify_any_should_check_log")
{
ScopedFastFlag luauUnifyAnyTxnLog{"LuauUnifyAnyTxnLog", true};
CheckResult result = check(R"(
repeat
_._,_ = nil
until _
local l0:(any)&(typeof(_)),l0:(any)|(any) = _,_
)");
LUAU_REQUIRE_ERRORS(result);
}
TEST_SUITE_END();

View File

@ -1039,8 +1039,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "generalize_expectedTypes_with_proper_scope")
TEST_CASE_FIXTURE(Fixture, "fuzz_typepack_iter_follow")
{
ScopedFastFlag luauTxnLogTypePackIterator{"LuauTxnLogTypePackIterator", true};
CheckResult result = check(R"(
local _
local _ = _,_(),_(_)
@ -1051,8 +1049,6 @@ local _ = _,_(),_(_)
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_typepack_iter_follow_2")
{
ScopedFastFlag luauTxnLogTypePackIterator{"LuauTxnLogTypePackIterator", true};
CheckResult result = check(R"(
function test(name, searchTerm)
local found = string.find(name:lower(), searchTerm:lower())

View File

@ -544,11 +544,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect")
TEST_CASE_FIXTURE(Fixture, "union_true_and_false")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : boolean
local y1 : (true | false) = x -- OK
@ -562,11 +557,6 @@ TEST_CASE_FIXTURE(Fixture, "union_true_and_false")
TEST_CASE_FIXTURE(Fixture, "union_of_functions")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : (number) -> number?
local y : ((number?) -> number?) | ((number) -> number) = x -- OK
@ -599,11 +589,6 @@ TEST_CASE_FIXTURE(Fixture, "union_of_generic_typepack_functions")
TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f<a,b>()
local x : (a) -> a?
@ -619,11 +604,6 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics")
TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f<a...>()
local x : (number, a...) -> (number?, a...)
@ -639,11 +619,6 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks")
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : (number) -> number?
local y : ((number?) -> number) | ((number | string) -> nil) = x -- OK
@ -657,11 +632,6 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : () -> (number | string)
local y : (() -> number) | (() -> string) = x -- OK
@ -675,11 +645,6 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : (...nil) -> (...number?)
local y : ((...string?) -> (...number)) | ((...number?) -> nil) = x -- OK
@ -693,11 +658,6 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : (number) -> ()
local y : ((number?) -> ()) | ((...number) -> ()) = x -- OK
@ -711,11 +671,6 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics")
{
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : () -> (number?, ...number)
local y : (() -> (...number)) | (() -> nil) = x -- OK

File diff suppressed because it is too large Load Diff

View File

@ -923,6 +923,21 @@ assert((function()
return table.concat(res, ',')
end)() == "6,8,10")
-- checking for a CFG issue that was missed in IR
assert((function(b)
local res = 0
if b then
for i = 1, 100 do
res += i
end
else
res += 100000
end
return res
end)(true) == 5050)
-- typeof and type require an argument
assert(pcall(typeof) == false)
assert(pcall(type) == false)

View File

@ -42,7 +42,6 @@ AutocompleteTest.type_correct_suggestion_in_argument
AutocompleteTest.type_correct_suggestion_in_table
BuiltinTests.aliased_string_format
BuiltinTests.assert_removes_falsy_types
BuiltinTests.assert_removes_falsy_types2
BuiltinTests.assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type
BuiltinTests.assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy
BuiltinTests.bad_select_should_not_crash
@ -53,9 +52,6 @@ BuiltinTests.dont_add_definitions_to_persistent_types
BuiltinTests.find_capture_types
BuiltinTests.find_capture_types2
BuiltinTests.find_capture_types3
BuiltinTests.gmatch_capture_types_balanced_escaped_parens
BuiltinTests.gmatch_capture_types_default_capture
BuiltinTests.gmatch_capture_types_parens_in_sets_are_ignored
BuiltinTests.gmatch_definition
BuiltinTests.ipairs_iterator_should_infer_types_and_type_check
BuiltinTests.match_capture_types
@ -80,7 +76,6 @@ BuiltinTests.table_insert_correctly_infers_type_of_array_3_args_overload
BuiltinTests.table_pack
BuiltinTests.table_pack_reduce
BuiltinTests.table_pack_variadic
BuiltinTests.tonumber_returns_optional_number_type
DefinitionTests.class_definition_overload_metamethods
DefinitionTests.class_definition_string_props
DefinitionTests.declaring_generic_functions
@ -103,7 +98,6 @@ GenericsTests.duplicate_generic_type_packs
GenericsTests.duplicate_generic_types
GenericsTests.generic_argument_count_too_few
GenericsTests.generic_argument_count_too_many
GenericsTests.generic_factories
GenericsTests.generic_functions_should_be_memory_safe
GenericsTests.generic_table_method
GenericsTests.generic_type_pack_parentheses
@ -140,7 +134,6 @@ NonstrictModeTests.parameters_having_type_any_are_optional
NonstrictModeTests.table_dot_insert_and_recursive_calls
NonstrictModeTests.table_props_are_any
Normalize.cyclic_table_normalizes_sensibly
Normalize.negations_of_classes
ParseErrorRecovery.generic_type_list_recovery
ParseErrorRecovery.recovery_of_parenthesized_expressions
ParserTests.parse_nesting_based_end_detection_failsafe_earlier
@ -160,16 +153,13 @@ ProvisionalTests.specialization_binds_with_prototypes_too_early
ProvisionalTests.table_insert_with_a_singleton_argument
ProvisionalTests.typeguard_inference_incomplete
ProvisionalTests.weirditer_should_not_loop_forever
ProvisionalTests.while_body_are_also_refined
RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string
RefinementTest.call_an_incompatible_function_after_using_typeguard
RefinementTest.correctly_lookup_property_whose_base_was_previously_refined2
RefinementTest.discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false
RefinementTest.discriminate_tag
RefinementTest.eliminate_subclasses_of_instance
RefinementTest.else_with_no_explicit_expression_should_also_refine_the_tagged_union
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
RefinementTest.narrow_from_subclasses_of_instance_or_string_or_vector3
RefinementTest.narrow_property_of_a_bounded_variable
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
RefinementTest.refine_a_property_not_to_be_nil_through_an_intersection_table
@ -179,7 +169,6 @@ RefinementTest.type_guard_narrowed_into_nothingness
RefinementTest.type_narrow_for_all_the_userdata
RefinementTest.type_narrow_to_vector
RefinementTest.typeguard_cast_free_table_to_vector
RefinementTest.typeguard_cast_instance_or_vector3_to_vector
RefinementTest.typeguard_in_assert_position
RefinementTest.typeguard_narrows_for_table
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
@ -192,8 +181,6 @@ TableTests.accidentally_checked_prop_in_opposite_branch
TableTests.builtin_table_names
TableTests.call_method
TableTests.call_method_with_explicit_self_argument
TableTests.cannot_augment_sealed_table
TableTests.casting_sealed_tables_with_props_into_table_with_indexer
TableTests.casting_tables_with_props_into_table_with_indexer3
TableTests.casting_tables_with_props_into_table_with_indexer4
TableTests.checked_prop_too_early
@ -218,12 +205,10 @@ TableTests.function_calls_produces_sealed_table_given_unsealed_table
TableTests.generic_table_instantiation_potential_regression
TableTests.getmetatable_returns_pointer_to_metatable
TableTests.give_up_after_one_metatable_index_look_up
TableTests.hide_table_error_properties
TableTests.indexer_on_sealed_table_must_unify_with_free_table
TableTests.indexing_from_a_table_should_prefer_properties_when_possible
TableTests.inequality_operators_imply_exactly_matching_types
TableTests.infer_array_2
TableTests.infer_indexer_from_value_property_in_literal
TableTests.inferred_return_type_of_free_table
TableTests.inferring_crazy_table_should_also_be_quick
TableTests.instantiate_table_cloning_3
@ -243,7 +228,6 @@ TableTests.only_ascribe_synthetic_names_at_module_scope
TableTests.oop_indexer_works
TableTests.oop_polymorphic
TableTests.open_table_unification_2
TableTests.persistent_sealed_table_is_immutable
TableTests.property_lookup_through_tabletypevar_metatable
TableTests.quantify_even_that_table_was_never_exported_at_all
TableTests.quantify_metatables_of_metatables_of_table
@ -252,7 +236,6 @@ TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_
TableTests.result_is_always_any_if_lhs_is_any
TableTests.result_is_bool_for_equality_operators_if_lhs_is_any
TableTests.right_table_missing_key2
TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type
TableTests.shared_selfs
TableTests.shared_selfs_from_free_param
TableTests.shared_selfs_through_metatables
@ -261,7 +244,6 @@ TableTests.table_function_check_use_after_free
TableTests.table_indexing_error_location
TableTests.table_insert_should_cope_with_optional_properties_in_nonstrict
TableTests.table_insert_should_cope_with_optional_properties_in_strict
TableTests.table_param_row_polymorphism_2
TableTests.table_param_row_polymorphism_3
TableTests.table_simple_call
TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors
@ -276,12 +258,10 @@ TableTests.used_colon_correctly
TableTests.used_colon_instead_of_dot
TableTests.used_dot_instead_of_colon
TableTests.used_dot_instead_of_colon_but_correctly
ToDot.bound_table
ToDot.function
ToDot.table
ToString.exhaustive_toString_of_cyclic_table
ToString.function_type_with_argument_names_and_self
ToString.function_type_with_argument_names_generic
ToString.named_metatable_toStringNamedFunction
ToString.toStringDetailed2
ToString.toStringErrorPack
ToString.toStringNamedFunction_generic_pack
@ -297,10 +277,12 @@ TryUnifyTests.result_of_failed_typepack_unification_is_constrained
TryUnifyTests.typepack_unification_should_trim_free_tails
TryUnifyTests.variadics_should_use_reversed_properly
TypeAliases.cannot_create_cyclic_type_with_unknown_module
TypeAliases.corecursive_types_generic
TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any
TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any_2
TypeAliases.generic_param_remap
TypeAliases.mismatched_generic_type_param
TypeAliases.mutually_recursive_types_errors
TypeAliases.mutually_recursive_types_restriction_not_ok_1
TypeAliases.mutually_recursive_types_restriction_not_ok_2
TypeAliases.mutually_recursive_types_swapsies_not_ok
@ -308,8 +290,6 @@ TypeAliases.recursive_types_restriction_not_ok
TypeAliases.report_shadowed_aliases
TypeAliases.stringify_optional_parameterized_alias
TypeAliases.stringify_type_alias_of_recursive_template_table_type
TypeAliases.stringify_type_alias_of_recursive_template_table_type2
TypeAliases.type_alias_fwd_declaration_is_precise
TypeAliases.type_alias_local_mutation
TypeAliases.type_alias_local_rename
TypeAliases.type_alias_of_an_imported_recursive_generic_type
@ -337,15 +317,12 @@ TypeInferAnyError.metatable_of_any_can_be_a_table
TypeInferClasses.can_read_prop_of_base_class_using_string
TypeInferClasses.class_type_mismatch_with_name_conflict
TypeInferClasses.classes_without_overloaded_operators_cannot_be_added
TypeInferClasses.detailed_class_unification_error
TypeInferClasses.higher_order_function_arguments_are_contravariant
TypeInferClasses.index_instance_property
TypeInferClasses.optional_class_field_access_error
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties
TypeInferClasses.warn_when_prop_almost_matches
TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class
TypeInferFunctions.calling_function_with_anytypepack_doesnt_leak_free_types
TypeInferFunctions.calling_function_with_incorrect_argument_type_yields_errors_spanning_argument
TypeInferFunctions.cannot_hoist_interior_defns_into_signature
TypeInferFunctions.dont_give_other_overloads_message_if_only_one_argument_matching_overload_exists
TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site
@ -374,9 +351,7 @@ TypeInferFunctions.return_type_by_overload
TypeInferFunctions.too_few_arguments_variadic
TypeInferFunctions.too_few_arguments_variadic_generic
TypeInferFunctions.too_few_arguments_variadic_generic2
TypeInferFunctions.too_many_arguments
TypeInferFunctions.too_many_arguments_error_location
TypeInferFunctions.too_many_return_values
TypeInferFunctions.too_many_return_values_in_parentheses
TypeInferFunctions.too_many_return_values_no_function
TypeInferFunctions.vararg_function_is_quantified
@ -399,8 +374,6 @@ TypeInferModules.module_type_conflict_instantiated
TypeInferModules.require_a_variadic_function
TypeInferModules.type_error_of_unknown_qualified_type
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_another_overload_works
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon
TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
TypeInferOOP.method_depends_on_table
TypeInferOOP.methods_are_topologically_sorted
@ -461,10 +434,7 @@ TypePackTests.type_pack_type_parameters
TypePackTests.unify_variadic_tails_in_arguments
TypePackTests.unify_variadic_tails_in_arguments_free
TypePackTests.variadic_packs
TypeReductionTests.discriminable_unions
TypeReductionTests.intersections_with_negations
TypeReductionTests.negations
TypeReductionTests.unions_with_negations
TypeSingletons.error_detailed_tagged_union_mismatch_bool
TypeSingletons.error_detailed_tagged_union_mismatch_string
TypeSingletons.function_call_with_singletons