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*> 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); AstNode* findNodeAtPosition(const SourceModule& source, Position pos);
AstExpr* findExprAtPosition(const SourceModule& source, Position pos); AstExpr* findExprAtPosition(const SourceModule& source, Position pos);
ScopePtr findScopeAtPosition(const Module& module, Position pos); ScopePtr findScopeAtPosition(const Module& module, Position pos);

View file

@ -240,20 +240,28 @@ struct ConstraintGraphBuilder
* Resolves a type from its AST annotation. * Resolves a type from its AST annotation.
* @param scope the scope that the type annotation appears within. * @param scope the scope that the type annotation appears within.
* @param ty the AST annotation to resolve. * @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. * @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. * Resolves a type pack from its AST annotation.
* @param scope the scope that the type annotation appears within. * @param scope the scope that the type annotation appears within.
* @param tp the AST annotation to resolve. * @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. * @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, GenericTypeDefinition>> createGenerics(const ScopePtr& scope, AstArray<AstGenericType> generics);
std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks(const ScopePtr& scope, AstArray<AstGenericTypePack> packs); 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 FunctionCallConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const PrimitiveTypeConstraint& 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 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); bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint);
// for a, ... in some_table do // 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> exportedTypeBindings;
std::unordered_map<Name, TypeFun> privateTypeBindings; std::unordered_map<Name, TypeFun> privateTypeBindings;
std::unordered_map<Name, Location> typeAliasLocations; 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; std::unordered_map<Name, std::unordered_map<Name, TypeFun>> importedTypeBindings;
DenseHashSet<Name> builtinTypeNames{""}; 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 // 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 // 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(TypeId ty);
std::string dump(const std::optional<TypeId>& ty);
std::string dump(TypePackId ty); std::string dump(TypePackId ty);
std::string dump(const std::optional<TypePackId>& ty);
std::string dump(const Constraint& c); std::string dump(const Constraint& c);
std::string dump(const std::shared_ptr<Scope>& scope, const char* name); std::string dump(const std::shared_ptr<Scope>& scope, const char* name);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,6 +16,7 @@ LUAU_FASTFLAG(DebugLuauLogSolverToJson);
LUAU_FASTFLAG(DebugLuauMagicTypes); LUAU_FASTFLAG(DebugLuauMagicTypes);
LUAU_FASTFLAG(LuauNegatedClassTypes); LUAU_FASTFLAG(LuauNegatedClassTypes);
LUAU_FASTFLAG(LuauScopelessModule); LUAU_FASTFLAG(LuauScopelessModule);
LUAU_FASTFLAG(SupportTypeAliasGoToDeclaration);
namespace Luau namespace Luau
{ {
@ -418,7 +419,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
TypeId ty = nullptr; TypeId ty = nullptr;
if (local->annotation) if (local->annotation)
ty = resolveType(scope, local->annotation, /* topLevel */ true); ty = resolveType(scope, local->annotation, /* inTypeArguments */ false);
varTypes.push_back(ty); 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}; const Name name{local->vars.data[i]->name.value};
if (ModulePtr module = moduleResolver->getModule(moduleInfo->name)) if (ModulePtr module = moduleResolver->getModule(moduleInfo->name))
{
scope->importedTypeBindings[name] = scope->importedTypeBindings[name] =
FFlag::LuauScopelessModule ? module->exportedTypeBindings : module->getModuleScope()->exportedTypeBindings; 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; ScopePtr resolvingScope = *defnIt;
TypeId ty = resolveType(resolvingScope, alias->type, /* topLevel */ true); TypeId ty = resolveType(resolvingScope, alias->type, /* inTypeArguments */ false);
if (alias->exported) if (alias->exported)
{ {
@ -798,7 +803,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareGlobal*
{ {
LUAU_ASSERT(global->type); LUAU_ASSERT(global->type);
TypeId globalTy = resolveType(scope, global->type); TypeId globalTy = resolveType(scope, global->type, /* inTypeArguments */ false);
Name globalName(global->name.value); Name globalName(global->name.value);
module->declaredGlobals[globalName] = globalTy; module->declaredGlobals[globalName] = globalTy;
@ -854,7 +859,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareClass* d
for (const AstDeclaredClassProp& prop : declaredClass->props) for (const AstDeclaredClassProp& prop : declaredClass->props)
{ {
Name propName(prop.name.value); Name propName(prop.name.value);
TypeId propTy = resolveType(scope, prop.ty); TypeId propTy = resolveType(scope, prop.ty, /* inTypeArguments */ false);
bool assignToMetatable = isMetamethod(propName); bool assignToMetatable = isMetamethod(propName);
@ -937,8 +942,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareFunction
if (!generics.empty() || !genericPacks.empty()) if (!generics.empty() || !genericPacks.empty())
funScope = childScope(global, scope); funScope = childScope(global, scope);
TypePackId paramPack = resolveTypePack(funScope, global->params); TypePackId paramPack = resolveTypePack(funScope, global->params, /* inTypeArguments */ false);
TypePackId retPack = resolveTypePack(funScope, global->retTypes); TypePackId retPack = resolveTypePack(funScope, global->retTypes, /* inTypeArguments */ false);
TypeId fnType = arena->addType(FunctionType{TypeLevel{}, funScope.get(), std::move(genericTys), std::move(genericTps), paramPack, retPack}); TypeId fnType = arena->addType(FunctionType{TypeLevel{}, funScope.get(), std::move(genericTys), std::move(genericTps), paramPack, retPack});
FunctionType* ftv = getMutable<FunctionType>(fnType); 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) Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert)
{ {
check(scope, typeAssert->expr, std::nullopt); 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( std::tuple<TypeId, TypeId, ConnectiveId> ConstraintGraphBuilder::checkBinary(
@ -1563,7 +1568,7 @@ std::tuple<TypeId, TypeId, ConnectiveId> ConstraintGraphBuilder::checkBinary(
TypeId ty = follow(typeFun->type); TypeId ty = follow(typeFun->type);
// We're only interested in the root class of any classes. // 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; discriminantTy = ty;
} }
@ -1618,39 +1623,6 @@ TypePackId ConstraintGraphBuilder::checkLValues(const ScopePtr& scope, AstArray<
return arena->addTypePack(std::move(types)); 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. * 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>()) else if (!expr->is<AstExprIndexName>())
return check(scope, expr).ty; return check(scope, expr).ty;
auto dottedPath = extractDottedName(expr); Symbol sym;
if (!dottedPath) std::vector<std::string> segments;
return check(scope, expr).ty; std::vector<AstExpr*> exprs;
const auto [sym, segments] = std::move(*dottedPath);
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()); LUAU_ASSERT(!segments.empty());
std::reverse(begin(segments), end(segments));
std::reverse(begin(exprs), end(exprs));
auto lookupResult = scope->lookupEx(sym); auto lookupResult = scope->lookupEx(sym);
if (!lookupResult) if (!lookupResult)
return check(scope, expr).ty; return check(scope, expr).ty;
@ -1695,7 +1692,18 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
symbolScope->bindings[sym].typeId = updatedType; symbolScope->bindings[sym].typeId = updatedType;
symbolScope->dcrRefinements[*def] = 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; return propTy;
} }
@ -1845,7 +1853,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
if (local->annotation) 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}); addConstraint(signatureScope, local->annotation->location, SubtypeConstraint{t, annotationTy});
} }
else if (i < expectedArgPack.head.size()) else if (i < expectedArgPack.head.size())
@ -1866,7 +1874,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
{ {
if (fn->varargAnnotation) if (fn->varargAnnotation)
{ {
TypePackId annotationType = resolveTypePack(signatureScope, fn->varargAnnotation); TypePackId annotationType = resolveTypePack(signatureScope, fn->varargAnnotation, /* inTypeArguments */ false);
varargPack = annotationType; varargPack = annotationType;
} }
else if (expectedArgPack.tail && get<VariadicTypePack>(*expectedArgPack.tail)) else if (expectedArgPack.tail && get<VariadicTypePack>(*expectedArgPack.tail))
@ -1893,7 +1901,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
// Type checking will sort out any discrepancies later. // Type checking will sort out any discrepancies later.
if (fn->returnAnnotation) 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 // We bind the annotated type directly here so that, when we need to
// generate constraints for return types, we have a guarantee that we // 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; TypeId result = nullptr;
@ -1960,7 +1968,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
return builtinTypes->errorRecoveryType(); return builtinTypes->errorRecoveryType();
} }
else 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. // that is done in the parser.
if (p.type) if (p.type)
{ {
parameters.push_back(resolveType(scope, p.type)); parameters.push_back(resolveType(scope, p.type, /* inTypeArguments */ true));
} }
else if (p.typePack) else if (p.typePack)
{ {
packParameters.push_back(resolveTypePack(scope, p.typePack)); packParameters.push_back(resolveTypePack(scope, p.typePack, /* inTypeArguments */ true));
} }
else else
{ {
@ -2010,10 +2018,11 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
result = arena->addType(PendingExpansionType{ref->prefix, ref->name, parameters, packParameters}); 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}); addConstraint(scope, ty->location, TypeAliasExpansionConstraint{/* target */ result});
}
} }
} }
else else
@ -2035,7 +2044,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
{ {
std::string name = prop.name.value; std::string name = prop.name.value;
// TODO: Recursion limit. // TODO: Recursion limit.
TypeId propTy = resolveType(scope, prop.type); TypeId propTy = resolveType(scope, prop.type, inTypeArguments);
// TODO: Fill in location. // TODO: Fill in location.
props[name] = {propTy}; props[name] = {propTy};
} }
@ -2044,8 +2053,8 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
{ {
// TODO: Recursion limit. // TODO: Recursion limit.
indexer = TableIndexer{ indexer = TableIndexer{
resolveType(scope, tab->indexer->indexType), resolveType(scope, tab->indexer->indexType, inTypeArguments),
resolveType(scope, tab->indexer->resultType), resolveType(scope, tab->indexer->resultType, inTypeArguments),
}; };
} }
@ -2089,8 +2098,8 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
signatureScope = scope; signatureScope = scope;
} }
TypePackId argTypes = resolveTypePack(signatureScope, fn->argTypes); TypePackId argTypes = resolveTypePack(signatureScope, fn->argTypes, inTypeArguments);
TypePackId returnTypes = resolveTypePack(signatureScope, fn->returnTypes); TypePackId returnTypes = resolveTypePack(signatureScope, fn->returnTypes, inTypeArguments);
// TODO: FunctionType needs a pointer to the scope so that we know // TODO: FunctionType needs a pointer to the scope so that we know
// how to quantify/instantiate it. // how to quantify/instantiate it.
@ -2130,7 +2139,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
for (AstType* part : unionAnnotation->types) for (AstType* part : unionAnnotation->types)
{ {
// TODO: Recursion limit. // TODO: Recursion limit.
parts.push_back(resolveType(scope, part, topLevel)); parts.push_back(resolveType(scope, part, inTypeArguments));
} }
result = arena->addType(UnionType{parts}); result = arena->addType(UnionType{parts});
@ -2141,7 +2150,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
for (AstType* part : intersectionAnnotation->types) for (AstType* part : intersectionAnnotation->types)
{ {
// TODO: Recursion limit. // TODO: Recursion limit.
parts.push_back(resolveType(scope, part, topLevel)); parts.push_back(resolveType(scope, part, inTypeArguments));
} }
result = arena->addType(IntersectionType{parts}); result = arena->addType(IntersectionType{parts});
@ -2168,16 +2177,16 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
return result; return result;
} }
TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTypePack* tp) TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTypePack* tp, bool inTypeArgument)
{ {
TypePackId result; TypePackId result;
if (auto expl = tp->as<AstTypePackExplicit>()) if (auto expl = tp->as<AstTypePackExplicit>())
{ {
result = resolveTypePack(scope, expl->typeList); result = resolveTypePack(scope, expl->typeList, inTypeArgument);
} }
else if (auto var = tp->as<AstTypePackVariadic>()) 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}}); result = arena->addTypePack(TypePackVar{VariadicTypePack{ty}});
} }
else if (auto gen = tp->as<AstTypePackGeneric>()) else if (auto gen = tp->as<AstTypePackGeneric>())
@ -2202,19 +2211,19 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTyp
return result; 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; std::vector<TypeId> head;
for (AstType* headTy : list.types) for (AstType* headTy : list.types)
{ {
head.push_back(resolveType(scope, headTy)); head.push_back(resolveType(scope, headTy, inTypeArguments));
} }
std::optional<TypePackId> tail = std::nullopt; std::optional<TypePackId> tail = std::nullopt;
if (list.tailType) if (list.tailType)
{ {
tail = resolveTypePack(scope, list.tailType); tail = resolveTypePack(scope, list.tailType, inTypeArguments);
} }
return arena->addTypePack(TypePack{head, tail}); return arena->addTypePack(TypePack{head, tail});
@ -2229,7 +2238,7 @@ std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGraphBuilder::crea
std::optional<TypeId> defaultTy = std::nullopt; std::optional<TypeId> defaultTy = std::nullopt;
if (generic.defaultValue) if (generic.defaultValue)
defaultTy = resolveType(scope, generic.defaultValue); defaultTy = resolveType(scope, generic.defaultValue, /* inTypeArguments */ false);
result.push_back({generic.name.value, GenericTypeDefinition{genericTy, defaultTy}}); 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; std::optional<TypePackId> defaultTy = std::nullopt;
if (generic.defaultValue) if (generic.defaultValue)
defaultTy = resolveTypePack(scope, generic.defaultValue); defaultTy = resolveTypePack(scope, generic.defaultValue, /* inTypeArguments */ false);
result.push_back({generic.name.value, GenericTypePackDefinition{genericTy, defaultTy}}); 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)) else if (auto hpc = get<HasPropConstraint>(*constraint))
success = tryDispatch(*hpc, constraint); success = tryDispatch(*hpc, constraint);
else if (auto spc = get<SetPropConstraint>(*constraint)) else if (auto spc = get<SetPropConstraint>(*constraint))
success = tryDispatch(*spc, constraint); success = tryDispatch(*spc, constraint, force);
else if (auto sottc = get<SingletonOrTopTypeConstraint>(*constraint)) else if (auto sottc = get<SingletonOrTopTypeConstraint>(*constraint))
success = tryDispatch(*sottc, constraint); success = tryDispatch(*sottc, constraint);
else else
@ -933,13 +933,11 @@ struct InfiniteTypeFinder : TypeOnceVisitor
struct InstantiationQueuer : TypeOnceVisitor struct InstantiationQueuer : TypeOnceVisitor
{ {
ConstraintSolver* solver; ConstraintSolver* solver;
const InstantiationSignature& signature;
NotNull<Scope> scope; NotNull<Scope> scope;
Location location; 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) : solver(solver)
, signature(signature)
, scope(scope) , scope(scope)
, location(location) , location(location)
{ {
@ -1061,8 +1059,17 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
TypeId instantiated = *maybeInstantiated; TypeId instantiated = *maybeInstantiated;
TypeId target = follow(instantiated); 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) if (target->persistent)
{
bindResult(target);
return true; return true;
}
// Type function application will happily give us the exact same type if // Type function application will happily give us the exact same type if
// there are e.g. generic saturatedTypeArguments that go unused. // there are e.g. generic saturatedTypeArguments that go unused.
@ -1102,12 +1109,6 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
bindResult(target); 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; instantiatedAliases[signature] = target;
return true; return true;
@ -1326,13 +1327,16 @@ static std::optional<TypeId> updateTheTableType(NotNull<TypeArena> arena, TypeId
return res; 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); TypeId subjectType = follow(c.subjectType);
if (isBlocked(subjectType)) if (isBlocked(subjectType))
return block(subjectType, constraint); return block(subjectType, constraint);
if (!force && get<FreeType>(subjectType))
return block(subjectType, constraint);
std::optional<TypeId> existingPropType = subjectType; std::optional<TypeId> existingPropType = subjectType;
for (const std::string& segment : c.path) for (const std::string& segment : c.path)
{ {
@ -1399,6 +1403,13 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
return true; 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)) else if (get<AnyType>(subjectType) || get<ErrorType>(subjectType))
{ {
bind(c.resultType, subjectType); bind(c.resultType, subjectType);

View file

@ -13,7 +13,6 @@
#include <limits.h> #include <limits.h>
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4) LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
LUAU_FASTFLAGVARIABLE(LuauLintGlobalNeverReadBeforeWritten, false)
namespace Luau 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", "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); g.firstRef->name.value, top->location.begin.line + 1);
} }
else if (FFlag::LuauLintGlobalNeverReadBeforeWritten && g.assigned && !g.readBeforeWritten && !g.definedInModuleScope && else if (g.assigned && !g.readBeforeWritten && !g.definedInModuleScope && g.firstRef->name != context->placeholder)
g.firstRef->name != context->placeholder)
{ {
emitWarning(*context, LintWarning::Code_GlobalUsedAsLocal, g.firstRef->location, 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); "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 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]; Global& g = globals[node->name];
g.readBeforeWritten = true; g.readBeforeWritten = true;
@ -386,18 +384,15 @@ private:
{ {
Global& g = globals[gv->name]; 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; functionStack.back().dominatedGlobals.insert(gv->name);
}
else
{
if (!functionStack.back().conditionalExecution)
{
functionStack.back().dominatedGlobals.insert(gv->name);
}
} }
} }
@ -437,11 +432,8 @@ private:
else else
{ {
g.assigned = true; g.assigned = true;
if (FFlag::LuauLintGlobalNeverReadBeforeWritten) g.definedAsFunction = true;
{ g.definedInModuleScope = functionStack.empty();
g.definedAsFunction = true;
g.definedInModuleScope = functionStack.empty();
}
} }
trackGlobalRef(gv); trackGlobalRef(gv);
@ -475,9 +467,6 @@ private:
bool visit(AstStatIf* node) override bool visit(AstStatIf* node) override
{ {
if (!FFlag::LuauLintGlobalNeverReadBeforeWritten)
return true;
HoldConditionalExecution ce(*this); HoldConditionalExecution ce(*this);
node->condition->visit(this); node->condition->visit(this);
node->thenbody->visit(this); node->thenbody->visit(this);
@ -489,9 +478,6 @@ private:
bool visit(AstStatWhile* node) override bool visit(AstStatWhile* node) override
{ {
if (!FFlag::LuauLintGlobalNeverReadBeforeWritten)
return true;
HoldConditionalExecution ce(*this); HoldConditionalExecution ce(*this);
node->condition->visit(this); node->condition->visit(this);
node->body->visit(this); node->body->visit(this);
@ -501,9 +487,6 @@ private:
bool visit(AstStatRepeat* node) override bool visit(AstStatRepeat* node) override
{ {
if (!FFlag::LuauLintGlobalNeverReadBeforeWritten)
return true;
HoldConditionalExecution ce(*this); HoldConditionalExecution ce(*this);
node->condition->visit(this); node->condition->visit(this);
node->body->visit(this); node->body->visit(this);
@ -513,9 +496,6 @@ private:
bool visit(AstStatFor* node) override bool visit(AstStatFor* node) override
{ {
if (!FFlag::LuauLintGlobalNeverReadBeforeWritten)
return true;
HoldConditionalExecution ce(*this); HoldConditionalExecution ce(*this);
node->from->visit(this); node->from->visit(this);
node->to->visit(this); node->to->visit(this);
@ -530,9 +510,6 @@ private:
bool visit(AstStatForIn* node) override bool visit(AstStatForIn* node) override
{ {
if (!FFlag::LuauLintGlobalNeverReadBeforeWritten)
return true;
HoldConditionalExecution ce(*this); HoldConditionalExecution ce(*this);
for (AstExpr* expr : node->values) for (AstExpr* expr : node->values)
expr->visit(this); expr->visit(this);

View file

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

View file

@ -157,6 +157,8 @@ struct PureQuantifier : Substitution
Scope* scope; Scope* scope;
std::vector<TypeId> insertedGenerics; std::vector<TypeId> insertedGenerics;
std::vector<TypePackId> insertedGenericPacks; std::vector<TypePackId> insertedGenericPacks;
bool seenMutableType = false;
bool seenGenericType = false;
PureQuantifier(TypeArena* arena, Scope* scope) PureQuantifier(TypeArena* arena, Scope* scope)
: Substitution(TxnLog::empty(), arena) : Substitution(TxnLog::empty(), arena)
@ -170,11 +172,18 @@ struct PureQuantifier : Substitution
if (auto ftv = get<FreeType>(ty)) 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)) 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; return false;
@ -207,7 +216,11 @@ struct PureQuantifier : Substitution
*resultTable = *ttv; *resultTable = *ttv;
resultTable->level = TypeLevel{}; resultTable->level = TypeLevel{};
resultTable->scope = scope; 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; return result;
} }
@ -251,7 +264,7 @@ TypeId quantify(TypeArena* arena, TypeId ty, Scope* scope)
ftv->scope = scope; ftv->scope = scope;
ftv->generics.insert(ftv->generics.end(), quantifier.insertedGenerics.begin(), quantifier.insertedGenerics.end()); 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->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; return *result;
} }

View file

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

View file

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

View file

@ -4,6 +4,7 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/AstQuery.h" #include "Luau/AstQuery.h"
#include "Luau/Clone.h" #include "Luau/Clone.h"
#include "Luau/Error.h"
#include "Luau/Instantiation.h" #include "Luau/Instantiation.h"
#include "Luau/Metamethods.h" #include "Luau/Metamethods.h"
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
@ -212,6 +213,12 @@ struct TypeChecker2
return bestScope; return bestScope;
} }
enum ValueContext
{
LValue,
RValue
};
void visit(AstStat* stat) void visit(AstStat* stat)
{ {
auto pusher = pushStack(stat); auto pusher = pushStack(stat);
@ -273,7 +280,7 @@ struct TypeChecker2
void visit(AstStatIf* ifStatement) void visit(AstStatIf* ifStatement)
{ {
visit(ifStatement->condition); visit(ifStatement->condition, RValue);
visit(ifStatement->thenbody); visit(ifStatement->thenbody);
if (ifStatement->elsebody) if (ifStatement->elsebody)
visit(ifStatement->elsebody); visit(ifStatement->elsebody);
@ -281,14 +288,14 @@ struct TypeChecker2
void visit(AstStatWhile* whileStatement) void visit(AstStatWhile* whileStatement)
{ {
visit(whileStatement->condition); visit(whileStatement->condition, RValue);
visit(whileStatement->body); visit(whileStatement->body);
} }
void visit(AstStatRepeat* repeatStatement) void visit(AstStatRepeat* repeatStatement)
{ {
visit(repeatStatement->body); visit(repeatStatement->body);
visit(repeatStatement->condition); visit(repeatStatement->condition, RValue);
} }
void visit(AstStatBreak*) {} void visit(AstStatBreak*) {}
@ -315,12 +322,12 @@ struct TypeChecker2
} }
for (AstExpr* expr : ret->list) for (AstExpr* expr : ret->list)
visit(expr); visit(expr, RValue);
} }
void visit(AstStatExpr* expr) void visit(AstStatExpr* expr)
{ {
visit(expr->expr); visit(expr->expr, RValue);
} }
void visit(AstStatLocal* local) void visit(AstStatLocal* local)
@ -331,7 +338,7 @@ struct TypeChecker2
AstExpr* value = i < local->values.size ? local->values.data[i] : nullptr; AstExpr* value = i < local->values.size ? local->values.data[i] : nullptr;
if (value) if (value)
visit(value); visit(value, RValue);
TypeId* maybeValueType = value ? module->astTypes.find(value) : nullptr; TypeId* maybeValueType = value ? module->astTypes.find(value) : nullptr;
if (i != local->values.size - 1 || maybeValueType) if (i != local->values.size - 1 || maybeValueType)
@ -387,10 +394,10 @@ struct TypeChecker2
if (forStatement->var->annotation) if (forStatement->var->annotation)
visit(forStatement->var->annotation); visit(forStatement->var->annotation);
visit(forStatement->from); visit(forStatement->from, RValue);
visit(forStatement->to); visit(forStatement->to, RValue);
if (forStatement->step) if (forStatement->step)
visit(forStatement->step); visit(forStatement->step, RValue);
visit(forStatement->body); visit(forStatement->body);
} }
@ -403,7 +410,7 @@ struct TypeChecker2
} }
for (AstExpr* expr : forInStatement->values) for (AstExpr* expr : forInStatement->values)
visit(expr); visit(expr, RValue);
visit(forInStatement->body); visit(forInStatement->body);
@ -610,11 +617,11 @@ struct TypeChecker2
for (size_t i = 0; i < count; ++i) for (size_t i = 0; i < count; ++i)
{ {
AstExpr* lhs = assign->vars.data[i]; AstExpr* lhs = assign->vars.data[i];
visit(lhs); visit(lhs, LValue);
TypeId lhsType = lookupType(lhs); TypeId lhsType = lookupType(lhs);
AstExpr* rhs = assign->values.data[i]; AstExpr* rhs = assign->values.data[i];
visit(rhs); visit(rhs, RValue);
TypeId rhsType = lookupType(rhs); TypeId rhsType = lookupType(rhs);
if (!isSubtype(rhsType, lhsType, stack.back())) if (!isSubtype(rhsType, lhsType, stack.back()))
@ -635,7 +642,7 @@ struct TypeChecker2
void visit(AstStatFunction* stat) void visit(AstStatFunction* stat)
{ {
visit(stat->name); visit(stat->name, LValue);
visit(stat->func); visit(stat->func);
} }
@ -698,13 +705,13 @@ struct TypeChecker2
void visit(AstStatError* stat) void visit(AstStatError* stat)
{ {
for (AstExpr* expr : stat->expressions) for (AstExpr* expr : stat->expressions)
visit(expr); visit(expr, RValue);
for (AstStat* s : stat->statements) for (AstStat* s : stat->statements)
visit(s); visit(s);
} }
void visit(AstExpr* expr) void visit(AstExpr* expr, ValueContext context)
{ {
auto StackPusher = pushStack(expr); auto StackPusher = pushStack(expr);
@ -712,7 +719,7 @@ struct TypeChecker2
{ {
} }
else if (auto e = expr->as<AstExprGroup>()) else if (auto e = expr->as<AstExprGroup>())
return visit(e); return visit(e, context);
else if (auto e = expr->as<AstExprConstantNil>()) else if (auto e = expr->as<AstExprConstantNil>())
return visit(e); return visit(e);
else if (auto e = expr->as<AstExprConstantBool>()) else if (auto e = expr->as<AstExprConstantBool>())
@ -730,9 +737,9 @@ struct TypeChecker2
else if (auto e = expr->as<AstExprCall>()) else if (auto e = expr->as<AstExprCall>())
return visit(e); return visit(e);
else if (auto e = expr->as<AstExprIndexName>()) else if (auto e = expr->as<AstExprIndexName>())
return visit(e); return visit(e, context);
else if (auto e = expr->as<AstExprIndexExpr>()) else if (auto e = expr->as<AstExprIndexExpr>())
return visit(e); return visit(e, context);
else if (auto e = expr->as<AstExprFunction>()) else if (auto e = expr->as<AstExprFunction>())
return visit(e); return visit(e);
else if (auto e = expr->as<AstExprTable>()) else if (auto e = expr->as<AstExprTable>())
@ -754,9 +761,9 @@ struct TypeChecker2
LUAU_ASSERT(!"TypeChecker2 encountered an unknown expression type"); 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) void visit(AstExprConstantNil* expr)
@ -808,10 +815,10 @@ struct TypeChecker2
void visit(AstExprCall* call) void visit(AstExprCall* call)
{ {
visit(call->func); visit(call->func, RValue);
for (AstExpr* arg : call->args) for (AstExpr* arg : call->args)
visit(arg); visit(arg, RValue);
TypeArena* arena = &testArena; TypeArena* arena = &testArena;
Instantiation instantiation{TxnLog::empty(), arena, TypeLevel{}, stack.back()}; Instantiation instantiation{TxnLog::empty(), arena, TypeLevel{}, stack.back()};
@ -820,6 +827,8 @@ struct TypeChecker2
TypeId functionType = lookupType(call->func); TypeId functionType = lookupType(call->func);
TypeId testFunctionType = functionType; TypeId testFunctionType = functionType;
TypePack args; TypePack args;
std::vector<Location> argLocs;
argLocs.reserve(call->args.size + 1);
if (get<AnyType>(functionType) || get<ErrorType>(functionType)) if (get<AnyType>(functionType) || get<ErrorType>(functionType))
return; return;
@ -830,6 +839,7 @@ struct TypeChecker2
if (std::optional<TypeId> instantiatedCallMm = instantiation.substitute(*callMm)) if (std::optional<TypeId> instantiatedCallMm = instantiation.substitute(*callMm))
{ {
args.head.push_back(functionType); args.head.push_back(functionType);
argLocs.push_back(call->func->location);
testFunctionType = follow(*instantiatedCallMm); testFunctionType = follow(*instantiatedCallMm);
} }
else else
@ -899,11 +909,13 @@ struct TypeChecker2
ice.ice("method call expression has no 'self'"); ice.ice("method call expression has no 'self'");
args.head.push_back(lookupType(indexExpr->expr)); args.head.push_back(lookupType(indexExpr->expr));
argLocs.push_back(indexExpr->expr->location);
} }
for (size_t i = 0; i < call->args.size; ++i) for (size_t i = 0; i < call->args.size; ++i)
{ {
AstExpr* arg = call->args.data[i]; AstExpr* arg = call->args.data[i];
argLocs.push_back(arg->location);
TypeId* argTy = module->astTypes.find(arg); TypeId* argTy = module->astTypes.find(arg);
if (argTy) if (argTy)
args.head.push_back(*argTy); args.head.push_back(*argTy);
@ -919,19 +931,34 @@ struct TypeChecker2
args.head.push_back(builtinTypes->anyType); args.head.push_back(builtinTypes->anyType);
} }
TypePackId argsTp = arena->addTypePack(args); TypePackId expectedArgTypes = arena->addTypePack(args);
FunctionType ftv{argsTp, expectedRetType};
TypeId expectedType = arena->addType(ftv);
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; Location argLoc = (argIndex >= argLocs.size()) ? argLocs.back() : argLocs[argIndex];
expectedType = clone(expectedType, testArena, cloneState); reportErrors(tryUnify(stack.back(), argLoc, *expectedArgIt, *inferredArgIt));
reportError(TypeMismatch{expectedType, functionType}, call->location);
++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); TypeId leftType = lookupType(indexName->expr);
@ -939,14 +966,14 @@ struct TypeChecker2
if (!norm) if (!norm)
reportError(NormalizationTooComplex{}, indexName->indexLocation); 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! // TODO!
visit(indexExpr->expr); visit(indexExpr->expr, LValue);
visit(indexExpr->index); visit(indexExpr->index, RValue);
} }
void visit(AstExprFunction* fn) void visit(AstExprFunction* fn)
@ -986,14 +1013,14 @@ struct TypeChecker2
for (const AstExprTable::Item& item : expr->items) for (const AstExprTable::Item& item : expr->items)
{ {
if (item.key) if (item.key)
visit(item.key); visit(item.key, LValue);
visit(item.value); visit(item.value, RValue);
} }
} }
void visit(AstExprUnary* expr) void visit(AstExprUnary* expr)
{ {
visit(expr->expr); visit(expr->expr, RValue);
NotNull<Scope> scope = stack.back(); NotNull<Scope> scope = stack.back();
TypeId operandType = lookupType(expr->expr); TypeId operandType = lookupType(expr->expr);
@ -1053,8 +1080,8 @@ struct TypeChecker2
TypeId visit(AstExprBinary* expr, void* overrideKey = nullptr) TypeId visit(AstExprBinary* expr, void* overrideKey = nullptr)
{ {
visit(expr->left); visit(expr->left, LValue);
visit(expr->right); visit(expr->right, LValue);
NotNull<Scope> scope = stack.back(); NotNull<Scope> scope = stack.back();
@ -1307,7 +1334,7 @@ struct TypeChecker2
void visit(AstExprTypeAssertion* expr) void visit(AstExprTypeAssertion* expr)
{ {
visit(expr->expr); visit(expr->expr, RValue);
visit(expr->annotation); visit(expr->annotation);
TypeId annotationType = lookupAnnotation(expr->annotation); TypeId annotationType = lookupAnnotation(expr->annotation);
@ -1326,16 +1353,16 @@ struct TypeChecker2
void visit(AstExprIfElse* expr) void visit(AstExprIfElse* expr)
{ {
// TODO! // TODO!
visit(expr->condition); visit(expr->condition, RValue);
visit(expr->trueExpr); visit(expr->trueExpr, RValue);
visit(expr->falseExpr); visit(expr->falseExpr, RValue);
} }
void visit(AstExprError* expr) void visit(AstExprError* expr)
{ {
// TODO! // TODO!
for (AstExpr* e : expr->expressions) for (AstExpr* e : expr->expressions)
visit(e); visit(e, RValue);
} }
/** Extract a TypeId for the first type of the provided pack. /** Extract a TypeId for the first type of the provided pack.
@ -1550,7 +1577,7 @@ struct TypeChecker2
void visit(AstTypeTypeof* ty) void visit(AstTypeTypeof* ty)
{ {
visit(ty->expr); visit(ty->expr, RValue);
} }
void visit(AstTypeUnion* ty) void visit(AstTypeUnion* ty)
@ -1630,9 +1657,10 @@ struct TypeChecker2
} }
template<typename TID> 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}; Unifier u{NotNull{&normalizer}, Mode::Strict, scope, location, Covariant};
u.ctx = context;
u.useScopes = true; u.useScopes = true;
u.tryUnify(subTy, superTy); u.tryUnify(subTy, superTy);
@ -1658,7 +1686,7 @@ struct TypeChecker2
reportError(std::move(e)); 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; bool foundOneProp = false;
std::vector<TypeId> typesMissingTheProp; std::vector<TypeId> typesMissingTheProp;
@ -1723,9 +1751,11 @@ struct TypeChecker2
if (!typesMissingTheProp.empty()) if (!typesMissingTheProp.empty())
{ {
if (foundOneProp) 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 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(LuauCheckRecursionLimit, 300)
LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500) LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauTypeNormalization2)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false. LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false) LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
LUAU_FASTFLAGVARIABLE(LuauUnknownAndNeverType, false) LUAU_FASTFLAGVARIABLE(LuauUnknownAndNeverType, false)
LUAU_FASTFLAGVARIABLE(LuauTypeInferMissingFollows, false)
LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false) LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false)
LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false) LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false)
LUAU_FASTFLAGVARIABLE(LuauScopelessModule, false) LUAU_FASTFLAGVARIABLE(LuauScopelessModule, false)
LUAU_FASTFLAGVARIABLE(LuauFollowInLvalueIndexCheck, false)
LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false) LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false)
LUAU_FASTFLAGVARIABLE(LuauTryhardAnd, false) LUAU_FASTFLAGVARIABLE(LuauTryhardAnd, false)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
@ -52,9 +49,8 @@ LUAU_FASTFLAGVARIABLE(LuauIntersectionTestForEquality, false)
LUAU_FASTFLAGVARIABLE(LuauImplicitElseRefinement, false) LUAU_FASTFLAGVARIABLE(LuauImplicitElseRefinement, false)
LUAU_FASTFLAG(LuauNegatedClassTypes) LUAU_FASTFLAG(LuauNegatedClassTypes)
LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false) LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false)
LUAU_FASTFLAGVARIABLE(LuauDeclareClassPrototype, false)
LUAU_FASTFLAG(LuauUninhabitedSubAnything2) LUAU_FASTFLAG(LuauUninhabitedSubAnything2)
LUAU_FASTFLAGVARIABLE(LuauCallableClasses, false) LUAU_FASTFLAG(SupportTypeAliasGoToDeclaration)
namespace Luau namespace Luau
{ {
@ -333,12 +329,9 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
prepareErrorsForDisplay(currentModule->errors); prepareErrorsForDisplay(currentModule->errors);
if (FFlag::LuauTypeNormalization2) // Clear the normalizer caches, since they contain types from the internal type surface
{ normalizer.clearCaches();
// Clear the normalizer caches, since they contain types from the internal type surface normalizer.arena = nullptr;
normalizer.clearCaches();
normalizer.arena = nullptr;
}
currentModule->clonePublicInterface(builtinTypes, *iceHandler); currentModule->clonePublicInterface(builtinTypes, *iceHandler);
@ -512,7 +505,7 @@ void TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, const A
prototype(scope, *typealias, subLevel); prototype(scope, *typealias, subLevel);
++subLevel; ++subLevel;
} }
else if (const auto& declaredClass = stat->as<AstStatDeclareClass>(); FFlag::LuauDeclareClassPrototype && declaredClass) else if (const auto& declaredClass = stat->as<AstStatDeclareClass>())
{ {
prototype(scope, *declaredClass); 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}; const Name name{local.vars.data[i]->name.value};
if (ModulePtr module = resolver->getModule(moduleInfo->name)) if (ModulePtr module = resolver->getModule(moduleInfo->name))
{
scope->importedTypeBindings[name] = scope->importedTypeBindings[name] =
FFlag::LuauScopelessModule ? module->exportedTypeBindings : module->getModuleScope()->exportedTypeBindings; 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 // In non-strict mode we force the module type on the variable, in strict mode it is already unified
if (isNonstrictMode()) if (isNonstrictMode())
@ -1622,6 +1619,8 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty}; bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
scope->typeAliasLocations[name] = typealias.location; scope->typeAliasLocations[name] = typealias.location;
if (FFlag::SupportTypeAliasGoToDeclaration)
scope->typeAliasNameLocations[name] = typealias.nameLocation;
} }
} }
else else
@ -1640,12 +1639,13 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty}; bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
scope->typeAliasLocations[name] = typealias.location; scope->typeAliasLocations[name] = typealias.location;
if (FFlag::SupportTypeAliasGoToDeclaration)
scope->typeAliasNameLocations[name] = typealias.nameLocation;
} }
} }
void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass) 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; std::optional<TypeId> superTy = FFlag::LuauNegatedClassTypes ? std::make_optional(builtinTypes->classType) : std::nullopt;
if (declaredClass.superName) 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) 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 bool assignToMetatable = isMetamethod(propName);
if (incorrectClassDefinitions.find(&declaredClass)) Luau::ClassType::Props& assignTo = assignToMetatable ? metatable->props : ctv->props;
return;
std::optional<TypeFun> binding; // Function types always take 'self', but this isn't reflected in the
if (auto it = scope->exportedTypeBindings.find(className); it != scope->exportedTypeBindings.end()) // parsed annotation. Add it here.
binding = it->second; if (prop.isMethod)
// 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 propName(prop.name.value); if (FunctionType* ftv = getMutable<FunctionType>(propTy))
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)) ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
{ ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes});
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}}); ftv->hasSelf = true;
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;
} }
} }
Name className(declaredClass.name.value); if (assignTo.count(propName) == 0)
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)
{ {
Name propName(prop.name.value); assignTo[propName] = {propTy};
TypeId propTy = resolveType(scope, *prop.ty); }
else
{
TypeId currentTy = assignTo[propName].type;
bool assignToMetatable = isMetamethod(propName); // We special-case this logic to keep the intersection flat; otherwise we
Luau::ClassType::Props& assignTo = assignToMetatable ? metatable->props : ctv->props; // would create a ton of nested intersection types.
if (const IntersectionType* itv = get<IntersectionType>(currentTy))
// 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)) std::vector<TypeId> options = itv->parts;
{ options.push_back(propTy);
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}}); TypeId newItv = addType(IntersectionType{std::move(options)});
ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes});
ftv->hasSelf = true; assignTo[propName] = {newItv};
}
} }
else if (get<FunctionType>(currentTy))
if (assignTo.count(propName) == 0)
{ {
assignTo[propName] = {propTy}; TypeId intersection = addType(IntersectionType{{currentTy, propTy}});
assignTo[propName] = {intersection};
} }
else else
{ {
TypeId currentTy = assignTo[propName].type; reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
// 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())});
}
} }
} }
} }
@ -3364,8 +3272,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
TypeId indexType = checkExpr(scope, *expr.index).type; TypeId indexType = checkExpr(scope, *expr.index).type;
if (FFlag::LuauFollowInLvalueIndexCheck) exprType = follow(exprType);
exprType = follow(exprType);
if (get<AnyType>(exprType) || get<ErrorType>(exprType)) if (get<AnyType>(exprType) || get<ErrorType>(exprType))
return 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); 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); 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; std::string s;
for (size_t i = 0; i < overloadTypes.size(); ++i) 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); Unifier state = mkUnifier(scope, expr.location);
// Unify return types // Unify return types
@ -4861,7 +4768,7 @@ TypePackId TypeChecker::anyifyModuleReturnTypePackGenerics(TypePackId tp)
if (const VariadicTypePack* vtp = get<VariadicTypePack>(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; return get<GenericType>(ty) ? anyTypePack : tp;
} }
@ -6105,11 +6012,11 @@ void TypeChecker::resolve(const EqPredicate& eqP, RefinementMap& refis, const Sc
if (optionIsSubtype && !targetIsSubtype) if (optionIsSubtype && !targetIsSubtype)
return option; return option;
else if (!optionIsSubtype && targetIsSubtype) else if (!optionIsSubtype && targetIsSubtype)
return FFlag::LuauTypeInferMissingFollows ? follow(eqP.type) : eqP.type; return follow(eqP.type);
else if (!optionIsSubtype && !targetIsSubtype) else if (!optionIsSubtype && !targetIsSubtype)
return nope; return nope;
else if (optionIsSubtype && targetIsSubtype) else if (optionIsSubtype && targetIsSubtype)
return FFlag::LuauTypeInferMissingFollows ? follow(eqP.type) : eqP.type; return follow(eqP.type);
} }
else else
{ {

View file

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

View file

@ -4,6 +4,7 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/VisitType.h"
#include <numeric> #include <numeric>
#include <deque> #include <deque>
@ -18,24 +19,7 @@ namespace Luau
namespace namespace
{ {
struct RecursionGuard : RecursionLimiter using detail::ReductionContext;
{
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();
}
};
template<typename A, typename B, typename Thing> template<typename A, typename B, typename Thing>
std::pair<const A*, const B*> get2(const Thing& one, const Thing& two) std::pair<const A*, const B*> get2(const Thing& one, const Thing& two)
@ -51,15 +35,11 @@ struct TypeReducer
NotNull<BuiltinTypes> builtinTypes; NotNull<BuiltinTypes> builtinTypes;
NotNull<InternalErrorReporter> handle; NotNull<InternalErrorReporter> handle;
std::unordered_map<TypeId, TypeId> copies; DenseHashMap<TypeId, ReductionContext<TypeId>>* memoizedTypes;
std::deque<const void*> seen; DenseHashMap<TypePackId, ReductionContext<TypePackId>>* memoizedTypePacks;
int depth = 0; DenseHashSet<TypeId>* cyclicTypes;
// When we encounter _any type_ that which is usually mutated in-place, we need to not cache the result. int depth = 0;
// 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;
TypeId reduce(TypeId ty); TypeId reduce(TypeId ty);
TypePackId reduce(TypePackId tp); TypePackId reduce(TypePackId tp);
@ -70,62 +50,73 @@ struct TypeReducer
TypeId functionType(TypeId ty); TypeId functionType(TypeId ty);
TypeId negationType(TypeId ty); TypeId negationType(TypeId ty);
RecursionGuard guard(TypeId ty); bool isIrreducible(TypeId ty);
RecursionGuard guard(TypePackId tp); bool isIrreducible(TypePackId tp);
void checkCacheable(TypeId ty); TypeId memoize(TypeId ty, TypeId reducedTy);
void checkCacheable(TypePackId tp); 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> template<typename T>
LUAU_NOINLINE std::pair<TypeId, T*> copy(TypeId ty, const T* t) LUAU_NOINLINE std::pair<TypeId, T*> copy(TypeId ty, const T* t)
{ {
if (auto it = copies.find(ty); it != copies.end()) ty = follow(ty);
return {it->second, getMutable<T>(it->second)};
if (auto ctx = memoizedTypes->find(ty))
return {ctx->type, getMutable<T>(ctx->type)};
TypeId copiedTy = arena->addType(*t); TypeId copiedTy = arena->addType(*t);
copies[ty] = copiedTy; (*memoizedTypes)[ty] = {copiedTy, false};
(*memoizedTypes)[copiedTy] = {copiedTy, false};
return {copiedTy, getMutable<T>(copiedTy)}; return {copiedTy, getMutable<T>(copiedTy)};
} }
using Folder = std::optional<TypeId> (TypeReducer::*)(TypeId, TypeId);
template<typename T, typename Iter> 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) while (it != endIt)
{ {
bool replaced = false; TypeId right = reduce(*it);
TypeId currentTy = reduce(*it); *didReduce |= right != follow(*it);
RecursionGuard rg = guard(*it);
// We're hitting a case where the `currentTy` returned a type that's the same as `T`. // 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. // 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. // 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; ++it;
continue; continue;
} }
bool replaced = false;
auto resultIt = result->begin(); auto resultIt = result->begin();
while (resultIt != result->end()) while (resultIt != result->end())
{ {
TypeId& ty = *resultIt; TypeId left = *resultIt;
if (left == right)
std::optional<TypeId> reduced = (this->*f)(ty, currentTy);
if (reduced && replaced)
{ {
// We want to erase any other elements that occurs after the first replacement too. replaced = true;
// e.g. `"a" | "b" | string` where `"a"` and `"b"` is in the `result` vector, then `string` replaces both `"a"` and `"b"`. ++resultIt;
// If we don't erase redundant elements, `"b"` may be kept or be replaced by `string`, leaving us with `string | string`. continue;
resultIt = result->erase(resultIt);
} }
else if (reduced && !replaced)
std::optional<TypeId> reduced = (this->*f)(left, right);
if (reduced)
{ {
*resultIt = *reduced;
++resultIt; ++resultIt;
replaced = true; replaced = true;
ty = *reduced;
} }
else else
{ {
@ -135,21 +126,65 @@ struct TypeReducer
} }
if (!replaced) if (!replaced)
result->push_back(currentTy); result->push_back(right);
*didReduce |= replaced;
++it; ++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> 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; std::vector<TypeId> result;
foldl_impl<T>(it, endIt, f, NotNull{&result}); bool didReduce = false;
if (result.size() == 1) foldl_impl<T>(it, endIt, f, &result, &didReduce);
return result[0]; if (!didReduce && ty)
return *ty;
else 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); ty = follow(ty);
if (std::find(seen.begin(), seen.end(), ty) != seen.end()) if (auto ctx = memoizedTypes->find(ty); ctx && ctx->irreducible)
return ty; return ctx->type;
else if (auto cyclicTy = cyclicTypes->find(ty))
return *cyclicTy;
RecursionGuard rg = guard(ty); RecursionLimiter rl{&depth, FInt::LuauTypeReductionRecursionLimit};
checkCacheable(ty);
TypeId result = nullptr;
if (auto i = get<IntersectionType>(ty)) 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)) 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)) else if (get<TableType>(ty) || get<MetatableType>(ty))
return tableType(ty); result = tableType(ty);
else if (get<FunctionType>(ty)) else if (get<FunctionType>(ty))
return functionType(ty); result = functionType(ty);
else if (auto n = get<NegationType>(ty)) else if (get<NegationType>(ty))
return negationType(follow(n->ty)); result = negationType(ty);
else else
return ty; result = ty;
return memoize(ty, result);
} }
TypePackId TypeReducer::reduce(TypePackId tp) TypePackId TypeReducer::reduce(TypePackId tp)
{ {
tp = follow(tp); tp = follow(tp);
if (std::find(seen.begin(), seen.end(), tp) != seen.end()) if (auto ctx = memoizedTypePacks->find(tp); ctx && ctx->irreducible)
return tp; return ctx->type;
RecursionGuard rg = guard(tp); RecursionLimiter rl{&depth, FInt::LuauTypeReductionRecursionLimit};
checkCacheable(tp);
bool didReduce = false;
TypePackIterator it = begin(tp); TypePackIterator it = begin(tp);
std::vector<TypeId> head; std::vector<TypeId> head;
while (it != end(tp)) while (it != end(tp))
{ {
head.push_back(reduce(*it)); TypeId reducedTy = reduce(*it);
head.push_back(reducedTy);
didReduce |= follow(*it) != follow(reducedTy);
++it; ++it;
} }
@ -200,10 +241,22 @@ TypePackId TypeReducer::reduce(TypePackId tp)
if (tail) if (tail)
{ {
if (auto vtp = get<VariadicTypePack>(follow(*it.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) 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)) else if (get<ErrorType>(right))
return std::nullopt; // T & error ~ T & error return std::nullopt; // T & error ~ T & error
else if (auto ut = get<UnionType>(left)) else if (auto ut = get<UnionType>(left))
{ return reduce(distribute<IntersectionType>(begin(ut), end(ut), &TypeReducer::intersectionType, right)); // (A | B) & T ~ (A & T) | (B & T)
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)
}
else if (get<UnionType>(right)) else if (get<UnionType>(right))
return intersectionType(right, left); // T & (A | B) ~ (A | B) & T return intersectionType(right, left); // T & (A | B) ~ (A | B) & T
else if (auto [p1, p2] = get2<PrimitiveType, PrimitiveType>(left, right); p1 && p2) 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 return builtinTypes->neverType; // Base & Unrelated ~ never
} }
else if (auto [f1, f2] = get2<FunctionType, FunctionType>(left, right); f1 && f2) 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 return std::nullopt; // TODO
}
else if (auto [t1, t2] = get2<TableType, TableType>(left, right); t1 && t2) else if (auto [t1, t2] = get2<TableType, TableType>(left, right); t1 && t2)
{ {
if (t1->state == TableState::Free || t2->state == TableState::Free) 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) else if (t1->state == TableState::Generic || t2->state == TableState::Generic)
return std::nullopt; // '{ x: T } & { x: U } ~ '{ x: T } & { x: U } return std::nullopt; // '{ x: T } & { x: U } ~ '{ x: T } & { x: U }
if (std::find(seen.begin(), seen.end(), left) != seen.end()) if (cyclicTypes->find(left))
return std::nullopt; return std::nullopt; // (t1 where t1 = { p: t1 }) & {} ~ t1 & {}
else if (std::find(seen.begin(), seen.end(), right) != seen.end()) else if (cyclicTypes->find(right))
return std::nullopt; return std::nullopt; // {} & (t1 where t1 = { p: t1 }) ~ {} & t1
TypeId resultTy = arena->addType(TableType{}); TypeId resultTy = arena->addType(TableType{});
TableType* table = getMutable<TableType>(resultTy); 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. // even if we have the corresponding property in the other one.
if (auto other = t2->props.find(name); other != t2->props.end()) if (auto other = t2->props.find(name); other != t2->props.end())
{ {
std::vector<TypeId> parts{prop.type, other->second.type}; TypeId propTy = apply<IntersectionType>(&TypeReducer::intersectionType, prop.type, other->second.type);
TypeId propTy = foldl<IntersectionType>(begin(parts), end(parts), &TypeReducer::intersectionType);
if (get<NeverType>(propTy)) if (get<NeverType>(propTy))
return builtinTypes->neverType; // { p : string } & { p : number } ~ { p : string & number } ~ { p : never } ~ never return builtinTypes->neverType; // { p : string } & { p : number } ~ { p : string & number } ~ { p : never } ~ never
else 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, // TODO: And vice versa, t2 properties against t1 indexer if it exists,
// even if we have the corresponding property in the other one. // even if we have the corresponding property in the other one.
if (!t1->props.count(name)) 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) if (t1->indexer && t2->indexer)
{ {
std::vector<TypeId> keyParts{t1->indexer->indexType, t2->indexer->indexType}; TypeId keyTy = apply<IntersectionType>(&TypeReducer::intersectionType, t1->indexer->indexType, t2->indexer->indexType);
TypeId keyTy = foldl<IntersectionType>(begin(keyParts), end(keyParts), &TypeReducer::intersectionType);
if (get<NeverType>(keyTy)) 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 = apply<IntersectionType>(&TypeReducer::intersectionType, t1->indexer->indexResultType, t2->indexer->indexResultType);
TypeId valueTy = foldl<IntersectionType>(begin(valueParts), end(valueParts), &TypeReducer::intersectionType);
if (get<NeverType>(valueTy)) if (get<NeverType>(valueTy))
return builtinTypes->neverType; // { [_]: string } & { [_]: number } ~ { [_]: string & number } ~ { [_]: never } ~ never return builtinTypes->neverType; // { [_]: string } & { [_]: number } ~ { [_]: string & number } ~ { [_]: never } ~ never
table->indexer = TableIndexer{keyTy, valueTy}; table->indexer = TableIndexer{keyTy, valueTy};
} }
else if (t1->indexer) 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) 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; return resultTy;
} }
@ -506,22 +546,7 @@ std::optional<TypeId> TypeReducer::unionType(TypeId left, TypeId right)
return std::nullopt; // Base | Unrelated ~ Base | Unrelated return std::nullopt; // Base | Unrelated ~ Base | Unrelated
} }
else if (auto [nt, it] = get2<NegationType, IntersectionType>(left, right); nt && it) else if (auto [nt, it] = get2<NegationType, IntersectionType>(left, right); nt && it)
{ return reduce(distribute<UnionType>(begin(it), end(it), &TypeReducer::unionType, left)); // ~T | (A & B) ~ (~T | A) & (~T | B)
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)
}
else if (auto [it, nt] = get2<IntersectionType, NegationType>(left, right); it && nt) else if (auto [it, nt] = get2<IntersectionType, NegationType>(left, right); it && nt)
return unionType(right, left); // (A & B) | ~T ~ ~T | (A & B) return unionType(right, left); // (A & B) | ~T ~ ~T | (A & B)
else if (auto [nl, nr] = get2<NegationType, NegationType>(left, right); nl && nr) 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) TypeId TypeReducer::tableType(TypeId ty)
{ {
RecursionGuard rg = guard(ty);
if (auto mt = get<MetatableType>(ty)) if (auto mt = get<MetatableType>(ty))
{ {
auto [copiedTy, copied] = copy(ty, mt); auto [copiedTy, copied] = copy(ty, mt);
@ -639,15 +662,30 @@ TypeId TypeReducer::tableType(TypeId ty)
} }
else if (auto tt = get<TableType>(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); auto [copiedTy, copied] = copy(ty, tt);
for (auto& [name, prop] : copied->props) for (auto& [name, prop] : copied->props)
prop.type = reduce(prop.type);
if (auto& indexer = copied->indexer)
{ {
indexer->indexType = reduce(indexer->indexType); TypeId propTy = reduce(prop.type);
indexer->indexResultType = reduce(indexer->indexResultType); 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) for (TypeId& ty : copied->instantiatedTypeParams)
@ -659,16 +697,14 @@ TypeId TypeReducer::tableType(TypeId ty)
return copiedTy; return copiedTy;
} }
else else
handle->ice("Unexpected type in TypeReducer::tableType"); handle->ice("TypeReducer::tableType expects a TableType or MetatableType");
} }
TypeId TypeReducer::functionType(TypeId ty) TypeId TypeReducer::functionType(TypeId ty)
{ {
RecursionGuard rg = guard(ty);
const FunctionType* f = get<FunctionType>(ty); const FunctionType* f = get<FunctionType>(ty);
if (!f) 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. // TODO: once we have bounded quantification, we need to be able to reduce the generic bounds.
auto [copiedTy, copied] = copy(ty, f); auto [copiedTy, copied] = copy(ty, f);
@ -679,140 +715,238 @@ TypeId TypeReducer::functionType(TypeId ty)
TypeId TypeReducer::negationType(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 return nn->ty; // ~~T ~ T
else if (get<NeverType>(ty)) else if (get<NeverType>(n->ty))
return builtinTypes->unknownType; // ~never ~ unknown return builtinTypes->unknownType; // ~never ~ unknown
else if (get<UnknownType>(ty)) else if (get<UnknownType>(n->ty))
return builtinTypes->neverType; // ~unknown ~ never return builtinTypes->neverType; // ~unknown ~ never
else if (get<AnyType>(ty)) else if (get<AnyType>(n->ty))
return builtinTypes->anyType; // ~any ~ any return builtinTypes->anyType; // ~any ~ any
else if (auto ni = get<IntersectionType>(ty)) else if (auto ni = get<IntersectionType>(n->ty))
{ {
std::vector<TypeId> options; std::vector<TypeId> options;
for (TypeId part : ni) for (TypeId part : ni)
options.push_back(negationType(part)); options.push_back(negationType(arena->addType(NegationType{part})));
return foldl<UnionType>(begin(options), end(options), &TypeReducer::unionType); // ~(T & U) ~ (~T | ~U) 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; std::vector<TypeId> parts;
for (TypeId option : nu) for (TypeId option : nu)
parts.push_back(negationType(option)); parts.push_back(negationType(arena->addType(NegationType{option})));
return foldl<IntersectionType>(begin(parts), end(parts), &TypeReducer::intersectionType); // ~(T | U) ~ (~T & ~U) return reduce(flatten<IntersectionType>(std::move(parts))); // ~(T | U) ~ (~T & ~U)
} }
else 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); ty = follow(ty);
// Only does shallow check, the TypeReducer itself already does deep traversal. // Only does shallow check, the TypeReducer itself already does deep traversal.
if (get<FreeType>(ty) || get<BlockedType>(ty) || get<PendingExpansionType>(ty)) if (auto ctx = memoizedTypes->find(ty); ctx && ctx->irreducible)
cacheOk = false; 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)) 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); tp = follow(tp);
// Only does shallow check, the TypeReducer itself already does deep traversal. // Only does shallow check, the TypeReducer itself already does deep traversal.
if (get<FreeTypePack>(tp) || get<BlockedTypePack>(tp)) if (auto ctx = memoizedTypePacks->find(tp); ctx && ctx->irreducible)
cacheOk = false; 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 } // 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) : arena(arena)
, builtinTypes(builtinTypes) , builtinTypes(builtinTypes)
, handle(handle) , handle(handle)
, options(opts)
{ {
} }
std::optional<TypeId> TypeReduction::reduce(TypeId ty) std::optional<TypeId> TypeReduction::reduce(TypeId ty)
{ {
if (auto found = cachedTypes.find(ty)) ty = follow(ty);
return *found;
auto [reducedTy, cacheOk] = reduceImpl(ty); if (FFlag::DebugLuauDontReduceTypes)
if (cacheOk) return ty;
cachedTypes[ty] = *reducedTy; 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) std::optional<TypePackId> TypeReduction::reduce(TypePackId tp)
{ {
if (auto found = cachedTypePacks.find(tp)) tp = follow(tp);
return *found;
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) if (FFlag::DebugLuauDontReduceTypes)
return {ty, false}; return tp;
else if (!options.allowTypeReductionsFromOtherArenas && tp->owningArena != arena)
if (hasExceededCartesianProductLimit(ty)) return tp;
return {std::nullopt, false}; else if (auto ctx = memoizedTypePacks.find(tp); ctx && ctx->irreducible)
return ctx->type;
else if (hasExceededCartesianProductLimit(tp))
return std::nullopt;
try try
{ {
TypeReducer reducer{arena, builtinTypes, handle}; MarkCycles finder;
return {reducer.reduce(ty), reducer.cacheOk}; finder.traverse(tp);
TypeReducer reducer{arena, builtinTypes, handle, &memoizedTypes, &memoizedTypePacks, &finder.cyclicTypes};
return reducer.reduce(tp);
} }
catch (const RecursionLimitException&) catch (const RecursionLimitException&)
{ {
return {std::nullopt, false}; return std::nullopt;
}
}
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};
} }
} }

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

View file

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

View file

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

View file

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

View file

@ -768,7 +768,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported)
AstType* type = parseTypeAnnotation(); 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() 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)]); emitInstForNPrep(build, pc, i, labelarr[i + 1 + LUAU_INSN_D(*pc)]);
break; break;
case LOP_FORNLOOP: 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; break;
case LOP_FORGLOOP: case LOP_FORGLOOP:
emitinstForGLoop(build, pc, i, labelarr[i + 1 + LUAU_INSN_D(*pc)], next, fallback); 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 // rArg1 is already prepared
build.mov(rArg2, tm); build.mov(rArg2, tm);
build.mov(rax, qword[rState + offsetof(lua_State, global)]); 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)]); build.call(qword[rNativeContext + offsetof(NativeContext, luaT_gettm)]);
} }

View file

@ -1010,17 +1010,15 @@ static int emitInstFastCallN(
if (nparams == LUA_MULTRET) if (nparams == LUA_MULTRET)
{ {
// TODO: for SystemV ABI we can compute the result directly into rArg6
// L->top - (ra + 1) // 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.lea(rdx, addr[rBase + (ra + 1) * sizeof(TValue)]);
build.sub(rcx, rdx); build.sub(reg, rdx);
build.shr(rcx, kTValueSizeLog2); build.shr(reg, kTValueSizeLog2);
if (build.abi == ABIX64::Windows) if (build.abi == ABIX64::Windows)
build.mov(sArg6, rcx); build.mov(sArg6, reg);
else
build.mov(rArg6, rcx);
} }
else else
{ {
@ -1126,7 +1124,7 @@ void emitInstForNPrep(AssemblyBuilderX64& build, const Instruction* pc, int pcpo
build.setLabel(exit); 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); emitInterrupt(build, pcpos);
@ -1144,20 +1142,18 @@ void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpo
build.vaddsd(idx, idx, step); build.vaddsd(idx, idx, step);
build.vmovsd(luauRegValue(ra + 2), idx); build.vmovsd(luauRegValue(ra + 2), idx);
Label reverse, exit; Label reverse;
// step <= 0 // step <= 0
jumpOnNumberCmp(build, noreg, step, zero, ConditionX64::LessEqual, reverse); jumpOnNumberCmp(build, noreg, step, zero, ConditionX64::LessEqual, reverse);
// false: idx <= limit // false: idx <= limit
jumpOnNumberCmp(build, noreg, idx, limit, ConditionX64::LessEqual, loopRepeat); jumpOnNumberCmp(build, noreg, idx, limit, ConditionX64::LessEqual, loopRepeat);
build.jmp(exit); build.jmp(loopExit);
// true: limit <= idx // true: limit <= idx
build.setLabel(reverse); build.setLabel(reverse);
jumpOnNumberCmp(build, noreg, limit, idx, ConditionX64::LessEqual, loopRepeat); 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) 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 emitInstFastCall2K(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& fallback);
int emitInstFastCall(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 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 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 emitinstForGLoopFallback(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat);
void emitInstForGPrepNext(AssemblyBuilderX64& build, const Instruction* pc, Label& target, Label& fallback); 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> #include <stdint.h>
struct Proto;
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
@ -99,6 +101,7 @@ enum class IrCmd : uint8_t
// Operations that don't have an IR representation yet // Operations that don't have an IR representation yet
LOP_SETLIST, LOP_SETLIST,
LOP_NAMECALL,
LOP_CALL, LOP_CALL,
LOP_RETURN, LOP_RETURN,
LOP_FASTCALL, LOP_FASTCALL,
@ -116,21 +119,21 @@ enum class IrCmd : uint8_t
LOP_ANDK, LOP_ANDK,
LOP_OR, LOP_OR,
LOP_ORK, LOP_ORK,
LOP_COVERAGE,
// Operations that have a translation, but use a full instruction fallback // Operations that have a translation, but use a full instruction fallback
FALLBACK_GETGLOBAL, FALLBACK_GETGLOBAL,
FALLBACK_SETGLOBAL, FALLBACK_SETGLOBAL,
FALLBACK_GETTABLEKS, FALLBACK_GETTABLEKS,
FALLBACK_SETTABLEKS, FALLBACK_SETTABLEKS,
FALLBACK_NAMECALL,
// Operations that don't have assembly lowering at all // Operations that don't have assembly lowering at all
FALLBACK_NAMECALL,
FALLBACK_PREPVARARGS, FALLBACK_PREPVARARGS,
FALLBACK_GETVARARGS, FALLBACK_GETVARARGS,
FALLBACK_NEWCLOSURE, FALLBACK_NEWCLOSURE,
FALLBACK_DUPCLOSURE, FALLBACK_DUPCLOSURE,
FALLBACK_FORGPREP, FALLBACK_FORGPREP,
FALLBACK_COVERAGE,
}; };
enum class IrConstKind : uint8_t enum class IrConstKind : uint8_t
@ -274,6 +277,8 @@ struct IrFunction
std::vector<IrConst> constants; std::vector<IrConst> constants;
std::vector<BytecodeMapping> bcMapping; std::vector<BytecodeMapping> bcMapping;
Proto* proto = nullptr;
}; };
} // namespace CodeGen } // namespace CodeGen

View file

@ -186,6 +186,8 @@ const char* getCmdName(IrCmd cmd)
return "CAPTURE"; return "CAPTURE";
case IrCmd::LOP_SETLIST: case IrCmd::LOP_SETLIST:
return "LOP_SETLIST"; return "LOP_SETLIST";
case IrCmd::LOP_NAMECALL:
return "LOP_NAMECALL";
case IrCmd::LOP_CALL: case IrCmd::LOP_CALL:
return "LOP_CALL"; return "LOP_CALL";
case IrCmd::LOP_RETURN: case IrCmd::LOP_RETURN:
@ -220,6 +222,8 @@ const char* getCmdName(IrCmd cmd)
return "LOP_OR"; return "LOP_OR";
case IrCmd::LOP_ORK: case IrCmd::LOP_ORK:
return "LOP_ORK"; return "LOP_ORK";
case IrCmd::LOP_COVERAGE:
return "LOP_COVERAGE";
case IrCmd::FALLBACK_GETGLOBAL: case IrCmd::FALLBACK_GETGLOBAL:
return "FALLBACK_GETGLOBAL"; return "FALLBACK_GETGLOBAL";
case IrCmd::FALLBACK_SETGLOBAL: case IrCmd::FALLBACK_SETGLOBAL:
@ -240,8 +244,6 @@ const char* getCmdName(IrCmd cmd)
return "FALLBACK_DUPCLOSURE"; return "FALLBACK_DUPCLOSURE";
case IrCmd::FALLBACK_FORGPREP: case IrCmd::FALLBACK_FORGPREP:
return "FALLBACK_FORGPREP"; return "FALLBACK_FORGPREP";
case IrCmd::FALLBACK_COVERAGE:
return "FALLBACK_COVERAGE";
} }
LUAU_UNREACHABLE(); 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); 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 CodeGen
} // namespace Luau } // namespace Luau

View file

@ -28,5 +28,7 @@ void toString(std::string& result, IrConst constant);
void toStringDetailed(IrToStringContext& ctx, IrInst inst, uint32_t index); void toStringDetailed(IrToStringContext& ctx, IrInst inst, uint32_t index);
std::string dump(IrFunction& function);
} // namespace CodeGen } // namespace CodeGen
} // namespace Luau } // 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_NUM:
case IrCmd::JUMP_CMP_STR: case IrCmd::JUMP_CMP_STR:
case IrCmd::JUMP_CMP_ANY: case IrCmd::JUMP_CMP_ANY:
case IrCmd::LOP_NAMECALL:
case IrCmd::LOP_RETURN: case IrCmd::LOP_RETURN:
case IrCmd::LOP_FORNPREP: case IrCmd::LOP_FORNPREP:
case IrCmd::LOP_FORNLOOP: case IrCmd::LOP_FORNLOOP:

View file

@ -35,7 +35,10 @@ inline AssertHandler& assertHandler()
return handler; 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()) if (AssertHandler handler = assertHandler())
return handler(expression, file, line, function); 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 target_sources(Luau.Common PRIVATE
Common/include/Luau/Common.h Common/include/Luau/Common.h
Common/include/Luau/Bytecode.h Common/include/Luau/Bytecode.h
Common/include/Luau/DenseHash.h
Common/include/Luau/ExperimentalFlags.h Common/include/Luau/ExperimentalFlags.h
) )
endif() endif()
@ -12,7 +13,6 @@ endif()
target_sources(Luau.Ast PRIVATE target_sources(Luau.Ast PRIVATE
Ast/include/Luau/Ast.h Ast/include/Luau/Ast.h
Ast/include/Luau/Confusables.h Ast/include/Luau/Confusables.h
Ast/include/Luau/DenseHash.h
Ast/include/Luau/Lexer.h Ast/include/Luau/Lexer.h
Ast/include/Luau/Location.h Ast/include/Luau/Location.h
Ast/include/Luau/ParseOptions.h Ast/include/Luau/ParseOptions.h
@ -82,7 +82,9 @@ target_sources(Luau.CodeGen PRIVATE
CodeGen/src/EmitCommonX64.cpp CodeGen/src/EmitCommonX64.cpp
CodeGen/src/EmitInstructionX64.cpp CodeGen/src/EmitInstructionX64.cpp
CodeGen/src/Fallbacks.cpp CodeGen/src/Fallbacks.cpp
CodeGen/src/IrBuilder.cpp
CodeGen/src/IrDump.cpp CodeGen/src/IrDump.cpp
CodeGen/src/IrTranslation.cpp
CodeGen/src/NativeState.cpp CodeGen/src/NativeState.cpp
CodeGen/src/UnwindBuilderDwarf2.cpp CodeGen/src/UnwindBuilderDwarf2.cpp
CodeGen/src/UnwindBuilderWin.cpp CodeGen/src/UnwindBuilderWin.cpp
@ -96,8 +98,10 @@ target_sources(Luau.CodeGen PRIVATE
CodeGen/src/EmitInstructionX64.h CodeGen/src/EmitInstructionX64.h
CodeGen/src/Fallbacks.h CodeGen/src/Fallbacks.h
CodeGen/src/FallbacksProlog.h CodeGen/src/FallbacksProlog.h
CodeGen/src/IrBuilder.h
CodeGen/src/IrDump.h CodeGen/src/IrDump.h
CodeGen/src/IrData.h CodeGen/src/IrData.h
CodeGen/src/IrTranslation.h
CodeGen/src/IrUtils.h CodeGen/src/IrUtils.h
CodeGen/src/NativeState.h CodeGen/src/NativeState.h
) )

View file

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

View file

@ -282,6 +282,8 @@ struct ProtoToLuau
print(expr.binary()); print(expr.binary());
else if (expr.has_ifelse()) else if (expr.has_ifelse())
print(expr.ifelse()); print(expr.ifelse());
else if (expr.has_interpstring())
print(expr.interpstring());
else else
source += "_"; 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) void print(const luau::LValue& expr)
{ {
if (expr.has_local()) if (expr.has_local())

View file

@ -16,6 +16,8 @@
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTFLAG(LuauFixAutocompleteInIf) LUAU_FASTFLAG(LuauFixAutocompleteInIf)
LUAU_FASTFLAG(LuauFixAutocompleteInWhile)
LUAU_FASTFLAG(LuauFixAutocompleteInFor)
using namespace Luau; using namespace Luau;
@ -380,7 +382,7 @@ TEST_CASE_FIXTURE(ACFixture, "table_intersection")
{ {
check(R"( check(R"(
type t1 = { a1 : string, b2 : number } type t1 = { a1 : string, b2 : number }
type t2 = { b2 : string, c3 : string } type t2 = { b2 : number, c3 : string }
function func(abc : t1 & t2) function func(abc : t1 & t2)
abc. @1 abc. @1
end end
@ -629,9 +631,19 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_for_middle_keywords")
)"); )");
auto ac5 = autocomplete('1'); auto ac5 = autocomplete('1');
CHECK_EQ(ac5.entryMap.count("do"), 1); if (FFlag::LuauFixAutocompleteInFor)
CHECK_EQ(ac5.entryMap.count("end"), 0); {
CHECK_EQ(ac5.context, AutocompleteContext::Keyword); 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"( check(R"(
for x = 1, 2, 5 f@1 for x = 1, 2, 5 f@1
@ -649,6 +661,31 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_for_middle_keywords")
auto ac7 = autocomplete('1'); auto ac7 = autocomplete('1');
CHECK_EQ(ac7.entryMap.count("end"), 1); CHECK_EQ(ac7.entryMap.count("end"), 1);
CHECK_EQ(ac7.context, AutocompleteContext::Statement); 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") 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'); auto ac2 = autocomplete('1');
CHECK_EQ(1, ac2.entryMap.size()); if (FFlag::LuauFixAutocompleteInWhile)
CHECK_EQ(ac2.entryMap.count("do"), 1); {
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_EQ(ac2.context, AutocompleteContext::Keyword);
check(R"( check(R"(
@ -757,9 +804,31 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_while_middle_keywords")
)"); )");
auto ac4 = autocomplete('1'); auto ac4 = autocomplete('1');
CHECK_EQ(1, ac4.entryMap.size()); if (FFlag::LuauFixAutocompleteInWhile)
CHECK_EQ(ac4.entryMap.count("do"), 1); {
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); 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") 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("elseif"), 0);
CHECK_EQ(ac5.entryMap.count("end"), 0); CHECK_EQ(ac5.entryMap.count("end"), 0);
CHECK_EQ(ac5.context, AutocompleteContext::Statement); CHECK_EQ(ac5.context, AutocompleteContext::Statement);
if (FFlag::LuauFixAutocompleteInIf) if (FFlag::LuauFixAutocompleteInIf)
{ {
check(R"( 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") TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback")
{ {
ScopedFastFlag luauAutocompleteStringContent{"LuauAutocompleteStringContent", true};
loadDefinition(R"( loadDefinition(R"(
declare function require(path: string): any declare function require(path: string): any
)"); )");
@ -3414,10 +3485,9 @@ TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback")
)"); )");
bool isCorrect = false; bool isCorrect = false;
auto ac1 = autocomplete('1', auto ac1 = autocomplete(
[&isCorrect](std::string, std::optional<const ClassType*>, std::optional<std::string> contents) -> std::optional<AutocompleteEntryMap> '1', [&isCorrect](std::string, std::optional<const ClassType*>, std::optional<std::string> contents) -> std::optional<AutocompleteEntryMap> {
{ isCorrect = contents && *contents == "testing/";
isCorrect = contents.has_value() && contents.value() == "testing/";
return std::nullopt; return std::nullopt;
}); });

View file

@ -701,8 +701,7 @@ TEST_CASE("NDebugGetUpValue")
copts.optimizationLevel = 0; copts.optimizationLevel = 0;
runConformance( runConformance(
"ndebug_upvalues.lua", "ndebug_upvalues.lua", nullptr,
nullptr,
[](lua_State* L) { [](lua_State* L) {
lua_checkstack(L, LUA_MINSTACK); 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"}); 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}); addGlobalBinding(*frontend, "Child", {childType});
moduleScope->exportedTypeBindings["Child"] = TypeFun{{}, 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"}); TypeId unrelatedType = arena.addType(ClassType{"Unrelated", {}, frontend->builtinTypes->classType, std::nullopt, {}, nullptr, "Test"});
addGlobalBinding(*frontend, "Unrelated", {unrelatedType}); addGlobalBinding(*frontend, "Unrelated", {unrelatedType});

View file

@ -171,7 +171,6 @@ return bar()
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMultiFx") TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMultiFx")
{ {
ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true};
LintResult result = lint(R"( LintResult result = lint(R"(
function bar() function bar()
foo = 6 foo = 6
@ -192,7 +191,6 @@ return bar() + baz()
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMultiFxWithRead") TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMultiFxWithRead")
{ {
ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true};
LintResult result = lint(R"( LintResult result = lint(R"(
function bar() function bar()
foo = 6 foo = 6
@ -216,7 +214,6 @@ return bar() + baz() + read()
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalWithConditional") TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalWithConditional")
{ {
ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true};
LintResult result = lint(R"( LintResult result = lint(R"(
function bar() function bar()
if true then foo = 6 end if true then foo = 6 end
@ -236,7 +233,6 @@ return bar() + baz()
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocal3WithConditionalRead") TEST_CASE_FIXTURE(Fixture, "GlobalAsLocal3WithConditionalRead")
{ {
ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true};
LintResult result = lint(R"( LintResult result = lint(R"(
function bar() function bar()
foo = 6 foo = 6
@ -260,7 +256,6 @@ return bar() + baz() + read()
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalInnerRead") TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalInnerRead")
{ {
ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true};
LintResult result = lint(R"( LintResult result = lint(R"(
function foo() function foo()
local f = function() return bar end 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") TEST_CASE_FIXTURE(IsSubtypeFixture, "intersection")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
check(R"( check(R"(
local a: number & string local a: number & string
local b: number local b: number

View file

@ -9,6 +9,8 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
struct ToDotClassFixture : Fixture struct ToDotClassFixture : Fixture
{ {
ToDotClassFixture() ToDotClassFixture()
@ -109,7 +111,27 @@ local function f(a, ...: string) return a end
ToDotOptions opts; ToDotOptions opts;
opts.showPointers = false; 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 [label="FunctionType 1"];
n1 -> n2 [label="arg"]; n1 -> n2 [label="arg"];
n2 [label="TypePack 2"]; n2 [label="TypePack 2"];
@ -125,7 +147,8 @@ n6 -> n7;
n7 [label="TypePack 7"]; n7 [label="TypePack 7"];
n7 -> n3; n7 -> n3;
})", })",
toDot(requireType("f"), opts)); toDot(requireType("f"), opts));
}
} }
TEST_CASE_FIXTURE(Fixture, "union") TEST_CASE_FIXTURE(Fixture, "union")
@ -176,7 +199,35 @@ local a: A<number, ...string>
ToDotOptions opts; ToDotOptions opts;
opts.showPointers = false; 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 [label="TableType A"];
n1 -> n2 [label="x"]; n1 -> n2 [label="x"];
n2 [label="number"]; n2 [label="number"];
@ -196,7 +247,8 @@ n1 -> n9 [label="typeParam"];
n9 [label="number"]; n9 [label="number"];
n1 -> n4 [label="typePackParam"]; n1 -> n4 [label="typePackParam"];
})", })",
toDot(requireType("a"), opts)); toDot(requireType("a"), opts));
}
// Extra coverage with pointers (unstable values) // Extra coverage with pointers (unstable values)
(void)toDot(requireType("a")); (void)toDot(requireType("a"));
@ -357,14 +409,31 @@ b = a
ToDotOptions opts; ToDotOptions opts;
opts.showPointers = false; 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 [label="TableType 1"];
n1 -> n2 [label="boundTo"]; n1 -> n2 [label="boundTo"];
n2 [label="TableType a"]; n2 [label="TableType a"];
n2 -> n3 [label="x"]; n2 -> n3 [label="x"];
n3 [label="number"]; n3 [label="number"];
})", })",
toDot(*ty, opts)); toDot(*ty, opts));
}
} }
TEST_CASE_FIXTURE(Fixture, "builtintypes") 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") TEST_CASE_FIXTURE(Fixture, "tostring_unsee_ttv_if_array")
{ {
ScopedFastFlag sff("LuauUnseeArrayTtv", true);
CheckResult result = check(R"( CheckResult result = check(R"(
local x: {string} local x: {string}
-- This code is constructed very specifically to use the same (by pointer -- This code is constructed very specifically to use the same (by pointer

View file

@ -8,7 +8,8 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauMatchReturnsOptionalString);
TEST_SUITE_BEGIN("BuiltinTests"); TEST_SUITE_BEGIN("BuiltinTests");
@ -174,7 +175,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "math_max_checks_for_numbers")
local n = math.max(1,2,"3") 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])); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "set_metatable_needs_arguments")
{ {
ScopedFastFlag sff{"LuauSetMetaTableArgsCheck", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local a = {b=setmetatable} local a = {b=setmetatable}
a.b() a.b()
@ -1055,6 +1055,20 @@ end
LUAU_REQUIRE_NO_ERRORS(result); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types")
{ {
CheckResult result = check(R"END( CheckResult result = check(R"END(
@ -1063,12 +1077,21 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a")), "string"); if (FFlag::LuauMatchReturnsOptionalString)
CHECK_EQ(toString(requireType("b")), "number"); {
CHECK_EQ(toString(requireType("c")), "string"); 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( CheckResult result = check(R"END(
local a, b, c = ("This is a string"):gmatch("(.()(%a+))")() 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); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a")), "string"); if (FFlag::LuauMatchReturnsOptionalString)
CHECK_EQ(toString(requireType("b")), "number"); {
CHECK_EQ(toString(requireType("c")), "string"); 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") 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->expected, 1);
CHECK_EQ(acm->actual, 4); 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") 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->expected, 3);
CHECK_EQ(acm->actual, 4); CHECK_EQ(acm->actual, 4);
CHECK_EQ(toString(requireType("a")), "string"); if (FFlag::LuauMatchReturnsOptionalString)
CHECK_EQ(toString(requireType("b")), "string"); {
CHECK_EQ(toString(requireType("c")), "number"); 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") 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->expected, 2);
CHECK_EQ(acm->actual, 3); CHECK_EQ(acm->actual, 3);
CHECK_EQ(toString(requireType("a")), "string"); if (FFlag::LuauMatchReturnsOptionalString)
CHECK_EQ(toString(requireType("b")), "number"); {
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") 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); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a")), "number"); if (FFlag::LuauMatchReturnsOptionalString)
CHECK_EQ(toString(requireType("b")), "string"); {
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") 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); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a")), "string"); if (FFlag::LuauMatchReturnsOptionalString)
CHECK_EQ(toString(requireType("b")), "number"); {
CHECK_EQ(toString(requireType("c")), "string"); 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") 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->wantedType), "number?");
CHECK_EQ(toString(tm->givenType), "string"); CHECK_EQ(toString(tm->givenType), "string");
CHECK_EQ(toString(requireType("a")), "string"); if (FFlag::LuauMatchReturnsOptionalString)
CHECK_EQ(toString(requireType("b")), "number"); {
CHECK_EQ(toString(requireType("c")), "string"); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types")
@ -1223,9 +1301,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a")), "string"); if (FFlag::LuauMatchReturnsOptionalString)
CHECK_EQ(toString(requireType("b")), "number"); {
CHECK_EQ(toString(requireType("c")), "string"); 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("d")), "number?");
CHECK_EQ(toString(requireType("e")), "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->wantedType), "number?");
CHECK_EQ(toString(tm->givenType), "string"); CHECK_EQ(toString(tm->givenType), "string");
CHECK_EQ(toString(requireType("a")), "string"); if (FFlag::LuauMatchReturnsOptionalString)
CHECK_EQ(toString(requireType("b")), "number"); {
CHECK_EQ(toString(requireType("c")), "string"); 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("d")), "number?");
CHECK_EQ(toString(requireType("e")), "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->wantedType), "boolean?");
CHECK_EQ(toString(tm->givenType), "string"); CHECK_EQ(toString(tm->givenType), "string");
CHECK_EQ(toString(requireType("a")), "string"); if (FFlag::LuauMatchReturnsOptionalString)
CHECK_EQ(toString(requireType("b")), "number"); {
CHECK_EQ(toString(requireType("c")), "string"); 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("d")), "number?");
CHECK_EQ(toString(requireType("e")), "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") TEST_CASE_FIXTURE(ClassFixture, "intersections_of_unions_of_classes")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local x : (BaseClass | Vector2) & (ChildClass | AnotherChild) local x : (BaseClass | Vector2) & (ChildClass | AnotherChild)
local y : (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") TEST_CASE_FIXTURE(ClassFixture, "unions_of_intersections_of_classes")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local x : (BaseClass & ChildClass) | (BaseClass & AnotherChild) | (BaseClass & Vector2) local x : (BaseClass & ChildClass) | (BaseClass & AnotherChild) | (BaseClass & Vector2)
local y : (ChildClass | AnotherChild) local y : (ChildClass | AnotherChild)
@ -482,8 +472,6 @@ caused by:
TEST_CASE_FIXTURE(ClassFixture, "callable_classes") TEST_CASE_FIXTURE(ClassFixture, "callable_classes")
{ {
ScopedFastFlag luauCallableClasses{"LuauCallableClasses", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local x : CallableClass local x : CallableClass
local y = x("testing") 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") TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
{ {
ScopedFastFlag LuauDeclareClassPrototype("LuauDeclareClassPrototype", true);
unfreeze(typeChecker.globalTypes); unfreeze(typeChecker.globalTypes);
LoadDefinitionFileResult result = loadDefinitionFile(typeChecker, typeChecker.globalScope, R"( LoadDefinitionFileResult result = loadDefinitionFile(typeChecker, typeChecker.globalScope, R"(
declare class Channel declare class Channel

View file

@ -1726,12 +1726,6 @@ foo(string.find("hello", "e"))
TEST_CASE_FIXTURE(Fixture, "luau_subtyping_is_np_hard") TEST_CASE_FIXTURE(Fixture, "luau_subtyping_is_np_hard")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
{"LuauOverloadedFunctionSubtypingPerf", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!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") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_must_follow_in_overload_resolution")
{ {
ScopedFastFlag luauTypeInferMissingFollows{"LuauTypeInferMissingFollows", true};
CheckResult result = check(R"( CheckResult result = check(R"(
for _ in function<t0>():(t0)&((()->())&(()->())) for _ in function<t0>():(t0)&((()->())&(()->()))
end do 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") TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local x : ((number?) -> number?) & ((string?) -> string?) local x : ((number?) -> number?) & ((string?) -> string?)
local y : (nil) -> nil = x -- OK 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") TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local x : ((number) -> number) & ((string) -> string) local x : ((number) -> number) & ((string) -> string)
local y : ((number | string) -> (number | string)) = x -- OK 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") TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local x : { p : number?, q : string? } & { p : number?, q : number?, r : number? } local x : { p : number?, q : string? } & { p : number?, q : number?, r : number? }
local y : { p : number?, q : nil, r : number? } = x -- OK 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") TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties")
{ {
ScopedFastFlag sffs[]{ ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
{"LuauUninhabitedSubAnything2", 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") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local x : ((number?) -> ({ p : number } & { q : number })) & ((string?) -> ({ p : number } & { r : number })) local x : ((number?) -> ({ p : number } & { q : number })) & ((string?) -> ({ p : number } & { r : number }))
local y : (nil) -> { p : number, q : number, r : number} = x -- OK 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") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f<a>() function f<a>()
local x : ((number?) -> (a | number)) & ((string?) -> (a | string)) 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") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f<a,b,c>() function f<a,b,c>()
local x : ((a?) -> (a | b)) & ((c?) -> (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") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f<a...,b...>() function f<a...,b...>()
local x : ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, 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") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f<a...,b...>() function f<a...,b...>()
local x : ((number) -> number) & ((nil) -> unknown) 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") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f<a...,b...>() function f<a...,b...>()
local x : ((number) -> number?) & ((unknown) -> string?) 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") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f<a...,b...>() function f<a...,b...>()
local x : ((number) -> number) & ((nil) -> never) 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") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f<a...,b...>() function f<a...,b...>()
local x : ((number) -> number?) & ((never) -> string?) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local a : string? = nil local a : string? = nil
local b : number? = nil local b : number? = nil
@ -807,11 +745,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables")
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_subtypes") TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_subtypes")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local x = setmetatable({ a = 5 }, { p = 5 }); local x = setmetatable({ a = 5 }, { p = 5 });
local y = setmetatable({ b = "hi" }, { p = 5, q = "hi" }); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables_with_properties")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local x = setmetatable({ a = 5 }, { p = 5 }); local x = setmetatable({ a = 5 }, { p = 5 });
local y = setmetatable({ b = "hi" }, { q = "hi" }); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_with_table")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local x = setmetatable({ a = 5 }, { p = 5 }); local x = setmetatable({ a = 5 }, { p = 5 });
local z = setmetatable({ a = 5, b = "hi" }, { 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") TEST_CASE_FIXTURE(Fixture, "CLI-44817")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
type X = {x: number} type X = {x: number}
type Y = {y: number} type Y = {y: number}

View file

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

View file

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

View file

@ -72,17 +72,6 @@ TEST_CASE_FIXTURE(Fixture, "string_function_indirect")
CHECK_EQ(*requireType("p"), *typeChecker.stringType); 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") TEST_CASE_FIXTURE(Fixture, "CheckMethodsOfNumber")
{ {
CheckResult result = check(R"( 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); 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}))); 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}))); 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") TEST_CASE_FIXTURE(RefinementClassFixture, "eliminate_subclasses_of_instance")
{ {
CheckResult result = check(R"( 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") TEST_CASE_FIXTURE(Fixture, "fuzz_filtered_refined_types_are_followed")
{ {
ScopedFastFlag luauTypeInferMissingFollows{"LuauTypeInferMissingFollows", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local _ local _
do do

View file

@ -55,7 +55,10 @@ TEST_CASE_FIXTURE(Fixture, "augment_table")
TEST_CASE_FIXTURE(Fixture, "augment_nested_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); LUAU_REQUIRE_NO_ERRORS(result);
TableType* tType = getMutable<TableType>(requireType("t")); 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") 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); LUAU_REQUIRE_ERROR_COUNT(1, result);
TypeError& err = result.errors[0]; TypeError& err = result.errors[0];
CHECK(err.location == Location{Position{6, 8}, Position{6, 13}});
CannotExtendTable* error = get<CannotExtendTable>(err); 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 // TODO: better, more robust comparison of type vars
auto s = toString(error->tableType, ToStringOptions{/*exhaustive*/ true}); auto s = toString(error->tableType, ToStringOptions{/*exhaustive*/ true});
CHECK_EQ(s, "{| prop: number |}"); CHECK_EQ(s, "{| prop: number |}");
CHECK_EQ(error->prop, "foo"); CHECK_EQ(error->prop, "foo");
CHECK_EQ(error->context, CannotExtendTable::Property); 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") 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") TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
{ {
ScopedFastInt sfi("LuauTypeInferRecursionLimit", 10); ScopedFastInt sfi("LuauTypeInferRecursionLimit", 10);
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f<a,b,c,d,e,f,g,h,i,j>() 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") TEST_CASE_FIXTURE(Fixture, "type_infer_cache_limit_normalizer")
{ {
ScopedFastInt sfi("LuauNormalizeCacheLimit", 10); ScopedFastInt sfi("LuauNormalizeCacheLimit", 10);
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local x : ((number) -> number) & ((string) -> string) & ((nil) -> nil) & (({}) -> {}) 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") TEST_CASE_FIXTURE(Fixture, "fuzz_free_table_type_change_during_index_check")
{ {
ScopedFastFlag luauFollowInLvalueIndexCheck{"LuauFollowInLvalueIndexCheck", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local _ = nil local _ = nil
while _["" >= _] do while _["" >= _] do

View file

@ -112,11 +112,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_tables_are_preserved")
TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_never") TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_never")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f(arg : string & number) : never function f(arg : string & number) : never
return arg return arg
@ -127,11 +122,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_never")
TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_anything") TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_anything")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f(arg : string & number) : boolean function f(arg : string & number) : boolean
return arg return arg
@ -143,8 +133,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_anything")
TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_never") TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_never")
{ {
ScopedFastFlag sffs[]{ ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
{"LuauUninhabitedSubAnything2", true}, {"LuauUninhabitedSubAnything2", true},
}; };
@ -159,8 +147,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_never")
TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_anything") TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_anything")
{ {
ScopedFastFlag sffs[]{ ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
{"LuauUninhabitedSubAnything2", 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") TEST_CASE_FIXTURE(TryUnifyFixture, "fuzz_tail_unification_issue")
{ {
ScopedFastFlag luauTxnLogTypePackIterator{"LuauTxnLogTypePackIterator", true};
TypePackVar variadicAny{VariadicTypePack{typeChecker.anyType}}; TypePackVar variadicAny{VariadicTypePack{typeChecker.anyType}};
TypePackVar packTmp{TypePack{{typeChecker.anyType}, &variadicAny}}; TypePackVar packTmp{TypePack{{typeChecker.anyType}, &variadicAny}};
TypePackVar packSub{TypePack{{typeChecker.anyType, typeChecker.anyType}, &packTmp}}; TypePackVar packSub{TypePack{{typeChecker.anyType, typeChecker.anyType}, &packTmp}};
@ -376,4 +360,18 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "fuzz_tail_unification_issue")
state.tryUnify(&packSub, &packSuper); 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(); 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") TEST_CASE_FIXTURE(Fixture, "fuzz_typepack_iter_follow")
{ {
ScopedFastFlag luauTxnLogTypePackIterator{"LuauTxnLogTypePackIterator", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local _ local _
local _ = _,_(),_(_) local _ = _,_(),_(_)
@ -1051,8 +1049,6 @@ local _ = _,_(),_(_)
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_typepack_iter_follow_2") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_typepack_iter_follow_2")
{ {
ScopedFastFlag luauTxnLogTypePackIterator{"LuauTxnLogTypePackIterator", true};
CheckResult result = check(R"( CheckResult result = check(R"(
function test(name, searchTerm) function test(name, searchTerm)
local found = string.find(name:lower(), searchTerm:lower()) 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") TEST_CASE_FIXTURE(Fixture, "union_true_and_false")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local x : boolean local x : boolean
local y1 : (true | false) = x -- OK 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local x : (number) -> number? local x : (number) -> number?
local y : ((number?) -> number?) | ((number) -> number) = x -- OK 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f<a,b>() function f<a,b>()
local x : (a) -> a? 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function f<a...>() function f<a...>()
local x : (number, a...) -> (number?, 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local x : (number) -> number? local x : (number) -> number?
local y : ((number?) -> number) | ((number | string) -> nil) = x -- OK 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local x : () -> (number | string) local x : () -> (number | string)
local y : (() -> number) | (() -> string) = x -- OK 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local x : (...nil) -> (...number?) local x : (...nil) -> (...number?)
local y : ((...string?) -> (...number)) | ((...number?) -> nil) = x -- OK 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local x : (number) -> () local x : (number) -> ()
local y : ((number?) -> ()) | ((...number) -> ()) = x -- OK 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics")
{ {
ScopedFastFlag sffs[]{
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local x : () -> (number?, ...number) local x : () -> (number?, ...number)
local y : (() -> (...number)) | (() -> nil) = x -- OK 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, ',') return table.concat(res, ',')
end)() == "6,8,10") 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 -- typeof and type require an argument
assert(pcall(typeof) == false) assert(pcall(typeof) == false)
assert(pcall(type) == false) assert(pcall(type) == false)

View file

@ -42,7 +42,6 @@ AutocompleteTest.type_correct_suggestion_in_argument
AutocompleteTest.type_correct_suggestion_in_table AutocompleteTest.type_correct_suggestion_in_table
BuiltinTests.aliased_string_format BuiltinTests.aliased_string_format
BuiltinTests.assert_removes_falsy_types 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_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.assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy
BuiltinTests.bad_select_should_not_crash BuiltinTests.bad_select_should_not_crash
@ -53,9 +52,6 @@ BuiltinTests.dont_add_definitions_to_persistent_types
BuiltinTests.find_capture_types BuiltinTests.find_capture_types
BuiltinTests.find_capture_types2 BuiltinTests.find_capture_types2
BuiltinTests.find_capture_types3 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.gmatch_definition
BuiltinTests.ipairs_iterator_should_infer_types_and_type_check BuiltinTests.ipairs_iterator_should_infer_types_and_type_check
BuiltinTests.match_capture_types 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
BuiltinTests.table_pack_reduce BuiltinTests.table_pack_reduce
BuiltinTests.table_pack_variadic BuiltinTests.table_pack_variadic
BuiltinTests.tonumber_returns_optional_number_type
DefinitionTests.class_definition_overload_metamethods DefinitionTests.class_definition_overload_metamethods
DefinitionTests.class_definition_string_props DefinitionTests.class_definition_string_props
DefinitionTests.declaring_generic_functions DefinitionTests.declaring_generic_functions
@ -103,7 +98,6 @@ GenericsTests.duplicate_generic_type_packs
GenericsTests.duplicate_generic_types GenericsTests.duplicate_generic_types
GenericsTests.generic_argument_count_too_few GenericsTests.generic_argument_count_too_few
GenericsTests.generic_argument_count_too_many GenericsTests.generic_argument_count_too_many
GenericsTests.generic_factories
GenericsTests.generic_functions_should_be_memory_safe GenericsTests.generic_functions_should_be_memory_safe
GenericsTests.generic_table_method GenericsTests.generic_table_method
GenericsTests.generic_type_pack_parentheses 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_dot_insert_and_recursive_calls
NonstrictModeTests.table_props_are_any NonstrictModeTests.table_props_are_any
Normalize.cyclic_table_normalizes_sensibly Normalize.cyclic_table_normalizes_sensibly
Normalize.negations_of_classes
ParseErrorRecovery.generic_type_list_recovery ParseErrorRecovery.generic_type_list_recovery
ParseErrorRecovery.recovery_of_parenthesized_expressions ParseErrorRecovery.recovery_of_parenthesized_expressions
ParserTests.parse_nesting_based_end_detection_failsafe_earlier 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.table_insert_with_a_singleton_argument
ProvisionalTests.typeguard_inference_incomplete ProvisionalTests.typeguard_inference_incomplete
ProvisionalTests.weirditer_should_not_loop_forever ProvisionalTests.weirditer_should_not_loop_forever
ProvisionalTests.while_body_are_also_refined
RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string
RefinementTest.call_an_incompatible_function_after_using_typeguard RefinementTest.call_an_incompatible_function_after_using_typeguard
RefinementTest.correctly_lookup_property_whose_base_was_previously_refined2 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_on_properties_of_disjoint_tables_where_that_property_is_true_or_false
RefinementTest.discriminate_tag RefinementTest.discriminate_tag
RefinementTest.eliminate_subclasses_of_instance
RefinementTest.else_with_no_explicit_expression_should_also_refine_the_tagged_union RefinementTest.else_with_no_explicit_expression_should_also_refine_the_tagged_union
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
RefinementTest.narrow_from_subclasses_of_instance_or_string_or_vector3
RefinementTest.narrow_property_of_a_bounded_variable RefinementTest.narrow_property_of_a_bounded_variable
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
RefinementTest.refine_a_property_not_to_be_nil_through_an_intersection_table 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_for_all_the_userdata
RefinementTest.type_narrow_to_vector RefinementTest.type_narrow_to_vector
RefinementTest.typeguard_cast_free_table_to_vector RefinementTest.typeguard_cast_free_table_to_vector
RefinementTest.typeguard_cast_instance_or_vector3_to_vector
RefinementTest.typeguard_in_assert_position RefinementTest.typeguard_in_assert_position
RefinementTest.typeguard_narrows_for_table RefinementTest.typeguard_narrows_for_table
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_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.builtin_table_names
TableTests.call_method TableTests.call_method
TableTests.call_method_with_explicit_self_argument 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_indexer3
TableTests.casting_tables_with_props_into_table_with_indexer4 TableTests.casting_tables_with_props_into_table_with_indexer4
TableTests.checked_prop_too_early 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.generic_table_instantiation_potential_regression
TableTests.getmetatable_returns_pointer_to_metatable TableTests.getmetatable_returns_pointer_to_metatable
TableTests.give_up_after_one_metatable_index_look_up 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.indexer_on_sealed_table_must_unify_with_free_table
TableTests.indexing_from_a_table_should_prefer_properties_when_possible TableTests.indexing_from_a_table_should_prefer_properties_when_possible
TableTests.inequality_operators_imply_exactly_matching_types TableTests.inequality_operators_imply_exactly_matching_types
TableTests.infer_array_2 TableTests.infer_array_2
TableTests.infer_indexer_from_value_property_in_literal
TableTests.inferred_return_type_of_free_table TableTests.inferred_return_type_of_free_table
TableTests.inferring_crazy_table_should_also_be_quick TableTests.inferring_crazy_table_should_also_be_quick
TableTests.instantiate_table_cloning_3 TableTests.instantiate_table_cloning_3
@ -243,7 +228,6 @@ TableTests.only_ascribe_synthetic_names_at_module_scope
TableTests.oop_indexer_works TableTests.oop_indexer_works
TableTests.oop_polymorphic TableTests.oop_polymorphic
TableTests.open_table_unification_2 TableTests.open_table_unification_2
TableTests.persistent_sealed_table_is_immutable
TableTests.property_lookup_through_tabletypevar_metatable TableTests.property_lookup_through_tabletypevar_metatable
TableTests.quantify_even_that_table_was_never_exported_at_all TableTests.quantify_even_that_table_was_never_exported_at_all
TableTests.quantify_metatables_of_metatables_of_table 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_always_any_if_lhs_is_any
TableTests.result_is_bool_for_equality_operators_if_lhs_is_any TableTests.result_is_bool_for_equality_operators_if_lhs_is_any
TableTests.right_table_missing_key2 TableTests.right_table_missing_key2
TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type
TableTests.shared_selfs TableTests.shared_selfs
TableTests.shared_selfs_from_free_param TableTests.shared_selfs_from_free_param
TableTests.shared_selfs_through_metatables TableTests.shared_selfs_through_metatables
@ -261,7 +244,6 @@ TableTests.table_function_check_use_after_free
TableTests.table_indexing_error_location TableTests.table_indexing_error_location
TableTests.table_insert_should_cope_with_optional_properties_in_nonstrict TableTests.table_insert_should_cope_with_optional_properties_in_nonstrict
TableTests.table_insert_should_cope_with_optional_properties_in_strict TableTests.table_insert_should_cope_with_optional_properties_in_strict
TableTests.table_param_row_polymorphism_2
TableTests.table_param_row_polymorphism_3 TableTests.table_param_row_polymorphism_3
TableTests.table_simple_call TableTests.table_simple_call
TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors 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_colon_instead_of_dot
TableTests.used_dot_instead_of_colon TableTests.used_dot_instead_of_colon
TableTests.used_dot_instead_of_colon_but_correctly TableTests.used_dot_instead_of_colon_but_correctly
ToDot.bound_table
ToDot.function
ToDot.table
ToString.exhaustive_toString_of_cyclic_table ToString.exhaustive_toString_of_cyclic_table
ToString.function_type_with_argument_names_and_self ToString.function_type_with_argument_names_and_self
ToString.function_type_with_argument_names_generic ToString.function_type_with_argument_names_generic
ToString.named_metatable_toStringNamedFunction
ToString.toStringDetailed2 ToString.toStringDetailed2
ToString.toStringErrorPack ToString.toStringErrorPack
ToString.toStringNamedFunction_generic_pack ToString.toStringNamedFunction_generic_pack
@ -297,10 +277,12 @@ TryUnifyTests.result_of_failed_typepack_unification_is_constrained
TryUnifyTests.typepack_unification_should_trim_free_tails TryUnifyTests.typepack_unification_should_trim_free_tails
TryUnifyTests.variadics_should_use_reversed_properly TryUnifyTests.variadics_should_use_reversed_properly
TypeAliases.cannot_create_cyclic_type_with_unknown_module TypeAliases.cannot_create_cyclic_type_with_unknown_module
TypeAliases.corecursive_types_generic
TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any
TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any_2 TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any_2
TypeAliases.generic_param_remap TypeAliases.generic_param_remap
TypeAliases.mismatched_generic_type_param TypeAliases.mismatched_generic_type_param
TypeAliases.mutually_recursive_types_errors
TypeAliases.mutually_recursive_types_restriction_not_ok_1 TypeAliases.mutually_recursive_types_restriction_not_ok_1
TypeAliases.mutually_recursive_types_restriction_not_ok_2 TypeAliases.mutually_recursive_types_restriction_not_ok_2
TypeAliases.mutually_recursive_types_swapsies_not_ok TypeAliases.mutually_recursive_types_swapsies_not_ok
@ -308,8 +290,6 @@ TypeAliases.recursive_types_restriction_not_ok
TypeAliases.report_shadowed_aliases TypeAliases.report_shadowed_aliases
TypeAliases.stringify_optional_parameterized_alias TypeAliases.stringify_optional_parameterized_alias
TypeAliases.stringify_type_alias_of_recursive_template_table_type 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_mutation
TypeAliases.type_alias_local_rename TypeAliases.type_alias_local_rename
TypeAliases.type_alias_of_an_imported_recursive_generic_type TypeAliases.type_alias_of_an_imported_recursive_generic_type
@ -337,15 +317,12 @@ TypeInferAnyError.metatable_of_any_can_be_a_table
TypeInferClasses.can_read_prop_of_base_class_using_string TypeInferClasses.can_read_prop_of_base_class_using_string
TypeInferClasses.class_type_mismatch_with_name_conflict TypeInferClasses.class_type_mismatch_with_name_conflict
TypeInferClasses.classes_without_overloaded_operators_cannot_be_added TypeInferClasses.classes_without_overloaded_operators_cannot_be_added
TypeInferClasses.detailed_class_unification_error
TypeInferClasses.higher_order_function_arguments_are_contravariant TypeInferClasses.higher_order_function_arguments_are_contravariant
TypeInferClasses.index_instance_property TypeInferClasses.index_instance_property
TypeInferClasses.optional_class_field_access_error TypeInferClasses.optional_class_field_access_error
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties
TypeInferClasses.warn_when_prop_almost_matches 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_anytypepack_doesnt_leak_free_types
TypeInferFunctions.calling_function_with_incorrect_argument_type_yields_errors_spanning_argument
TypeInferFunctions.cannot_hoist_interior_defns_into_signature TypeInferFunctions.cannot_hoist_interior_defns_into_signature
TypeInferFunctions.dont_give_other_overloads_message_if_only_one_argument_matching_overload_exists TypeInferFunctions.dont_give_other_overloads_message_if_only_one_argument_matching_overload_exists
TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site TypeInferFunctions.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
TypeInferFunctions.too_few_arguments_variadic_generic TypeInferFunctions.too_few_arguments_variadic_generic
TypeInferFunctions.too_few_arguments_variadic_generic2 TypeInferFunctions.too_few_arguments_variadic_generic2
TypeInferFunctions.too_many_arguments
TypeInferFunctions.too_many_arguments_error_location TypeInferFunctions.too_many_arguments_error_location
TypeInferFunctions.too_many_return_values
TypeInferFunctions.too_many_return_values_in_parentheses TypeInferFunctions.too_many_return_values_in_parentheses
TypeInferFunctions.too_many_return_values_no_function TypeInferFunctions.too_many_return_values_no_function
TypeInferFunctions.vararg_function_is_quantified TypeInferFunctions.vararg_function_is_quantified
@ -399,8 +374,6 @@ TypeInferModules.module_type_conflict_instantiated
TypeInferModules.require_a_variadic_function TypeInferModules.require_a_variadic_function
TypeInferModules.type_error_of_unknown_qualified_type 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_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.inferring_hundreds_of_self_calls_should_not_suffocate_memory
TypeInferOOP.method_depends_on_table TypeInferOOP.method_depends_on_table
TypeInferOOP.methods_are_topologically_sorted 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
TypePackTests.unify_variadic_tails_in_arguments_free TypePackTests.unify_variadic_tails_in_arguments_free
TypePackTests.variadic_packs TypePackTests.variadic_packs
TypeReductionTests.discriminable_unions
TypeReductionTests.intersections_with_negations
TypeReductionTests.negations TypeReductionTests.negations
TypeReductionTests.unions_with_negations
TypeSingletons.error_detailed_tagged_union_mismatch_bool TypeSingletons.error_detailed_tagged_union_mismatch_bool
TypeSingletons.error_detailed_tagged_union_mismatch_string TypeSingletons.error_detailed_tagged_union_mismatch_string
TypeSingletons.function_call_with_singletons TypeSingletons.function_call_with_singletons