mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
Sync to upstream/release/567 (#860)
* Fix #817 * Fix #850 * Optimize math.floor/ceil/round with SSE4.1 * Results in a ~7-9% speedup on the math-cordic benchmark. * Optimized table.sort. * table.sort is now ~4.1x faster (when not using a predicate) and ~2.1x faster when using a simple predicate. Performance may improve further in the future. * Reorganize the memory ownership of builtin type definitions. * This is a small initial step toward affording parallel typechecking. The new type solver is coming along nicely. We are working on fixing crashes and bugs. A few major changes to native codegen landed this week: * Fixed lowering of Luau IR mod instruction when first argument is a constant * Added VM register data-flow/capture analysis * Fixed issues with optimizations in unreachable blocks --------- Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
parent
78798d4641
commit
1fa8311a18
@ -10,12 +10,13 @@ namespace Luau
|
||||
{
|
||||
|
||||
struct Frontend;
|
||||
struct GlobalTypes;
|
||||
struct TypeChecker;
|
||||
struct TypeArena;
|
||||
|
||||
void registerBuiltinTypes(Frontend& frontend);
|
||||
void registerBuiltinTypes(GlobalTypes& globals);
|
||||
|
||||
void registerBuiltinGlobals(TypeChecker& typeChecker);
|
||||
void registerBuiltinGlobals(TypeChecker& typeChecker, GlobalTypes& globals);
|
||||
void registerBuiltinGlobals(Frontend& frontend);
|
||||
|
||||
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types);
|
||||
@ -23,8 +24,7 @@ TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types);
|
||||
|
||||
/** Build an optional 't'
|
||||
*/
|
||||
TypeId makeOption(TypeChecker& typeChecker, TypeArena& arena, TypeId t);
|
||||
TypeId makeOption(Frontend& frontend, TypeArena& arena, TypeId t);
|
||||
TypeId makeOption(NotNull<BuiltinTypes> builtinTypes, TypeArena& arena, TypeId t);
|
||||
|
||||
/** Small utility function for building up type definitions from C++.
|
||||
*/
|
||||
@ -52,17 +52,12 @@ void assignPropDocumentationSymbols(TableType::Props& props, const std::string&
|
||||
|
||||
std::string getBuiltinDefinitionSource();
|
||||
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, Binding binding);
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, TypeId ty, const std::string& packageName);
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName);
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, Binding binding);
|
||||
void addGlobalBinding(Frontend& frontend, const std::string& name, TypeId ty, const std::string& packageName);
|
||||
void addGlobalBinding(Frontend& frontend, const std::string& name, Binding binding);
|
||||
void addGlobalBinding(Frontend& frontend, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName);
|
||||
void addGlobalBinding(Frontend& frontend, const ScopePtr& scope, const std::string& name, Binding binding);
|
||||
std::optional<Binding> tryGetGlobalBinding(Frontend& frontend, const std::string& name);
|
||||
Binding* tryGetGlobalBindingRef(TypeChecker& typeChecker, const std::string& name);
|
||||
TypeId getGlobalBinding(Frontend& frontend, const std::string& name);
|
||||
TypeId getGlobalBinding(TypeChecker& typeChecker, const std::string& name);
|
||||
void addGlobalBinding(GlobalTypes& globals, const std::string& name, TypeId ty, const std::string& packageName);
|
||||
void addGlobalBinding(GlobalTypes& globals, const std::string& name, Binding binding);
|
||||
void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName);
|
||||
void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, Binding binding);
|
||||
std::optional<Binding> tryGetGlobalBinding(GlobalTypes& globals, const std::string& name);
|
||||
Binding* tryGetGlobalBindingRef(GlobalTypes& globals, const std::string& name);
|
||||
TypeId getGlobalBinding(GlobalTypes& globals, const std::string& name);
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -411,8 +411,8 @@ struct InternalErrorReporter
|
||||
std::function<void(const char*)> onInternalError;
|
||||
std::string moduleName;
|
||||
|
||||
[[noreturn]] void ice(const std::string& message, const Location& location);
|
||||
[[noreturn]] void ice(const std::string& message);
|
||||
[[noreturn]] void ice(const std::string& message, const Location& location) const;
|
||||
[[noreturn]] void ice(const std::string& message) const;
|
||||
};
|
||||
|
||||
class InternalCompilerError : public std::exception
|
||||
|
@ -21,6 +21,7 @@ class ParseError;
|
||||
struct Frontend;
|
||||
struct TypeError;
|
||||
struct LintWarning;
|
||||
struct GlobalTypes;
|
||||
struct TypeChecker;
|
||||
struct FileResolver;
|
||||
struct ModuleResolver;
|
||||
@ -31,11 +32,12 @@ struct LoadDefinitionFileResult
|
||||
{
|
||||
bool success;
|
||||
ParseResult parseResult;
|
||||
SourceModule sourceModule;
|
||||
ModulePtr module;
|
||||
};
|
||||
|
||||
LoadDefinitionFileResult loadDefinitionFile(
|
||||
TypeChecker& typeChecker, ScopePtr targetScope, std::string_view definition, const std::string& packageName);
|
||||
LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, GlobalTypes& globals, ScopePtr targetScope, std::string_view definition,
|
||||
const std::string& packageName, bool captureComments);
|
||||
|
||||
std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments);
|
||||
|
||||
@ -152,14 +154,12 @@ struct Frontend
|
||||
void clear();
|
||||
|
||||
ScopePtr addEnvironment(const std::string& environmentName);
|
||||
ScopePtr getEnvironmentScope(const std::string& environmentName);
|
||||
ScopePtr getEnvironmentScope(const std::string& environmentName) const;
|
||||
|
||||
void registerBuiltinDefinition(const std::string& name, std::function<void(TypeChecker&, ScopePtr)>);
|
||||
void registerBuiltinDefinition(const std::string& name, std::function<void(TypeChecker&, GlobalTypes&, ScopePtr)>);
|
||||
void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName);
|
||||
|
||||
LoadDefinitionFileResult loadDefinitionFile(std::string_view source, const std::string& packageName);
|
||||
|
||||
ScopePtr getGlobalScope();
|
||||
LoadDefinitionFileResult loadDefinitionFile(std::string_view source, const std::string& packageName, bool captureComments);
|
||||
|
||||
private:
|
||||
ModulePtr check(const SourceModule& sourceModule, Mode mode, std::vector<RequireCycle> requireCycles, bool forAutocomplete = false, bool recordJsonLog = false);
|
||||
@ -171,10 +171,10 @@ private:
|
||||
|
||||
static LintResult classifyLints(const std::vector<LintWarning>& warnings, const Config& config);
|
||||
|
||||
ScopePtr getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete);
|
||||
ScopePtr getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const;
|
||||
|
||||
std::unordered_map<std::string, ScopePtr> environments;
|
||||
std::unordered_map<std::string, std::function<void(TypeChecker&, ScopePtr)>> builtinDefinitions;
|
||||
std::unordered_map<std::string, std::function<void(TypeChecker&, GlobalTypes&, ScopePtr)>> builtinDefinitions;
|
||||
|
||||
BuiltinTypes builtinTypes_;
|
||||
|
||||
@ -184,21 +184,19 @@ public:
|
||||
FileResolver* fileResolver;
|
||||
FrontendModuleResolver moduleResolver;
|
||||
FrontendModuleResolver moduleResolverForAutocomplete;
|
||||
GlobalTypes globals;
|
||||
GlobalTypes globalsForAutocomplete;
|
||||
TypeChecker typeChecker;
|
||||
TypeChecker typeCheckerForAutocomplete;
|
||||
ConfigResolver* configResolver;
|
||||
FrontendOptions options;
|
||||
InternalErrorReporter iceHandler;
|
||||
TypeArena globalTypes;
|
||||
|
||||
std::unordered_map<ModuleName, SourceNode> sourceNodes;
|
||||
std::unordered_map<ModuleName, SourceModule> sourceModules;
|
||||
std::unordered_map<ModuleName, RequireTraceResult> requireTrace;
|
||||
|
||||
Stats stats = {};
|
||||
|
||||
private:
|
||||
ScopePtr globalScope;
|
||||
};
|
||||
|
||||
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
|
@ -10,6 +10,6 @@ struct TypeArena;
|
||||
struct Scope;
|
||||
|
||||
void quantify(TypeId ty, TypeLevel level);
|
||||
TypeId quantify(TypeArena* arena, TypeId ty, Scope* scope);
|
||||
std::optional<TypeId> quantify(TypeArena* arena, TypeId ty, Scope* scope);
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -640,10 +640,10 @@ struct BuiltinTypes
|
||||
BuiltinTypes(const BuiltinTypes&) = delete;
|
||||
void operator=(const BuiltinTypes&) = delete;
|
||||
|
||||
TypeId errorRecoveryType(TypeId guess);
|
||||
TypePackId errorRecoveryTypePack(TypePackId guess);
|
||||
TypeId errorRecoveryType();
|
||||
TypePackId errorRecoveryTypePack();
|
||||
TypeId errorRecoveryType(TypeId guess) const;
|
||||
TypePackId errorRecoveryTypePack(TypePackId guess) const;
|
||||
TypeId errorRecoveryType() const;
|
||||
TypePackId errorRecoveryTypePack() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<struct TypeArena> arena;
|
||||
|
@ -63,11 +63,22 @@ enum class ValueContext
|
||||
RValue
|
||||
};
|
||||
|
||||
struct GlobalTypes
|
||||
{
|
||||
GlobalTypes(NotNull<BuiltinTypes> builtinTypes);
|
||||
|
||||
NotNull<BuiltinTypes> builtinTypes; // Global types are based on builtin types
|
||||
|
||||
TypeArena globalTypes;
|
||||
SourceModule globalNames; // names for symbols entered into globalScope
|
||||
ScopePtr globalScope; // shared by all modules
|
||||
};
|
||||
|
||||
// All Types are retained via Environment::types. All TypeIds
|
||||
// within a program are borrowed pointers into this set.
|
||||
struct TypeChecker
|
||||
{
|
||||
explicit TypeChecker(ModuleResolver* resolver, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter* iceHandler);
|
||||
explicit TypeChecker(const GlobalTypes& globals, ModuleResolver* resolver, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter* iceHandler);
|
||||
TypeChecker(const TypeChecker&) = delete;
|
||||
TypeChecker& operator=(const TypeChecker&) = delete;
|
||||
|
||||
@ -355,11 +366,10 @@ public:
|
||||
*/
|
||||
std::vector<TypeId> unTypePack(const ScopePtr& scope, TypePackId pack, size_t expectedLength, const Location& location);
|
||||
|
||||
TypeArena globalTypes;
|
||||
// TODO: only const version of global scope should be available to make sure nothing else is modified inside of from users of TypeChecker
|
||||
const GlobalTypes& globals;
|
||||
|
||||
ModuleResolver* resolver;
|
||||
SourceModule globalNames; // names for symbols entered into globalScope
|
||||
ScopePtr globalScope; // shared by all modules
|
||||
ModulePtr currentModule;
|
||||
ModuleName currentModuleName;
|
||||
|
||||
|
@ -90,6 +90,11 @@ struct Unifier
|
||||
private:
|
||||
void tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false);
|
||||
void tryUnifyUnionWithType(TypeId subTy, const UnionType* uv, TypeId superTy);
|
||||
|
||||
// Traverse the two types provided and block on any BlockedTypes we find.
|
||||
// Returns true if any types were blocked on.
|
||||
bool blockOnBlockedTypes(TypeId subTy, TypeId superTy);
|
||||
|
||||
void tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionType* uv, bool cacheEnabled, bool isFunctionCall);
|
||||
void tryUnifyTypeWithIntersection(TypeId subTy, TypeId superTy, const IntersectionType* uv);
|
||||
void tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType* uv, TypeId superTy, bool cacheEnabled, bool isFunctionCall);
|
||||
|
@ -12,7 +12,6 @@
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauCompleteTableKeysBetter);
|
||||
LUAU_FASTFLAGVARIABLE(SupportTypeAliasGoToDeclaration, false);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -194,19 +193,12 @@ struct FindFullAncestry final : public AstVisitor
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
|
@ -14,8 +14,6 @@
|
||||
#include <utility>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompleteTableKeysBetter, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInWhile, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInFor, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteSkipNormalization, false);
|
||||
|
||||
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
||||
@ -1424,8 +1422,6 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
else if (AstStatFor* statFor = extractStat<AstStatFor>(ancestry))
|
||||
{
|
||||
if (!statFor->hasDo || position < statFor->doLocation.begin)
|
||||
{
|
||||
if (FFlag::LuauFixAutocompleteInFor)
|
||||
{
|
||||
if (statFor->from->location.containsClosed(position) || statFor->to->location.containsClosed(position) ||
|
||||
(statFor->step && statFor->step->location.containsClosed(position)))
|
||||
@ -1433,16 +1429,6 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
|
||||
if (!statFor->from->is<AstExprError>() && !statFor->to->is<AstExprError>() && (!statFor->step || !statFor->step->is<AstExprError>()))
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!statFor->from->is<AstExprError>() && !statFor->to->is<AstExprError>() && (!statFor->step || !statFor->step->is<AstExprError>()))
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
||||
|
||||
if (statFor->from->location.containsClosed(position) || statFor->to->location.containsClosed(position) ||
|
||||
(statFor->step && statFor->step->location.containsClosed(position)))
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -1492,16 +1478,9 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
else if (AstStatWhile* statWhile = parent->as<AstStatWhile>(); node->is<AstStatBlock>() && statWhile)
|
||||
{
|
||||
if (!statWhile->hasDo && !statWhile->condition->is<AstStatError>() && position > statWhile->condition->location.end)
|
||||
{
|
||||
if (FFlag::LuauFixAutocompleteInWhile)
|
||||
{
|
||||
return autocompleteWhileLoopKeywords(ancestry);
|
||||
}
|
||||
else
|
||||
{
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
||||
}
|
||||
}
|
||||
|
||||
if (!statWhile->hasDo || position < statWhile->doLocation.begin)
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
||||
@ -1511,19 +1490,11 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
}
|
||||
|
||||
else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(ancestry);
|
||||
FFlag::LuauFixAutocompleteInWhile ? (statWhile && (!statWhile->hasDo || statWhile->doLocation.containsClosed(position)) &&
|
||||
statWhile->condition && !statWhile->condition->location.containsClosed(position))
|
||||
: (statWhile && !statWhile->hasDo))
|
||||
{
|
||||
if (FFlag::LuauFixAutocompleteInWhile)
|
||||
(statWhile && (!statWhile->hasDo || statWhile->doLocation.containsClosed(position)) && statWhile->condition &&
|
||||
!statWhile->condition->location.containsClosed(position)))
|
||||
{
|
||||
return autocompleteWhileLoopKeywords(ancestry);
|
||||
}
|
||||
else
|
||||
{
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
||||
}
|
||||
}
|
||||
else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->elseLocation.has_value())
|
||||
{
|
||||
return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}},
|
||||
@ -1672,7 +1643,7 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName
|
||||
return {};
|
||||
|
||||
NotNull<BuiltinTypes> builtinTypes = frontend.builtinTypes;
|
||||
Scope* globalScope = frontend.typeCheckerForAutocomplete.globalScope.get();
|
||||
Scope* globalScope = frontend.globalsForAutocomplete.globalScope.get();
|
||||
|
||||
TypeArena typeArena;
|
||||
return autocomplete(*sourceModule, module, builtinTypes, &typeArena, globalScope, position, callback);
|
||||
|
@ -52,14 +52,9 @@ TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types)
|
||||
return arena.addType(IntersectionType{std::move(types)});
|
||||
}
|
||||
|
||||
TypeId makeOption(Frontend& frontend, TypeArena& arena, TypeId t)
|
||||
TypeId makeOption(NotNull<BuiltinTypes> builtinTypes, TypeArena& arena, TypeId t)
|
||||
{
|
||||
return makeUnion(arena, {frontend.typeChecker.nilType, t});
|
||||
}
|
||||
|
||||
TypeId makeOption(TypeChecker& typeChecker, TypeArena& arena, TypeId t)
|
||||
{
|
||||
return makeUnion(arena, {typeChecker.nilType, t});
|
||||
return makeUnion(arena, {builtinTypes->nilType, t});
|
||||
}
|
||||
|
||||
TypeId makeFunction(
|
||||
@ -148,85 +143,52 @@ Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol)
|
||||
};
|
||||
}
|
||||
|
||||
void addGlobalBinding(Frontend& frontend, const std::string& name, TypeId ty, const std::string& packageName)
|
||||
void addGlobalBinding(GlobalTypes& globals, const std::string& name, TypeId ty, const std::string& packageName)
|
||||
{
|
||||
addGlobalBinding(frontend, frontend.getGlobalScope(), name, ty, packageName);
|
||||
addGlobalBinding(globals, globals.globalScope, name, ty, packageName);
|
||||
}
|
||||
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName);
|
||||
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, TypeId ty, const std::string& packageName)
|
||||
void addGlobalBinding(GlobalTypes& globals, const std::string& name, Binding binding)
|
||||
{
|
||||
addGlobalBinding(typeChecker, typeChecker.globalScope, name, ty, packageName);
|
||||
addGlobalBinding(globals, globals.globalScope, name, binding);
|
||||
}
|
||||
|
||||
void addGlobalBinding(Frontend& frontend, const std::string& name, Binding binding)
|
||||
{
|
||||
addGlobalBinding(frontend, frontend.getGlobalScope(), name, binding);
|
||||
}
|
||||
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, Binding binding)
|
||||
{
|
||||
addGlobalBinding(typeChecker, typeChecker.globalScope, name, binding);
|
||||
}
|
||||
|
||||
void addGlobalBinding(Frontend& frontend, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName)
|
||||
void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName)
|
||||
{
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
addGlobalBinding(frontend, scope, name, Binding{ty, Location{}, {}, {}, documentationSymbol});
|
||||
addGlobalBinding(globals, scope, name, Binding{ty, Location{}, {}, {}, documentationSymbol});
|
||||
}
|
||||
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName)
|
||||
void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, Binding binding)
|
||||
{
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
addGlobalBinding(typeChecker, scope, name, Binding{ty, Location{}, {}, {}, documentationSymbol});
|
||||
scope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = binding;
|
||||
}
|
||||
|
||||
void addGlobalBinding(Frontend& frontend, const ScopePtr& scope, const std::string& name, Binding binding)
|
||||
std::optional<Binding> tryGetGlobalBinding(GlobalTypes& globals, const std::string& name)
|
||||
{
|
||||
addGlobalBinding(frontend.typeChecker, scope, name, binding);
|
||||
}
|
||||
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, Binding binding)
|
||||
{
|
||||
scope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = binding;
|
||||
}
|
||||
|
||||
std::optional<Binding> tryGetGlobalBinding(TypeChecker& typeChecker, const std::string& name)
|
||||
{
|
||||
AstName astName = typeChecker.globalNames.names->getOrAdd(name.c_str());
|
||||
auto it = typeChecker.globalScope->bindings.find(astName);
|
||||
if (it != typeChecker.globalScope->bindings.end())
|
||||
AstName astName = globals.globalNames.names->getOrAdd(name.c_str());
|
||||
auto it = globals.globalScope->bindings.find(astName);
|
||||
if (it != globals.globalScope->bindings.end())
|
||||
return it->second;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TypeId getGlobalBinding(TypeChecker& typeChecker, const std::string& name)
|
||||
TypeId getGlobalBinding(GlobalTypes& globals, const std::string& name)
|
||||
{
|
||||
auto t = tryGetGlobalBinding(typeChecker, name);
|
||||
auto t = tryGetGlobalBinding(globals, name);
|
||||
LUAU_ASSERT(t.has_value());
|
||||
return t->typeId;
|
||||
}
|
||||
|
||||
TypeId getGlobalBinding(Frontend& frontend, const std::string& name)
|
||||
Binding* tryGetGlobalBindingRef(GlobalTypes& globals, const std::string& name)
|
||||
{
|
||||
return getGlobalBinding(frontend.typeChecker, name);
|
||||
}
|
||||
|
||||
std::optional<Binding> tryGetGlobalBinding(Frontend& frontend, const std::string& name)
|
||||
{
|
||||
return tryGetGlobalBinding(frontend.typeChecker, name);
|
||||
}
|
||||
|
||||
Binding* tryGetGlobalBindingRef(TypeChecker& typeChecker, const std::string& name)
|
||||
{
|
||||
AstName astName = typeChecker.globalNames.names->get(name.c_str());
|
||||
AstName astName = globals.globalNames.names->get(name.c_str());
|
||||
if (astName == AstName())
|
||||
return nullptr;
|
||||
|
||||
auto it = typeChecker.globalScope->bindings.find(astName);
|
||||
if (it != typeChecker.globalScope->bindings.end())
|
||||
auto it = globals.globalScope->bindings.find(astName);
|
||||
if (it != globals.globalScope->bindings.end())
|
||||
return &it->second;
|
||||
|
||||
return nullptr;
|
||||
@ -240,34 +202,33 @@ void assignPropDocumentationSymbols(TableType::Props& props, const std::string&
|
||||
}
|
||||
}
|
||||
|
||||
void registerBuiltinTypes(Frontend& frontend)
|
||||
void registerBuiltinTypes(GlobalTypes& globals)
|
||||
{
|
||||
frontend.getGlobalScope()->addBuiltinTypeBinding("any", TypeFun{{}, frontend.builtinTypes->anyType});
|
||||
frontend.getGlobalScope()->addBuiltinTypeBinding("nil", TypeFun{{}, frontend.builtinTypes->nilType});
|
||||
frontend.getGlobalScope()->addBuiltinTypeBinding("number", TypeFun{{}, frontend.builtinTypes->numberType});
|
||||
frontend.getGlobalScope()->addBuiltinTypeBinding("string", TypeFun{{}, frontend.builtinTypes->stringType});
|
||||
frontend.getGlobalScope()->addBuiltinTypeBinding("boolean", TypeFun{{}, frontend.builtinTypes->booleanType});
|
||||
frontend.getGlobalScope()->addBuiltinTypeBinding("thread", TypeFun{{}, frontend.builtinTypes->threadType});
|
||||
frontend.getGlobalScope()->addBuiltinTypeBinding("unknown", TypeFun{{}, frontend.builtinTypes->unknownType});
|
||||
frontend.getGlobalScope()->addBuiltinTypeBinding("never", TypeFun{{}, frontend.builtinTypes->neverType});
|
||||
globals.globalScope->addBuiltinTypeBinding("any", TypeFun{{}, globals.builtinTypes->anyType});
|
||||
globals.globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, globals.builtinTypes->nilType});
|
||||
globals.globalScope->addBuiltinTypeBinding("number", TypeFun{{}, globals.builtinTypes->numberType});
|
||||
globals.globalScope->addBuiltinTypeBinding("string", TypeFun{{}, globals.builtinTypes->stringType});
|
||||
globals.globalScope->addBuiltinTypeBinding("boolean", TypeFun{{}, globals.builtinTypes->booleanType});
|
||||
globals.globalScope->addBuiltinTypeBinding("thread", TypeFun{{}, globals.builtinTypes->threadType});
|
||||
globals.globalScope->addBuiltinTypeBinding("unknown", TypeFun{{}, globals.builtinTypes->unknownType});
|
||||
globals.globalScope->addBuiltinTypeBinding("never", TypeFun{{}, globals.builtinTypes->neverType});
|
||||
}
|
||||
|
||||
void registerBuiltinGlobals(TypeChecker& typeChecker)
|
||||
void registerBuiltinGlobals(TypeChecker& typeChecker, GlobalTypes& globals)
|
||||
{
|
||||
LUAU_ASSERT(!typeChecker.globalTypes.types.isFrozen());
|
||||
LUAU_ASSERT(!typeChecker.globalTypes.typePacks.isFrozen());
|
||||
LUAU_ASSERT(!globals.globalTypes.types.isFrozen());
|
||||
LUAU_ASSERT(!globals.globalTypes.typePacks.isFrozen());
|
||||
|
||||
TypeId nilType = typeChecker.nilType;
|
||||
TypeArena& arena = globals.globalTypes;
|
||||
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
|
||||
|
||||
TypeArena& arena = typeChecker.globalTypes;
|
||||
NotNull<BuiltinTypes> builtinTypes = typeChecker.builtinTypes;
|
||||
|
||||
LoadDefinitionFileResult loadResult = Luau::loadDefinitionFile(typeChecker, typeChecker.globalScope, getBuiltinDefinitionSource(), "@luau");
|
||||
LoadDefinitionFileResult loadResult =
|
||||
Luau::loadDefinitionFile(typeChecker, globals, globals.globalScope, getBuiltinDefinitionSource(), "@luau", /* captureComments */ false);
|
||||
LUAU_ASSERT(loadResult.success);
|
||||
|
||||
TypeId genericK = arena.addType(GenericType{"K"});
|
||||
TypeId genericV = arena.addType(GenericType{"V"});
|
||||
TypeId mapOfKtoV = arena.addType(TableType{{}, TableIndexer(genericK, genericV), typeChecker.globalScope->level, TableState::Generic});
|
||||
TypeId mapOfKtoV = arena.addType(TableType{{}, TableIndexer(genericK, genericV), globals.globalScope->level, TableState::Generic});
|
||||
|
||||
std::optional<TypeId> stringMetatableTy = getMetatable(builtinTypes->stringType, builtinTypes);
|
||||
LUAU_ASSERT(stringMetatableTy);
|
||||
@ -277,33 +238,33 @@ void registerBuiltinGlobals(TypeChecker& typeChecker)
|
||||
auto it = stringMetatableTable->props.find("__index");
|
||||
LUAU_ASSERT(it != stringMetatableTable->props.end());
|
||||
|
||||
addGlobalBinding(typeChecker, "string", it->second.type, "@luau");
|
||||
addGlobalBinding(globals, "string", it->second.type, "@luau");
|
||||
|
||||
// next<K, V>(t: Table<K, V>, i: K?) -> (K?, V)
|
||||
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(typeChecker, arena, genericK)}});
|
||||
TypePackId nextRetsTypePack = arena.addTypePack(TypePack{{makeOption(typeChecker, arena, genericK), genericV}});
|
||||
addGlobalBinding(typeChecker, "next", arena.addType(FunctionType{{genericK, genericV}, {}, nextArgsTypePack, nextRetsTypePack}), "@luau");
|
||||
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(builtinTypes, arena, genericK)}});
|
||||
TypePackId nextRetsTypePack = arena.addTypePack(TypePack{{makeOption(builtinTypes, arena, genericK), genericV}});
|
||||
addGlobalBinding(globals, "next", arena.addType(FunctionType{{genericK, genericV}, {}, nextArgsTypePack, nextRetsTypePack}), "@luau");
|
||||
|
||||
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
|
||||
|
||||
TypeId pairsNext = arena.addType(FunctionType{nextArgsTypePack, nextRetsTypePack});
|
||||
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, nilType}});
|
||||
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, builtinTypes->nilType}});
|
||||
|
||||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
|
||||
addGlobalBinding(typeChecker, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
addGlobalBinding(globals, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
|
||||
TypeId genericMT = arena.addType(GenericType{"MT"});
|
||||
|
||||
TableType tab{TableState::Generic, typeChecker.globalScope->level};
|
||||
TableType tab{TableState::Generic, globals.globalScope->level};
|
||||
TypeId tabTy = arena.addType(tab);
|
||||
|
||||
TypeId tableMetaMT = arena.addType(MetatableType{tabTy, genericMT});
|
||||
|
||||
addGlobalBinding(typeChecker, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau");
|
||||
addGlobalBinding(globals, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau");
|
||||
|
||||
// clang-format off
|
||||
// setmetatable<T: {}, MT>(T, MT) -> { @metatable MT, T }
|
||||
addGlobalBinding(typeChecker, "setmetatable",
|
||||
addGlobalBinding(globals, "setmetatable",
|
||||
arena.addType(
|
||||
FunctionType{
|
||||
{genericMT},
|
||||
@ -315,7 +276,7 @@ void registerBuiltinGlobals(TypeChecker& typeChecker)
|
||||
);
|
||||
// clang-format on
|
||||
|
||||
for (const auto& pair : typeChecker.globalScope->bindings)
|
||||
for (const auto& pair : globals.globalScope->bindings)
|
||||
{
|
||||
persist(pair.second.typeId);
|
||||
|
||||
@ -326,12 +287,12 @@ void registerBuiltinGlobals(TypeChecker& typeChecker)
|
||||
}
|
||||
}
|
||||
|
||||
attachMagicFunction(getGlobalBinding(typeChecker, "assert"), magicFunctionAssert);
|
||||
attachMagicFunction(getGlobalBinding(typeChecker, "setmetatable"), magicFunctionSetMetaTable);
|
||||
attachMagicFunction(getGlobalBinding(typeChecker, "select"), magicFunctionSelect);
|
||||
attachDcrMagicFunction(getGlobalBinding(typeChecker, "select"), dcrMagicFunctionSelect);
|
||||
attachMagicFunction(getGlobalBinding(globals, "assert"), magicFunctionAssert);
|
||||
attachMagicFunction(getGlobalBinding(globals, "setmetatable"), magicFunctionSetMetaTable);
|
||||
attachMagicFunction(getGlobalBinding(globals, "select"), magicFunctionSelect);
|
||||
attachDcrMagicFunction(getGlobalBinding(globals, "select"), dcrMagicFunctionSelect);
|
||||
|
||||
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(typeChecker, "table")))
|
||||
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(globals, "table")))
|
||||
{
|
||||
// tabTy is a generic table type which we can't express via declaration syntax yet
|
||||
ttv->props["freeze"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.freeze");
|
||||
@ -349,26 +310,28 @@ void registerBuiltinGlobals(TypeChecker& typeChecker)
|
||||
attachDcrMagicFunction(ttv->props["pack"].type, dcrMagicFunctionPack);
|
||||
}
|
||||
|
||||
attachMagicFunction(getGlobalBinding(typeChecker, "require"), magicFunctionRequire);
|
||||
attachDcrMagicFunction(getGlobalBinding(typeChecker, "require"), dcrMagicFunctionRequire);
|
||||
attachMagicFunction(getGlobalBinding(globals, "require"), magicFunctionRequire);
|
||||
attachDcrMagicFunction(getGlobalBinding(globals, "require"), dcrMagicFunctionRequire);
|
||||
}
|
||||
|
||||
void registerBuiltinGlobals(Frontend& frontend)
|
||||
{
|
||||
LUAU_ASSERT(!frontend.globalTypes.types.isFrozen());
|
||||
LUAU_ASSERT(!frontend.globalTypes.typePacks.isFrozen());
|
||||
GlobalTypes& globals = frontend.globals;
|
||||
|
||||
registerBuiltinTypes(frontend);
|
||||
LUAU_ASSERT(!globals.globalTypes.types.isFrozen());
|
||||
LUAU_ASSERT(!globals.globalTypes.typePacks.isFrozen());
|
||||
|
||||
TypeArena& arena = frontend.globalTypes;
|
||||
NotNull<BuiltinTypes> builtinTypes = frontend.builtinTypes;
|
||||
registerBuiltinTypes(globals);
|
||||
|
||||
LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile(getBuiltinDefinitionSource(), "@luau");
|
||||
TypeArena& arena = globals.globalTypes;
|
||||
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
|
||||
|
||||
LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile(getBuiltinDefinitionSource(), "@luau", /* captureComments */ false);
|
||||
LUAU_ASSERT(loadResult.success);
|
||||
|
||||
TypeId genericK = arena.addType(GenericType{"K"});
|
||||
TypeId genericV = arena.addType(GenericType{"V"});
|
||||
TypeId mapOfKtoV = arena.addType(TableType{{}, TableIndexer(genericK, genericV), frontend.getGlobalScope()->level, TableState::Generic});
|
||||
TypeId mapOfKtoV = arena.addType(TableType{{}, TableIndexer(genericK, genericV), globals.globalScope->level, TableState::Generic});
|
||||
|
||||
std::optional<TypeId> stringMetatableTy = getMetatable(builtinTypes->stringType, builtinTypes);
|
||||
LUAU_ASSERT(stringMetatableTy);
|
||||
@ -378,33 +341,33 @@ void registerBuiltinGlobals(Frontend& frontend)
|
||||
auto it = stringMetatableTable->props.find("__index");
|
||||
LUAU_ASSERT(it != stringMetatableTable->props.end());
|
||||
|
||||
addGlobalBinding(frontend, "string", it->second.type, "@luau");
|
||||
addGlobalBinding(globals, "string", it->second.type, "@luau");
|
||||
|
||||
// next<K, V>(t: Table<K, V>, i: K?) -> (K?, V)
|
||||
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(frontend, arena, genericK)}});
|
||||
TypePackId nextRetsTypePack = arena.addTypePack(TypePack{{makeOption(frontend, arena, genericK), genericV}});
|
||||
addGlobalBinding(frontend, "next", arena.addType(FunctionType{{genericK, genericV}, {}, nextArgsTypePack, nextRetsTypePack}), "@luau");
|
||||
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(builtinTypes, arena, genericK)}});
|
||||
TypePackId nextRetsTypePack = arena.addTypePack(TypePack{{makeOption(builtinTypes, arena, genericK), genericV}});
|
||||
addGlobalBinding(globals, "next", arena.addType(FunctionType{{genericK, genericV}, {}, nextArgsTypePack, nextRetsTypePack}), "@luau");
|
||||
|
||||
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
|
||||
|
||||
TypeId pairsNext = arena.addType(FunctionType{nextArgsTypePack, nextRetsTypePack});
|
||||
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, frontend.builtinTypes->nilType}});
|
||||
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, builtinTypes->nilType}});
|
||||
|
||||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K?, V), Table<K, V>, nil)
|
||||
addGlobalBinding(frontend, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
addGlobalBinding(globals, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
|
||||
TypeId genericMT = arena.addType(GenericType{"MT"});
|
||||
|
||||
TableType tab{TableState::Generic, frontend.getGlobalScope()->level};
|
||||
TableType tab{TableState::Generic, globals.globalScope->level};
|
||||
TypeId tabTy = arena.addType(tab);
|
||||
|
||||
TypeId tableMetaMT = arena.addType(MetatableType{tabTy, genericMT});
|
||||
|
||||
addGlobalBinding(frontend, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau");
|
||||
addGlobalBinding(globals, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau");
|
||||
|
||||
// clang-format off
|
||||
// setmetatable<T: {}, MT>(T, MT) -> { @metatable MT, T }
|
||||
addGlobalBinding(frontend, "setmetatable",
|
||||
addGlobalBinding(globals, "setmetatable",
|
||||
arena.addType(
|
||||
FunctionType{
|
||||
{genericMT},
|
||||
@ -416,7 +379,7 @@ void registerBuiltinGlobals(Frontend& frontend)
|
||||
);
|
||||
// clang-format on
|
||||
|
||||
for (const auto& pair : frontend.getGlobalScope()->bindings)
|
||||
for (const auto& pair : globals.globalScope->bindings)
|
||||
{
|
||||
persist(pair.second.typeId);
|
||||
|
||||
@ -427,12 +390,12 @@ void registerBuiltinGlobals(Frontend& frontend)
|
||||
}
|
||||
}
|
||||
|
||||
attachMagicFunction(getGlobalBinding(frontend, "assert"), magicFunctionAssert);
|
||||
attachMagicFunction(getGlobalBinding(frontend, "setmetatable"), magicFunctionSetMetaTable);
|
||||
attachMagicFunction(getGlobalBinding(frontend, "select"), magicFunctionSelect);
|
||||
attachDcrMagicFunction(getGlobalBinding(frontend, "select"), dcrMagicFunctionSelect);
|
||||
attachMagicFunction(getGlobalBinding(globals, "assert"), magicFunctionAssert);
|
||||
attachMagicFunction(getGlobalBinding(globals, "setmetatable"), magicFunctionSetMetaTable);
|
||||
attachMagicFunction(getGlobalBinding(globals, "select"), magicFunctionSelect);
|
||||
attachDcrMagicFunction(getGlobalBinding(globals, "select"), dcrMagicFunctionSelect);
|
||||
|
||||
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(frontend, "table")))
|
||||
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(globals, "table")))
|
||||
{
|
||||
// tabTy is a generic table type which we can't express via declaration syntax yet
|
||||
ttv->props["freeze"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.freeze");
|
||||
@ -449,8 +412,8 @@ void registerBuiltinGlobals(Frontend& frontend)
|
||||
attachMagicFunction(ttv->props["pack"].type, magicFunctionPack);
|
||||
}
|
||||
|
||||
attachMagicFunction(getGlobalBinding(frontend, "require"), magicFunctionRequire);
|
||||
attachDcrMagicFunction(getGlobalBinding(frontend, "require"), dcrMagicFunctionRequire);
|
||||
attachMagicFunction(getGlobalBinding(globals, "require"), magicFunctionRequire);
|
||||
attachDcrMagicFunction(getGlobalBinding(globals, "require"), dcrMagicFunctionRequire);
|
||||
}
|
||||
|
||||
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
|
||||
|
@ -18,7 +18,6 @@
|
||||
LUAU_FASTINT(LuauCheckRecursionLimit);
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||
LUAU_FASTFLAG(LuauNegatedClassTypes);
|
||||
LUAU_FASTFLAG(SupportTypeAliasGoToDeclaration);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -587,8 +586,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
|
||||
if (ModulePtr module = moduleResolver->getModule(moduleInfo->name))
|
||||
{
|
||||
scope->importedTypeBindings[name] = module->exportedTypeBindings;
|
||||
if (FFlag::SupportTypeAliasGoToDeclaration)
|
||||
scope->importedModules[name] = moduleName;
|
||||
scope->importedModules[name] = moduleInfo->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -521,12 +521,19 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
|
||||
if (isBlocked(c.sourceType))
|
||||
return block(c.sourceType, constraint);
|
||||
|
||||
TypeId generalized = quantify(arena, c.sourceType, constraint->scope);
|
||||
|
||||
std::optional<TypeId> generalized = quantify(arena, c.sourceType, constraint->scope);
|
||||
if (generalized)
|
||||
{
|
||||
if (isBlocked(c.generalizedType))
|
||||
asMutable(c.generalizedType)->ty.emplace<BoundType>(generalized);
|
||||
asMutable(c.generalizedType)->ty.emplace<BoundType>(*generalized);
|
||||
else
|
||||
unify(c.generalizedType, generalized, constraint->scope);
|
||||
unify(c.generalizedType, *generalized, constraint->scope);
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(CodeTooComplex{}, constraint->location);
|
||||
asMutable(c.generalizedType)->ty.emplace<BoundType>(builtinTypes->errorRecoveryType());
|
||||
}
|
||||
|
||||
unblock(c.generalizedType);
|
||||
unblock(c.sourceType);
|
||||
@ -1365,7 +1372,7 @@ static std::optional<TypeId> updateTheTableType(NotNull<TypeArena> arena, TypeId
|
||||
if (it == tbl->props.end())
|
||||
return std::nullopt;
|
||||
|
||||
t = it->second.type;
|
||||
t = follow(it->second.type);
|
||||
}
|
||||
|
||||
// The last path segment should not be a property of the table at all.
|
||||
@ -1450,12 +1457,6 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
||||
if (auto mt = get<MetatableType>(subjectType))
|
||||
subjectType = follow(mt->table);
|
||||
|
||||
if (get<AnyType>(subjectType) || get<ErrorType>(subjectType) || get<NeverType>(subjectType))
|
||||
{
|
||||
bind(c.resultType, subjectType);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (get<FreeType>(subjectType))
|
||||
{
|
||||
TypeId ty = arena->freshType(constraint->scope);
|
||||
@ -1501,16 +1502,13 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (get<ClassType>(subjectType))
|
||||
else
|
||||
{
|
||||
// Classes and intersections never change shape as a result of property
|
||||
// assignments. The result is always the subject.
|
||||
// Other kinds of types don't change shape when properties are assigned
|
||||
// to them. (if they allow properties at all!)
|
||||
bind(c.resultType, subjectType);
|
||||
return true;
|
||||
}
|
||||
|
||||
LUAU_ASSERT(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
|
@ -959,7 +959,7 @@ void copyErrors(ErrorVec& errors, TypeArena& destArena)
|
||||
visit(visitErrorData, error.data);
|
||||
}
|
||||
|
||||
void InternalErrorReporter::ice(const std::string& message, const Location& location)
|
||||
void InternalErrorReporter::ice(const std::string& message, const Location& location) const
|
||||
{
|
||||
InternalCompilerError error(message, moduleName, location);
|
||||
|
||||
@ -969,7 +969,7 @@ void InternalErrorReporter::ice(const std::string& message, const Location& loca
|
||||
throw error;
|
||||
}
|
||||
|
||||
void InternalErrorReporter::ice(const std::string& message)
|
||||
void InternalErrorReporter::ice(const std::string& message) const
|
||||
{
|
||||
InternalCompilerError error(message, moduleName);
|
||||
|
||||
|
@ -31,6 +31,7 @@ LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
||||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDefinitionFileSourceModule, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false);
|
||||
|
||||
namespace Luau
|
||||
@ -83,32 +84,31 @@ static void generateDocumentationSymbols(TypeId ty, const std::string& rootName)
|
||||
}
|
||||
}
|
||||
|
||||
LoadDefinitionFileResult Frontend::loadDefinitionFile(std::string_view source, const std::string& packageName)
|
||||
LoadDefinitionFileResult Frontend::loadDefinitionFile(std::string_view source, const std::string& packageName, bool captureComments)
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return Luau::loadDefinitionFile(typeChecker, typeChecker.globalScope, source, packageName);
|
||||
return Luau::loadDefinitionFile(typeChecker, globals, globals.globalScope, source, packageName, captureComments);
|
||||
|
||||
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
|
||||
|
||||
Luau::Allocator allocator;
|
||||
Luau::AstNameTable names(allocator);
|
||||
Luau::SourceModule sourceModule;
|
||||
|
||||
ParseOptions options;
|
||||
options.allowDeclarationSyntax = true;
|
||||
options.captureComments = captureComments;
|
||||
|
||||
Luau::ParseResult parseResult = Luau::Parser::parse(source.data(), source.size(), names, allocator, options);
|
||||
Luau::ParseResult parseResult = Luau::Parser::parse(source.data(), source.size(), *sourceModule.names, *sourceModule.allocator, options);
|
||||
|
||||
if (parseResult.errors.size() > 0)
|
||||
return LoadDefinitionFileResult{false, parseResult, nullptr};
|
||||
return LoadDefinitionFileResult{false, parseResult, sourceModule, nullptr};
|
||||
|
||||
Luau::SourceModule module;
|
||||
module.root = parseResult.root;
|
||||
module.mode = Mode::Definition;
|
||||
sourceModule.root = parseResult.root;
|
||||
sourceModule.mode = Mode::Definition;
|
||||
|
||||
ModulePtr checkedModule = check(module, Mode::Definition, {});
|
||||
ModulePtr checkedModule = check(sourceModule, Mode::Definition, {});
|
||||
|
||||
if (checkedModule->errors.size() > 0)
|
||||
return LoadDefinitionFileResult{false, parseResult, checkedModule};
|
||||
return LoadDefinitionFileResult{false, parseResult, sourceModule, checkedModule};
|
||||
|
||||
CloneState cloneState;
|
||||
|
||||
@ -117,20 +117,20 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(std::string_view source, c
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
TypeId globalTy = clone(ty, globalTypes, cloneState);
|
||||
TypeId globalTy = clone(ty, globals.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
globalScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
globals.globalScope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
|
||||
typesToPersist.push_back(globalTy);
|
||||
}
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, globalTypes, cloneState);
|
||||
TypeFun globalTy = clone(ty, globals.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
globalScope->exportedTypeBindings[name] = globalTy;
|
||||
globals.globalScope->exportedTypeBindings[name] = globalTy;
|
||||
|
||||
typesToPersist.push_back(globalTy.type);
|
||||
}
|
||||
@ -140,10 +140,11 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(std::string_view source, c
|
||||
persist(ty);
|
||||
}
|
||||
|
||||
return LoadDefinitionFileResult{true, parseResult, checkedModule};
|
||||
return LoadDefinitionFileResult{true, parseResult, sourceModule, checkedModule};
|
||||
}
|
||||
|
||||
LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr targetScope, std::string_view source, const std::string& packageName)
|
||||
LoadDefinitionFileResult loadDefinitionFile_DEPRECATED(
|
||||
TypeChecker& typeChecker, GlobalTypes& globals, ScopePtr targetScope, std::string_view source, const std::string& packageName)
|
||||
{
|
||||
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
|
||||
|
||||
@ -156,7 +157,7 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr t
|
||||
Luau::ParseResult parseResult = Luau::Parser::parse(source.data(), source.size(), names, allocator, options);
|
||||
|
||||
if (parseResult.errors.size() > 0)
|
||||
return LoadDefinitionFileResult{false, parseResult, nullptr};
|
||||
return LoadDefinitionFileResult{false, parseResult, {}, nullptr};
|
||||
|
||||
Luau::SourceModule module;
|
||||
module.root = parseResult.root;
|
||||
@ -165,7 +166,7 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr t
|
||||
ModulePtr checkedModule = typeChecker.check(module, Mode::Definition);
|
||||
|
||||
if (checkedModule->errors.size() > 0)
|
||||
return LoadDefinitionFileResult{false, parseResult, checkedModule};
|
||||
return LoadDefinitionFileResult{false, parseResult, {}, checkedModule};
|
||||
|
||||
CloneState cloneState;
|
||||
|
||||
@ -174,17 +175,17 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr t
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
TypeId globalTy = clone(ty, typeChecker.globalTypes, cloneState);
|
||||
TypeId globalTy = clone(ty, globals.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
targetScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
targetScope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
|
||||
typesToPersist.push_back(globalTy);
|
||||
}
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, typeChecker.globalTypes, cloneState);
|
||||
TypeFun globalTy = clone(ty, globals.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
targetScope->exportedTypeBindings[name] = globalTy;
|
||||
@ -197,7 +198,67 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr t
|
||||
persist(ty);
|
||||
}
|
||||
|
||||
return LoadDefinitionFileResult{true, parseResult, checkedModule};
|
||||
return LoadDefinitionFileResult{true, parseResult, {}, checkedModule};
|
||||
}
|
||||
|
||||
LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, GlobalTypes& globals, ScopePtr targetScope, std::string_view source,
|
||||
const std::string& packageName, bool captureComments)
|
||||
{
|
||||
if (!FFlag::LuauDefinitionFileSourceModule)
|
||||
return loadDefinitionFile_DEPRECATED(typeChecker, globals, targetScope, source, packageName);
|
||||
|
||||
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
|
||||
|
||||
Luau::SourceModule sourceModule;
|
||||
|
||||
ParseOptions options;
|
||||
options.allowDeclarationSyntax = true;
|
||||
options.captureComments = captureComments;
|
||||
|
||||
Luau::ParseResult parseResult = Luau::Parser::parse(source.data(), source.size(), *sourceModule.names, *sourceModule.allocator, options);
|
||||
|
||||
if (parseResult.errors.size() > 0)
|
||||
return LoadDefinitionFileResult{false, parseResult, sourceModule, nullptr};
|
||||
|
||||
sourceModule.root = parseResult.root;
|
||||
sourceModule.mode = Mode::Definition;
|
||||
|
||||
ModulePtr checkedModule = typeChecker.check(sourceModule, Mode::Definition);
|
||||
|
||||
if (checkedModule->errors.size() > 0)
|
||||
return LoadDefinitionFileResult{false, parseResult, sourceModule, checkedModule};
|
||||
|
||||
CloneState cloneState;
|
||||
|
||||
std::vector<TypeId> typesToPersist;
|
||||
typesToPersist.reserve(checkedModule->declaredGlobals.size() + checkedModule->exportedTypeBindings.size());
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
TypeId globalTy = clone(ty, globals.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
targetScope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
|
||||
typesToPersist.push_back(globalTy);
|
||||
}
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, globals.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
targetScope->exportedTypeBindings[name] = globalTy;
|
||||
|
||||
typesToPersist.push_back(globalTy.type);
|
||||
}
|
||||
|
||||
for (TypeId ty : typesToPersist)
|
||||
{
|
||||
persist(ty);
|
||||
}
|
||||
|
||||
return LoadDefinitionFileResult{true, parseResult, sourceModule, checkedModule};
|
||||
}
|
||||
|
||||
std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr)
|
||||
@ -414,11 +475,12 @@ Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, c
|
||||
, fileResolver(fileResolver)
|
||||
, moduleResolver(this)
|
||||
, moduleResolverForAutocomplete(this)
|
||||
, typeChecker(&moduleResolver, builtinTypes, &iceHandler)
|
||||
, typeCheckerForAutocomplete(&moduleResolverForAutocomplete, builtinTypes, &iceHandler)
|
||||
, globals(builtinTypes)
|
||||
, globalsForAutocomplete(builtinTypes)
|
||||
, typeChecker(globals, &moduleResolver, builtinTypes, &iceHandler)
|
||||
, typeCheckerForAutocomplete(globalsForAutocomplete, &moduleResolverForAutocomplete, builtinTypes, &iceHandler)
|
||||
, configResolver(configResolver)
|
||||
, options(options)
|
||||
, globalScope(typeChecker.globalScope)
|
||||
{
|
||||
}
|
||||
|
||||
@ -704,13 +766,13 @@ bool Frontend::parseGraph(std::vector<ModuleName>& buildQueue, const ModuleName&
|
||||
return cyclic;
|
||||
}
|
||||
|
||||
ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete)
|
||||
ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const
|
||||
{
|
||||
ScopePtr result;
|
||||
if (forAutocomplete)
|
||||
result = typeCheckerForAutocomplete.globalScope;
|
||||
result = globalsForAutocomplete.globalScope;
|
||||
else
|
||||
result = typeChecker.globalScope;
|
||||
result = globals.globalScope;
|
||||
|
||||
if (module.environmentName)
|
||||
result = getEnvironmentScope(*module.environmentName);
|
||||
@ -848,16 +910,6 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons
|
||||
return const_cast<Frontend*>(this)->getSourceModule(moduleName);
|
||||
}
|
||||
|
||||
ScopePtr Frontend::getGlobalScope()
|
||||
{
|
||||
if (!globalScope)
|
||||
{
|
||||
globalScope = typeChecker.globalScope;
|
||||
}
|
||||
|
||||
return globalScope;
|
||||
}
|
||||
|
||||
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& globalScope, FrontendOptions options)
|
||||
@ -946,7 +998,7 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vect
|
||||
{
|
||||
return Luau::check(sourceModule, requireCycles, builtinTypes, NotNull{&iceHandler},
|
||||
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, NotNull{fileResolver},
|
||||
forAutocomplete ? typeCheckerForAutocomplete.globalScope : typeChecker.globalScope, options, recordJsonLog);
|
||||
forAutocomplete ? globalsForAutocomplete.globalScope : globals.globalScope, options, recordJsonLog);
|
||||
}
|
||||
|
||||
// Read AST into sourceModules if necessary. Trace require()s. Report parse errors.
|
||||
@ -1115,7 +1167,7 @@ ScopePtr Frontend::addEnvironment(const std::string& environmentName)
|
||||
|
||||
if (environments.count(environmentName) == 0)
|
||||
{
|
||||
ScopePtr scope = std::make_shared<Scope>(typeChecker.globalScope);
|
||||
ScopePtr scope = std::make_shared<Scope>(globals.globalScope);
|
||||
environments[environmentName] = scope;
|
||||
return scope;
|
||||
}
|
||||
@ -1123,14 +1175,16 @@ ScopePtr Frontend::addEnvironment(const std::string& environmentName)
|
||||
return environments[environmentName];
|
||||
}
|
||||
|
||||
ScopePtr Frontend::getEnvironmentScope(const std::string& environmentName)
|
||||
ScopePtr Frontend::getEnvironmentScope(const std::string& environmentName) const
|
||||
{
|
||||
LUAU_ASSERT(environments.count(environmentName) > 0);
|
||||
if (auto it = environments.find(environmentName); it != environments.end())
|
||||
return it->second;
|
||||
|
||||
return environments[environmentName];
|
||||
LUAU_ASSERT(!"environment doesn't exist");
|
||||
return {};
|
||||
}
|
||||
|
||||
void Frontend::registerBuiltinDefinition(const std::string& name, std::function<void(TypeChecker&, ScopePtr)> applicator)
|
||||
void Frontend::registerBuiltinDefinition(const std::string& name, std::function<void(TypeChecker&, GlobalTypes&, ScopePtr)> applicator)
|
||||
{
|
||||
LUAU_ASSERT(builtinDefinitions.count(name) == 0);
|
||||
|
||||
@ -1143,7 +1197,7 @@ void Frontend::applyBuiltinDefinitionToEnvironment(const std::string& environmen
|
||||
LUAU_ASSERT(builtinDefinitions.count(definitionName) > 0);
|
||||
|
||||
if (builtinDefinitions.count(definitionName) > 0)
|
||||
builtinDefinitions[definitionName](typeChecker, getEnvironmentScope(environmentName));
|
||||
builtinDefinitions[definitionName](typeChecker, globals, getEnvironmentScope(environmentName));
|
||||
}
|
||||
|
||||
LintResult Frontend::classifyLints(const std::vector<LintWarning>& warnings, const Config& config)
|
||||
|
@ -253,11 +253,12 @@ struct PureQuantifier : Substitution
|
||||
}
|
||||
};
|
||||
|
||||
TypeId quantify(TypeArena* arena, TypeId ty, Scope* scope)
|
||||
std::optional<TypeId> quantify(TypeArena* arena, TypeId ty, Scope* scope)
|
||||
{
|
||||
PureQuantifier quantifier{arena, scope};
|
||||
std::optional<TypeId> result = quantifier.substitute(ty);
|
||||
LUAU_ASSERT(result);
|
||||
if (!result)
|
||||
return std::nullopt;
|
||||
|
||||
FunctionType* ftv = getMutable<FunctionType>(*result);
|
||||
LUAU_ASSERT(ftv);
|
||||
|
@ -276,6 +276,7 @@ PendingTypePack* TxnLog::replace(TypePackId tp, TypePackVar replacement)
|
||||
PendingType* TxnLog::bindTable(TypeId ty, std::optional<TypeId> newBoundTo)
|
||||
{
|
||||
LUAU_ASSERT(get<TableType>(ty));
|
||||
LUAU_ASSERT(ty != newBoundTo);
|
||||
|
||||
PendingType* newTy = queue(ty);
|
||||
if (TableType* ttv = Luau::getMutable<TableType>(newTy))
|
||||
|
@ -856,22 +856,22 @@ TypeId BuiltinTypes::makeStringMetatable()
|
||||
return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
|
||||
}
|
||||
|
||||
TypeId BuiltinTypes::errorRecoveryType()
|
||||
TypeId BuiltinTypes::errorRecoveryType() const
|
||||
{
|
||||
return errorType;
|
||||
}
|
||||
|
||||
TypePackId BuiltinTypes::errorRecoveryTypePack()
|
||||
TypePackId BuiltinTypes::errorRecoveryTypePack() const
|
||||
{
|
||||
return errorTypePack;
|
||||
}
|
||||
|
||||
TypeId BuiltinTypes::errorRecoveryType(TypeId guess)
|
||||
TypeId BuiltinTypes::errorRecoveryType(TypeId guess) const
|
||||
{
|
||||
return guess;
|
||||
}
|
||||
|
||||
TypePackId BuiltinTypes::errorRecoveryTypePack(TypePackId guess)
|
||||
TypePackId BuiltinTypes::errorRecoveryTypePack(TypePackId guess) const
|
||||
{
|
||||
return guess;
|
||||
}
|
||||
|
@ -857,7 +857,7 @@ struct TypeChecker2
|
||||
}
|
||||
|
||||
void reportOverloadResolutionErrors(AstExprCall* call, std::vector<TypeId> overloads, TypePackId expectedArgTypes,
|
||||
const std::vector<TypeId>& overloadsThatMatchArgCount, std::vector<std::pair<ErrorVec, const FunctionType*>> overloadsErrors)
|
||||
const std::vector<TypeId>& overloadsThatMatchArgCount, std::vector<std::pair<ErrorVec, TypeId>> overloadsErrors)
|
||||
{
|
||||
if (overloads.size() == 1)
|
||||
{
|
||||
@ -883,8 +883,8 @@ struct TypeChecker2
|
||||
const FunctionType* ftv = get<FunctionType>(overload);
|
||||
LUAU_ASSERT(ftv); // overload must be a function type here
|
||||
|
||||
auto error = std::find_if(overloadsErrors.begin(), overloadsErrors.end(), [ftv](const std::pair<ErrorVec, const FunctionType*>& e) {
|
||||
return ftv == std::get<1>(e);
|
||||
auto error = std::find_if(overloadsErrors.begin(), overloadsErrors.end(), [overload](const std::pair<ErrorVec, TypeId>& e) {
|
||||
return overload == e.second;
|
||||
});
|
||||
|
||||
LUAU_ASSERT(error != overloadsErrors.end());
|
||||
@ -1036,7 +1036,7 @@ struct TypeChecker2
|
||||
TypePackId expectedArgTypes = arena->addTypePack(args);
|
||||
|
||||
std::vector<TypeId> overloads = flattenIntersection(testFunctionType);
|
||||
std::vector<std::pair<ErrorVec, const FunctionType*>> overloadsErrors;
|
||||
std::vector<std::pair<ErrorVec, TypeId>> overloadsErrors;
|
||||
overloadsErrors.reserve(overloads.size());
|
||||
|
||||
std::vector<TypeId> overloadsThatMatchArgCount;
|
||||
@ -1060,7 +1060,7 @@ struct TypeChecker2
|
||||
}
|
||||
else
|
||||
{
|
||||
overloadsErrors.emplace_back(std::vector{TypeError{call->func->location, UnificationTooComplex{}}}, overloadFn);
|
||||
overloadsErrors.emplace_back(std::vector{TypeError{call->func->location, UnificationTooComplex{}}}, overload);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -1086,7 +1086,7 @@ struct TypeChecker2
|
||||
if (!argMismatch)
|
||||
overloadsThatMatchArgCount.push_back(overload);
|
||||
|
||||
overloadsErrors.emplace_back(std::move(overloadErrors), overloadFn);
|
||||
overloadsErrors.emplace_back(std::move(overloadErrors), overload);
|
||||
}
|
||||
|
||||
reportOverloadResolutionErrors(call, overloads, expectedArgTypes, overloadsThatMatchArgCount, overloadsErrors);
|
||||
@ -1102,11 +1102,54 @@ struct TypeChecker2
|
||||
visitCall(call);
|
||||
}
|
||||
|
||||
std::optional<TypeId> tryStripUnionFromNil(TypeId ty)
|
||||
{
|
||||
if (const UnionType* utv = get<UnionType>(ty))
|
||||
{
|
||||
if (!std::any_of(begin(utv), end(utv), isNil))
|
||||
return ty;
|
||||
|
||||
std::vector<TypeId> result;
|
||||
|
||||
for (TypeId option : utv)
|
||||
{
|
||||
if (!isNil(option))
|
||||
result.push_back(option);
|
||||
}
|
||||
|
||||
if (result.empty())
|
||||
return std::nullopt;
|
||||
|
||||
return result.size() == 1 ? result[0] : module->internalTypes.addType(UnionType{std::move(result)});
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TypeId stripFromNilAndReport(TypeId ty, const Location& location)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
if (auto utv = get<UnionType>(ty))
|
||||
{
|
||||
if (!std::any_of(begin(utv), end(utv), isNil))
|
||||
return ty;
|
||||
}
|
||||
|
||||
if (std::optional<TypeId> strippedUnion = tryStripUnionFromNil(ty))
|
||||
{
|
||||
reportError(OptionalValueAccess{ty}, location);
|
||||
return follow(*strippedUnion);
|
||||
}
|
||||
|
||||
return ty;
|
||||
}
|
||||
|
||||
void visitExprName(AstExpr* expr, Location location, const std::string& propName, ValueContext context)
|
||||
{
|
||||
visit(expr, RValue);
|
||||
|
||||
TypeId leftType = lookupType(expr);
|
||||
TypeId leftType = stripFromNilAndReport(lookupType(expr), location);
|
||||
const NormalizedType* norm = normalizer.normalize(leftType);
|
||||
if (!norm)
|
||||
reportError(NormalizationTooComplex{}, location);
|
||||
@ -1766,7 +1809,15 @@ struct TypeChecker2
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(UnknownSymbol{ty->name.value, UnknownSymbol::Context::Type}, ty->location);
|
||||
std::string symbol = "";
|
||||
if (ty->prefix)
|
||||
{
|
||||
symbol += (*(ty->prefix)).value;
|
||||
symbol += ".";
|
||||
}
|
||||
symbol += ty->name.value;
|
||||
|
||||
reportError(UnknownSymbol{symbol, UnknownSymbol::Context::Type}, ty->location);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2032,7 +2083,11 @@ struct TypeChecker2
|
||||
{
|
||||
if (foundOneProp)
|
||||
reportError(MissingUnionProperty{tableTy, typesMissingTheProp, prop}, location);
|
||||
else if (context == LValue)
|
||||
// For class LValues, we don't want to report an extension error,
|
||||
// because classes come into being with full knowledge of their
|
||||
// shape. We instead want to report the unknown property error of
|
||||
// the `else` branch.
|
||||
else if (context == LValue && !get<ClassType>(tableTy))
|
||||
reportError(CannotExtendTable{tableTy, CannotExtendTable::Property, prop}, location);
|
||||
else
|
||||
reportError(UnknownProperty{tableTy, prop}, location);
|
||||
|
@ -42,7 +42,6 @@ LUAU_FASTFLAGVARIABLE(LuauIntersectionTestForEquality, false)
|
||||
LUAU_FASTFLAG(LuauNegatedClassTypes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false)
|
||||
LUAU_FASTFLAG(LuauUninhabitedSubAnything2)
|
||||
LUAU_FASTFLAG(SupportTypeAliasGoToDeclaration)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckTypeguards, false)
|
||||
|
||||
namespace Luau
|
||||
@ -212,8 +211,24 @@ size_t HashBoolNamePair::operator()(const std::pair<bool, Name>& pair) const
|
||||
return std::hash<bool>()(pair.first) ^ std::hash<Name>()(pair.second);
|
||||
}
|
||||
|
||||
TypeChecker::TypeChecker(ModuleResolver* resolver, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter* iceHandler)
|
||||
: resolver(resolver)
|
||||
GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
|
||||
: builtinTypes(builtinTypes)
|
||||
{
|
||||
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
|
||||
|
||||
globalScope->addBuiltinTypeBinding("any", TypeFun{{}, builtinTypes->anyType});
|
||||
globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, builtinTypes->nilType});
|
||||
globalScope->addBuiltinTypeBinding("number", TypeFun{{}, builtinTypes->numberType});
|
||||
globalScope->addBuiltinTypeBinding("string", TypeFun{{}, builtinTypes->stringType});
|
||||
globalScope->addBuiltinTypeBinding("boolean", TypeFun{{}, builtinTypes->booleanType});
|
||||
globalScope->addBuiltinTypeBinding("thread", TypeFun{{}, builtinTypes->threadType});
|
||||
globalScope->addBuiltinTypeBinding("unknown", TypeFun{{}, builtinTypes->unknownType});
|
||||
globalScope->addBuiltinTypeBinding("never", TypeFun{{}, builtinTypes->neverType});
|
||||
}
|
||||
|
||||
TypeChecker::TypeChecker(const GlobalTypes& globals, ModuleResolver* resolver, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter* iceHandler)
|
||||
: globals(globals)
|
||||
, resolver(resolver)
|
||||
, builtinTypes(builtinTypes)
|
||||
, iceHandler(iceHandler)
|
||||
, unifierState(iceHandler)
|
||||
@ -231,16 +246,6 @@ TypeChecker::TypeChecker(ModuleResolver* resolver, NotNull<BuiltinTypes> builtin
|
||||
, uninhabitableTypePack(builtinTypes->uninhabitableTypePack)
|
||||
, duplicateTypeAliases{{false, {}}}
|
||||
{
|
||||
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
|
||||
|
||||
globalScope->addBuiltinTypeBinding("any", TypeFun{{}, anyType});
|
||||
globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, nilType});
|
||||
globalScope->addBuiltinTypeBinding("number", TypeFun{{}, numberType});
|
||||
globalScope->addBuiltinTypeBinding("string", TypeFun{{}, stringType});
|
||||
globalScope->addBuiltinTypeBinding("boolean", TypeFun{{}, booleanType});
|
||||
globalScope->addBuiltinTypeBinding("thread", TypeFun{{}, threadType});
|
||||
globalScope->addBuiltinTypeBinding("unknown", TypeFun{{}, unknownType});
|
||||
globalScope->addBuiltinTypeBinding("never", TypeFun{{}, neverType});
|
||||
}
|
||||
|
||||
ModulePtr TypeChecker::check(const SourceModule& module, Mode mode, std::optional<ScopePtr> environmentScope)
|
||||
@ -273,7 +278,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
||||
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
||||
unifierState.counters.iterationLimit = unifierIterationLimit ? *unifierIterationLimit : FInt::LuauTypeInferIterationLimit;
|
||||
|
||||
ScopePtr parentScope = environmentScope.value_or(globalScope);
|
||||
ScopePtr parentScope = environmentScope.value_or(globals.globalScope);
|
||||
ScopePtr moduleScope = std::make_shared<Scope>(parentScope);
|
||||
|
||||
if (module.cyclic)
|
||||
@ -1121,7 +1126,6 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatLocal& local)
|
||||
if (ModulePtr module = resolver->getModule(moduleInfo->name))
|
||||
{
|
||||
scope->importedTypeBindings[name] = module->exportedTypeBindings;
|
||||
if (FFlag::SupportTypeAliasGoToDeclaration)
|
||||
scope->importedModules[name] = moduleInfo->name;
|
||||
}
|
||||
|
||||
@ -1580,7 +1584,7 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
|
||||
}
|
||||
else
|
||||
{
|
||||
if (globalScope->builtinTypeNames.contains(name))
|
||||
if (globals.globalScope->builtinTypeNames.contains(name))
|
||||
{
|
||||
reportError(typealias.location, DuplicateTypeDefinition{name});
|
||||
duplicateTypeAliases.insert({typealias.exported, name});
|
||||
@ -1601,7 +1605,6 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
|
||||
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
|
||||
|
||||
scope->typeAliasLocations[name] = typealias.location;
|
||||
if (FFlag::SupportTypeAliasGoToDeclaration)
|
||||
scope->typeAliasNameLocations[name] = typealias.nameLocation;
|
||||
}
|
||||
}
|
||||
@ -3360,19 +3363,19 @@ TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName, T
|
||||
|
||||
if (auto globalName = funName.as<AstExprGlobal>())
|
||||
{
|
||||
const ScopePtr& globalScope = currentModule->getModuleScope();
|
||||
const ScopePtr& moduleScope = currentModule->getModuleScope();
|
||||
Symbol name = globalName->name;
|
||||
if (globalScope->bindings.count(name))
|
||||
if (moduleScope->bindings.count(name))
|
||||
{
|
||||
if (isNonstrictMode())
|
||||
return globalScope->bindings[name].typeId;
|
||||
return moduleScope->bindings[name].typeId;
|
||||
|
||||
return errorRecoveryType(scope);
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeId ty = freshTy();
|
||||
globalScope->bindings[name] = {ty, funName.location};
|
||||
moduleScope->bindings[name] = {ty, funName.location};
|
||||
return ty;
|
||||
}
|
||||
}
|
||||
@ -5898,7 +5901,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
|
||||
if (!typeguardP.isTypeof)
|
||||
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
|
||||
|
||||
auto typeFun = globalScope->lookupType(typeguardP.kind);
|
||||
auto typeFun = globals.globalScope->lookupType(typeguardP.kind);
|
||||
if (!typeFun || !typeFun->typeParams.empty() || !typeFun->typePackParams.empty())
|
||||
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
|
||||
|
||||
|
@ -407,9 +407,6 @@ TypePackId TypeReducer::reduce(TypePackId tp)
|
||||
|
||||
std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
|
||||
{
|
||||
LUAU_ASSERT(!get<IntersectionType>(left));
|
||||
LUAU_ASSERT(!get<IntersectionType>(right));
|
||||
|
||||
if (get<NeverType>(left))
|
||||
return left; // never & T ~ never
|
||||
else if (get<NeverType>(right))
|
||||
@ -442,6 +439,17 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
|
||||
return std::nullopt; // *pending* & T ~ *pending* & T
|
||||
else if (get<PendingExpansionType>(right))
|
||||
return std::nullopt; // T & *pending* ~ T & *pending*
|
||||
else if (auto [utl, utr] = get2<UnionType, UnionType>(left, right); utl && utr)
|
||||
{
|
||||
std::vector<TypeId> parts;
|
||||
for (TypeId optionl : utl)
|
||||
{
|
||||
for (TypeId optionr : utr)
|
||||
parts.push_back(apply<IntersectionType>(&TypeReducer::intersectionType, optionl, optionr));
|
||||
}
|
||||
|
||||
return reduce(flatten<UnionType>(std::move(parts))); // (T | U) & (A | B) ~ (T & A) | (T & B) | (U & A) | (U & B)
|
||||
}
|
||||
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)
|
||||
else if (get<UnionType>(right))
|
||||
@ -789,6 +797,36 @@ std::optional<TypeId> TypeReducer::unionType(TypeId left, TypeId right)
|
||||
return reduce(distribute<UnionType>(begin(it), end(it), &TypeReducer::unionType, left)); // ~T | (A & B) ~ (~T | A) & (~T | B)
|
||||
else if (auto [it, nt] = get2<IntersectionType, NegationType>(left, right); it && nt)
|
||||
return unionType(right, left); // (A & B) | ~T ~ ~T | (A & B)
|
||||
else if (auto it = get<IntersectionType>(left))
|
||||
{
|
||||
bool didReduce = false;
|
||||
std::vector<TypeId> parts;
|
||||
for (TypeId part : it)
|
||||
{
|
||||
auto nt = get<NegationType>(part);
|
||||
if (!nt)
|
||||
{
|
||||
parts.push_back(part);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto redex = unionType(part, right);
|
||||
if (redex && get<UnknownType>(*redex))
|
||||
{
|
||||
didReduce = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
parts.push_back(part);
|
||||
}
|
||||
|
||||
if (didReduce)
|
||||
return flatten<IntersectionType>(std::move(parts)); // (T & ~nil) | nil ~ T
|
||||
else
|
||||
return std::nullopt; // (T & ~nil) | U
|
||||
}
|
||||
else if (get<IntersectionType>(right))
|
||||
return unionType(right, left); // A | (T & U) ~ (T & U) | A
|
||||
else if (auto [nl, nr] = get2<NegationType, NegationType>(left, right); nl && nr)
|
||||
{
|
||||
// These should've been reduced already.
|
||||
|
@ -18,10 +18,9 @@
|
||||
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
|
||||
LUAU_FASTFLAG(LuauErrorRecoveryType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauScalarShapeUnifyToMtOwner2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUninhabitedSubAnything2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMaintainScopesInUnifier, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableUnifyInstantiationFix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTinyUnifyNormalsFix, false)
|
||||
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauNegatedFunctionTypes)
|
||||
@ -108,7 +107,7 @@ struct PromoteTypeLevels final : TypeOnceVisitor
|
||||
|
||||
// Surprise, it's actually a BoundTypePack that hasn't been committed yet.
|
||||
// Calling getMutable on this will trigger an assertion.
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner2 && !log.is<FunctionType>(ty))
|
||||
if (!log.is<FunctionType>(ty))
|
||||
return true;
|
||||
|
||||
promote(ty, log.getMutable<FunctionType>(ty));
|
||||
@ -126,7 +125,7 @@ struct PromoteTypeLevels final : TypeOnceVisitor
|
||||
|
||||
// Surprise, it's actually a BoundTypePack that hasn't been committed yet.
|
||||
// Calling getMutable on this will trigger an assertion.
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner2 && !log.is<TableType>(ty))
|
||||
if (!log.is<TableType>(ty))
|
||||
return true;
|
||||
|
||||
promote(ty, log.getMutable<TableType>(ty));
|
||||
@ -690,6 +689,31 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionType* subUnion, Typ
|
||||
}
|
||||
}
|
||||
|
||||
struct BlockedTypeFinder : TypeOnceVisitor
|
||||
{
|
||||
std::unordered_set<TypeId> blockedTypes;
|
||||
|
||||
bool visit(TypeId ty, const BlockedType&) override
|
||||
{
|
||||
blockedTypes.insert(ty);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
bool Unifier::blockOnBlockedTypes(TypeId subTy, TypeId superTy)
|
||||
{
|
||||
BlockedTypeFinder blockedTypeFinder;
|
||||
blockedTypeFinder.traverse(subTy);
|
||||
blockedTypeFinder.traverse(superTy);
|
||||
if (!blockedTypeFinder.blockedTypes.empty())
|
||||
{
|
||||
blockedTypes.insert(end(blockedTypes), begin(blockedTypeFinder.blockedTypes), end(blockedTypeFinder.blockedTypes));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionType* uv, bool cacheEnabled, bool isFunctionCall)
|
||||
{
|
||||
// T <: A | B if T <: A or T <: B
|
||||
@ -788,6 +812,11 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
|
||||
}
|
||||
else if (!found && normalize)
|
||||
{
|
||||
// We cannot normalize a type that contains blocked types. We have to
|
||||
// stop for now if we find any.
|
||||
if (blockOnBlockedTypes(subTy, superTy))
|
||||
return;
|
||||
|
||||
// It is possible that T <: A | B even though T </: A and T </:B
|
||||
// for example boolean <: true | false.
|
||||
// We deal with this by type normalization.
|
||||
@ -888,6 +917,11 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType*
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution && normalize)
|
||||
{
|
||||
// We cannot normalize a type that contains blocked types. We have to
|
||||
// stop for now if we find any.
|
||||
if (blockOnBlockedTypes(subTy, superTy))
|
||||
return;
|
||||
|
||||
// Sometimes a negation type is inside one of the types, e.g. { p: number } & { p: ~number }.
|
||||
NegationTypeFinder finder;
|
||||
finder.traverse(subTy);
|
||||
@ -941,6 +975,11 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType*
|
||||
reportError(*unificationTooComplex);
|
||||
else if (!found && normalize)
|
||||
{
|
||||
// We cannot normalize a type that contains blocked types. We have to
|
||||
// stop for now if we find any.
|
||||
if (blockOnBlockedTypes(subTy, superTy))
|
||||
return;
|
||||
|
||||
// It is possible that A & B <: T even though A </: T and B </: T
|
||||
// for example string? & number? <: nil.
|
||||
// We deal with this by type normalization.
|
||||
@ -1085,12 +1124,19 @@ void Unifier::tryUnifyNormalizedTypes(
|
||||
}
|
||||
|
||||
Unifier innerState = makeChildUnifier();
|
||||
|
||||
if (FFlag::LuauTinyUnifyNormalsFix)
|
||||
innerState.tryUnify(subTable, superTable);
|
||||
else
|
||||
{
|
||||
if (get<MetatableType>(superTable))
|
||||
innerState.tryUnifyWithMetatable(subTable, superTable, /* reversed */ false);
|
||||
else if (get<MetatableType>(subTable))
|
||||
innerState.tryUnifyWithMetatable(superTable, subTable, /* reversed */ true);
|
||||
else
|
||||
innerState.tryUnifyTables(subTable, superTable);
|
||||
}
|
||||
|
||||
if (innerState.errors.empty())
|
||||
{
|
||||
found = true;
|
||||
@ -1782,7 +1828,6 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
TypeId activeSubTy = subTy;
|
||||
TableType* superTable = log.getMutable<TableType>(superTy);
|
||||
TableType* subTable = log.getMutable<TableType>(subTy);
|
||||
TableType* instantiatedSubTable = subTable; // TODO: remove with FFlagLuauTableUnifyInstantiationFix
|
||||
|
||||
if (!superTable || !subTable)
|
||||
ice("passed non-table types to unifyTables");
|
||||
@ -1798,17 +1843,9 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
|
||||
std::optional<TypeId> instantiated = instantiation.substitute(subTy);
|
||||
if (instantiated.has_value())
|
||||
{
|
||||
if (FFlag::LuauTableUnifyInstantiationFix)
|
||||
{
|
||||
activeSubTy = *instantiated;
|
||||
subTable = log.getMutable<TableType>(activeSubTy);
|
||||
}
|
||||
else
|
||||
{
|
||||
subTable = log.getMutable<TableType>(*instantiated);
|
||||
instantiatedSubTable = subTable;
|
||||
}
|
||||
|
||||
if (!subTable)
|
||||
ice("instantiation made a table type into a non-table type in tryUnifyTables");
|
||||
@ -1910,21 +1947,18 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
// Recursive unification can change the txn log, and invalidate the old
|
||||
// table. If we detect that this has happened, we start over, with the updated
|
||||
// txn log.
|
||||
TypeId superTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(superTy) : superTy;
|
||||
TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(activeSubTy) : activeSubTy;
|
||||
TypeId superTyNew = log.follow(superTy);
|
||||
TypeId subTyNew = log.follow(activeSubTy);
|
||||
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner2)
|
||||
{
|
||||
// If one of the types stopped being a table altogether, we need to restart from the top
|
||||
if ((superTy != superTyNew || activeSubTy != subTyNew) && errors.empty())
|
||||
return tryUnify(subTy, superTy, false, isIntersection);
|
||||
}
|
||||
|
||||
// Otherwise, restart only the table unification
|
||||
TableType* newSuperTable = log.getMutable<TableType>(superTyNew);
|
||||
TableType* newSubTable = log.getMutable<TableType>(subTyNew);
|
||||
|
||||
if (superTable != newSuperTable || (subTable != newSubTable && (FFlag::LuauTableUnifyInstantiationFix || subTable != instantiatedSubTable)))
|
||||
if (superTable != newSuperTable || subTable != newSubTable)
|
||||
{
|
||||
if (errors.empty())
|
||||
return tryUnifyTables(subTy, superTy, isIntersection);
|
||||
@ -1981,15 +2015,12 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
else
|
||||
extraProperties.push_back(name);
|
||||
|
||||
TypeId superTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(superTy) : superTy;
|
||||
TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(activeSubTy) : activeSubTy;
|
||||
TypeId superTyNew = log.follow(superTy);
|
||||
TypeId subTyNew = log.follow(activeSubTy);
|
||||
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner2)
|
||||
{
|
||||
// If one of the types stopped being a table altogether, we need to restart from the top
|
||||
if ((superTy != superTyNew || activeSubTy != subTyNew) && errors.empty())
|
||||
return tryUnify(subTy, superTy, false, isIntersection);
|
||||
}
|
||||
|
||||
// Recursive unification can change the txn log, and invalidate the old
|
||||
// table. If we detect that this has happened, we start over, with the updated
|
||||
@ -1997,7 +2028,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
TableType* newSuperTable = log.getMutable<TableType>(superTyNew);
|
||||
TableType* newSubTable = log.getMutable<TableType>(subTyNew);
|
||||
|
||||
if (superTable != newSuperTable || (subTable != newSubTable && (FFlag::LuauTableUnifyInstantiationFix || subTable != instantiatedSubTable)))
|
||||
if (superTable != newSuperTable || subTable != newSubTable)
|
||||
{
|
||||
if (errors.empty())
|
||||
return tryUnifyTables(subTy, superTy, isIntersection);
|
||||
@ -2050,19 +2081,11 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
}
|
||||
|
||||
// Changing the indexer can invalidate the table pointers.
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner2)
|
||||
{
|
||||
superTable = log.getMutable<TableType>(log.follow(superTy));
|
||||
subTable = log.getMutable<TableType>(log.follow(activeSubTy));
|
||||
|
||||
if (!superTable || !subTable)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
superTable = log.getMutable<TableType>(superTy);
|
||||
subTable = log.getMutable<TableType>(activeSubTy);
|
||||
}
|
||||
|
||||
if (!missingProperties.empty())
|
||||
{
|
||||
@ -2135,8 +2158,6 @@ void Unifier::tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed)
|
||||
Unifier child = makeChildUnifier();
|
||||
child.tryUnify_(ty, superTy);
|
||||
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner2)
|
||||
{
|
||||
// To perform subtype <: free table unification, we have tried to unify (subtype's metatable) <: free table
|
||||
// There is a chance that it was unified with the origial subtype, but then, (subtype's metatable) <: subtype could've failed
|
||||
// Here we check if we have a new supertype instead of the original free table and try original subtype <: new supertype check
|
||||
@ -2147,7 +2168,6 @@ void Unifier::tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed)
|
||||
log.replace(superTy, BoundType{subTy});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto e = hasUnificationTooComplex(child.errors))
|
||||
reportError(*e);
|
||||
@ -2156,13 +2176,10 @@ void Unifier::tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed)
|
||||
|
||||
log.concat(std::move(child.log));
|
||||
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner2)
|
||||
{
|
||||
// To perform subtype <: free table unification, we have tried to unify (subtype's metatable) <: free table
|
||||
// We return success because subtype <: free table which means that correct unification is to replace free table with the subtype
|
||||
if (child.errors.empty())
|
||||
log.replace(superTy, BoundType{subTy});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@ -2379,6 +2396,11 @@ void Unifier::tryUnifyNegations(TypeId subTy, TypeId superTy)
|
||||
if (!log.get<NegationType>(subTy) && !log.get<NegationType>(superTy))
|
||||
ice("tryUnifyNegations superTy or subTy must be a negation type");
|
||||
|
||||
// We cannot normalize a type that contains blocked types. We have to
|
||||
// stop for now if we find any.
|
||||
if (blockOnBlockedTypes(subTy, superTy))
|
||||
return;
|
||||
|
||||
const NormalizedType* subNorm = normalizer->normalize(subTy);
|
||||
const NormalizedType* superNorm = normalizer->normalize(superTy);
|
||||
if (!subNorm || !superNorm)
|
||||
|
@ -121,6 +121,7 @@ static void displayHelp(const char* argv0)
|
||||
static int assertionHandler(const char* expr, const char* file, int line, const char* function)
|
||||
{
|
||||
printf("%s(%d): ASSERTION FAILED: %s\n", file, line, expr);
|
||||
fflush(stdout);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -267,8 +268,8 @@ int main(int argc, char** argv)
|
||||
CliConfigResolver configResolver(mode);
|
||||
Luau::Frontend frontend(&fileResolver, &configResolver, frontendOptions);
|
||||
|
||||
Luau::registerBuiltinGlobals(frontend.typeChecker);
|
||||
Luau::freeze(frontend.typeChecker.globalTypes);
|
||||
Luau::registerBuiltinGlobals(frontend.typeChecker, frontend.globals);
|
||||
Luau::freeze(frontend.globals.globalTypes);
|
||||
|
||||
#ifdef CALLGRIND
|
||||
CALLGRIND_ZERO_STATS;
|
||||
|
@ -1,7 +1,9 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <bitset>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
@ -22,5 +24,59 @@ std::pair<uint32_t, uint32_t> getLiveInOutValueCount(IrFunction& function, IrBlo
|
||||
uint32_t getLiveInValueCount(IrFunction& function, IrBlock& block);
|
||||
uint32_t getLiveOutValueCount(IrFunction& function, IrBlock& block);
|
||||
|
||||
struct RegisterSet
|
||||
{
|
||||
std::bitset<256> regs;
|
||||
|
||||
// If variadic sequence is active, we track register from which it starts
|
||||
bool varargSeq = false;
|
||||
uint8_t varargStart = 0;
|
||||
};
|
||||
|
||||
struct CfgInfo
|
||||
{
|
||||
std::vector<uint32_t> predecessors;
|
||||
std::vector<uint32_t> predecessorsOffsets;
|
||||
|
||||
std::vector<uint32_t> successors;
|
||||
std::vector<uint32_t> successorsOffsets;
|
||||
|
||||
std::vector<RegisterSet> in;
|
||||
std::vector<RegisterSet> out;
|
||||
|
||||
RegisterSet captured;
|
||||
};
|
||||
|
||||
void computeCfgInfo(IrFunction& function);
|
||||
|
||||
struct BlockIteratorWrapper
|
||||
{
|
||||
uint32_t* itBegin = nullptr;
|
||||
uint32_t* itEnd = nullptr;
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return itBegin == itEnd;
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return size_t(itEnd - itBegin);
|
||||
}
|
||||
|
||||
uint32_t* begin() const
|
||||
{
|
||||
return itBegin;
|
||||
}
|
||||
|
||||
uint32_t* end() const
|
||||
{
|
||||
return itEnd;
|
||||
}
|
||||
};
|
||||
|
||||
BlockIteratorWrapper predecessors(CfgInfo& cfg, uint32_t blockIdx);
|
||||
BlockIteratorWrapper successors(CfgInfo& cfg, uint32_t blockIdx);
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
||||
|
@ -1,6 +1,7 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/IrAnalysis.h"
|
||||
#include "Luau/Label.h"
|
||||
#include "Luau/RegisterX64.h"
|
||||
#include "Luau/RegisterA64.h"
|
||||
@ -261,6 +262,7 @@ enum class IrCmd : uint8_t
|
||||
// A: Rn (value start)
|
||||
// B: unsigned int (number of registers to go over)
|
||||
// Note: result is stored in the register specified in 'A'
|
||||
// Note: all referenced registers might be modified in the operation
|
||||
CONCAT,
|
||||
|
||||
// Load function upvalue into stack slot
|
||||
@ -382,16 +384,16 @@ enum class IrCmd : uint8_t
|
||||
LOP_RETURN,
|
||||
|
||||
// Adjust loop variables for one iteration of a generic for loop, jump back to the loop header if loop needs to continue
|
||||
// A: Rn (loop variable start, updates Rn+2 Rn+3 Rn+4)
|
||||
// B: int (loop variable count, is more than 2, additional registers are set to nil)
|
||||
// A: Rn (loop variable start, updates Rn+2 and 'B' number of registers starting from Rn+3)
|
||||
// B: int (loop variable count, if more than 2, registers starting from Rn+5 are set to nil)
|
||||
// C: block (repeat)
|
||||
// D: block (exit)
|
||||
LOP_FORGLOOP,
|
||||
|
||||
// Handle LOP_FORGLOOP fallback when variable being iterated is not a table
|
||||
// A: unsigned int (bytecode instruction index)
|
||||
// B: Rn (loop state start, updates Rn+2 Rn+3 Rn+4 Rn+5)
|
||||
// C: int (extra variable count or -1 for ipairs-style iteration)
|
||||
// B: Rn (loop state start, updates Rn+2 and 'C' number of registers starting from Rn+3)
|
||||
// C: int (loop variable count and a MSB set when it's an ipairs-like iteration loop)
|
||||
// D: block (repeat)
|
||||
// E: block (exit)
|
||||
LOP_FORGLOOP_FALLBACK,
|
||||
@ -638,6 +640,8 @@ struct IrFunction
|
||||
|
||||
Proto* proto = nullptr;
|
||||
|
||||
CfgInfo cfg;
|
||||
|
||||
IrBlock& blockOp(IrOp op)
|
||||
{
|
||||
LUAU_ASSERT(op.kind == IrOpKind::Block);
|
||||
|
@ -11,6 +11,8 @@ namespace Luau
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
struct CfgInfo;
|
||||
|
||||
const char* getCmdName(IrCmd cmd);
|
||||
const char* getBlockKindName(IrBlockKind kind);
|
||||
|
||||
@ -19,6 +21,7 @@ struct IrToStringContext
|
||||
std::string& result;
|
||||
std::vector<IrBlock>& blocks;
|
||||
std::vector<IrConst>& constants;
|
||||
CfgInfo& cfg;
|
||||
};
|
||||
|
||||
void toString(IrToStringContext& ctx, const IrInst& inst, uint32_t index);
|
||||
@ -27,10 +30,10 @@ void toString(IrToStringContext& ctx, IrOp op);
|
||||
|
||||
void toString(std::string& result, IrConst constant);
|
||||
|
||||
void toStringDetailed(IrToStringContext& ctx, const IrInst& inst, uint32_t index);
|
||||
void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t index); // Block title
|
||||
void toStringDetailed(IrToStringContext& ctx, const IrInst& inst, uint32_t index, bool includeUseInfo);
|
||||
void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t index, bool includeUseInfo); // Block title
|
||||
|
||||
std::string toString(IrFunction& function, bool includeDetails);
|
||||
std::string toString(IrFunction& function, bool includeUseInfo);
|
||||
|
||||
std::string dump(IrFunction& function);
|
||||
|
||||
|
@ -183,9 +183,6 @@ void kill(IrFunction& function, uint32_t start, uint32_t end);
|
||||
// Remove a block, including all instructions inside
|
||||
void kill(IrFunction& function, IrBlock& block);
|
||||
|
||||
void removeUse(IrFunction& function, IrInst& inst);
|
||||
void removeUse(IrFunction& function, IrBlock& block);
|
||||
|
||||
// Replace a single operand and update use counts (can cause chain removal of dead code)
|
||||
void replace(IrFunction& function, IrOp& original, IrOp replacement);
|
||||
|
||||
|
@ -68,9 +68,24 @@ static NativeProto* assembleFunction(X64::AssemblyBuilderX64& build, NativeState
|
||||
if (options.includeAssembly || options.includeIr)
|
||||
{
|
||||
if (proto->debugname)
|
||||
build.logAppend("; function %s()", getstr(proto->debugname));
|
||||
build.logAppend("; function %s(", getstr(proto->debugname));
|
||||
else
|
||||
build.logAppend("; function()");
|
||||
build.logAppend("; function(");
|
||||
|
||||
for (int i = 0; i < proto->numparams; i++)
|
||||
{
|
||||
LocVar* var = proto->locvars ? &proto->locvars[proto->sizelocvars - proto->numparams + i] : nullptr;
|
||||
|
||||
if (var && var->varname)
|
||||
build.logAppend("%s%s", i == 0 ? "" : ", ", getstr(var->varname));
|
||||
else
|
||||
build.logAppend("%s$arg%d", i == 0 ? "" : ", ", i);
|
||||
}
|
||||
|
||||
if (proto->numparams != 0 && proto->is_vararg)
|
||||
build.logAppend(", ...)");
|
||||
else
|
||||
build.logAppend(")");
|
||||
|
||||
if (proto->linedefined >= 0)
|
||||
build.logAppend(" line %d\n", proto->linedefined);
|
||||
@ -90,6 +105,10 @@ static NativeProto* assembleFunction(X64::AssemblyBuilderX64& build, NativeState
|
||||
constPropInBlockChains(builder);
|
||||
}
|
||||
|
||||
// TODO: cfg info has to be computed earlier to use in optimizations
|
||||
// It's done here to appear in text output and to measure performance impact on code generation
|
||||
computeCfgInfo(builder.function);
|
||||
|
||||
optimizeMemoryOperandsX64(builder.function);
|
||||
|
||||
X64::IrLoweringX64 lowering(build, helpers, data, proto, builder.function);
|
||||
|
@ -5,6 +5,10 @@
|
||||
#include "Luau/IrData.h"
|
||||
#include "Luau/IrUtils.h"
|
||||
|
||||
#include "lobject.h"
|
||||
|
||||
#include <bitset>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace Luau
|
||||
@ -116,5 +120,518 @@ uint32_t getLiveOutValueCount(IrFunction& function, IrBlock& block)
|
||||
return getLiveInOutValueCount(function, block).second;
|
||||
}
|
||||
|
||||
static void requireVariadicSequence(RegisterSet& sourceRs, const RegisterSet& defRs, uint8_t varargStart)
|
||||
{
|
||||
if (!defRs.varargSeq)
|
||||
{
|
||||
LUAU_ASSERT(!sourceRs.varargSeq || sourceRs.varargStart == varargStart);
|
||||
|
||||
sourceRs.varargSeq = true;
|
||||
sourceRs.varargStart = varargStart;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Variadic use sequence might include registers before def sequence
|
||||
for (int i = varargStart; i < defRs.varargStart; i++)
|
||||
{
|
||||
if (!defRs.regs.test(i))
|
||||
sourceRs.regs.set(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& block, RegisterSet& defRs, std::bitset<256>& capturedRegs)
|
||||
{
|
||||
RegisterSet inRs;
|
||||
|
||||
auto def = [&](IrOp op, int offset = 0) {
|
||||
LUAU_ASSERT(op.kind == IrOpKind::VmReg);
|
||||
defRs.regs.set(op.index + offset, true);
|
||||
};
|
||||
|
||||
auto use = [&](IrOp op, int offset = 0) {
|
||||
LUAU_ASSERT(op.kind == IrOpKind::VmReg);
|
||||
if (!defRs.regs.test(op.index + offset))
|
||||
inRs.regs.set(op.index + offset, true);
|
||||
};
|
||||
|
||||
auto maybeDef = [&](IrOp op) {
|
||||
if (op.kind == IrOpKind::VmReg)
|
||||
defRs.regs.set(op.index, true);
|
||||
};
|
||||
|
||||
auto maybeUse = [&](IrOp op) {
|
||||
if (op.kind == IrOpKind::VmReg)
|
||||
{
|
||||
if (!defRs.regs.test(op.index))
|
||||
inRs.regs.set(op.index, true);
|
||||
}
|
||||
};
|
||||
|
||||
auto defVarargs = [&](uint8_t varargStart) {
|
||||
defRs.varargSeq = true;
|
||||
defRs.varargStart = varargStart;
|
||||
};
|
||||
|
||||
auto useVarargs = [&](uint8_t varargStart) {
|
||||
requireVariadicSequence(inRs, defRs, varargStart);
|
||||
|
||||
// Variadic sequence has been consumed
|
||||
defRs.varargSeq = false;
|
||||
defRs.varargStart = 0;
|
||||
};
|
||||
|
||||
auto defRange = [&](int start, int count) {
|
||||
if (count == -1)
|
||||
{
|
||||
defVarargs(start);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = start; i < start + count; i++)
|
||||
defRs.regs.set(i, true);
|
||||
}
|
||||
};
|
||||
|
||||
auto useRange = [&](int start, int count) {
|
||||
if (count == -1)
|
||||
{
|
||||
useVarargs(start);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = start; i < start + count; i++)
|
||||
{
|
||||
if (!defRs.regs.test(i))
|
||||
inRs.regs.set(i, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (uint32_t instIdx = block.start; instIdx <= block.finish; instIdx++)
|
||||
{
|
||||
const IrInst& inst = function.instructions[instIdx];
|
||||
|
||||
// For correct analysis, all instruction uses must be handled before handling the definitions
|
||||
switch (inst.cmd)
|
||||
{
|
||||
case IrCmd::LOAD_TAG:
|
||||
case IrCmd::LOAD_POINTER:
|
||||
case IrCmd::LOAD_DOUBLE:
|
||||
case IrCmd::LOAD_INT:
|
||||
case IrCmd::LOAD_TVALUE:
|
||||
maybeUse(inst.a); // Argument can also be a VmConst
|
||||
break;
|
||||
case IrCmd::STORE_TAG:
|
||||
case IrCmd::STORE_POINTER:
|
||||
case IrCmd::STORE_DOUBLE:
|
||||
case IrCmd::STORE_INT:
|
||||
case IrCmd::STORE_TVALUE:
|
||||
maybeDef(inst.a); // Argument can also be a pointer value
|
||||
break;
|
||||
case IrCmd::JUMP_IF_TRUTHY:
|
||||
case IrCmd::JUMP_IF_FALSY:
|
||||
use(inst.a);
|
||||
break;
|
||||
case IrCmd::JUMP_CMP_ANY:
|
||||
use(inst.a);
|
||||
use(inst.b);
|
||||
break;
|
||||
// A <- B, C
|
||||
case IrCmd::DO_ARITH:
|
||||
case IrCmd::GET_TABLE:
|
||||
case IrCmd::SET_TABLE:
|
||||
use(inst.b);
|
||||
maybeUse(inst.c); // Argument can also be a VmConst
|
||||
|
||||
def(inst.a);
|
||||
break;
|
||||
// A <- B
|
||||
case IrCmd::DO_LEN:
|
||||
use(inst.b);
|
||||
|
||||
def(inst.a);
|
||||
break;
|
||||
case IrCmd::GET_IMPORT:
|
||||
def(inst.a);
|
||||
break;
|
||||
case IrCmd::CONCAT:
|
||||
useRange(inst.a.index, function.uintOp(inst.b));
|
||||
|
||||
defRange(inst.a.index, function.uintOp(inst.b));
|
||||
break;
|
||||
case IrCmd::GET_UPVALUE:
|
||||
def(inst.a);
|
||||
break;
|
||||
case IrCmd::SET_UPVALUE:
|
||||
use(inst.b);
|
||||
break;
|
||||
case IrCmd::PREPARE_FORN:
|
||||
use(inst.a);
|
||||
use(inst.b);
|
||||
use(inst.c);
|
||||
|
||||
def(inst.a);
|
||||
def(inst.b);
|
||||
def(inst.c);
|
||||
break;
|
||||
case IrCmd::INTERRUPT:
|
||||
break;
|
||||
case IrCmd::BARRIER_OBJ:
|
||||
case IrCmd::BARRIER_TABLE_FORWARD:
|
||||
use(inst.b);
|
||||
break;
|
||||
case IrCmd::CLOSE_UPVALS:
|
||||
// Closing an upvalue should be counted as a register use (it copies the fresh register value)
|
||||
// But we lack the required information about the specific set of registers that are affected
|
||||
// Because we don't plan to optimize captured registers atm, we skip full dataflow analysis for them right now
|
||||
break;
|
||||
case IrCmd::CAPTURE:
|
||||
maybeUse(inst.a);
|
||||
|
||||
if (function.boolOp(inst.b))
|
||||
capturedRegs.set(inst.a.index, true);
|
||||
break;
|
||||
case IrCmd::LOP_SETLIST:
|
||||
use(inst.b);
|
||||
useRange(inst.c.index, function.intOp(inst.d));
|
||||
break;
|
||||
case IrCmd::LOP_NAMECALL:
|
||||
use(inst.c);
|
||||
|
||||
defRange(inst.b.index, 2);
|
||||
break;
|
||||
case IrCmd::LOP_CALL:
|
||||
use(inst.b);
|
||||
useRange(inst.b.index + 1, function.intOp(inst.c));
|
||||
|
||||
defRange(inst.b.index, function.intOp(inst.d));
|
||||
break;
|
||||
case IrCmd::LOP_RETURN:
|
||||
useRange(inst.b.index, function.intOp(inst.c));
|
||||
break;
|
||||
case IrCmd::FASTCALL:
|
||||
case IrCmd::INVOKE_FASTCALL:
|
||||
if (int count = function.intOp(inst.e); count != -1)
|
||||
{
|
||||
if (count >= 3)
|
||||
{
|
||||
LUAU_ASSERT(inst.d.kind == IrOpKind::VmReg && inst.d.index == inst.c.index + 1);
|
||||
|
||||
useRange(inst.c.index, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (count >= 1)
|
||||
use(inst.c);
|
||||
|
||||
if (count >= 2)
|
||||
maybeUse(inst.d); // Argument can also be a VmConst
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
useVarargs(inst.c.index);
|
||||
}
|
||||
|
||||
defRange(inst.b.index, function.intOp(inst.f));
|
||||
break;
|
||||
case IrCmd::LOP_FORGLOOP:
|
||||
// First register is not used by instruction, we check that it's still 'nil' with CHECK_TAG
|
||||
use(inst.a, 1);
|
||||
use(inst.a, 2);
|
||||
|
||||
def(inst.a, 2);
|
||||
defRange(inst.a.index + 3, function.intOp(inst.b));
|
||||
break;
|
||||
case IrCmd::LOP_FORGLOOP_FALLBACK:
|
||||
useRange(inst.b.index, 3);
|
||||
|
||||
def(inst.b, 2);
|
||||
defRange(inst.b.index + 3, uint8_t(function.intOp(inst.c))); // ignore most significant bit
|
||||
break;
|
||||
case IrCmd::LOP_FORGPREP_XNEXT_FALLBACK:
|
||||
use(inst.b);
|
||||
break;
|
||||
// B <- C, D
|
||||
case IrCmd::LOP_AND:
|
||||
case IrCmd::LOP_OR:
|
||||
use(inst.c);
|
||||
use(inst.d);
|
||||
|
||||
def(inst.b);
|
||||
break;
|
||||
// B <- C
|
||||
case IrCmd::LOP_ANDK:
|
||||
case IrCmd::LOP_ORK:
|
||||
use(inst.c);
|
||||
|
||||
def(inst.b);
|
||||
break;
|
||||
case IrCmd::FALLBACK_GETGLOBAL:
|
||||
def(inst.b);
|
||||
break;
|
||||
case IrCmd::FALLBACK_SETGLOBAL:
|
||||
use(inst.b);
|
||||
break;
|
||||
case IrCmd::FALLBACK_GETTABLEKS:
|
||||
use(inst.c);
|
||||
|
||||
def(inst.b);
|
||||
break;
|
||||
case IrCmd::FALLBACK_SETTABLEKS:
|
||||
use(inst.b);
|
||||
use(inst.c);
|
||||
break;
|
||||
case IrCmd::FALLBACK_NAMECALL:
|
||||
use(inst.c);
|
||||
|
||||
defRange(inst.b.index, 2);
|
||||
break;
|
||||
case IrCmd::FALLBACK_PREPVARARGS:
|
||||
// No effect on explicitly referenced registers
|
||||
break;
|
||||
case IrCmd::FALLBACK_GETVARARGS:
|
||||
defRange(inst.b.index, function.intOp(inst.c));
|
||||
break;
|
||||
case IrCmd::FALLBACK_NEWCLOSURE:
|
||||
def(inst.b);
|
||||
break;
|
||||
case IrCmd::FALLBACK_DUPCLOSURE:
|
||||
def(inst.b);
|
||||
break;
|
||||
case IrCmd::FALLBACK_FORGPREP:
|
||||
use(inst.b);
|
||||
|
||||
defRange(inst.b.index, 3);
|
||||
break;
|
||||
case IrCmd::ADJUST_STACK_TO_REG:
|
||||
case IrCmd::ADJUST_STACK_TO_TOP:
|
||||
// While these can be considered as vararg producers and consumers, it is already handled in fastcall instruction
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return inRs;
|
||||
}
|
||||
|
||||
// The algorithm used here is commonly known as backwards data-flow analysis.
|
||||
// For each block, we track 'upward-exposed' (live-in) uses of registers - a use of a register that hasn't been defined in the block yet.
|
||||
// We also track the set of registers that were defined in the block.
|
||||
// When initial live-in sets of registers are computed, propagation of those uses upwards through predecessors is performed.
|
||||
// If predecessor doesn't define the register, we have to add it to the live-in set.
|
||||
// Extending the set of live-in registers of a block requires re-checking of that block.
|
||||
// Propagation runs iteratively, using a worklist of blocks to visit until a fixed point is reached.
|
||||
// This algorithm can be easily extended to cover phi instructions, but we don't use those yet.
|
||||
static void computeCfgLiveInOutRegSets(IrFunction& function)
|
||||
{
|
||||
CfgInfo& info = function.cfg;
|
||||
|
||||
// Try to compute Luau VM register use-def info
|
||||
info.in.resize(function.blocks.size());
|
||||
info.out.resize(function.blocks.size());
|
||||
|
||||
// Captured registers are tracked for the whole function
|
||||
// It should be possible to have a more precise analysis for them in the future
|
||||
std::bitset<256> capturedRegs;
|
||||
|
||||
std::vector<RegisterSet> defRss;
|
||||
defRss.resize(function.blocks.size());
|
||||
|
||||
// First we compute live-in set of each block
|
||||
for (size_t blockIdx = 0; blockIdx < function.blocks.size(); blockIdx++)
|
||||
{
|
||||
const IrBlock& block = function.blocks[blockIdx];
|
||||
|
||||
if (block.kind == IrBlockKind::Dead)
|
||||
continue;
|
||||
|
||||
info.in[blockIdx] = computeBlockLiveInRegSet(function, block, defRss[blockIdx], capturedRegs);
|
||||
}
|
||||
|
||||
info.captured.regs = capturedRegs;
|
||||
|
||||
// With live-in sets ready, we can arrive at a fixed point for both in/out registers by requesting required registers from predecessors
|
||||
std::vector<uint32_t> worklist;
|
||||
|
||||
std::vector<uint8_t> inWorklist;
|
||||
inWorklist.resize(function.blocks.size(), false);
|
||||
|
||||
// We will have to visit each block at least once, so we add all of them to the worklist immediately
|
||||
for (size_t blockIdx = 0; blockIdx < function.blocks.size(); blockIdx++)
|
||||
{
|
||||
const IrBlock& block = function.blocks[blockIdx];
|
||||
|
||||
if (block.kind == IrBlockKind::Dead)
|
||||
continue;
|
||||
|
||||
worklist.push_back(uint32_t(blockIdx));
|
||||
inWorklist[blockIdx] = true;
|
||||
}
|
||||
|
||||
while (!worklist.empty())
|
||||
{
|
||||
uint32_t blockIdx = worklist.back();
|
||||
worklist.pop_back();
|
||||
inWorklist[blockIdx] = false;
|
||||
|
||||
IrBlock& curr = function.blocks[blockIdx];
|
||||
RegisterSet& inRs = info.in[blockIdx];
|
||||
RegisterSet& outRs = info.out[blockIdx];
|
||||
RegisterSet& defRs = defRss[blockIdx];
|
||||
|
||||
// Current block has to provide all registers in successor blocks
|
||||
for (uint32_t succIdx : successors(info, blockIdx))
|
||||
{
|
||||
IrBlock& succ = function.blocks[succIdx];
|
||||
|
||||
// This is a step away from the usual definition of live range flow through CFG
|
||||
// Exit from a regular block to a fallback block is not considered a block terminator
|
||||
// This is because fallback blocks define an alternative implementation of the same operations
|
||||
// This can cause the current block to define more registers that actually were available at fallback entry
|
||||
if (curr.kind != IrBlockKind::Fallback && succ.kind == IrBlockKind::Fallback)
|
||||
continue;
|
||||
|
||||
const RegisterSet& succRs = info.in[succIdx];
|
||||
|
||||
outRs.regs |= succRs.regs;
|
||||
|
||||
if (succRs.varargSeq)
|
||||
{
|
||||
LUAU_ASSERT(!outRs.varargSeq || outRs.varargStart == succRs.varargStart);
|
||||
|
||||
outRs.varargSeq = true;
|
||||
outRs.varargStart = succRs.varargStart;
|
||||
}
|
||||
}
|
||||
|
||||
RegisterSet oldInRs = inRs;
|
||||
|
||||
// If current block didn't define a live-out, it has to be live-in
|
||||
inRs.regs |= outRs.regs & ~defRs.regs;
|
||||
|
||||
if (outRs.varargSeq)
|
||||
requireVariadicSequence(inRs, defRs, outRs.varargStart);
|
||||
|
||||
// If we have new live-ins, we have to notify all predecessors
|
||||
// We don't allow changes to the start of the variadic sequence, so we skip checking that member
|
||||
if (inRs.regs != oldInRs.regs || inRs.varargSeq != oldInRs.varargSeq)
|
||||
{
|
||||
for (uint32_t predIdx : predecessors(info, blockIdx))
|
||||
{
|
||||
if (!inWorklist[predIdx])
|
||||
{
|
||||
worklist.push_back(predIdx);
|
||||
inWorklist[predIdx] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If Proto data is available, validate that entry block arguments match required registers
|
||||
if (function.proto)
|
||||
{
|
||||
RegisterSet& entryIn = info.in[0];
|
||||
|
||||
LUAU_ASSERT(!entryIn.varargSeq);
|
||||
|
||||
for (size_t i = 0; i < entryIn.regs.size(); i++)
|
||||
LUAU_ASSERT(!entryIn.regs.test(i) || i < function.proto->numparams);
|
||||
}
|
||||
}
|
||||
|
||||
static void computeCfgBlockEdges(IrFunction& function)
|
||||
{
|
||||
CfgInfo& info = function.cfg;
|
||||
|
||||
// Compute predecessors block edges
|
||||
info.predecessorsOffsets.reserve(function.blocks.size());
|
||||
info.successorsOffsets.reserve(function.blocks.size());
|
||||
|
||||
int edgeCount = 0;
|
||||
|
||||
for (const IrBlock& block : function.blocks)
|
||||
{
|
||||
info.predecessorsOffsets.push_back(edgeCount);
|
||||
edgeCount += block.useCount;
|
||||
}
|
||||
|
||||
info.predecessors.resize(edgeCount);
|
||||
info.successors.resize(edgeCount);
|
||||
|
||||
edgeCount = 0;
|
||||
|
||||
for (size_t blockIdx = 0; blockIdx < function.blocks.size(); blockIdx++)
|
||||
{
|
||||
const IrBlock& block = function.blocks[blockIdx];
|
||||
|
||||
info.successorsOffsets.push_back(edgeCount);
|
||||
|
||||
if (block.kind == IrBlockKind::Dead)
|
||||
continue;
|
||||
|
||||
for (uint32_t instIdx = block.start; instIdx <= block.finish; instIdx++)
|
||||
{
|
||||
const IrInst& inst = function.instructions[instIdx];
|
||||
|
||||
auto checkOp = [&](IrOp op) {
|
||||
if (op.kind == IrOpKind::Block)
|
||||
{
|
||||
// We use a trick here, where we use the starting offset of the predecessor list as the position where to write next predecessor
|
||||
// The values will be adjusted back in a separate loop later
|
||||
info.predecessors[info.predecessorsOffsets[op.index]++] = uint32_t(blockIdx);
|
||||
|
||||
info.successors[edgeCount++] = op.index;
|
||||
}
|
||||
};
|
||||
|
||||
checkOp(inst.a);
|
||||
checkOp(inst.b);
|
||||
checkOp(inst.c);
|
||||
checkOp(inst.d);
|
||||
checkOp(inst.e);
|
||||
checkOp(inst.f);
|
||||
}
|
||||
}
|
||||
|
||||
// Offsets into the predecessor list were used as iterators in the previous loop
|
||||
// To adjust them back, block use count is subtracted (predecessor count is equal to how many uses block has)
|
||||
for (size_t blockIdx = 0; blockIdx < function.blocks.size(); blockIdx++)
|
||||
{
|
||||
const IrBlock& block = function.blocks[blockIdx];
|
||||
|
||||
info.predecessorsOffsets[blockIdx] -= block.useCount;
|
||||
}
|
||||
}
|
||||
|
||||
void computeCfgInfo(IrFunction& function)
|
||||
{
|
||||
computeCfgBlockEdges(function);
|
||||
computeCfgLiveInOutRegSets(function);
|
||||
}
|
||||
|
||||
BlockIteratorWrapper predecessors(CfgInfo& cfg, uint32_t blockIdx)
|
||||
{
|
||||
LUAU_ASSERT(blockIdx < cfg.predecessorsOffsets.size());
|
||||
|
||||
uint32_t start = cfg.predecessorsOffsets[blockIdx];
|
||||
uint32_t end = blockIdx + 1 < cfg.predecessorsOffsets.size() ? cfg.predecessorsOffsets[blockIdx + 1] : uint32_t(cfg.predecessors.size());
|
||||
|
||||
return BlockIteratorWrapper{cfg.predecessors.data() + start, cfg.predecessors.data() + end};
|
||||
}
|
||||
|
||||
BlockIteratorWrapper successors(CfgInfo& cfg, uint32_t blockIdx)
|
||||
{
|
||||
LUAU_ASSERT(blockIdx < cfg.successorsOffsets.size());
|
||||
|
||||
uint32_t start = cfg.successorsOffsets[blockIdx];
|
||||
uint32_t end = blockIdx + 1 < cfg.successorsOffsets.size() ? cfg.successorsOffsets[blockIdx + 1] : uint32_t(cfg.successors.size());
|
||||
|
||||
return BlockIteratorWrapper{cfg.successors.data() + start, cfg.successors.data() + end};
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
||||
|
@ -256,7 +256,7 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
|
||||
translateInstDupTable(*this, pc, i);
|
||||
break;
|
||||
case LOP_SETLIST:
|
||||
inst(IrCmd::LOP_SETLIST, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_A(*pc)), constInt(LUAU_INSN_C(*pc) - 1), constUint(pc[1]));
|
||||
inst(IrCmd::LOP_SETLIST, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), constInt(LUAU_INSN_C(*pc) - 1), constUint(pc[1]));
|
||||
break;
|
||||
case LOP_GETUPVAL:
|
||||
translateInstGetUpval(*this, pc, i);
|
||||
|
@ -306,7 +306,7 @@ void toString(IrToStringContext& ctx, const IrInst& inst, uint32_t index)
|
||||
|
||||
void toString(IrToStringContext& ctx, const IrBlock& block, uint32_t index)
|
||||
{
|
||||
append(ctx.result, "%s_%u:", getBlockKindName(block.kind), index);
|
||||
append(ctx.result, "%s_%u", getBlockKindName(block.kind), index);
|
||||
}
|
||||
|
||||
void toString(IrToStringContext& ctx, IrOp op)
|
||||
@ -362,11 +362,14 @@ void toString(std::string& result, IrConst constant)
|
||||
}
|
||||
}
|
||||
|
||||
void toStringDetailed(IrToStringContext& ctx, const IrInst& inst, uint32_t index)
|
||||
void toStringDetailed(IrToStringContext& ctx, const IrInst& inst, uint32_t index, bool includeUseInfo)
|
||||
{
|
||||
size_t start = ctx.result.size();
|
||||
|
||||
toString(ctx, inst, index);
|
||||
|
||||
if (includeUseInfo)
|
||||
{
|
||||
padToDetailColumn(ctx.result, start);
|
||||
|
||||
if (inst.useCount == 0 && hasSideEffects(inst.cmd))
|
||||
@ -374,21 +377,136 @@ void toStringDetailed(IrToStringContext& ctx, const IrInst& inst, uint32_t index
|
||||
else
|
||||
append(ctx.result, "; useCount: %d, lastUse: %%%u\n", inst.useCount, inst.lastUse);
|
||||
}
|
||||
|
||||
void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t index)
|
||||
else
|
||||
{
|
||||
ctx.result.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void appendBlockSet(IrToStringContext& ctx, BlockIteratorWrapper blocks)
|
||||
{
|
||||
bool comma = false;
|
||||
|
||||
for (uint32_t target : blocks)
|
||||
{
|
||||
if (comma)
|
||||
append(ctx.result, ", ");
|
||||
comma = true;
|
||||
|
||||
toString(ctx, ctx.blocks[target], target);
|
||||
}
|
||||
}
|
||||
|
||||
static void appendRegisterSet(IrToStringContext& ctx, const RegisterSet& rs)
|
||||
{
|
||||
bool comma = false;
|
||||
|
||||
for (size_t i = 0; i < rs.regs.size(); i++)
|
||||
{
|
||||
if (rs.regs.test(i))
|
||||
{
|
||||
if (comma)
|
||||
append(ctx.result, ", ");
|
||||
comma = true;
|
||||
|
||||
append(ctx.result, "R%d", int(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (rs.varargSeq)
|
||||
{
|
||||
if (comma)
|
||||
append(ctx.result, ", ");
|
||||
|
||||
append(ctx.result, "R%d...", rs.varargStart);
|
||||
}
|
||||
}
|
||||
|
||||
void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t index, bool includeUseInfo)
|
||||
{
|
||||
// Report captured registers for entry block
|
||||
if (block.useCount == 0 && block.kind != IrBlockKind::Dead && ctx.cfg.captured.regs.any())
|
||||
{
|
||||
append(ctx.result, "; captured regs: ");
|
||||
appendRegisterSet(ctx, ctx.cfg.captured);
|
||||
append(ctx.result, "\n\n");
|
||||
}
|
||||
|
||||
size_t start = ctx.result.size();
|
||||
|
||||
toString(ctx, block, index);
|
||||
append(ctx.result, ":");
|
||||
|
||||
if (includeUseInfo)
|
||||
{
|
||||
padToDetailColumn(ctx.result, start);
|
||||
|
||||
append(ctx.result, "; useCount: %d\n", block.useCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.result.append("\n");
|
||||
}
|
||||
|
||||
std::string toString(IrFunction& function, bool includeDetails)
|
||||
// Predecessor list
|
||||
if (!ctx.cfg.predecessors.empty())
|
||||
{
|
||||
BlockIteratorWrapper pred = predecessors(ctx.cfg, index);
|
||||
|
||||
if (!pred.empty())
|
||||
{
|
||||
append(ctx.result, "; predecessors: ");
|
||||
|
||||
appendBlockSet(ctx, pred);
|
||||
append(ctx.result, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Successor list
|
||||
if (!ctx.cfg.successors.empty())
|
||||
{
|
||||
BlockIteratorWrapper succ = successors(ctx.cfg, index);
|
||||
|
||||
if (!succ.empty())
|
||||
{
|
||||
append(ctx.result, "; successors: ");
|
||||
|
||||
appendBlockSet(ctx, succ);
|
||||
append(ctx.result, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Live-in VM regs
|
||||
if (index < ctx.cfg.in.size())
|
||||
{
|
||||
const RegisterSet& in = ctx.cfg.in[index];
|
||||
|
||||
if (in.regs.any() || in.varargSeq)
|
||||
{
|
||||
append(ctx.result, "; in regs: ");
|
||||
appendRegisterSet(ctx, in);
|
||||
append(ctx.result, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Live-out VM regs
|
||||
if (index < ctx.cfg.out.size())
|
||||
{
|
||||
const RegisterSet& out = ctx.cfg.out[index];
|
||||
|
||||
if (out.regs.any() || out.varargSeq)
|
||||
{
|
||||
append(ctx.result, "; out regs: ");
|
||||
appendRegisterSet(ctx, out);
|
||||
append(ctx.result, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string toString(IrFunction& function, bool includeUseInfo)
|
||||
{
|
||||
std::string result;
|
||||
IrToStringContext ctx{result, function.blocks, function.constants};
|
||||
IrToStringContext ctx{result, function.blocks, function.constants, function.cfg};
|
||||
|
||||
for (size_t i = 0; i < function.blocks.size(); i++)
|
||||
{
|
||||
@ -397,15 +515,7 @@ std::string toString(IrFunction& function, bool includeDetails)
|
||||
if (block.kind == IrBlockKind::Dead)
|
||||
continue;
|
||||
|
||||
if (includeDetails)
|
||||
{
|
||||
toStringDetailed(ctx, block, uint32_t(i));
|
||||
}
|
||||
else
|
||||
{
|
||||
toString(ctx, block, uint32_t(i));
|
||||
ctx.result.append("\n");
|
||||
}
|
||||
toStringDetailed(ctx, block, uint32_t(i), includeUseInfo);
|
||||
|
||||
if (block.start == ~0u)
|
||||
{
|
||||
@ -423,16 +533,7 @@ std::string toString(IrFunction& function, bool includeDetails)
|
||||
continue;
|
||||
|
||||
append(ctx.result, " ");
|
||||
|
||||
if (includeDetails)
|
||||
{
|
||||
toStringDetailed(ctx, inst, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
toString(ctx, inst, index);
|
||||
ctx.result.append("\n");
|
||||
}
|
||||
toStringDetailed(ctx, inst, index, includeUseInfo);
|
||||
}
|
||||
|
||||
append(ctx.result, "\n");
|
||||
@ -443,7 +544,7 @@ std::string toString(IrFunction& function, bool includeDetails)
|
||||
|
||||
std::string dump(IrFunction& function)
|
||||
{
|
||||
std::string result = toString(function, /* includeDetails */ true);
|
||||
std::string result = toString(function, /* includeUseInfo */ true);
|
||||
|
||||
printf("%s\n", result.c_str());
|
||||
|
||||
|
@ -79,7 +79,7 @@ void IrLoweringX64::lower(AssemblyOptions options)
|
||||
}
|
||||
}
|
||||
|
||||
IrToStringContext ctx{build.text, function.blocks, function.constants};
|
||||
IrToStringContext ctx{build.text, function.blocks, function.constants, function.cfg};
|
||||
|
||||
// We use this to skip outlined fallback blocks from IR/asm text output
|
||||
size_t textSize = build.text.length();
|
||||
@ -112,7 +112,7 @@ void IrLoweringX64::lower(AssemblyOptions options)
|
||||
if (options.includeIr)
|
||||
{
|
||||
build.logAppend("# ");
|
||||
toStringDetailed(ctx, block, blockIndex);
|
||||
toStringDetailed(ctx, block, blockIndex, /* includeUseInfo */ true);
|
||||
}
|
||||
|
||||
build.setLabel(block.label);
|
||||
@ -145,7 +145,7 @@ void IrLoweringX64::lower(AssemblyOptions options)
|
||||
if (options.includeIr)
|
||||
{
|
||||
build.logAppend("# ");
|
||||
toStringDetailed(ctx, inst, index);
|
||||
toStringDetailed(ctx, inst, index, /* includeUseInfo */ true);
|
||||
}
|
||||
|
||||
IrBlock& next = i + 1 < sortedBlocks.size() ? function.blocks[sortedBlocks[i + 1]] : dummy;
|
||||
@ -416,7 +416,20 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
{
|
||||
inst.regX64 = regs.allocXmmRegOrReuse(index, {inst.a, inst.b});
|
||||
|
||||
RegisterX64 lhs = regOp(inst.a);
|
||||
ScopedRegX64 optLhsTmp{regs};
|
||||
RegisterX64 lhs;
|
||||
|
||||
if (inst.a.kind == IrOpKind::Constant)
|
||||
{
|
||||
optLhsTmp.alloc(SizeX64::xmmword);
|
||||
|
||||
build.vmovsd(optLhsTmp.reg, memRegDoubleOp(inst.a));
|
||||
lhs = optLhsTmp.reg;
|
||||
}
|
||||
else
|
||||
{
|
||||
lhs = regOp(inst.a);
|
||||
}
|
||||
|
||||
if (inst.b.kind == IrOpKind::Inst)
|
||||
{
|
||||
@ -444,14 +457,15 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
{
|
||||
inst.regX64 = regs.allocXmmRegOrReuse(index, {inst.a, inst.b});
|
||||
|
||||
ScopedRegX64 tmp{regs, SizeX64::xmmword};
|
||||
|
||||
ScopedRegX64 optLhsTmp{regs};
|
||||
RegisterX64 lhs;
|
||||
|
||||
if (inst.a.kind == IrOpKind::Constant)
|
||||
{
|
||||
build.vmovsd(tmp.reg, memRegDoubleOp(inst.a));
|
||||
lhs = tmp.reg;
|
||||
optLhsTmp.alloc(SizeX64::xmmword);
|
||||
|
||||
build.vmovsd(optLhsTmp.reg, memRegDoubleOp(inst.a));
|
||||
lhs = optLhsTmp.reg;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -169,13 +169,17 @@ void IrRegAllocX64::assertAllFree() const
|
||||
LUAU_ASSERT(free);
|
||||
}
|
||||
|
||||
ScopedRegX64::ScopedRegX64(IrRegAllocX64& owner)
|
||||
: owner(owner)
|
||||
, reg(noreg)
|
||||
{
|
||||
}
|
||||
|
||||
ScopedRegX64::ScopedRegX64(IrRegAllocX64& owner, SizeX64 size)
|
||||
: owner(owner)
|
||||
, reg(noreg)
|
||||
{
|
||||
if (size == SizeX64::xmmword)
|
||||
reg = owner.allocXmmReg();
|
||||
else
|
||||
reg = owner.allocGprReg(size);
|
||||
alloc(size);
|
||||
}
|
||||
|
||||
ScopedRegX64::ScopedRegX64(IrRegAllocX64& owner, RegisterX64 reg)
|
||||
@ -190,6 +194,16 @@ ScopedRegX64::~ScopedRegX64()
|
||||
owner.freeReg(reg);
|
||||
}
|
||||
|
||||
void ScopedRegX64::alloc(SizeX64 size)
|
||||
{
|
||||
LUAU_ASSERT(reg == noreg);
|
||||
|
||||
if (size == SizeX64::xmmword)
|
||||
reg = owner.allocXmmReg();
|
||||
else
|
||||
reg = owner.allocGprReg(size);
|
||||
}
|
||||
|
||||
void ScopedRegX64::free()
|
||||
{
|
||||
LUAU_ASSERT(reg != noreg);
|
||||
|
@ -40,6 +40,7 @@ struct IrRegAllocX64
|
||||
|
||||
struct ScopedRegX64
|
||||
{
|
||||
explicit ScopedRegX64(IrRegAllocX64& owner);
|
||||
ScopedRegX64(IrRegAllocX64& owner, SizeX64 size);
|
||||
ScopedRegX64(IrRegAllocX64& owner, RegisterX64 reg);
|
||||
~ScopedRegX64();
|
||||
@ -47,6 +48,7 @@ struct ScopedRegX64
|
||||
ScopedRegX64(const ScopedRegX64&) = delete;
|
||||
ScopedRegX64& operator=(const ScopedRegX64&) = delete;
|
||||
|
||||
void alloc(SizeX64 size);
|
||||
void free();
|
||||
|
||||
IrRegAllocX64& owner;
|
||||
|
@ -14,6 +14,29 @@ namespace Luau
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
static void removeInstUse(IrFunction& function, uint32_t instIdx)
|
||||
{
|
||||
IrInst& inst = function.instructions[instIdx];
|
||||
|
||||
LUAU_ASSERT(inst.useCount);
|
||||
inst.useCount--;
|
||||
|
||||
if (inst.useCount == 0)
|
||||
kill(function, inst);
|
||||
}
|
||||
|
||||
static void removeBlockUse(IrFunction& function, uint32_t blockIdx)
|
||||
{
|
||||
IrBlock& block = function.blocks[blockIdx];
|
||||
|
||||
LUAU_ASSERT(block.useCount);
|
||||
block.useCount--;
|
||||
|
||||
// Entry block is never removed because is has an implicit use
|
||||
if (block.useCount == 0 && blockIdx != 0)
|
||||
kill(function, block);
|
||||
}
|
||||
|
||||
void addUse(IrFunction& function, IrOp op)
|
||||
{
|
||||
if (op.kind == IrOpKind::Inst)
|
||||
@ -25,9 +48,9 @@ void addUse(IrFunction& function, IrOp op)
|
||||
void removeUse(IrFunction& function, IrOp op)
|
||||
{
|
||||
if (op.kind == IrOpKind::Inst)
|
||||
removeUse(function, function.instructions[op.index]);
|
||||
removeInstUse(function, op.index);
|
||||
else if (op.kind == IrOpKind::Block)
|
||||
removeUse(function, function.blocks[op.index]);
|
||||
removeBlockUse(function, op.index);
|
||||
}
|
||||
|
||||
bool isGCO(uint8_t tag)
|
||||
@ -83,24 +106,6 @@ void kill(IrFunction& function, IrBlock& block)
|
||||
block.finish = ~0u;
|
||||
}
|
||||
|
||||
void removeUse(IrFunction& function, IrInst& inst)
|
||||
{
|
||||
LUAU_ASSERT(inst.useCount);
|
||||
inst.useCount--;
|
||||
|
||||
if (inst.useCount == 0)
|
||||
kill(function, inst);
|
||||
}
|
||||
|
||||
void removeUse(IrFunction& function, IrBlock& block)
|
||||
{
|
||||
LUAU_ASSERT(block.useCount);
|
||||
block.useCount--;
|
||||
|
||||
if (block.useCount == 0)
|
||||
kill(function, block);
|
||||
}
|
||||
|
||||
void replace(IrFunction& function, IrOp& original, IrOp replacement)
|
||||
{
|
||||
// Add use before removing new one if that's the last one keeping target operand alive
|
||||
@ -122,6 +127,9 @@ void replace(IrFunction& function, IrBlock& block, uint32_t instIdx, IrInst repl
|
||||
addUse(function, replacement.e);
|
||||
addUse(function, replacement.f);
|
||||
|
||||
// An extra reference is added so block will not remove itself
|
||||
block.useCount++;
|
||||
|
||||
// If we introduced an earlier terminating instruction, all following instructions become dead
|
||||
if (!isBlockTerminator(inst.cmd) && isBlockTerminator(replacement.cmd))
|
||||
{
|
||||
@ -142,6 +150,10 @@ void replace(IrFunction& function, IrBlock& block, uint32_t instIdx, IrInst repl
|
||||
removeUse(function, inst.f);
|
||||
|
||||
inst = replacement;
|
||||
|
||||
// Removing the earlier extra reference, this might leave the block without users without marking it as dead
|
||||
// This will have to be handled by separate dead code elimination
|
||||
block.useCount--;
|
||||
}
|
||||
|
||||
void substitute(IrFunction& function, IrInst& inst, IrOp replacement)
|
||||
|
@ -12,7 +12,6 @@ inline bool isFlagExperimental(const char* flag)
|
||||
// or critical bugs that are found after the code has been submitted.
|
||||
static const char* const kList[] = {
|
||||
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||
"LuauTryhardAnd", // waiting for a fix in graphql-lua -> apollo-client-lia -> lua-apps
|
||||
"LuauTypecheckTypeguards", // requires some fixes to lua-apps code (CLI-67030)
|
||||
// makes sure we always have at least one entry
|
||||
nullptr,
|
||||
|
@ -20,6 +20,18 @@
|
||||
#define LUAU_FASTMATH_END
|
||||
#endif
|
||||
|
||||
// Some functions like floor/ceil have SSE4.1 equivalents but we currently support systems without SSE4.1
|
||||
// Note that we only need to do this when SSE4.1 support is not guaranteed by compiler settings, as otherwise compiler will optimize these for us.
|
||||
#if (defined(__x86_64__) || defined(_M_X64)) && !defined(__SSE4_1__) && !defined(__AVX__)
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
#define LUAU_TARGET_SSE41
|
||||
#elif defined(__GNUC__) && defined(__has_attribute)
|
||||
#if __has_attribute(target)
|
||||
#define LUAU_TARGET_SSE41 __attribute__((target("sse4.1")))
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Used on functions that have a printf-like interface to validate them statically
|
||||
#if defined(__GNUC__)
|
||||
#define LUA_PRINTF_ATTR(fmt, arg) __attribute__((format(printf, fmt, arg)))
|
||||
|
@ -15,6 +15,16 @@
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
#ifdef LUAU_TARGET_SSE41
|
||||
#include <smmintrin.h>
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#include <cpuid.h> // on MSVC this comes from intrin.h
|
||||
#endif
|
||||
#endif
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBuiltinSSE41, false)
|
||||
|
||||
// luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM
|
||||
// The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack.
|
||||
// If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path
|
||||
@ -95,7 +105,9 @@ static int luauF_atan(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
||||
return -1;
|
||||
}
|
||||
|
||||
// TODO: LUAU_NOINLINE can be removed with LuauBuiltinSSE41
|
||||
LUAU_FASTMATH_BEGIN
|
||||
LUAU_NOINLINE
|
||||
static int luauF_ceil(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0))
|
||||
@ -158,7 +170,9 @@ static int luauF_exp(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
||||
return -1;
|
||||
}
|
||||
|
||||
// TODO: LUAU_NOINLINE can be removed with LuauBuiltinSSE41
|
||||
LUAU_FASTMATH_BEGIN
|
||||
LUAU_NOINLINE
|
||||
static int luauF_floor(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0))
|
||||
@ -935,7 +949,9 @@ static int luauF_sign(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
||||
return -1;
|
||||
}
|
||||
|
||||
// TODO: LUAU_NOINLINE can be removed with LuauBuiltinSSE41
|
||||
LUAU_FASTMATH_BEGIN
|
||||
LUAU_NOINLINE
|
||||
static int luauF_round(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0))
|
||||
@ -1244,6 +1260,78 @@ static int luauF_missing(lua_State* L, StkId res, TValue* arg0, int nresults, St
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef LUAU_TARGET_SSE41
|
||||
template<int Rounding>
|
||||
LUAU_TARGET_SSE41 inline double roundsd_sse41(double v)
|
||||
{
|
||||
__m128d av = _mm_set_sd(v);
|
||||
__m128d rv = _mm_round_sd(av, av, Rounding | _MM_FROUND_NO_EXC);
|
||||
return _mm_cvtsd_f64(rv);
|
||||
}
|
||||
|
||||
LUAU_TARGET_SSE41 static int luauF_floor_sse41(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (!FFlag::LuauBuiltinSSE41)
|
||||
return luauF_floor(L, res, arg0, nresults, args, nparams);
|
||||
|
||||
if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0))
|
||||
{
|
||||
double a1 = nvalue(arg0);
|
||||
setnvalue(res, roundsd_sse41<_MM_FROUND_TO_NEG_INF>(a1));
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
LUAU_TARGET_SSE41 static int luauF_ceil_sse41(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (!FFlag::LuauBuiltinSSE41)
|
||||
return luauF_ceil(L, res, arg0, nresults, args, nparams);
|
||||
|
||||
if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0))
|
||||
{
|
||||
double a1 = nvalue(arg0);
|
||||
setnvalue(res, roundsd_sse41<_MM_FROUND_TO_POS_INF>(a1));
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
LUAU_TARGET_SSE41 static int luauF_round_sse41(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (!FFlag::LuauBuiltinSSE41)
|
||||
return luauF_round(L, res, arg0, nresults, args, nparams);
|
||||
|
||||
if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0))
|
||||
{
|
||||
double a1 = nvalue(arg0);
|
||||
// roundsd only supports bankers rounding natively, so we need to emulate rounding by using truncation
|
||||
// offset is prevfloat(0.5), which is important so that we round prevfloat(0.5) to 0.
|
||||
const double offset = 0.49999999999999994;
|
||||
setnvalue(res, roundsd_sse41<_MM_FROUND_TO_ZERO>(a1 + (a1 < 0 ? -offset : offset)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool luau_hassse41()
|
||||
{
|
||||
int cpuinfo[4] = {};
|
||||
#ifdef _MSC_VER
|
||||
__cpuid(cpuinfo, 1);
|
||||
#else
|
||||
__cpuid(1, cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]);
|
||||
#endif
|
||||
|
||||
// We requre SSE4.1 support for ROUNDSD
|
||||
// https://en.wikipedia.org/wiki/CPUID#EAX=1:_Processor_Info_and_Feature_Bits
|
||||
return (cpuinfo[2] & (1 << 19)) != 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
const luau_FastFunction luauF_table[256] = {
|
||||
NULL,
|
||||
luauF_assert,
|
||||
@ -1253,12 +1341,24 @@ const luau_FastFunction luauF_table[256] = {
|
||||
luauF_asin,
|
||||
luauF_atan2,
|
||||
luauF_atan,
|
||||
|
||||
#ifdef LUAU_TARGET_SSE41
|
||||
luau_hassse41() ? luauF_ceil_sse41 : luauF_ceil,
|
||||
#else
|
||||
luauF_ceil,
|
||||
#endif
|
||||
|
||||
luauF_cosh,
|
||||
luauF_cos,
|
||||
luauF_deg,
|
||||
luauF_exp,
|
||||
|
||||
#ifdef LUAU_TARGET_SSE41
|
||||
luau_hassse41() ? luauF_floor_sse41 : luauF_floor,
|
||||
#else
|
||||
luauF_floor,
|
||||
#endif
|
||||
|
||||
luauF_fmod,
|
||||
luauF_frexp,
|
||||
luauF_ldexp,
|
||||
@ -1300,7 +1400,12 @@ const luau_FastFunction luauF_table[256] = {
|
||||
|
||||
luauF_clamp,
|
||||
luauF_sign,
|
||||
|
||||
#ifdef LUAU_TARGET_SSE41
|
||||
luau_hassse41() ? luauF_round_sse41 : luauF_round,
|
||||
#else
|
||||
luauF_round,
|
||||
#endif
|
||||
|
||||
luauF_rawset,
|
||||
luauF_rawget,
|
||||
|
@ -12,8 +12,6 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCheckGetInfoIndex, false)
|
||||
|
||||
static const char* getfuncname(Closure* f);
|
||||
|
||||
static int currentpc(lua_State* L, CallInfo* ci)
|
||||
@ -175,20 +173,11 @@ int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar)
|
||||
Closure* f = NULL;
|
||||
CallInfo* ci = NULL;
|
||||
if (level < 0)
|
||||
{
|
||||
if (FFlag::LuauCheckGetInfoIndex)
|
||||
{
|
||||
const TValue* func = luaA_toobject(L, level);
|
||||
api_check(L, ttisfunction(func));
|
||||
f = clvalue(func);
|
||||
}
|
||||
else
|
||||
{
|
||||
StkId func = L->top + level;
|
||||
api_check(L, ttisfunction(func));
|
||||
f = clvalue(func);
|
||||
}
|
||||
}
|
||||
else if (unsigned(level) < unsigned(L->ci - L->base_ci))
|
||||
{
|
||||
ci = L->ci - level;
|
||||
|
@ -343,7 +343,8 @@ static float perlin(float x, float y, float z)
|
||||
int bb = p[b + 1] + zi;
|
||||
|
||||
return math_lerp(w,
|
||||
math_lerp(v, math_lerp(u, grad(p[aa], xf, yf, zf), grad(p[ba], xf - 1, yf, zf)), math_lerp(u, grad(p[ab], xf, yf - 1, zf), grad(p[bb], xf - 1, yf - 1, zf))),
|
||||
math_lerp(v, math_lerp(u, grad(p[aa], xf, yf, zf), grad(p[ba], xf - 1, yf, zf)),
|
||||
math_lerp(u, grad(p[ab], xf, yf - 1, zf), grad(p[bb], xf - 1, yf - 1, zf))),
|
||||
math_lerp(v, math_lerp(u, grad(p[aa + 1], xf, yf, zf - 1), grad(p[ba + 1], xf - 1, yf, zf - 1)),
|
||||
math_lerp(u, grad(p[ab + 1], xf, yf - 1, zf - 1), grad(p[bb + 1], xf - 1, yf - 1, zf - 1))));
|
||||
}
|
||||
|
@ -10,6 +10,8 @@
|
||||
#include "ldebug.h"
|
||||
#include "lvm.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauOptimizedSort, false)
|
||||
|
||||
static int foreachi(lua_State* L)
|
||||
{
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
@ -305,12 +307,14 @@ static int tunpack(lua_State* L)
|
||||
|
||||
static void set2(lua_State* L, int i, int j)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauOptimizedSort);
|
||||
lua_rawseti(L, 1, i);
|
||||
lua_rawseti(L, 1, j);
|
||||
}
|
||||
|
||||
static int sort_comp(lua_State* L, int a, int b)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauOptimizedSort);
|
||||
if (!lua_isnil(L, 2))
|
||||
{ // function?
|
||||
int res;
|
||||
@ -328,6 +332,7 @@ static int sort_comp(lua_State* L, int a, int b)
|
||||
|
||||
static void auxsort(lua_State* L, int l, int u)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauOptimizedSort);
|
||||
while (l < u)
|
||||
{ // for tail recursion
|
||||
int i, j;
|
||||
@ -407,7 +412,135 @@ static void auxsort(lua_State* L, int l, int u)
|
||||
} // repeat the routine for the larger one
|
||||
}
|
||||
|
||||
static int sort(lua_State* L)
|
||||
typedef int (*SortPredicate)(lua_State* L, const TValue* l, const TValue* r);
|
||||
|
||||
static int sort_func(lua_State* L, const TValue* l, const TValue* r)
|
||||
{
|
||||
LUAU_ASSERT(L->top == L->base + 2); // table, function
|
||||
|
||||
setobj2s(L, L->top, &L->base[1]);
|
||||
setobj2s(L, L->top + 1, l);
|
||||
setobj2s(L, L->top + 2, r);
|
||||
L->top += 3; // safe because of LUA_MINSTACK guarantee
|
||||
luaD_call(L, L->top - 3, 1);
|
||||
L->top -= 1; // maintain stack depth
|
||||
|
||||
return !l_isfalse(L->top);
|
||||
}
|
||||
|
||||
inline void sort_swap(lua_State* L, Table* t, int i, int j)
|
||||
{
|
||||
TValue* arr = t->array;
|
||||
int n = t->sizearray;
|
||||
LUAU_ASSERT(unsigned(i) < unsigned(n) && unsigned(j) < unsigned(n)); // contract maintained in sort_less after predicate call
|
||||
|
||||
// no barrier required because both elements are in the array before and after the swap
|
||||
TValue temp;
|
||||
setobj2s(L, &temp, &arr[i]);
|
||||
setobj2t(L, &arr[i], &arr[j]);
|
||||
setobj2t(L, &arr[j], &temp);
|
||||
}
|
||||
|
||||
inline int sort_less(lua_State* L, Table* t, int i, int j, SortPredicate pred)
|
||||
{
|
||||
TValue* arr = t->array;
|
||||
int n = t->sizearray;
|
||||
LUAU_ASSERT(unsigned(i) < unsigned(n) && unsigned(j) < unsigned(n)); // contract maintained in sort_less after predicate call
|
||||
|
||||
int res = pred(L, &arr[i], &arr[j]);
|
||||
|
||||
// predicate call may resize the table, which is invalid
|
||||
if (t->sizearray != n)
|
||||
luaL_error(L, "table modified during sorting");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void sort_rec(lua_State* L, Table* t, int l, int u, SortPredicate pred)
|
||||
{
|
||||
// sort range [l..u] (inclusive, 0-based)
|
||||
while (l < u)
|
||||
{
|
||||
int i, j;
|
||||
// sort elements a[l], a[(l+u)/2] and a[u]
|
||||
if (sort_less(L, t, u, l, pred)) // a[u] < a[l]?
|
||||
sort_swap(L, t, u, l); // swap a[l] - a[u]
|
||||
if (u - l == 1)
|
||||
break; // only 2 elements
|
||||
i = l + ((u - l) >> 1); // midpoint
|
||||
if (sort_less(L, t, i, l, pred)) // a[i]<a[l]?
|
||||
sort_swap(L, t, i, l);
|
||||
else if (sort_less(L, t, u, i, pred)) // a[u]<a[i]?
|
||||
sort_swap(L, t, i, u);
|
||||
if (u - l == 2)
|
||||
break; // only 3 elements
|
||||
// here l, i, u are ordered; i will become the new pivot
|
||||
int p = u - 1;
|
||||
sort_swap(L, t, i, u - 1); // pivot is now (and always) at u-1
|
||||
// a[l] <= P == a[u-1] <= a[u], only need to sort from l+1 to u-2
|
||||
i = l;
|
||||
j = u - 1;
|
||||
for (;;)
|
||||
{ // invariant: a[l..i] <= P <= a[j..u]
|
||||
// repeat ++i until a[i] >= P
|
||||
while (sort_less(L, t, ++i, p, pred))
|
||||
{
|
||||
if (i >= u)
|
||||
luaL_error(L, "invalid order function for sorting");
|
||||
}
|
||||
// repeat --j until a[j] <= P
|
||||
while (sort_less(L, t, p, --j, pred))
|
||||
{
|
||||
if (j <= l)
|
||||
luaL_error(L, "invalid order function for sorting");
|
||||
}
|
||||
if (j < i)
|
||||
break;
|
||||
sort_swap(L, t, i, j);
|
||||
}
|
||||
// swap pivot (a[u-1]) with a[i], which is the new midpoint
|
||||
sort_swap(L, t, u - 1, i);
|
||||
// a[l..i-1] <= a[i] == P <= a[i+1..u]
|
||||
// adjust so that smaller half is in [j..i] and larger one in [l..u]
|
||||
if (i - l < u - i)
|
||||
{
|
||||
j = l;
|
||||
i = i - 1;
|
||||
l = i + 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
j = i + 1;
|
||||
i = u;
|
||||
u = j - 2;
|
||||
}
|
||||
sort_rec(L, t, j, i, pred); // call recursively the smaller one
|
||||
} // repeat the routine for the larger one
|
||||
}
|
||||
|
||||
static int tsort(lua_State* L)
|
||||
{
|
||||
if (FFlag::LuauOptimizedSort)
|
||||
{
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
Table* t = hvalue(L->base);
|
||||
int n = luaH_getn(t);
|
||||
if (t->readonly)
|
||||
luaG_readonlyerror(L);
|
||||
|
||||
SortPredicate pred = luaV_lessthan;
|
||||
if (!lua_isnoneornil(L, 2)) // is there a 2nd argument?
|
||||
{
|
||||
luaL_checktype(L, 2, LUA_TFUNCTION);
|
||||
pred = sort_func;
|
||||
}
|
||||
lua_settop(L, 2); // make sure there are two arguments
|
||||
|
||||
if (n > 0)
|
||||
sort_rec(L, t, 0, n - 1, pred);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
int n = lua_objlen(L, 1);
|
||||
@ -418,6 +551,7 @@ static int sort(lua_State* L)
|
||||
auxsort(L, 1, n);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// }======================================================
|
||||
|
||||
@ -530,7 +664,7 @@ static const luaL_Reg tab_funcs[] = {
|
||||
{"maxn", maxn},
|
||||
{"insert", tinsert},
|
||||
{"remove", tremove},
|
||||
{"sort", sort},
|
||||
{"sort", tsort},
|
||||
{"pack", tpack},
|
||||
{"unpack", tunpack},
|
||||
{"move", tmove},
|
||||
|
@ -145,15 +145,16 @@ LUAU_NOINLINE void luau_callhook(lua_State* L, lua_Hook hook, void* userdata)
|
||||
L->base = L->ci->base;
|
||||
}
|
||||
|
||||
// note: the pc expectations of the hook are matching the general "pc points to next instruction"
|
||||
// however, for the hook to be able to continue execution from the same point, this is called with savedpc at the *current* instruction
|
||||
// this needs to be called before luaD_checkstack in case it fails to reallocate stack
|
||||
if (L->ci->savedpc)
|
||||
L->ci->savedpc++;
|
||||
|
||||
luaD_checkstack(L, LUA_MINSTACK); // ensure minimum stack size
|
||||
L->ci->top = L->top + LUA_MINSTACK;
|
||||
LUAU_ASSERT(L->ci->top <= L->stack_last);
|
||||
|
||||
// note: the pc expectations of the hook are matching the general "pc points to next instruction"
|
||||
// however, for the hook to be able to continue execution from the same point, this is called with savedpc at the *current* instruction
|
||||
if (L->ci->savedpc)
|
||||
L->ci->savedpc++;
|
||||
|
||||
Closure* cl = clvalue(L->ci->func);
|
||||
|
||||
lua_Debug ar;
|
||||
|
@ -201,15 +201,23 @@ static const TValue* get_compTM(lua_State* L, Table* mt1, Table* mt2, TMS event)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int call_orderTM(lua_State* L, const TValue* p1, const TValue* p2, TMS event)
|
||||
static int call_orderTM(lua_State* L, const TValue* p1, const TValue* p2, TMS event, bool error = false)
|
||||
{
|
||||
const TValue* tm1 = luaT_gettmbyobj(L, p1, event);
|
||||
const TValue* tm2;
|
||||
if (ttisnil(tm1))
|
||||
{
|
||||
if (error)
|
||||
luaG_ordererror(L, p1, p2, event);
|
||||
return -1; // no metamethod?
|
||||
}
|
||||
tm2 = luaT_gettmbyobj(L, p2, event);
|
||||
if (!luaO_rawequalObj(tm1, tm2)) // different metamethods?
|
||||
{
|
||||
if (error)
|
||||
luaG_ordererror(L, p1, p2, event);
|
||||
return -1;
|
||||
}
|
||||
callTMres(L, L->top, tm1, p1, p2);
|
||||
return !l_isfalse(L->top);
|
||||
}
|
||||
@ -239,16 +247,14 @@ int luaV_strcmp(const TString* ls, const TString* rs)
|
||||
|
||||
int luaV_lessthan(lua_State* L, const TValue* l, const TValue* r)
|
||||
{
|
||||
int res;
|
||||
if (ttype(l) != ttype(r))
|
||||
if (LUAU_UNLIKELY(ttype(l) != ttype(r)))
|
||||
luaG_ordererror(L, l, r, TM_LT);
|
||||
else if (ttisnumber(l))
|
||||
else if (LUAU_LIKELY(ttisnumber(l)))
|
||||
return luai_numlt(nvalue(l), nvalue(r));
|
||||
else if (ttisstring(l))
|
||||
return luaV_strcmp(tsvalue(l), tsvalue(r)) < 0;
|
||||
else if ((res = call_orderTM(L, l, r, TM_LT)) == -1)
|
||||
luaG_ordererror(L, l, r, TM_LT);
|
||||
return res;
|
||||
else
|
||||
return call_orderTM(L, l, r, TM_LT, /* error= */ true);
|
||||
}
|
||||
|
||||
int luaV_lessequal(lua_State* L, const TValue* l, const TValue* r)
|
||||
|
@ -97,38 +97,39 @@ lua_State* createGlobalState()
|
||||
return L;
|
||||
}
|
||||
|
||||
int registerTypes(Luau::TypeChecker& env)
|
||||
int registerTypes(Luau::TypeChecker& typeChecker, Luau::GlobalTypes& globals)
|
||||
{
|
||||
using namespace Luau;
|
||||
using std::nullopt;
|
||||
|
||||
Luau::registerBuiltinGlobals(env);
|
||||
Luau::registerBuiltinGlobals(typeChecker, globals);
|
||||
|
||||
TypeArena& arena = env.globalTypes;
|
||||
TypeArena& arena = globals.globalTypes;
|
||||
BuiltinTypes& builtinTypes = *globals.builtinTypes;
|
||||
|
||||
// Vector3 stub
|
||||
TypeId vector3MetaType = arena.addType(TableType{});
|
||||
|
||||
TypeId vector3InstanceType = arena.addType(ClassType{"Vector3", {}, nullopt, vector3MetaType, {}, {}, "Test"});
|
||||
getMutable<ClassType>(vector3InstanceType)->props = {
|
||||
{"X", {env.numberType}},
|
||||
{"Y", {env.numberType}},
|
||||
{"Z", {env.numberType}},
|
||||
{"X", {builtinTypes.numberType}},
|
||||
{"Y", {builtinTypes.numberType}},
|
||||
{"Z", {builtinTypes.numberType}},
|
||||
};
|
||||
|
||||
getMutable<TableType>(vector3MetaType)->props = {
|
||||
{"__add", {makeFunction(arena, nullopt, {vector3InstanceType, vector3InstanceType}, {vector3InstanceType})}},
|
||||
};
|
||||
|
||||
env.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vector3InstanceType};
|
||||
globals.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vector3InstanceType};
|
||||
|
||||
// Instance stub
|
||||
TypeId instanceType = arena.addType(ClassType{"Instance", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassType>(instanceType)->props = {
|
||||
{"Name", {env.stringType}},
|
||||
{"Name", {builtinTypes.stringType}},
|
||||
};
|
||||
|
||||
env.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType};
|
||||
globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType};
|
||||
|
||||
// Part stub
|
||||
TypeId partType = arena.addType(ClassType{"Part", {}, instanceType, nullopt, {}, {}, "Test"});
|
||||
@ -136,9 +137,9 @@ int registerTypes(Luau::TypeChecker& env)
|
||||
{"Position", {vector3InstanceType}},
|
||||
};
|
||||
|
||||
env.globalScope->exportedTypeBindings["Part"] = TypeFun{{}, partType};
|
||||
globals.globalScope->exportedTypeBindings["Part"] = TypeFun{{}, partType};
|
||||
|
||||
for (const auto& [_, fun] : env.globalScope->exportedTypeBindings)
|
||||
for (const auto& [_, fun] : globals.globalScope->exportedTypeBindings)
|
||||
persist(fun.type);
|
||||
|
||||
return 0;
|
||||
@ -146,11 +147,11 @@ int registerTypes(Luau::TypeChecker& env)
|
||||
|
||||
static void setupFrontend(Luau::Frontend& frontend)
|
||||
{
|
||||
registerTypes(frontend.typeChecker);
|
||||
Luau::freeze(frontend.typeChecker.globalTypes);
|
||||
registerTypes(frontend.typeChecker, frontend.globals);
|
||||
Luau::freeze(frontend.globals.globalTypes);
|
||||
|
||||
registerTypes(frontend.typeCheckerForAutocomplete);
|
||||
Luau::freeze(frontend.typeCheckerForAutocomplete.globalTypes);
|
||||
registerTypes(frontend.typeCheckerForAutocomplete, frontend.globalsForAutocomplete);
|
||||
Luau::freeze(frontend.globalsForAutocomplete.globalTypes);
|
||||
|
||||
frontend.iceHandler.onInternalError = [](const char* error) {
|
||||
printf("ICE: %s\n", error);
|
||||
@ -264,6 +265,7 @@ DEFINE_PROTO_FUZZER(const luau::ModuleSet& message)
|
||||
static Luau::Frontend frontend(&fileResolver, &configResolver, options);
|
||||
|
||||
static int once = (setupFrontend(frontend), 0);
|
||||
(void)once;
|
||||
|
||||
// restart
|
||||
frontend.clear();
|
||||
@ -302,7 +304,7 @@ DEFINE_PROTO_FUZZER(const luau::ModuleSet& message)
|
||||
|
||||
// validate sharedEnv post-typecheck; valuable for debugging some typeck crashes but slows fuzzing down
|
||||
// note: it's important for typeck to be destroyed at this point!
|
||||
for (auto& p : frontend.typeChecker.globalScope->bindings)
|
||||
for (auto& p : frontend.globals.globalScope->bindings)
|
||||
{
|
||||
Luau::ToStringOptions opts;
|
||||
opts.exhaustive = true;
|
||||
|
@ -282,4 +282,17 @@ TEST_CASE_FIXTURE(Fixture, "Luau_selectively_query_for_a_different_boolean_2")
|
||||
REQUIRE(snd->value == true);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "include_types_ancestry")
|
||||
{
|
||||
check("local x: number = 4;");
|
||||
const Position pos(0, 10);
|
||||
|
||||
std::vector<AstNode*> ancestryNoTypes = findAstAncestryOfPosition(*getMainSourceModule(), pos);
|
||||
std::vector<AstNode*> ancestryTypes = findAstAncestryOfPosition(*getMainSourceModule(), pos, true);
|
||||
|
||||
CHECK(ancestryTypes.size() > ancestryNoTypes.size());
|
||||
CHECK(!ancestryNoTypes.back()->asType());
|
||||
CHECK(ancestryTypes.back()->asType());
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -15,8 +15,6 @@
|
||||
|
||||
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
|
||||
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
|
||||
LUAU_FASTFLAG(LuauFixAutocompleteInWhile)
|
||||
LUAU_FASTFLAG(LuauFixAutocompleteInFor)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
@ -85,10 +83,11 @@ struct ACFixtureImpl : BaseType
|
||||
|
||||
LoadDefinitionFileResult loadDefinition(const std::string& source)
|
||||
{
|
||||
TypeChecker& typeChecker = this->frontend.typeCheckerForAutocomplete;
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
LoadDefinitionFileResult result = loadDefinitionFile(typeChecker, typeChecker.globalScope, source, "@test");
|
||||
freeze(typeChecker.globalTypes);
|
||||
GlobalTypes& globals = this->frontend.globalsForAutocomplete;
|
||||
unfreeze(globals.globalTypes);
|
||||
LoadDefinitionFileResult result =
|
||||
loadDefinitionFile(this->frontend.typeChecker, globals, globals.globalScope, source, "@test", /* captureComments */ false);
|
||||
freeze(globals.globalTypes);
|
||||
|
||||
REQUIRE_MESSAGE(result.success, "loadDefinition: unable to load definition file");
|
||||
return result;
|
||||
@ -110,10 +109,10 @@ struct ACFixture : ACFixtureImpl<Fixture>
|
||||
ACFixture()
|
||||
: ACFixtureImpl<Fixture>()
|
||||
{
|
||||
addGlobalBinding(frontend, "table", Binding{typeChecker.anyType});
|
||||
addGlobalBinding(frontend, "math", Binding{typeChecker.anyType});
|
||||
addGlobalBinding(frontend.typeCheckerForAutocomplete, "table", Binding{typeChecker.anyType});
|
||||
addGlobalBinding(frontend.typeCheckerForAutocomplete, "math", Binding{typeChecker.anyType});
|
||||
addGlobalBinding(frontend.globals, "table", Binding{builtinTypes->anyType});
|
||||
addGlobalBinding(frontend.globals, "math", Binding{builtinTypes->anyType});
|
||||
addGlobalBinding(frontend.globalsForAutocomplete, "table", Binding{builtinTypes->anyType});
|
||||
addGlobalBinding(frontend.globalsForAutocomplete, "math", Binding{builtinTypes->anyType});
|
||||
}
|
||||
};
|
||||
|
||||
@ -630,19 +629,10 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_for_middle_keywords")
|
||||
)");
|
||||
|
||||
auto ac5 = autocomplete('1');
|
||||
if (FFlag::LuauFixAutocompleteInFor)
|
||||
{
|
||||
CHECK_EQ(ac5.entryMap.count("math"), 1);
|
||||
CHECK_EQ(ac5.entryMap.count("do"), 0);
|
||||
CHECK_EQ(ac5.entryMap.count("end"), 0);
|
||||
CHECK_EQ(ac5.context, AutocompleteContext::Expression);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(ac5.entryMap.count("do"), 1);
|
||||
CHECK_EQ(ac5.entryMap.count("end"), 0);
|
||||
CHECK_EQ(ac5.context, AutocompleteContext::Keyword);
|
||||
}
|
||||
|
||||
check(R"(
|
||||
for x = 1, 2, 5 f@1
|
||||
@ -661,8 +651,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_for_middle_keywords")
|
||||
CHECK_EQ(ac7.entryMap.count("end"), 1);
|
||||
CHECK_EQ(ac7.context, AutocompleteContext::Statement);
|
||||
|
||||
if (FFlag::LuauFixAutocompleteInFor)
|
||||
{
|
||||
check(R"(local Foo = 1
|
||||
for x = @11, @22, @35
|
||||
)");
|
||||
@ -685,7 +673,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_for_middle_keywords")
|
||||
CHECK_EQ(ac9.entryMap.count("do"), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_for_in_middle_keywords")
|
||||
{
|
||||
@ -776,18 +763,10 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_while_middle_keywords")
|
||||
)");
|
||||
|
||||
auto ac2 = autocomplete('1');
|
||||
if (FFlag::LuauFixAutocompleteInWhile)
|
||||
{
|
||||
CHECK_EQ(3, ac2.entryMap.size());
|
||||
CHECK_EQ(ac2.entryMap.count("do"), 1);
|
||||
CHECK_EQ(ac2.entryMap.count("and"), 1);
|
||||
CHECK_EQ(ac2.entryMap.count("or"), 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(1, ac2.entryMap.size());
|
||||
CHECK_EQ(ac2.entryMap.count("do"), 1);
|
||||
}
|
||||
CHECK_EQ(ac2.context, AutocompleteContext::Keyword);
|
||||
|
||||
check(R"(
|
||||
@ -803,22 +782,12 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_while_middle_keywords")
|
||||
)");
|
||||
|
||||
auto ac4 = autocomplete('1');
|
||||
if (FFlag::LuauFixAutocompleteInWhile)
|
||||
{
|
||||
CHECK_EQ(3, ac4.entryMap.size());
|
||||
CHECK_EQ(ac4.entryMap.count("do"), 1);
|
||||
CHECK_EQ(ac4.entryMap.count("and"), 1);
|
||||
CHECK_EQ(ac4.entryMap.count("or"), 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(1, ac4.entryMap.size());
|
||||
CHECK_EQ(ac4.entryMap.count("do"), 1);
|
||||
}
|
||||
CHECK_EQ(ac4.context, AutocompleteContext::Keyword);
|
||||
|
||||
if (FFlag::LuauFixAutocompleteInWhile)
|
||||
{
|
||||
check(R"(
|
||||
while t@1
|
||||
)");
|
||||
@ -828,7 +797,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_while_middle_keywords")
|
||||
CHECK_EQ(ac5.entryMap.count("true"), 1);
|
||||
CHECK_EQ(ac5.entryMap.count("false"), 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords")
|
||||
{
|
||||
@ -3460,11 +3428,11 @@ TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback")
|
||||
declare function require(path: string): any
|
||||
)");
|
||||
|
||||
std::optional<Binding> require = frontend.typeCheckerForAutocomplete.globalScope->linearSearchForBinding("require");
|
||||
std::optional<Binding> require = frontend.globalsForAutocomplete.globalScope->linearSearchForBinding("require");
|
||||
REQUIRE(require);
|
||||
Luau::unfreeze(frontend.typeCheckerForAutocomplete.globalTypes);
|
||||
Luau::unfreeze(frontend.globalsForAutocomplete.globalTypes);
|
||||
attachTag(require->typeId, "RequireCall");
|
||||
Luau::freeze(frontend.typeCheckerForAutocomplete.globalTypes);
|
||||
Luau::freeze(frontend.globalsForAutocomplete.globalTypes);
|
||||
|
||||
check(R"(
|
||||
local x = require("testing/@1")
|
||||
|
@ -12,9 +12,9 @@ TEST_SUITE_BEGIN("BuiltinDefinitionsTest");
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "lib_documentation_symbols")
|
||||
{
|
||||
CHECK(!typeChecker.globalScope->bindings.empty());
|
||||
CHECK(!frontend.globals.globalScope->bindings.empty());
|
||||
|
||||
for (const auto& [name, binding] : typeChecker.globalScope->bindings)
|
||||
for (const auto& [name, binding] : frontend.globals.globalScope->bindings)
|
||||
{
|
||||
std::string nameString(name.c_str());
|
||||
std::string expectedRootSymbol = "@luau/global/" + nameString;
|
||||
|
@ -11,8 +11,9 @@ namespace Luau
|
||||
|
||||
ClassFixture::ClassFixture()
|
||||
{
|
||||
TypeArena& arena = typeChecker.globalTypes;
|
||||
TypeId numberType = typeChecker.numberType;
|
||||
GlobalTypes& globals = frontend.globals;
|
||||
TypeArena& arena = globals.globalTypes;
|
||||
TypeId numberType = builtinTypes->numberType;
|
||||
|
||||
unfreeze(arena);
|
||||
|
||||
@ -28,47 +29,47 @@ ClassFixture::ClassFixture()
|
||||
{"Clone", {makeFunction(arena, nullopt, {baseClassInstanceType}, {baseClassInstanceType})}},
|
||||
{"New", {makeFunction(arena, nullopt, {}, {baseClassInstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType};
|
||||
addGlobalBinding(frontend, "BaseClass", baseClassType, "@test");
|
||||
globals.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType};
|
||||
addGlobalBinding(globals, "BaseClass", baseClassType, "@test");
|
||||
|
||||
TypeId childClassInstanceType = arena.addType(ClassType{"ChildClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test"});
|
||||
|
||||
getMutable<ClassType>(childClassInstanceType)->props = {
|
||||
{"Method", {makeFunction(arena, childClassInstanceType, {}, {typeChecker.stringType})}},
|
||||
{"Method", {makeFunction(arena, childClassInstanceType, {}, {builtinTypes->stringType})}},
|
||||
};
|
||||
|
||||
TypeId childClassType = arena.addType(ClassType{"ChildClass", {}, baseClassType, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassType>(childClassType)->props = {
|
||||
{"New", {makeFunction(arena, nullopt, {}, {childClassInstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType};
|
||||
addGlobalBinding(frontend, "ChildClass", childClassType, "@test");
|
||||
globals.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType};
|
||||
addGlobalBinding(globals, "ChildClass", childClassType, "@test");
|
||||
|
||||
TypeId grandChildInstanceType = arena.addType(ClassType{"GrandChild", {}, childClassInstanceType, nullopt, {}, {}, "Test"});
|
||||
|
||||
getMutable<ClassType>(grandChildInstanceType)->props = {
|
||||
{"Method", {makeFunction(arena, grandChildInstanceType, {}, {typeChecker.stringType})}},
|
||||
{"Method", {makeFunction(arena, grandChildInstanceType, {}, {builtinTypes->stringType})}},
|
||||
};
|
||||
|
||||
TypeId grandChildType = arena.addType(ClassType{"GrandChild", {}, baseClassType, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassType>(grandChildType)->props = {
|
||||
{"New", {makeFunction(arena, nullopt, {}, {grandChildInstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["GrandChild"] = TypeFun{{}, grandChildInstanceType};
|
||||
addGlobalBinding(frontend, "GrandChild", childClassType, "@test");
|
||||
globals.globalScope->exportedTypeBindings["GrandChild"] = TypeFun{{}, grandChildInstanceType};
|
||||
addGlobalBinding(globals, "GrandChild", childClassType, "@test");
|
||||
|
||||
TypeId anotherChildInstanceType = arena.addType(ClassType{"AnotherChild", {}, baseClassInstanceType, nullopt, {}, {}, "Test"});
|
||||
|
||||
getMutable<ClassType>(anotherChildInstanceType)->props = {
|
||||
{"Method", {makeFunction(arena, anotherChildInstanceType, {}, {typeChecker.stringType})}},
|
||||
{"Method", {makeFunction(arena, anotherChildInstanceType, {}, {builtinTypes->stringType})}},
|
||||
};
|
||||
|
||||
TypeId anotherChildType = arena.addType(ClassType{"AnotherChild", {}, baseClassType, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassType>(anotherChildType)->props = {
|
||||
{"New", {makeFunction(arena, nullopt, {}, {anotherChildInstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildInstanceType};
|
||||
addGlobalBinding(frontend, "AnotherChild", childClassType, "@test");
|
||||
globals.globalScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildInstanceType};
|
||||
addGlobalBinding(globals, "AnotherChild", childClassType, "@test");
|
||||
|
||||
TypeId unrelatedClassInstanceType = arena.addType(ClassType{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||
|
||||
@ -76,8 +77,8 @@ ClassFixture::ClassFixture()
|
||||
getMutable<ClassType>(unrelatedClassType)->props = {
|
||||
{"New", {makeFunction(arena, nullopt, {}, {unrelatedClassInstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["UnrelatedClass"] = TypeFun{{}, unrelatedClassInstanceType};
|
||||
addGlobalBinding(frontend, "UnrelatedClass", unrelatedClassType, "@test");
|
||||
globals.globalScope->exportedTypeBindings["UnrelatedClass"] = TypeFun{{}, unrelatedClassInstanceType};
|
||||
addGlobalBinding(globals, "UnrelatedClass", unrelatedClassType, "@test");
|
||||
|
||||
TypeId vector2MetaType = arena.addType(TableType{});
|
||||
|
||||
@ -94,17 +95,17 @@ ClassFixture::ClassFixture()
|
||||
getMutable<TableType>(vector2MetaType)->props = {
|
||||
{"__add", {makeFunction(arena, nullopt, {vector2InstanceType, vector2InstanceType}, {vector2InstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["Vector2"] = TypeFun{{}, vector2InstanceType};
|
||||
addGlobalBinding(frontend, "Vector2", vector2Type, "@test");
|
||||
globals.globalScope->exportedTypeBindings["Vector2"] = TypeFun{{}, vector2InstanceType};
|
||||
addGlobalBinding(globals, "Vector2", vector2Type, "@test");
|
||||
|
||||
TypeId callableClassMetaType = arena.addType(TableType{});
|
||||
TypeId callableClassType = arena.addType(ClassType{"CallableClass", {}, nullopt, callableClassMetaType, {}, {}, "Test"});
|
||||
getMutable<TableType>(callableClassMetaType)->props = {
|
||||
{"__call", {makeFunction(arena, nullopt, {callableClassType, typeChecker.stringType}, {typeChecker.numberType})}},
|
||||
{"__call", {makeFunction(arena, nullopt, {callableClassType, builtinTypes->stringType}, {builtinTypes->numberType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["CallableClass"] = TypeFun{{}, callableClassType};
|
||||
globals.globalScope->exportedTypeBindings["CallableClass"] = TypeFun{{}, callableClassType};
|
||||
|
||||
for (const auto& [name, tf] : typeChecker.globalScope->exportedTypeBindings)
|
||||
for (const auto& [name, tf] : globals.globalScope->exportedTypeBindings)
|
||||
persist(tf.type);
|
||||
|
||||
freeze(arena);
|
||||
|
@ -503,14 +503,15 @@ TEST_CASE("Types")
|
||||
Luau::NullModuleResolver moduleResolver;
|
||||
Luau::InternalErrorReporter iceHandler;
|
||||
Luau::BuiltinTypes builtinTypes;
|
||||
Luau::TypeChecker env(&moduleResolver, Luau::NotNull{&builtinTypes}, &iceHandler);
|
||||
Luau::GlobalTypes globals{Luau::NotNull{&builtinTypes}};
|
||||
Luau::TypeChecker env(globals, &moduleResolver, Luau::NotNull{&builtinTypes}, &iceHandler);
|
||||
|
||||
Luau::registerBuiltinGlobals(env);
|
||||
Luau::freeze(env.globalTypes);
|
||||
Luau::registerBuiltinGlobals(env, globals);
|
||||
Luau::freeze(globals.globalTypes);
|
||||
|
||||
lua_newtable(L);
|
||||
|
||||
for (const auto& [name, binding] : env.globalScope->bindings)
|
||||
for (const auto& [name, binding] : globals.globalScope->bindings)
|
||||
{
|
||||
populateRTTI(L, binding.typeId);
|
||||
lua_setfield(L, -2, toString(name).c_str());
|
||||
|
@ -22,7 +22,7 @@ void ConstraintGraphBuilderFixture::generateConstraints(const std::string& code)
|
||||
AstStatBlock* root = parse(code);
|
||||
dfg = std::make_unique<DataFlowGraph>(DataFlowGraphBuilder::build(root, NotNull{&ice}));
|
||||
cgb = std::make_unique<ConstraintGraphBuilder>("MainModule", mainModule, &arena, NotNull(&moduleResolver), builtinTypes, NotNull(&ice),
|
||||
frontend.getGlobalScope(), &logger, NotNull{dfg.get()});
|
||||
frontend.globals.globalScope, &logger, NotNull{dfg.get()});
|
||||
cgb->visit(root);
|
||||
rootScope = cgb->rootScope;
|
||||
constraints = Luau::borrowConstraints(cgb->constraints);
|
||||
|
@ -138,17 +138,16 @@ Fixture::Fixture(bool freeze, bool prepareAutocomplete)
|
||||
: sff_DebugLuauFreezeArena("DebugLuauFreezeArena", freeze)
|
||||
, frontend(&fileResolver, &configResolver,
|
||||
{/* retainFullTypeGraphs= */ true, /* forAutocomplete */ false, /* randomConstraintResolutionSeed */ randomSeed})
|
||||
, typeChecker(frontend.typeChecker)
|
||||
, builtinTypes(frontend.builtinTypes)
|
||||
{
|
||||
configResolver.defaultConfig.mode = Mode::Strict;
|
||||
configResolver.defaultConfig.enabledLint.warningMask = ~0ull;
|
||||
configResolver.defaultConfig.parseOptions.captureComments = true;
|
||||
|
||||
registerBuiltinTypes(frontend);
|
||||
registerBuiltinTypes(frontend.globals);
|
||||
|
||||
Luau::freeze(frontend.typeChecker.globalTypes);
|
||||
Luau::freeze(frontend.typeCheckerForAutocomplete.globalTypes);
|
||||
Luau::freeze(frontend.globals.globalTypes);
|
||||
Luau::freeze(frontend.globalsForAutocomplete.globalTypes);
|
||||
|
||||
Luau::setPrintLine([](auto s) {});
|
||||
}
|
||||
@ -178,11 +177,11 @@ AstStatBlock* Fixture::parse(const std::string& source, const ParseOptions& pars
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
Luau::check(*sourceModule, {}, frontend.builtinTypes, NotNull{&ice}, NotNull{&moduleResolver}, NotNull{&fileResolver},
|
||||
typeChecker.globalScope, frontend.options);
|
||||
Luau::check(*sourceModule, {}, builtinTypes, NotNull{&ice}, NotNull{&moduleResolver}, NotNull{&fileResolver},
|
||||
frontend.globals.globalScope, frontend.options);
|
||||
}
|
||||
else
|
||||
typeChecker.check(*sourceModule, sourceModule->mode.value_or(Luau::Mode::Nonstrict));
|
||||
frontend.typeChecker.check(*sourceModule, sourceModule->mode.value_or(Luau::Mode::Nonstrict));
|
||||
}
|
||||
|
||||
throw ParseErrors(result.errors);
|
||||
@ -447,9 +446,9 @@ void Fixture::dumpErrors(std::ostream& os, const std::vector<TypeError>& errors)
|
||||
|
||||
void Fixture::registerTestTypes()
|
||||
{
|
||||
addGlobalBinding(frontend, "game", typeChecker.anyType, "@luau");
|
||||
addGlobalBinding(frontend, "workspace", typeChecker.anyType, "@luau");
|
||||
addGlobalBinding(frontend, "script", typeChecker.anyType, "@luau");
|
||||
addGlobalBinding(frontend.globals, "game", builtinTypes->anyType, "@luau");
|
||||
addGlobalBinding(frontend.globals, "workspace", builtinTypes->anyType, "@luau");
|
||||
addGlobalBinding(frontend.globals, "script", builtinTypes->anyType, "@luau");
|
||||
}
|
||||
|
||||
void Fixture::dumpErrors(const CheckResult& cr)
|
||||
@ -499,9 +498,9 @@ void Fixture::validateErrors(const std::vector<Luau::TypeError>& errors)
|
||||
|
||||
LoadDefinitionFileResult Fixture::loadDefinition(const std::string& source)
|
||||
{
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
LoadDefinitionFileResult result = frontend.loadDefinitionFile(source, "@test");
|
||||
freeze(typeChecker.globalTypes);
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
LoadDefinitionFileResult result = frontend.loadDefinitionFile(source, "@test", /* captureComments */ false);
|
||||
freeze(frontend.globals.globalTypes);
|
||||
|
||||
if (result.module)
|
||||
dumpErrors(result.module);
|
||||
@ -512,16 +511,16 @@ LoadDefinitionFileResult Fixture::loadDefinition(const std::string& source)
|
||||
BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete)
|
||||
: Fixture(freeze, prepareAutocomplete)
|
||||
{
|
||||
Luau::unfreeze(frontend.typeChecker.globalTypes);
|
||||
Luau::unfreeze(frontend.typeCheckerForAutocomplete.globalTypes);
|
||||
Luau::unfreeze(frontend.globals.globalTypes);
|
||||
Luau::unfreeze(frontend.globalsForAutocomplete.globalTypes);
|
||||
|
||||
registerBuiltinGlobals(frontend);
|
||||
if (prepareAutocomplete)
|
||||
registerBuiltinGlobals(frontend.typeCheckerForAutocomplete);
|
||||
registerBuiltinGlobals(frontend.typeCheckerForAutocomplete, frontend.globalsForAutocomplete);
|
||||
registerTestTypes();
|
||||
|
||||
Luau::freeze(frontend.typeChecker.globalTypes);
|
||||
Luau::freeze(frontend.typeCheckerForAutocomplete.globalTypes);
|
||||
Luau::freeze(frontend.globals.globalTypes);
|
||||
Luau::freeze(frontend.globalsForAutocomplete.globalTypes);
|
||||
}
|
||||
|
||||
ModuleName fromString(std::string_view name)
|
||||
@ -581,23 +580,31 @@ std::optional<TypeId> linearSearchForBinding(Scope* scope, const char* name)
|
||||
|
||||
void registerHiddenTypes(Frontend* frontend)
|
||||
{
|
||||
TypeId t = frontend->globalTypes.addType(GenericType{"T"});
|
||||
GlobalTypes& globals = frontend->globals;
|
||||
|
||||
unfreeze(globals.globalTypes);
|
||||
|
||||
TypeId t = globals.globalTypes.addType(GenericType{"T"});
|
||||
GenericTypeDefinition genericT{t};
|
||||
|
||||
ScopePtr globalScope = frontend->getGlobalScope();
|
||||
globalScope->exportedTypeBindings["Not"] = TypeFun{{genericT}, frontend->globalTypes.addType(NegationType{t})};
|
||||
ScopePtr globalScope = globals.globalScope;
|
||||
globalScope->exportedTypeBindings["Not"] = TypeFun{{genericT}, globals.globalTypes.addType(NegationType{t})};
|
||||
globalScope->exportedTypeBindings["fun"] = TypeFun{{}, frontend->builtinTypes->functionType};
|
||||
globalScope->exportedTypeBindings["cls"] = TypeFun{{}, frontend->builtinTypes->classType};
|
||||
globalScope->exportedTypeBindings["err"] = TypeFun{{}, frontend->builtinTypes->errorType};
|
||||
globalScope->exportedTypeBindings["tbl"] = TypeFun{{}, frontend->builtinTypes->tableType};
|
||||
|
||||
freeze(globals.globalTypes);
|
||||
}
|
||||
|
||||
void createSomeClasses(Frontend* frontend)
|
||||
{
|
||||
TypeArena& arena = frontend->globalTypes;
|
||||
GlobalTypes& globals = frontend->globals;
|
||||
|
||||
TypeArena& arena = globals.globalTypes;
|
||||
unfreeze(arena);
|
||||
|
||||
ScopePtr moduleScope = frontend->getGlobalScope();
|
||||
ScopePtr moduleScope = globals.globalScope;
|
||||
|
||||
TypeId parentType = arena.addType(ClassType{"Parent", {}, frontend->builtinTypes->classType, std::nullopt, {}, nullptr, "Test"});
|
||||
|
||||
@ -606,22 +613,22 @@ void createSomeClasses(Frontend* frontend)
|
||||
|
||||
parentClass->props["virtual_method"] = {makeFunction(arena, parentType, {}, {})};
|
||||
|
||||
addGlobalBinding(*frontend, "Parent", {parentType});
|
||||
addGlobalBinding(globals, "Parent", {parentType});
|
||||
moduleScope->exportedTypeBindings["Parent"] = TypeFun{{}, parentType};
|
||||
|
||||
TypeId childType = arena.addType(ClassType{"Child", {}, parentType, std::nullopt, {}, nullptr, "Test"});
|
||||
|
||||
addGlobalBinding(*frontend, "Child", {childType});
|
||||
addGlobalBinding(globals, "Child", {childType});
|
||||
moduleScope->exportedTypeBindings["Child"] = TypeFun{{}, childType};
|
||||
|
||||
TypeId anotherChildType = arena.addType(ClassType{"AnotherChild", {}, parentType, std::nullopt, {}, nullptr, "Test"});
|
||||
|
||||
addGlobalBinding(*frontend, "AnotherChild", {anotherChildType});
|
||||
addGlobalBinding(globals, "AnotherChild", {anotherChildType});
|
||||
moduleScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildType};
|
||||
|
||||
TypeId unrelatedType = arena.addType(ClassType{"Unrelated", {}, frontend->builtinTypes->classType, std::nullopt, {}, nullptr, "Test"});
|
||||
|
||||
addGlobalBinding(*frontend, "Unrelated", {unrelatedType});
|
||||
addGlobalBinding(globals, "Unrelated", {unrelatedType});
|
||||
moduleScope->exportedTypeBindings["Unrelated"] = TypeFun{{}, unrelatedType};
|
||||
|
||||
for (const auto& [name, ty] : moduleScope->exportedTypeBindings)
|
||||
|
@ -101,7 +101,6 @@ struct Fixture
|
||||
std::unique_ptr<SourceModule> sourceModule;
|
||||
Frontend frontend;
|
||||
InternalErrorReporter ice;
|
||||
TypeChecker& typeChecker;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
|
||||
std::string decorateWithTypes(const std::string& code);
|
||||
|
@ -81,8 +81,8 @@ struct FrontendFixture : BuiltinsFixture
|
||||
{
|
||||
FrontendFixture()
|
||||
{
|
||||
addGlobalBinding(frontend, "game", frontend.typeChecker.anyType, "@test");
|
||||
addGlobalBinding(frontend, "script", frontend.typeChecker.anyType, "@test");
|
||||
addGlobalBinding(frontend.globals, "game", builtinTypes->anyType, "@test");
|
||||
addGlobalBinding(frontend.globals, "script", builtinTypes->anyType, "@test");
|
||||
}
|
||||
};
|
||||
|
||||
@ -852,12 +852,12 @@ TEST_CASE_FIXTURE(FrontendFixture, "environments")
|
||||
{
|
||||
ScopePtr testScope = frontend.addEnvironment("test");
|
||||
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
loadDefinitionFile(typeChecker, testScope, R"(
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
loadDefinitionFile(frontend.typeChecker, frontend.globals, testScope, R"(
|
||||
export type Foo = number | string
|
||||
)",
|
||||
"@test");
|
||||
freeze(typeChecker.globalTypes);
|
||||
"@test", /* captureComments */ false);
|
||||
freeze(frontend.globals.globalTypes);
|
||||
|
||||
fileResolver.source["A"] = R"(
|
||||
--!nonstrict
|
||||
|
@ -109,7 +109,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptCheckTag")
|
||||
optimizeMemoryOperandsX64(build.function);
|
||||
|
||||
// Load from memory is 'inlined' into CHECK_TAG
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
CHECK_TAG R2, tnil, bb_fallback_1
|
||||
CHECK_TAG K5, tnil, bb_fallback_1
|
||||
@ -135,7 +135,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptBinaryArith")
|
||||
optimizeMemoryOperandsX64(build.function);
|
||||
|
||||
// Load from memory is 'inlined' into second argument
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%0 = LOAD_DOUBLE R1
|
||||
%2 = ADD_NUM %0, R2
|
||||
@ -165,7 +165,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag1")
|
||||
optimizeMemoryOperandsX64(build.function);
|
||||
|
||||
// Load from memory is 'inlined' into first argument
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%1 = LOAD_TAG R2
|
||||
JUMP_EQ_TAG R1, %1, bb_1, bb_2
|
||||
@ -202,7 +202,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag2")
|
||||
|
||||
// Load from memory is 'inlined' into second argument is it can't be done for the first one
|
||||
// We also swap first and second argument to generate memory access on the LHS
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%0 = LOAD_TAG R1
|
||||
STORE_TAG R6, %0
|
||||
@ -239,7 +239,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag3")
|
||||
optimizeMemoryOperandsX64(build.function);
|
||||
|
||||
// Load from memory is 'inlined' into first argument
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%0 = LOAD_POINTER R1
|
||||
%1 = GET_ARR_ADDR %0, 0i
|
||||
@ -276,7 +276,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptJumpCmpNum")
|
||||
optimizeMemoryOperandsX64(build.function);
|
||||
|
||||
// Load from memory is 'inlined' into first argument
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%1 = LOAD_DOUBLE R2
|
||||
JUMP_CMP_NUM R1, %1, bb_1, bb_2
|
||||
@ -328,7 +328,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "Numeric")
|
||||
updateUseCounts(build.function);
|
||||
constantFold();
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_INT R0, 30i
|
||||
STORE_INT R0, -2147483648i
|
||||
@ -374,7 +374,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ControlFlowEq")
|
||||
updateUseCounts(build.function);
|
||||
constantFold();
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
JUMP bb_1
|
||||
|
||||
@ -423,7 +423,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NumToIndex")
|
||||
updateUseCounts(build.function);
|
||||
constantFold();
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_INT R0, 4i
|
||||
LOP_RETURN 0u
|
||||
@ -458,7 +458,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "Guards")
|
||||
updateUseCounts(build.function);
|
||||
constantFold();
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
LOP_RETURN 0u
|
||||
|
||||
@ -579,7 +579,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RememberTagsAndValues")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_TAG R0, tnumber
|
||||
STORE_INT R1, 10i
|
||||
@ -625,7 +625,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "PropagateThroughTvalue")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_TAG R0, tnumber
|
||||
STORE_DOUBLE R0, 0.5
|
||||
@ -655,7 +655,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SkipCheckTag")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_TAG R0, tnumber
|
||||
LOP_RETURN 0u
|
||||
@ -682,7 +682,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SkipOncePerBlockChecks")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
CHECK_SAFE_ENV
|
||||
CHECK_GC
|
||||
@ -721,7 +721,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RememberTableState")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%0 = LOAD_POINTER R0
|
||||
CHECK_NO_METATABLE %0, bb_fallback_1
|
||||
@ -753,7 +753,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SkipUselessBarriers")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_TAG R0, tnumber
|
||||
LOP_RETURN 0u
|
||||
@ -782,7 +782,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ConcatInvalidation")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_TAG R0, tnumber
|
||||
STORE_INT R1, 10i
|
||||
@ -829,7 +829,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "BuiltinFastcallsMayInvalidateMemory")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_DOUBLE R0, 0.5
|
||||
%1 = LOAD_POINTER R0
|
||||
@ -862,32 +862,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RedundantStoreCheckConstantType")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
bb_0:
|
||||
STORE_INT R0, 10i
|
||||
STORE_DOUBLE R0, 0.5
|
||||
STORE_INT R0, 10i
|
||||
LOP_RETURN 0u
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "RedundantStoreCheckConstantType")
|
||||
{
|
||||
IrOp block = build.block(IrBlockKind::Internal);
|
||||
|
||||
build.beginBlock(block);
|
||||
|
||||
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(10));
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(0.5));
|
||||
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(10));
|
||||
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_INT R0, 10i
|
||||
STORE_DOUBLE R0, 0.5
|
||||
@ -917,7 +892,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagCheckPropagation")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%0 = LOAD_TAG R0
|
||||
CHECK_TAG %0, tnumber, bb_fallback_1
|
||||
@ -949,7 +924,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagCheckPropagationConflicting")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%0 = LOAD_TAG R0
|
||||
CHECK_TAG %0, tnumber, bb_fallback_1
|
||||
@ -985,7 +960,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TruthyTestRemoval")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%0 = LOAD_TAG R1
|
||||
CHECK_TAG %0, tnumber, bb_fallback_3
|
||||
@ -1024,7 +999,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FalsyTestRemoval")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%0 = LOAD_TAG R1
|
||||
CHECK_TAG %0, tnumber, bb_fallback_3
|
||||
@ -1059,7 +1034,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagEqRemoval")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%0 = LOAD_TAG R1
|
||||
CHECK_TAG %0, tboolean
|
||||
@ -1091,7 +1066,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "IntEqRemoval")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_INT R1, 5i
|
||||
JUMP bb_1
|
||||
@ -1122,7 +1097,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NumCmpRemoval")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_DOUBLE R1, 4
|
||||
JUMP bb_2
|
||||
@ -1150,7 +1125,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DataFlowsThroughDirectJumpToUniqueSuccessor
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_TAG R0, tnumber
|
||||
JUMP bb_1
|
||||
@ -1183,7 +1158,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DataDoesNotFlowThroughDirectJumpToNonUnique
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_TAG R0, tnumber
|
||||
JUMP bb_1
|
||||
@ -1199,6 +1174,120 @@ bb_2:
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "EntryBlockUseRemoval")
|
||||
{
|
||||
IrOp entry = build.block(IrBlockKind::Internal);
|
||||
IrOp exit = build.block(IrBlockKind::Internal);
|
||||
IrOp repeat = build.block(IrBlockKind::Internal);
|
||||
|
||||
build.beginBlock(entry);
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
|
||||
build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(0), exit, repeat);
|
||||
|
||||
build.beginBlock(exit);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(0));
|
||||
|
||||
build.beginBlock(repeat);
|
||||
build.inst(IrCmd::INTERRUPT, build.constUint(0));
|
||||
build.inst(IrCmd::JUMP, entry);
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_TAG R0, tnumber
|
||||
JUMP bb_1
|
||||
|
||||
bb_1:
|
||||
LOP_RETURN 0u, R0, 0i
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval1")
|
||||
{
|
||||
IrOp entry = build.block(IrBlockKind::Internal);
|
||||
IrOp block = build.block(IrBlockKind::Internal);
|
||||
IrOp exit = build.block(IrBlockKind::Internal);
|
||||
IrOp repeat = build.block(IrBlockKind::Internal);
|
||||
|
||||
build.beginBlock(entry);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(0));
|
||||
|
||||
build.beginBlock(block);
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
|
||||
build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(0), exit, repeat);
|
||||
|
||||
build.beginBlock(exit);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(0));
|
||||
|
||||
build.beginBlock(repeat);
|
||||
build.inst(IrCmd::INTERRUPT, build.constUint(0));
|
||||
build.inst(IrCmd::JUMP, block);
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
LOP_RETURN 0u, R0, 0i
|
||||
|
||||
bb_1:
|
||||
STORE_TAG R0, tnumber
|
||||
JUMP bb_2
|
||||
|
||||
bb_2:
|
||||
LOP_RETURN 0u, R0, 0i
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval2")
|
||||
{
|
||||
IrOp entry = build.block(IrBlockKind::Internal);
|
||||
IrOp exit1 = build.block(IrBlockKind::Internal);
|
||||
IrOp block = build.block(IrBlockKind::Internal);
|
||||
IrOp exit2 = build.block(IrBlockKind::Internal);
|
||||
IrOp repeat = build.block(IrBlockKind::Internal);
|
||||
|
||||
build.beginBlock(entry);
|
||||
build.inst(IrCmd::JUMP_EQ_INT, build.constInt(0), build.constInt(1), block, exit1);
|
||||
|
||||
build.beginBlock(exit1);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(0));
|
||||
|
||||
build.beginBlock(block);
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
|
||||
build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(0), exit2, repeat);
|
||||
|
||||
build.beginBlock(exit2);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(0));
|
||||
|
||||
build.beginBlock(repeat);
|
||||
build.inst(IrCmd::INTERRUPT, build.constUint(0));
|
||||
build.inst(IrCmd::JUMP, block);
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
JUMP bb_1
|
||||
|
||||
bb_1:
|
||||
LOP_RETURN 0u, R0, 0i
|
||||
|
||||
bb_2:
|
||||
STORE_TAG R0, tnumber
|
||||
JUMP bb_3
|
||||
|
||||
bb_3:
|
||||
LOP_RETURN 0u, R0, 0i
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
TEST_SUITE_BEGIN("LinearExecutionFlowExtraction");
|
||||
@ -1240,7 +1329,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SimplePathExtraction")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%0 = LOAD_TAG R2
|
||||
CHECK_TAG %0, tnumber, bb_fallback_1
|
||||
@ -1315,7 +1404,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NoPathExtractionForBlocksWithLiveOutValues"
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%0 = LOAD_TAG R2
|
||||
CHECK_TAG %0, tnumber, bb_fallback_1
|
||||
@ -1366,7 +1455,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "InfiniteLoopInPathAnalysis")
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeDetails */ false) == R"(
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
STORE_TAG R0, tnumber
|
||||
JUMP bb_1
|
||||
@ -1379,3 +1468,212 @@ bb_1:
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
TEST_SUITE_BEGIN("Analysis");
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "SimpleDiamond")
|
||||
{
|
||||
IrOp entry = build.block(IrBlockKind::Internal);
|
||||
IrOp a = build.block(IrBlockKind::Internal);
|
||||
IrOp b = build.block(IrBlockKind::Internal);
|
||||
IrOp exit = build.block(IrBlockKind::Internal);
|
||||
|
||||
build.beginBlock(entry);
|
||||
build.inst(IrCmd::JUMP_EQ_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), a, b);
|
||||
|
||||
build.beginBlock(a);
|
||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
|
||||
build.inst(IrCmd::JUMP, exit);
|
||||
|
||||
build.beginBlock(b);
|
||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
|
||||
build.inst(IrCmd::JUMP, exit);
|
||||
|
||||
build.beginBlock(exit);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(2), build.constInt(2));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
; successors: bb_1, bb_2
|
||||
; in regs: R0, R1, R2, R3
|
||||
; out regs: R1, R2, R3
|
||||
%0 = LOAD_TAG R0
|
||||
JUMP_EQ_TAG %0, tnumber, bb_1, bb_2
|
||||
|
||||
bb_1:
|
||||
; predecessors: bb_0
|
||||
; successors: bb_3
|
||||
; in regs: R1, R3
|
||||
; out regs: R2, R3
|
||||
%2 = LOAD_TVALUE R1
|
||||
STORE_TVALUE R2, %2
|
||||
JUMP bb_3
|
||||
|
||||
bb_2:
|
||||
; predecessors: bb_0
|
||||
; successors: bb_3
|
||||
; in regs: R1, R2
|
||||
; out regs: R2, R3
|
||||
%5 = LOAD_TVALUE R1
|
||||
STORE_TVALUE R3, %5
|
||||
JUMP bb_3
|
||||
|
||||
bb_3:
|
||||
; predecessors: bb_1, bb_2
|
||||
; in regs: R2, R3
|
||||
LOP_RETURN 0u, R2, 2i
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "ImplicitFixedRegistersInVarargCall")
|
||||
{
|
||||
IrOp entry = build.block(IrBlockKind::Internal);
|
||||
IrOp exit = build.block(IrBlockKind::Internal);
|
||||
|
||||
build.beginBlock(entry);
|
||||
build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(3), build.constInt(-1));
|
||||
build.inst(IrCmd::LOP_CALL, build.constUint(0), build.vmReg(0), build.constInt(-1), build.constInt(5));
|
||||
build.inst(IrCmd::JUMP, exit);
|
||||
|
||||
build.beginBlock(exit);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(5));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
; successors: bb_1
|
||||
; in regs: R0, R1, R2
|
||||
; out regs: R0, R1, R2, R3, R4
|
||||
FALLBACK_GETVARARGS 0u, R3, -1i
|
||||
LOP_CALL 0u, R0, -1i, 5i
|
||||
JUMP bb_1
|
||||
|
||||
bb_1:
|
||||
; predecessors: bb_0
|
||||
; in regs: R0, R1, R2, R3, R4
|
||||
LOP_RETURN 0u, R0, 5i
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "ExplicitUseOfRegisterInVarargSequence")
|
||||
{
|
||||
IrOp entry = build.block(IrBlockKind::Internal);
|
||||
IrOp exit = build.block(IrBlockKind::Internal);
|
||||
|
||||
build.beginBlock(entry);
|
||||
build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(1), build.constInt(-1));
|
||||
build.inst(IrCmd::INVOKE_FASTCALL, build.constUint(0), build.vmReg(0), build.vmReg(1), build.vmReg(2), build.constInt(-1), build.constInt(-1));
|
||||
build.inst(IrCmd::JUMP, exit);
|
||||
|
||||
build.beginBlock(exit);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(-1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
; successors: bb_1
|
||||
; out regs: R0...
|
||||
FALLBACK_GETVARARGS 0u, R1, -1i
|
||||
%1 = INVOKE_FASTCALL 0u, R0, R1, R2, -1i, -1i
|
||||
JUMP bb_1
|
||||
|
||||
bb_1:
|
||||
; predecessors: bb_0
|
||||
; in regs: R0...
|
||||
LOP_RETURN 0u, R0, -1i
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "VariadicSequenceRestart")
|
||||
{
|
||||
IrOp entry = build.block(IrBlockKind::Internal);
|
||||
IrOp exit = build.block(IrBlockKind::Internal);
|
||||
|
||||
build.beginBlock(entry);
|
||||
build.inst(IrCmd::LOP_CALL, build.constUint(0), build.vmReg(1), build.constInt(0), build.constInt(-1));
|
||||
build.inst(IrCmd::LOP_CALL, build.constUint(0), build.vmReg(0), build.constInt(-1), build.constInt(-1));
|
||||
build.inst(IrCmd::JUMP, exit);
|
||||
|
||||
build.beginBlock(exit);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(-1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
; successors: bb_1
|
||||
; in regs: R0, R1
|
||||
; out regs: R0...
|
||||
LOP_CALL 0u, R1, 0i, -1i
|
||||
LOP_CALL 0u, R0, -1i, -1i
|
||||
JUMP bb_1
|
||||
|
||||
bb_1:
|
||||
; predecessors: bb_0
|
||||
; in regs: R0...
|
||||
LOP_RETURN 0u, R0, -1i
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "FallbackDoesNotFlowUp")
|
||||
{
|
||||
IrOp entry = build.block(IrBlockKind::Internal);
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
IrOp exit = build.block(IrBlockKind::Internal);
|
||||
|
||||
build.beginBlock(entry);
|
||||
build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(1), build.constInt(-1));
|
||||
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), fallback);
|
||||
build.inst(IrCmd::LOP_CALL, build.constUint(0), build.vmReg(0), build.constInt(-1), build.constInt(-1));
|
||||
build.inst(IrCmd::JUMP, exit);
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::LOP_CALL, build.constUint(0), build.vmReg(0), build.constInt(-1), build.constInt(-1));
|
||||
build.inst(IrCmd::JUMP, exit);
|
||||
|
||||
build.beginBlock(exit);
|
||||
build.inst(IrCmd::LOP_RETURN, build.constUint(0), build.vmReg(0), build.constInt(-1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
; successors: bb_fallback_1, bb_2
|
||||
; in regs: R0
|
||||
; out regs: R0...
|
||||
FALLBACK_GETVARARGS 0u, R1, -1i
|
||||
%1 = LOAD_TAG R0
|
||||
CHECK_TAG %1, tnumber, bb_fallback_1
|
||||
LOP_CALL 0u, R0, -1i, -1i
|
||||
JUMP bb_2
|
||||
|
||||
bb_fallback_1:
|
||||
; predecessors: bb_0
|
||||
; successors: bb_2
|
||||
; in regs: R0, R1...
|
||||
; out regs: R0...
|
||||
LOP_CALL 0u, R0, -1i, -1i
|
||||
JUMP bb_2
|
||||
|
||||
bb_2:
|
||||
; predecessors: bb_0, bb_fallback_1
|
||||
; in regs: R0...
|
||||
LOP_RETURN 0u, R0, -1i
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -35,7 +35,7 @@ TEST_CASE_FIXTURE(Fixture, "UnknownGlobal")
|
||||
TEST_CASE_FIXTURE(Fixture, "DeprecatedGlobal")
|
||||
{
|
||||
// Normally this would be defined externally, so hack it in for testing
|
||||
addGlobalBinding(frontend, "Wait", Binding{typeChecker.anyType, {}, true, "wait", "@test/global/Wait"});
|
||||
addGlobalBinding(frontend.globals, "Wait", Binding{builtinTypes->anyType, {}, true, "wait", "@test/global/Wait"});
|
||||
|
||||
LintResult result = lint("Wait(5)");
|
||||
|
||||
@ -47,7 +47,7 @@ TEST_CASE_FIXTURE(Fixture, "DeprecatedGlobalNoReplacement")
|
||||
{
|
||||
// Normally this would be defined externally, so hack it in for testing
|
||||
const char* deprecationReplacementString = "";
|
||||
addGlobalBinding(frontend, "Version", Binding{typeChecker.anyType, {}, true, deprecationReplacementString});
|
||||
addGlobalBinding(frontend.globals, "Version", Binding{builtinTypes->anyType, {}, true, deprecationReplacementString});
|
||||
|
||||
LintResult result = lint("Version()");
|
||||
|
||||
@ -373,7 +373,7 @@ return bar()
|
||||
TEST_CASE_FIXTURE(Fixture, "ImportUnused")
|
||||
{
|
||||
// Normally this would be defined externally, so hack it in for testing
|
||||
addGlobalBinding(frontend, "game", typeChecker.anyType, "@test");
|
||||
addGlobalBinding(frontend.globals, "game", builtinTypes->anyType, "@test");
|
||||
|
||||
LintResult result = lint(R"(
|
||||
local Roact = require(game.Packages.Roact)
|
||||
@ -604,16 +604,16 @@ return foo1
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "UnknownType")
|
||||
{
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
TableType::Props instanceProps{
|
||||
{"ClassName", {typeChecker.anyType}},
|
||||
{"ClassName", {builtinTypes->anyType}},
|
||||
};
|
||||
|
||||
TableType instanceTable{instanceProps, std::nullopt, typeChecker.globalScope->level, Luau::TableState::Sealed};
|
||||
TypeId instanceType = typeChecker.globalTypes.addType(instanceTable);
|
||||
TableType instanceTable{instanceProps, std::nullopt, frontend.globals.globalScope->level, Luau::TableState::Sealed};
|
||||
TypeId instanceType = frontend.globals.globalTypes.addType(instanceTable);
|
||||
TypeFun instanceTypeFun{{}, instanceType};
|
||||
|
||||
typeChecker.globalScope->exportedTypeBindings["Part"] = instanceTypeFun;
|
||||
frontend.globals.globalScope->exportedTypeBindings["Part"] = instanceTypeFun;
|
||||
|
||||
LintResult result = lint(R"(
|
||||
local game = ...
|
||||
@ -1270,12 +1270,12 @@ TEST_CASE_FIXTURE(Fixture, "no_spurious_warning_after_a_function_type_alias")
|
||||
TEST_CASE_FIXTURE(Fixture, "use_all_parent_scopes_for_globals")
|
||||
{
|
||||
ScopePtr testScope = frontend.addEnvironment("Test");
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
loadDefinitionFile(frontend.typeChecker, testScope, R"(
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
loadDefinitionFile(frontend.typeChecker, frontend.globals, testScope, R"(
|
||||
declare Foo: number
|
||||
)",
|
||||
"@test");
|
||||
freeze(typeChecker.globalTypes);
|
||||
"@test", /* captureComments */ false);
|
||||
freeze(frontend.globals.globalTypes);
|
||||
|
||||
fileResolver.environments["A"] = "Test";
|
||||
|
||||
@ -1444,31 +1444,32 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "DeprecatedApiTyped")
|
||||
{
|
||||
ScopedFastFlag sff("LuauImproveDeprecatedApiLint", true);
|
||||
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
TypeId instanceType = typeChecker.globalTypes.addType(ClassType{"Instance", {}, std::nullopt, std::nullopt, {}, {}, "Test"});
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
TypeId instanceType = frontend.globals.globalTypes.addType(ClassType{"Instance", {}, std::nullopt, std::nullopt, {}, {}, "Test"});
|
||||
persist(instanceType);
|
||||
typeChecker.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType};
|
||||
frontend.globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType};
|
||||
|
||||
getMutable<ClassType>(instanceType)->props = {
|
||||
{"Name", {typeChecker.stringType}},
|
||||
{"DataCost", {typeChecker.numberType, /* deprecated= */ true}},
|
||||
{"Wait", {typeChecker.anyType, /* deprecated= */ true}},
|
||||
{"Name", {builtinTypes->stringType}},
|
||||
{"DataCost", {builtinTypes->numberType, /* deprecated= */ true}},
|
||||
{"Wait", {builtinTypes->anyType, /* deprecated= */ true}},
|
||||
};
|
||||
|
||||
TypeId colorType = typeChecker.globalTypes.addType(TableType{{}, std::nullopt, typeChecker.globalScope->level, Luau::TableState::Sealed});
|
||||
TypeId colorType =
|
||||
frontend.globals.globalTypes.addType(TableType{{}, std::nullopt, frontend.globals.globalScope->level, Luau::TableState::Sealed});
|
||||
|
||||
getMutable<TableType>(colorType)->props = {{"toHSV", {typeChecker.anyType, /* deprecated= */ true, "Color3:ToHSV"}}};
|
||||
getMutable<TableType>(colorType)->props = {{"toHSV", {builtinTypes->anyType, /* deprecated= */ true, "Color3:ToHSV"}}};
|
||||
|
||||
addGlobalBinding(frontend, "Color3", Binding{colorType, {}});
|
||||
addGlobalBinding(frontend.globals, "Color3", Binding{colorType, {}});
|
||||
|
||||
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(typeChecker, "table")))
|
||||
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(frontend.globals, "table")))
|
||||
{
|
||||
ttv->props["foreach"].deprecated = true;
|
||||
ttv->props["getn"].deprecated = true;
|
||||
ttv->props["getn"].deprecatedSuggestion = "#";
|
||||
}
|
||||
|
||||
freeze(typeChecker.globalTypes);
|
||||
freeze(frontend.globals.globalTypes);
|
||||
|
||||
LintResult result = lintTyped(R"(
|
||||
return function (i: Instance)
|
||||
@ -1495,7 +1496,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "DeprecatedApiUntyped")
|
||||
{
|
||||
ScopedFastFlag sff("LuauImproveDeprecatedApiLint", true);
|
||||
|
||||
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(typeChecker, "table")))
|
||||
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(frontend.globals, "table")))
|
||||
{
|
||||
ttv->props["foreach"].deprecated = true;
|
||||
ttv->props["getn"].deprecated = true;
|
||||
|
@ -48,8 +48,8 @@ TEST_CASE_FIXTURE(Fixture, "dont_clone_persistent_primitive")
|
||||
CloneState cloneState;
|
||||
|
||||
// numberType is persistent. We leave it as-is.
|
||||
TypeId newNumber = clone(typeChecker.numberType, dest, cloneState);
|
||||
CHECK_EQ(newNumber, typeChecker.numberType);
|
||||
TypeId newNumber = clone(builtinTypes->numberType, dest, cloneState);
|
||||
CHECK_EQ(newNumber, builtinTypes->numberType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "deepClone_non_persistent_primitive")
|
||||
@ -58,9 +58,9 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_non_persistent_primitive")
|
||||
CloneState cloneState;
|
||||
|
||||
// Create a new number type that isn't persistent
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
TypeId oldNumber = typeChecker.globalTypes.addType(PrimitiveType{PrimitiveType::Number});
|
||||
freeze(typeChecker.globalTypes);
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
TypeId oldNumber = frontend.globals.globalTypes.addType(PrimitiveType{PrimitiveType::Number});
|
||||
freeze(frontend.globals.globalTypes);
|
||||
TypeId newNumber = clone(oldNumber, dest, cloneState);
|
||||
|
||||
CHECK_NE(newNumber, oldNumber);
|
||||
@ -170,10 +170,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_types_point_into_globalTypes_arena")
|
||||
REQUIRE(signType != nullptr);
|
||||
|
||||
CHECK(!isInArena(signType, module->interfaceTypes));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(isInArena(signType, frontend.globalTypes));
|
||||
else
|
||||
CHECK(isInArena(signType, typeChecker.globalTypes));
|
||||
CHECK(isInArena(signType, frontend.globals.globalTypes));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "deepClone_union")
|
||||
@ -181,9 +178,9 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_union")
|
||||
TypeArena dest;
|
||||
CloneState cloneState;
|
||||
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
TypeId oldUnion = typeChecker.globalTypes.addType(UnionType{{typeChecker.numberType, typeChecker.stringType}});
|
||||
freeze(typeChecker.globalTypes);
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
TypeId oldUnion = frontend.globals.globalTypes.addType(UnionType{{builtinTypes->numberType, builtinTypes->stringType}});
|
||||
freeze(frontend.globals.globalTypes);
|
||||
TypeId newUnion = clone(oldUnion, dest, cloneState);
|
||||
|
||||
CHECK_NE(newUnion, oldUnion);
|
||||
@ -196,9 +193,9 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_intersection")
|
||||
TypeArena dest;
|
||||
CloneState cloneState;
|
||||
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
TypeId oldIntersection = typeChecker.globalTypes.addType(IntersectionType{{typeChecker.numberType, typeChecker.stringType}});
|
||||
freeze(typeChecker.globalTypes);
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
TypeId oldIntersection = frontend.globals.globalTypes.addType(IntersectionType{{builtinTypes->numberType, builtinTypes->stringType}});
|
||||
freeze(frontend.globals.globalTypes);
|
||||
TypeId newIntersection = clone(oldIntersection, dest, cloneState);
|
||||
|
||||
CHECK_NE(newIntersection, oldIntersection);
|
||||
@ -210,13 +207,13 @@ TEST_CASE_FIXTURE(Fixture, "clone_class")
|
||||
{
|
||||
Type exampleMetaClass{ClassType{"ExampleClassMeta",
|
||||
{
|
||||
{"__add", {typeChecker.anyType}},
|
||||
{"__add", {builtinTypes->anyType}},
|
||||
},
|
||||
std::nullopt, std::nullopt, {}, {}, "Test"}};
|
||||
Type exampleClass{ClassType{"ExampleClass",
|
||||
{
|
||||
{"PropOne", {typeChecker.numberType}},
|
||||
{"PropTwo", {typeChecker.stringType}},
|
||||
{"PropOne", {builtinTypes->numberType}},
|
||||
{"PropTwo", {builtinTypes->stringType}},
|
||||
},
|
||||
std::nullopt, &exampleMetaClass, {}, {}, "Test"}};
|
||||
|
||||
|
@ -64,7 +64,7 @@ TEST_CASE_FIXTURE(Fixture, "return_annotation_is_still_checked")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
REQUIRE_NE(*typeChecker.anyType, *requireType("foo"));
|
||||
REQUIRE_NE(*builtinTypes->anyType, *requireType("foo"));
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -107,7 +107,7 @@ TEST_CASE_FIXTURE(Fixture, "locals_are_any_by_default")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(*typeChecker.anyType, *requireType("m"));
|
||||
CHECK_EQ(*builtinTypes->anyType, *requireType("m"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parameters_having_type_any_are_optional")
|
||||
@ -173,7 +173,7 @@ TEST_CASE_FIXTURE(Fixture, "table_props_are_any")
|
||||
TypeId fooProp = ttv->props["foo"].type;
|
||||
REQUIRE(fooProp != nullptr);
|
||||
|
||||
CHECK_EQ(*fooProp, *typeChecker.anyType);
|
||||
CHECK_EQ(*fooProp, *builtinTypes->anyType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "inline_table_props_are_also_any")
|
||||
@ -192,8 +192,8 @@ TEST_CASE_FIXTURE(Fixture, "inline_table_props_are_also_any")
|
||||
TableType* ttv = getMutable<TableType>(requireType("T"));
|
||||
REQUIRE_MESSAGE(ttv, "Should be a table: " << toString(requireType("T")));
|
||||
|
||||
CHECK_EQ(*typeChecker.anyType, *ttv->props["one"].type);
|
||||
CHECK_EQ(*typeChecker.anyType, *ttv->props["two"].type);
|
||||
CHECK_EQ(*builtinTypes->anyType, *ttv->props["one"].type);
|
||||
CHECK_EQ(*builtinTypes->anyType, *ttv->props["two"].type);
|
||||
CHECK_MESSAGE(get<FunctionType>(follow(ttv->props["three"].type)), "Should be a function: " << *ttv->props["three"].type);
|
||||
}
|
||||
|
||||
|
@ -325,9 +325,9 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "classes")
|
||||
|
||||
check(""); // Ensure that we have a main Module.
|
||||
|
||||
TypeId p = typeChecker.globalScope->lookupType("Parent")->type;
|
||||
TypeId c = typeChecker.globalScope->lookupType("Child")->type;
|
||||
TypeId u = typeChecker.globalScope->lookupType("Unrelated")->type;
|
||||
TypeId p = frontend.globals.globalScope->lookupType("Parent")->type;
|
||||
TypeId c = frontend.globals.globalScope->lookupType("Child")->type;
|
||||
TypeId u = frontend.globals.globalScope->lookupType("Unrelated")->type;
|
||||
|
||||
CHECK(isSubtype(c, p));
|
||||
CHECK(!isSubtype(p, c));
|
||||
|
@ -15,7 +15,7 @@ struct ToDotClassFixture : Fixture
|
||||
{
|
||||
ToDotClassFixture()
|
||||
{
|
||||
TypeArena& arena = typeChecker.globalTypes;
|
||||
TypeArena& arena = frontend.globals.globalTypes;
|
||||
|
||||
unfreeze(arena);
|
||||
|
||||
@ -23,17 +23,17 @@ struct ToDotClassFixture : Fixture
|
||||
|
||||
TypeId baseClassInstanceType = arena.addType(ClassType{"BaseClass", {}, std::nullopt, baseClassMetaType, {}, {}, "Test"});
|
||||
getMutable<ClassType>(baseClassInstanceType)->props = {
|
||||
{"BaseField", {typeChecker.numberType}},
|
||||
{"BaseField", {builtinTypes->numberType}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType};
|
||||
frontend.globals.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType};
|
||||
|
||||
TypeId childClassInstanceType = arena.addType(ClassType{"ChildClass", {}, baseClassInstanceType, std::nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassType>(childClassInstanceType)->props = {
|
||||
{"ChildField", {typeChecker.stringType}},
|
||||
{"ChildField", {builtinTypes->stringType}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType};
|
||||
frontend.globals.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType};
|
||||
|
||||
for (const auto& [name, ty] : typeChecker.globalScope->exportedTypeBindings)
|
||||
for (const auto& [name, ty] : frontend.globals.globalScope->exportedTypeBindings)
|
||||
persist(ty.type);
|
||||
|
||||
freeze(arena);
|
||||
@ -373,7 +373,7 @@ n1 [label="GenericTypePack T"];
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "bound_pack")
|
||||
{
|
||||
TypePackVar pack{TypePackVariant{TypePack{{typeChecker.numberType}, {}}}};
|
||||
TypePackVar pack{TypePackVariant{TypePack{{builtinTypes->numberType}, {}}}};
|
||||
TypePackVar bound{TypePackVariant{BoundTypePack{&pack}}};
|
||||
|
||||
ToDotOptions opts;
|
||||
|
@ -184,27 +184,27 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "exhaustive_toString_of_cyclic_table")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_parenthesized_only_if_needed")
|
||||
{
|
||||
auto utv = Type{UnionType{{typeChecker.numberType, typeChecker.stringType}}};
|
||||
auto itv = Type{IntersectionType{{&utv, typeChecker.booleanType}}};
|
||||
auto utv = Type{UnionType{{builtinTypes->numberType, builtinTypes->stringType}}};
|
||||
auto itv = Type{IntersectionType{{&utv, builtinTypes->booleanType}}};
|
||||
|
||||
CHECK_EQ(toString(&itv), "(number | string) & boolean");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_parenthesized_only_if_needed")
|
||||
{
|
||||
auto itv = Type{IntersectionType{{typeChecker.numberType, typeChecker.stringType}}};
|
||||
auto utv = Type{UnionType{{&itv, typeChecker.booleanType}}};
|
||||
auto itv = Type{IntersectionType{{builtinTypes->numberType, builtinTypes->stringType}}};
|
||||
auto utv = Type{UnionType{{&itv, builtinTypes->booleanType}}};
|
||||
|
||||
CHECK_EQ(toString(&utv), "(number & string) | boolean");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "functions_are_always_parenthesized_in_unions_or_intersections")
|
||||
{
|
||||
auto stringAndNumberPack = TypePackVar{TypePack{{typeChecker.stringType, typeChecker.numberType}}};
|
||||
auto numberAndStringPack = TypePackVar{TypePack{{typeChecker.numberType, typeChecker.stringType}}};
|
||||
auto stringAndNumberPack = TypePackVar{TypePack{{builtinTypes->stringType, builtinTypes->numberType}}};
|
||||
auto numberAndStringPack = TypePackVar{TypePack{{builtinTypes->numberType, builtinTypes->stringType}}};
|
||||
|
||||
auto sn2ns = Type{FunctionType{&stringAndNumberPack, &numberAndStringPack}};
|
||||
auto ns2sn = Type{FunctionType(typeChecker.globalScope->level, &numberAndStringPack, &stringAndNumberPack)};
|
||||
auto ns2sn = Type{FunctionType(frontend.globals.globalScope->level, &numberAndStringPack, &stringAndNumberPack)};
|
||||
|
||||
auto utv = Type{UnionType{{&ns2sn, &sn2ns}}};
|
||||
auto itv = Type{IntersectionType{{&ns2sn, &sn2ns}}};
|
||||
@ -250,7 +250,7 @@ TEST_CASE_FIXTURE(Fixture, "quit_stringifying_table_type_when_length_is_exceeded
|
||||
{
|
||||
TableType ttv{};
|
||||
for (char c : std::string("abcdefghijklmno"))
|
||||
ttv.props[std::string(1, c)] = {typeChecker.numberType};
|
||||
ttv.props[std::string(1, c)] = {builtinTypes->numberType};
|
||||
|
||||
Type tv{ttv};
|
||||
|
||||
@ -264,7 +264,7 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_table_type_is_still_capped_when_exhaust
|
||||
{
|
||||
TableType ttv{};
|
||||
for (char c : std::string("abcdefg"))
|
||||
ttv.props[std::string(1, c)] = {typeChecker.numberType};
|
||||
ttv.props[std::string(1, c)] = {builtinTypes->numberType};
|
||||
|
||||
Type tv{ttv};
|
||||
|
||||
@ -339,7 +339,7 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_table_type_correctly_use_matching_table
|
||||
{
|
||||
TableType ttv{TableState::Sealed, TypeLevel{}};
|
||||
for (char c : std::string("abcdefghij"))
|
||||
ttv.props[std::string(1, c)] = {typeChecker.numberType};
|
||||
ttv.props[std::string(1, c)] = {builtinTypes->numberType};
|
||||
|
||||
Type tv{ttv};
|
||||
|
||||
@ -350,7 +350,7 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_table_type_correctly_use_matching_table
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "stringifying_cyclic_union_type_bails_early")
|
||||
{
|
||||
Type tv{UnionType{{typeChecker.stringType, typeChecker.numberType}}};
|
||||
Type tv{UnionType{{builtinTypes->stringType, builtinTypes->numberType}}};
|
||||
UnionType* utv = getMutable<UnionType>(&tv);
|
||||
utv->options.push_back(&tv);
|
||||
utv->options.push_back(&tv);
|
||||
@ -371,11 +371,11 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_cyclic_intersection_type_bails_early")
|
||||
TEST_CASE_FIXTURE(Fixture, "stringifying_array_uses_array_syntax")
|
||||
{
|
||||
TableType ttv{TableState::Sealed, TypeLevel{}};
|
||||
ttv.indexer = TableIndexer{typeChecker.numberType, typeChecker.stringType};
|
||||
ttv.indexer = TableIndexer{builtinTypes->numberType, builtinTypes->stringType};
|
||||
|
||||
CHECK_EQ("{string}", toString(Type{ttv}));
|
||||
|
||||
ttv.props["A"] = {typeChecker.numberType};
|
||||
ttv.props["A"] = {builtinTypes->numberType};
|
||||
CHECK_EQ("{| [number]: string, A: number |}", toString(Type{ttv}));
|
||||
|
||||
ttv.props.clear();
|
||||
@ -562,15 +562,15 @@ TEST_CASE_FIXTURE(Fixture, "toString_the_boundTo_table_type_contained_within_a_T
|
||||
Type tv1{TableType{}};
|
||||
TableType* ttv = getMutable<TableType>(&tv1);
|
||||
ttv->state = TableState::Sealed;
|
||||
ttv->props["hello"] = {typeChecker.numberType};
|
||||
ttv->props["world"] = {typeChecker.numberType};
|
||||
ttv->props["hello"] = {builtinTypes->numberType};
|
||||
ttv->props["world"] = {builtinTypes->numberType};
|
||||
|
||||
TypePackVar tpv1{TypePack{{&tv1}}};
|
||||
|
||||
Type tv2{TableType{}};
|
||||
TableType* bttv = getMutable<TableType>(&tv2);
|
||||
bttv->state = TableState::Free;
|
||||
bttv->props["hello"] = {typeChecker.numberType};
|
||||
bttv->props["hello"] = {builtinTypes->numberType};
|
||||
bttv->boundTo = &tv1;
|
||||
|
||||
TypePackVar tpv2{TypePack{{&tv2}}};
|
||||
|
@ -168,7 +168,7 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_types_of_named_table_fields_do_not_expand_whe
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ("Node?", toString(tm->wantedType));
|
||||
CHECK_EQ(typeChecker.numberType, tm->givenType);
|
||||
CHECK_EQ(builtinTypes->numberType, tm->givenType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_aliases")
|
||||
@ -329,7 +329,7 @@ TEST_CASE_FIXTURE(Fixture, "stringify_type_alias_of_recursive_template_table_typ
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ("Wrapped", toString(tm->wantedType));
|
||||
CHECK_EQ(typeChecker.numberType, tm->givenType);
|
||||
CHECK_EQ(builtinTypes->numberType, tm->givenType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "stringify_type_alias_of_recursive_template_table_type2")
|
||||
@ -345,7 +345,7 @@ TEST_CASE_FIXTURE(Fixture, "stringify_type_alias_of_recursive_template_table_typ
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ("t1 where t1 = ({| a: t1 |}) -> string", toString(tm->wantedType));
|
||||
CHECK_EQ(typeChecker.numberType, tm->givenType);
|
||||
CHECK_EQ(builtinTypes->numberType, tm->givenType);
|
||||
}
|
||||
|
||||
// Check that recursive intersection type doesn't generate an OOM
|
||||
@ -520,7 +520,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_import_mutation")
|
||||
CheckResult result = check("type t10<x> = typeof(table)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
TypeId ty = getGlobalBinding(frontend, "table");
|
||||
TypeId ty = getGlobalBinding(frontend.globals, "table");
|
||||
|
||||
CHECK(toString(ty) == "typeof(table)");
|
||||
|
||||
@ -922,4 +922,29 @@ TEST_CASE_FIXTURE(Fixture, "cannot_create_cyclic_type_with_unknown_module")
|
||||
CHECK(toString(result.errors[0]) == "Unknown type 'B.AAA'");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_alias_locations")
|
||||
{
|
||||
check(R"(
|
||||
type T = number
|
||||
|
||||
do
|
||||
type T = string
|
||||
type X = boolean
|
||||
end
|
||||
)");
|
||||
|
||||
ModulePtr mod = getMainModule();
|
||||
REQUIRE(mod);
|
||||
REQUIRE(mod->scopes.size() == 8);
|
||||
|
||||
REQUIRE(mod->scopes[0].second->typeAliasNameLocations.count("T") > 0);
|
||||
CHECK(mod->scopes[0].second->typeAliasNameLocations["T"] == Location(Position(1, 13), 1));
|
||||
|
||||
REQUIRE(mod->scopes[3].second->typeAliasNameLocations.count("T") > 0);
|
||||
CHECK(mod->scopes[3].second->typeAliasNameLocations["T"] == Location(Position(4, 17), 1));
|
||||
|
||||
REQUIRE(mod->scopes[3].second->typeAliasNameLocations.count("X") > 0);
|
||||
CHECK(mod->scopes[3].second->typeAliasNameLocations["X"] == Location(Position(5, 17), 1));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -86,7 +86,7 @@ TEST_CASE_FIXTURE(Fixture, "function_return_annotations_are_checked")
|
||||
|
||||
REQUIRE_EQ(1, tp->head.size());
|
||||
|
||||
REQUIRE_EQ(typeChecker.anyType, follow(tp->head[0]));
|
||||
REQUIRE_EQ(builtinTypes->anyType, follow(tp->head[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "function_return_multret_annotations_are_checked")
|
||||
@ -166,11 +166,12 @@ TEST_CASE_FIXTURE(Fixture, "infer_type_of_value_a_via_typeof_with_assignment")
|
||||
a = "foo"
|
||||
)");
|
||||
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("a"));
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("b"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("a"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("b"));
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(result.errors[0], (TypeError{Location{Position{4, 12}, Position{4, 17}}, TypeMismatch{typeChecker.numberType, typeChecker.stringType}}));
|
||||
CHECK_EQ(
|
||||
result.errors[0], (TypeError{Location{Position{4, 12}, Position{4, 17}}, TypeMismatch{builtinTypes->numberType, builtinTypes->stringType}}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_annotation")
|
||||
@ -459,7 +460,7 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_always_resolve_to_a_real_type")
|
||||
)");
|
||||
|
||||
TypeId fType = requireType("aa");
|
||||
REQUIRE(follow(fType) == typeChecker.numberType);
|
||||
REQUIRE(follow(fType) == builtinTypes->numberType);
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
@ -480,7 +481,7 @@ TEST_CASE_FIXTURE(Fixture, "interface_types_belong_to_interface_arena")
|
||||
const TypeFun& a = mod.exportedTypeBindings["A"];
|
||||
|
||||
CHECK(isInArena(a.type, mod.interfaceTypes));
|
||||
CHECK(!isInArena(a.type, typeChecker.globalTypes));
|
||||
CHECK(!isInArena(a.type, frontend.globals.globalTypes));
|
||||
|
||||
std::optional<TypeId> exportsType = first(mod.returnType);
|
||||
REQUIRE(exportsType);
|
||||
@ -559,7 +560,7 @@ TEST_CASE_FIXTURE(Fixture, "cloned_interface_maintains_pointers_between_definiti
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "use_type_required_from_another_file")
|
||||
{
|
||||
addGlobalBinding(frontend, "script", frontend.typeChecker.anyType, "@test");
|
||||
addGlobalBinding(frontend.globals, "script", builtinTypes->anyType, "@test");
|
||||
|
||||
fileResolver.source["Modules/Main"] = R"(
|
||||
--!strict
|
||||
@ -585,7 +586,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "use_type_required_from_another_file")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "cannot_use_nonexported_type")
|
||||
{
|
||||
addGlobalBinding(frontend, "script", frontend.typeChecker.anyType, "@test");
|
||||
addGlobalBinding(frontend.globals, "script", builtinTypes->anyType, "@test");
|
||||
|
||||
fileResolver.source["Modules/Main"] = R"(
|
||||
--!strict
|
||||
@ -611,7 +612,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cannot_use_nonexported_type")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_types_are_not_exported")
|
||||
{
|
||||
addGlobalBinding(frontend, "script", frontend.typeChecker.anyType, "@test");
|
||||
addGlobalBinding(frontend.globals, "script", builtinTypes->anyType, "@test");
|
||||
|
||||
fileResolver.source["Modules/Main"] = R"(
|
||||
--!strict
|
||||
|
@ -30,7 +30,7 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_returns_any")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(typeChecker.anyType, requireType("a"));
|
||||
CHECK_EQ(builtinTypes->anyType, requireType("a"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_returns_any2")
|
||||
@ -209,7 +209,7 @@ TEST_CASE_FIXTURE(Fixture, "quantify_any_does_not_bind_to_itself")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
TypeId aType = requireType("A");
|
||||
CHECK_EQ(aType, typeChecker.anyType);
|
||||
CHECK_EQ(aType, builtinTypes->anyType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "calling_error_type_yields_error")
|
||||
|
@ -106,7 +106,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_concat_returns_string")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(*typeChecker.stringType, *requireType("r"));
|
||||
CHECK_EQ(*builtinTypes->stringType, *requireType("r"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "sort")
|
||||
@ -156,7 +156,7 @@ TEST_CASE_FIXTURE(Fixture, "strings_have_methods")
|
||||
)LUA");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(*typeChecker.stringType, *requireType("s"));
|
||||
CHECK_EQ(*builtinTypes->stringType, *requireType("s"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "math_max_variatic")
|
||||
@ -166,7 +166,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "math_max_variatic")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("n"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("n"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "math_max_checks_for_numbers")
|
||||
@ -365,7 +365,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_correctly_infers_type_of_array_
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(typeChecker.stringType, requireType("s"));
|
||||
CHECK_EQ(builtinTypes->stringType, requireType("s"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_correctly_infers_type_of_array_3_args_overload")
|
||||
@ -429,7 +429,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gcinfo")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("n"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("n"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "getfenv")
|
||||
@ -446,9 +446,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "os_time_takes_optional_date_table")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("n1"));
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("n2"));
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("n3"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("n1"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("n2"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("n3"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "thread_is_a_type")
|
||||
@ -552,8 +552,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_correctly_ordered_types")
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ(tm->wantedType, typeChecker.stringType);
|
||||
CHECK_EQ(tm->givenType, typeChecker.numberType);
|
||||
CHECK_EQ(tm->wantedType, builtinTypes->stringType);
|
||||
CHECK_EQ(tm->givenType, builtinTypes->numberType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_tostring_specifier")
|
||||
@ -722,8 +722,8 @@ TEST_CASE_FIXTURE(Fixture, "string_format_as_method")
|
||||
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ(tm->wantedType, typeChecker.stringType);
|
||||
CHECK_EQ(tm->givenType, typeChecker.numberType);
|
||||
CHECK_EQ(tm->wantedType, builtinTypes->stringType);
|
||||
CHECK_EQ(tm->givenType, builtinTypes->numberType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "string_format_use_correct_argument")
|
||||
@ -860,9 +860,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_report_all_type_errors_at_corr
|
||||
string.format("%s%d%s", 1, "hello", true)
|
||||
)");
|
||||
|
||||
TypeId stringType = typeChecker.stringType;
|
||||
TypeId numberType = typeChecker.numberType;
|
||||
TypeId booleanType = typeChecker.booleanType;
|
||||
TypeId stringType = builtinTypes->stringType;
|
||||
TypeId numberType = builtinTypes->numberType;
|
||||
TypeId booleanType = builtinTypes->booleanType;
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(6, result);
|
||||
|
||||
@ -1027,7 +1027,7 @@ local function f(a: typeof(f)) end
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "no_persistent_typelevel_change")
|
||||
{
|
||||
TypeId mathTy = requireType(typeChecker.globalScope, "math");
|
||||
TypeId mathTy = requireType(frontend.globals.globalScope, "math");
|
||||
REQUIRE(mathTy);
|
||||
TableType* ttv = getMutable<TableType>(mathTy);
|
||||
REQUIRE(ttv);
|
||||
|
@ -19,13 +19,13 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_simple")
|
||||
declare foo2: typeof(foo)
|
||||
)");
|
||||
|
||||
TypeId globalFooTy = getGlobalBinding(frontend, "foo");
|
||||
TypeId globalFooTy = getGlobalBinding(frontend.globals, "foo");
|
||||
CHECK_EQ(toString(globalFooTy), "number");
|
||||
|
||||
TypeId globalBarTy = getGlobalBinding(frontend, "bar");
|
||||
TypeId globalBarTy = getGlobalBinding(frontend.globals, "bar");
|
||||
CHECK_EQ(toString(globalBarTy), "(number) -> string");
|
||||
|
||||
TypeId globalFoo2Ty = getGlobalBinding(frontend, "foo2");
|
||||
TypeId globalFoo2Ty = getGlobalBinding(frontend.globals, "foo2");
|
||||
CHECK_EQ(toString(globalFoo2Ty), "number");
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -48,20 +48,20 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_loading")
|
||||
declare function var(...: any): string
|
||||
)");
|
||||
|
||||
TypeId globalFooTy = getGlobalBinding(frontend, "foo");
|
||||
TypeId globalFooTy = getGlobalBinding(frontend.globals, "foo");
|
||||
CHECK_EQ(toString(globalFooTy), "number");
|
||||
|
||||
std::optional<TypeFun> globalAsdfTy = frontend.getGlobalScope()->lookupType("Asdf");
|
||||
std::optional<TypeFun> globalAsdfTy = frontend.globals.globalScope->lookupType("Asdf");
|
||||
REQUIRE(bool(globalAsdfTy));
|
||||
CHECK_EQ(toString(globalAsdfTy->type), "number | string");
|
||||
|
||||
TypeId globalBarTy = getGlobalBinding(frontend, "bar");
|
||||
TypeId globalBarTy = getGlobalBinding(frontend.globals, "bar");
|
||||
CHECK_EQ(toString(globalBarTy), "(number) -> string");
|
||||
|
||||
TypeId globalFoo2Ty = getGlobalBinding(frontend, "foo2");
|
||||
TypeId globalFoo2Ty = getGlobalBinding(frontend.globals, "foo2");
|
||||
CHECK_EQ(toString(globalFoo2Ty), "number");
|
||||
|
||||
TypeId globalVarTy = getGlobalBinding(frontend, "var");
|
||||
TypeId globalVarTy = getGlobalBinding(frontend.globals, "var");
|
||||
|
||||
CHECK_EQ(toString(globalVarTy), "(...any) -> string");
|
||||
|
||||
@ -77,25 +77,25 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_loading")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "load_definition_file_errors_do_not_pollute_global_scope")
|
||||
{
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
LoadDefinitionFileResult parseFailResult = loadDefinitionFile(typeChecker, typeChecker.globalScope, R"(
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
LoadDefinitionFileResult parseFailResult = loadDefinitionFile(frontend.typeChecker, frontend.globals, frontend.globals.globalScope, R"(
|
||||
declare foo
|
||||
)",
|
||||
"@test");
|
||||
freeze(typeChecker.globalTypes);
|
||||
"@test", /* captureComments */ false);
|
||||
freeze(frontend.globals.globalTypes);
|
||||
|
||||
REQUIRE(!parseFailResult.success);
|
||||
std::optional<Binding> fooTy = tryGetGlobalBinding(frontend, "foo");
|
||||
std::optional<Binding> fooTy = tryGetGlobalBinding(frontend.globals, "foo");
|
||||
CHECK(!fooTy.has_value());
|
||||
|
||||
LoadDefinitionFileResult checkFailResult = loadDefinitionFile(typeChecker, typeChecker.globalScope, R"(
|
||||
LoadDefinitionFileResult checkFailResult = loadDefinitionFile(frontend.typeChecker, frontend.globals, frontend.globals.globalScope, R"(
|
||||
local foo: string = 123
|
||||
declare bar: typeof(foo)
|
||||
)",
|
||||
"@test");
|
||||
"@test", /* captureComments */ false);
|
||||
|
||||
REQUIRE(!checkFailResult.success);
|
||||
std::optional<Binding> barTy = tryGetGlobalBinding(frontend, "bar");
|
||||
std::optional<Binding> barTy = tryGetGlobalBinding(frontend.globals, "bar");
|
||||
CHECK(!barTy.has_value());
|
||||
}
|
||||
|
||||
@ -139,15 +139,15 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_classes")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_overload_non_function")
|
||||
{
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
LoadDefinitionFileResult result = loadDefinitionFile(typeChecker, typeChecker.globalScope, R"(
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
LoadDefinitionFileResult result = loadDefinitionFile(frontend.typeChecker, frontend.globals, frontend.globals.globalScope, R"(
|
||||
declare class A
|
||||
X: number
|
||||
X: string
|
||||
end
|
||||
)",
|
||||
"@test");
|
||||
freeze(typeChecker.globalTypes);
|
||||
"@test", /* captureComments */ false);
|
||||
freeze(frontend.globals.globalTypes);
|
||||
|
||||
REQUIRE(!result.success);
|
||||
CHECK_EQ(result.parseResult.errors.size(), 0);
|
||||
@ -160,15 +160,15 @@ TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_overload_non_function")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_extend_non_class")
|
||||
{
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
LoadDefinitionFileResult result = loadDefinitionFile(typeChecker, typeChecker.globalScope, R"(
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
LoadDefinitionFileResult result = loadDefinitionFile(frontend.typeChecker, frontend.globals, frontend.globals.globalScope, R"(
|
||||
type NotAClass = {}
|
||||
|
||||
declare class Foo extends NotAClass
|
||||
end
|
||||
)",
|
||||
"@test");
|
||||
freeze(typeChecker.globalTypes);
|
||||
"@test", /* captureComments */ false);
|
||||
freeze(frontend.globals.globalTypes);
|
||||
|
||||
REQUIRE(!result.success);
|
||||
CHECK_EQ(result.parseResult.errors.size(), 0);
|
||||
@ -181,16 +181,16 @@ TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_extend_non_class")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "no_cyclic_defined_classes")
|
||||
{
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
LoadDefinitionFileResult result = loadDefinitionFile(typeChecker, typeChecker.globalScope, R"(
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
LoadDefinitionFileResult result = loadDefinitionFile(frontend.typeChecker, frontend.globals, frontend.globals.globalScope, R"(
|
||||
declare class Foo extends Bar
|
||||
end
|
||||
|
||||
declare class Bar extends Foo
|
||||
end
|
||||
)",
|
||||
"@test");
|
||||
freeze(typeChecker.globalTypes);
|
||||
"@test", /* captureComments */ false);
|
||||
freeze(frontend.globals.globalTypes);
|
||||
|
||||
REQUIRE(!result.success);
|
||||
}
|
||||
@ -281,16 +281,16 @@ TEST_CASE_FIXTURE(Fixture, "definitions_documentation_symbols")
|
||||
}
|
||||
)");
|
||||
|
||||
std::optional<Binding> xBinding = typeChecker.globalScope->linearSearchForBinding("x");
|
||||
std::optional<Binding> xBinding = frontend.globals.globalScope->linearSearchForBinding("x");
|
||||
REQUIRE(bool(xBinding));
|
||||
// note: loadDefinition uses the @test package name.
|
||||
CHECK_EQ(xBinding->documentationSymbol, "@test/global/x");
|
||||
|
||||
std::optional<TypeFun> fooTy = typeChecker.globalScope->lookupType("Foo");
|
||||
std::optional<TypeFun> fooTy = frontend.globals.globalScope->lookupType("Foo");
|
||||
REQUIRE(bool(fooTy));
|
||||
CHECK_EQ(fooTy->type->documentationSymbol, "@test/globaltype/Foo");
|
||||
|
||||
std::optional<TypeFun> barTy = typeChecker.globalScope->lookupType("Bar");
|
||||
std::optional<TypeFun> barTy = frontend.globals.globalScope->lookupType("Bar");
|
||||
REQUIRE(bool(barTy));
|
||||
CHECK_EQ(barTy->type->documentationSymbol, "@test/globaltype/Bar");
|
||||
|
||||
@ -299,7 +299,7 @@ TEST_CASE_FIXTURE(Fixture, "definitions_documentation_symbols")
|
||||
REQUIRE_EQ(barClass->props.count("prop"), 1);
|
||||
CHECK_EQ(barClass->props["prop"].documentationSymbol, "@test/globaltype/Bar.prop");
|
||||
|
||||
std::optional<Binding> yBinding = typeChecker.globalScope->linearSearchForBinding("y");
|
||||
std::optional<Binding> yBinding = frontend.globals.globalScope->linearSearchForBinding("y");
|
||||
REQUIRE(bool(yBinding));
|
||||
CHECK_EQ(yBinding->documentationSymbol, "@test/global/y");
|
||||
|
||||
@ -319,7 +319,7 @@ TEST_CASE_FIXTURE(Fixture, "definitions_symbols_are_generated_for_recursively_re
|
||||
declare function myFunc(): MyClass
|
||||
)");
|
||||
|
||||
std::optional<TypeFun> myClassTy = typeChecker.globalScope->lookupType("MyClass");
|
||||
std::optional<TypeFun> myClassTy = frontend.globals.globalScope->lookupType("MyClass");
|
||||
REQUIRE(bool(myClassTy));
|
||||
CHECK_EQ(myClassTy->type->documentationSymbol, "@test/globaltype/MyClass");
|
||||
}
|
||||
@ -330,7 +330,7 @@ TEST_CASE_FIXTURE(Fixture, "documentation_symbols_dont_attach_to_persistent_type
|
||||
export type Evil = string
|
||||
)");
|
||||
|
||||
std::optional<TypeFun> ty = typeChecker.globalScope->lookupType("Evil");
|
||||
std::optional<TypeFun> ty = frontend.globals.globalScope->lookupType("Evil");
|
||||
REQUIRE(bool(ty));
|
||||
CHECK_EQ(ty->type->documentationSymbol, std::nullopt);
|
||||
}
|
||||
@ -396,8 +396,8 @@ TEST_CASE_FIXTURE(Fixture, "class_definition_string_props")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
|
||||
{
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
LoadDefinitionFileResult result = loadDefinitionFile(typeChecker, typeChecker.globalScope, R"(
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
LoadDefinitionFileResult result = loadDefinitionFile(frontend.typeChecker, frontend.globals, frontend.globals.globalScope, R"(
|
||||
declare class Channel
|
||||
Messages: { Message }
|
||||
OnMessage: (message: Message) -> ()
|
||||
@ -408,8 +408,8 @@ TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
|
||||
Channel: Channel
|
||||
end
|
||||
)",
|
||||
"@test");
|
||||
freeze(typeChecker.globalTypes);
|
||||
"@test", /* captureComments */ false);
|
||||
freeze(frontend.globals.globalTypes);
|
||||
|
||||
REQUIRE(result.success);
|
||||
}
|
||||
|
@ -33,8 +33,8 @@ TEST_CASE_FIXTURE(Fixture, "check_function_bodies")
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(result.errors[0], (TypeError{Location{Position{0, 44}, Position{0, 48}}, TypeMismatch{
|
||||
typeChecker.numberType,
|
||||
typeChecker.booleanType,
|
||||
builtinTypes->numberType,
|
||||
builtinTypes->booleanType,
|
||||
}}));
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_return_type")
|
||||
std::vector<TypeId> retVec = flatten(takeFiveType->retTypes).first;
|
||||
REQUIRE(!retVec.empty());
|
||||
|
||||
REQUIRE_EQ(*follow(retVec[0]), *typeChecker.numberType);
|
||||
REQUIRE_EQ(*follow(retVec[0]), *builtinTypes->numberType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "infer_from_function_return_type")
|
||||
@ -78,7 +78,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_from_function_return_type")
|
||||
CheckResult result = check("function take_five() return 5 end local five = take_five()");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(*typeChecker.numberType, *follow(requireType("five")));
|
||||
CHECK_EQ(*builtinTypes->numberType, *follow(requireType("five")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "infer_that_function_does_not_return_a_table")
|
||||
@ -92,7 +92,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_that_function_does_not_return_a_table")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(result.errors[0], (TypeError{Location{Position{5, 8}, Position{5, 24}}, NotATable{typeChecker.numberType}}));
|
||||
CHECK_EQ(result.errors[0], (TypeError{Location{Position{5, 8}, Position{5, 24}}, NotATable{builtinTypes->numberType}}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generalize_table_property")
|
||||
@ -171,8 +171,8 @@ TEST_CASE_FIXTURE(Fixture, "list_only_alternative_overloads_that_match_argument_
|
||||
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ(typeChecker.numberType, tm->wantedType);
|
||||
CHECK_EQ(typeChecker.stringType, tm->givenType);
|
||||
CHECK_EQ(builtinTypes->numberType, tm->wantedType);
|
||||
CHECK_EQ(builtinTypes->stringType, tm->givenType);
|
||||
|
||||
ExtraInformation* ei = get<ExtraInformation>(result.errors[1]);
|
||||
REQUIRE(ei);
|
||||
@ -208,8 +208,8 @@ TEST_CASE_FIXTURE(Fixture, "dont_give_other_overloads_message_if_only_one_argume
|
||||
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ(typeChecker.numberType, tm->wantedType);
|
||||
CHECK_EQ(typeChecker.stringType, tm->givenType);
|
||||
CHECK_EQ(builtinTypes->numberType, tm->wantedType);
|
||||
CHECK_EQ(builtinTypes->stringType, tm->givenType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "infer_return_type_from_selected_overload")
|
||||
@ -847,13 +847,13 @@ TEST_CASE_FIXTURE(Fixture, "calling_function_with_incorrect_argument_type_yields
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
CHECK_EQ(result.errors[0], (TypeError{Location{Position{3, 12}, Position{3, 18}}, TypeMismatch{
|
||||
typeChecker.numberType,
|
||||
typeChecker.stringType,
|
||||
builtinTypes->numberType,
|
||||
builtinTypes->stringType,
|
||||
}}));
|
||||
|
||||
CHECK_EQ(result.errors[1], (TypeError{Location{Position{3, 20}, Position{3, 23}}, TypeMismatch{
|
||||
typeChecker.stringType,
|
||||
typeChecker.numberType,
|
||||
builtinTypes->stringType,
|
||||
builtinTypes->numberType,
|
||||
}}));
|
||||
}
|
||||
|
||||
@ -1669,6 +1669,10 @@ TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("<a>(a) -> a", toString(requireType("f")));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("<a>({+ p: {+ q: a +} +}) -> a & ~false", toString(requireType("g")));
|
||||
else
|
||||
CHECK_EQ("({+ p: {+ q: nil +} +}) -> nil", toString(requireType("g")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "dont_mutate_the_underlying_head_of_typepack_when_calling_with_self")
|
||||
@ -1851,4 +1855,29 @@ end
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization")
|
||||
{
|
||||
ScopedFastInt sfi{"LuauTarjanChildLimit", 2};
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", true},
|
||||
{"LuauClonePublicInterfaceLess", true},
|
||||
{"LuauSubstitutionReentrant", true},
|
||||
{"LuauSubstitutionFixMissingFields", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(t)
|
||||
t.x.y.z = 441
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
CHECK_MESSAGE(get<CodeTooComplex>(result.errors[0]), "Expected CodeTooComplex but got: " << toString(result.errors[0]));
|
||||
CHECK(Location({1, 17}, {1, 18}) == result.errors[0].location);
|
||||
|
||||
CHECK_MESSAGE(get<UnificationTooComplex>(result.errors[1]), "Expected UnificationTooComplex but got: " << toString(result.errors[1]));
|
||||
CHECK(Location({0, 0}, {4, 4}) == result.errors[1].location);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -844,8 +844,8 @@ TEST_CASE_FIXTURE(Fixture, "generic_function")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("<a>(a) -> a", toString(requireType("id")));
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("a"));
|
||||
CHECK_EQ(*typeChecker.nilType, *requireType("b"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("a"));
|
||||
CHECK_EQ(*builtinTypes->nilType, *requireType("b"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_table_method")
|
||||
|
@ -22,8 +22,8 @@ TEST_CASE_FIXTURE(Fixture, "select_correct_union_fn")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(requireType("b"), typeChecker.stringType);
|
||||
CHECK_EQ(requireType("c"), typeChecker.numberType);
|
||||
CHECK_EQ(requireType("b"), builtinTypes->stringType);
|
||||
CHECK_EQ(requireType("c"), builtinTypes->numberType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_combines")
|
||||
@ -123,11 +123,11 @@ TEST_CASE_FIXTURE(Fixture, "should_still_pick_an_overload_whose_arguments_are_un
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(*requireType("a1"), *typeChecker.numberType);
|
||||
CHECK_EQ(*requireType("a2"), *typeChecker.numberType);
|
||||
CHECK_EQ(*requireType("a1"), *builtinTypes->numberType);
|
||||
CHECK_EQ(*requireType("a2"), *builtinTypes->numberType);
|
||||
|
||||
CHECK_EQ(*requireType("b1"), *typeChecker.stringType);
|
||||
CHECK_EQ(*requireType("b2"), *typeChecker.stringType);
|
||||
CHECK_EQ(*requireType("b1"), *builtinTypes->stringType);
|
||||
CHECK_EQ(*requireType("b2"), *builtinTypes->stringType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "propagates_name")
|
||||
@ -249,7 +249,7 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_one_property_of_t
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(*typeChecker.anyType, *requireType("r"));
|
||||
CHECK_EQ(*builtinTypes->anyType, *requireType("r"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_all_parts_missing_the_property")
|
||||
|
@ -28,7 +28,7 @@ TEST_CASE_FIXTURE(Fixture, "for_loop")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("q"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("q"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop")
|
||||
@ -44,8 +44,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("n"));
|
||||
CHECK_EQ(*typeChecker.stringType, *requireType("s"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("n"));
|
||||
CHECK_EQ(*builtinTypes->stringType, *requireType("s"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_next")
|
||||
@ -61,8 +61,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_next")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("n"));
|
||||
CHECK_EQ(*typeChecker.stringType, *requireType("s"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("n"));
|
||||
CHECK_EQ(*builtinTypes->stringType, *requireType("s"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "for_in_with_an_iterator_of_type_any")
|
||||
@ -218,8 +218,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_error_on_factory_not_returning_t
|
||||
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[1]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ(typeChecker.numberType, tm->wantedType);
|
||||
CHECK_EQ(typeChecker.stringType, tm->givenType);
|
||||
CHECK_EQ(builtinTypes->numberType, tm->wantedType);
|
||||
CHECK_EQ(builtinTypes->stringType, tm->givenType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_error_on_iterator_requiring_args_but_none_given")
|
||||
@ -281,8 +281,8 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_with_custom_iterator")
|
||||
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ(typeChecker.numberType, tm->wantedType);
|
||||
CHECK_EQ(typeChecker.stringType, tm->givenType);
|
||||
CHECK_EQ(builtinTypes->numberType, tm->wantedType);
|
||||
CHECK_EQ(builtinTypes->stringType, tm->givenType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "while_loop")
|
||||
@ -296,7 +296,7 @@ TEST_CASE_FIXTURE(Fixture, "while_loop")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("i"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("i"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "repeat_loop")
|
||||
@ -310,7 +310,7 @@ TEST_CASE_FIXTURE(Fixture, "repeat_loop")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(*typeChecker.stringType, *requireType("i"));
|
||||
CHECK_EQ(*builtinTypes->stringType, *requireType("i"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "repeat_loop_condition_binds_to_its_block")
|
||||
@ -547,7 +547,7 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_basic")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("key"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("key"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "loop_iter_trailing_nil")
|
||||
@ -561,7 +561,7 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_trailing_nil")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(0, result);
|
||||
CHECK_EQ(*typeChecker.nilType, *requireType("extra"));
|
||||
CHECK_EQ(*builtinTypes->nilType, *requireType("extra"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "loop_iter_no_indexer_strict")
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauTypeMismatchInvarianceInError)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
@ -482,4 +483,42 @@ return unpack(l0[_])
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "check_imported_module_names")
|
||||
{
|
||||
fileResolver.source["game/A"] = R"(
|
||||
return function(...) end
|
||||
)";
|
||||
|
||||
fileResolver.source["game/B"] = R"(
|
||||
local l0 = require(game.A)
|
||||
return l0
|
||||
)";
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local l0 = require(game.B)
|
||||
if true then
|
||||
local l1 = require(game.A)
|
||||
end
|
||||
return l0
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
ModulePtr mod = getMainModule();
|
||||
REQUIRE(mod);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
REQUIRE(mod->scopes.size() >= 4);
|
||||
CHECK(mod->scopes[0].second->importedModules["l0"] == "game/B");
|
||||
CHECK(mod->scopes[3].second->importedModules["l1"] == "game/A");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
REQUIRE(mod->scopes.size() >= 3);
|
||||
CHECK(mod->scopes[0].second->importedModules["l0"] == "game/B");
|
||||
CHECK(mod->scopes[2].second->importedModules["l1"] == "game/A");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -290,4 +290,23 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "set_prop_of_intersection_containing_metatabl
|
||||
)");
|
||||
}
|
||||
|
||||
// DCR once had a bug in the following code where it would erroneously bind the 'self' table to itself.
|
||||
TEST_CASE_FIXTURE(Fixture, "dont_bind_free_tables_to_themselves")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local T = {}
|
||||
local b: any
|
||||
|
||||
function T:m()
|
||||
local a = b[i]
|
||||
if a then
|
||||
self:n()
|
||||
if self:p(a) then
|
||||
self:n()
|
||||
end
|
||||
end
|
||||
end
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -48,7 +48,7 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union")
|
||||
local x:string = s
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(*requireType("s"), *typeChecker.stringType);
|
||||
CHECK_EQ(*requireType("s"), *builtinTypes->stringType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "and_does_not_always_add_boolean")
|
||||
@ -72,7 +72,7 @@ TEST_CASE_FIXTURE(Fixture, "and_adds_boolean_no_superfluous_union")
|
||||
local x:boolean = s
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(*requireType("x"), *typeChecker.booleanType);
|
||||
CHECK_EQ(*requireType("x"), *builtinTypes->booleanType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "and_or_ternary")
|
||||
@ -99,9 +99,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "primitive_arith_no_metatable")
|
||||
|
||||
std::optional<TypeId> retType = first(functionType->retTypes);
|
||||
REQUIRE(retType.has_value());
|
||||
CHECK_EQ(typeChecker.numberType, follow(*retType));
|
||||
CHECK_EQ(requireType("n"), typeChecker.numberType);
|
||||
CHECK_EQ(requireType("s"), typeChecker.stringType);
|
||||
CHECK_EQ(builtinTypes->numberType, follow(*retType));
|
||||
CHECK_EQ(requireType("n"), builtinTypes->numberType);
|
||||
CHECK_EQ(requireType("s"), builtinTypes->stringType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "primitive_arith_no_metatable_with_follows")
|
||||
@ -112,7 +112,7 @@ TEST_CASE_FIXTURE(Fixture, "primitive_arith_no_metatable_with_follows")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(requireType("SOLAR_MASS"), typeChecker.numberType);
|
||||
CHECK_EQ(requireType("SOLAR_MASS"), builtinTypes->numberType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "primitive_arith_possible_metatable")
|
||||
@ -248,7 +248,11 @@ TEST_CASE_FIXTURE(Fixture, "cannot_indirectly_compare_types_that_do_not_have_a_m
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
GenericError* gen = get<GenericError>(result.errors[0]);
|
||||
REQUIRE(gen != nullptr);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(gen->message == "Types 'a' and 'b' cannot be compared with < because neither type has a metatable");
|
||||
else
|
||||
REQUIRE_EQ(gen->message, "Type a cannot be compared with < because it has no metatable");
|
||||
}
|
||||
|
||||
@ -270,6 +274,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cannot_indirectly_compare_types_that_do_not_
|
||||
|
||||
GenericError* gen = get<GenericError>(result.errors[0]);
|
||||
REQUIRE(gen != nullptr);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(gen->message == "Types 'M' and 'M' cannot be compared with < because neither type's metatable has a '__lt' metamethod");
|
||||
else
|
||||
REQUIRE_EQ(gen->message, "Table M does not offer metamethod __lt");
|
||||
}
|
||||
|
||||
@ -353,7 +361,7 @@ TEST_CASE_FIXTURE(Fixture, "compound_assign_mismatch_op")
|
||||
s += true
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(result.errors[0], (TypeError{Location{{2, 13}, {2, 17}}, TypeMismatch{typeChecker.numberType, typeChecker.booleanType}}));
|
||||
CHECK_EQ(result.errors[0], (TypeError{Location{{2, 13}, {2, 17}}, TypeMismatch{builtinTypes->numberType, builtinTypes->booleanType}}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "compound_assign_mismatch_result")
|
||||
@ -364,8 +372,8 @@ TEST_CASE_FIXTURE(Fixture, "compound_assign_mismatch_result")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK_EQ(result.errors[0], (TypeError{Location{{2, 8}, {2, 9}}, TypeMismatch{typeChecker.numberType, typeChecker.stringType}}));
|
||||
CHECK_EQ(result.errors[1], (TypeError{Location{{2, 8}, {2, 15}}, TypeMismatch{typeChecker.stringType, typeChecker.numberType}}));
|
||||
CHECK_EQ(result.errors[0], (TypeError{Location{{2, 8}, {2, 9}}, TypeMismatch{builtinTypes->numberType, builtinTypes->stringType}}));
|
||||
CHECK_EQ(result.errors[1], (TypeError{Location{{2, 8}, {2, 15}}, TypeMismatch{builtinTypes->stringType, builtinTypes->numberType}}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "compound_assign_metatable")
|
||||
@ -521,7 +529,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus_error")
|
||||
CHECK_EQ("string", toString(requireType("a")));
|
||||
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE_EQ(*tm->wantedType, *typeChecker.booleanType);
|
||||
REQUIRE_EQ(*tm->wantedType, *builtinTypes->booleanType);
|
||||
// given type is the typeof(foo) which is complex to compare against
|
||||
}
|
||||
|
||||
@ -547,8 +555,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_len_error")
|
||||
CHECK_EQ("number", toString(requireType("a")));
|
||||
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE_EQ(*tm->wantedType, *typeChecker.numberType);
|
||||
REQUIRE_EQ(*tm->givenType, *typeChecker.stringType);
|
||||
REQUIRE_EQ(*tm->wantedType, *builtinTypes->numberType);
|
||||
REQUIRE_EQ(*tm->givenType, *builtinTypes->stringType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "unary_not_is_boolean")
|
||||
@ -596,8 +604,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "disallow_string_and_types_without_metatables
|
||||
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ(*tm->wantedType, *typeChecker.numberType);
|
||||
CHECK_EQ(*tm->givenType, *typeChecker.stringType);
|
||||
CHECK_EQ(*tm->wantedType, *builtinTypes->numberType);
|
||||
CHECK_EQ(*tm->givenType, *builtinTypes->stringType);
|
||||
|
||||
GenericError* gen1 = get<GenericError>(result.errors[1]);
|
||||
REQUIRE(gen1);
|
||||
@ -608,7 +616,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "disallow_string_and_types_without_metatables
|
||||
|
||||
TypeMismatch* tm2 = get<TypeMismatch>(result.errors[2]);
|
||||
REQUIRE(tm2);
|
||||
CHECK_EQ(*tm2->wantedType, *typeChecker.numberType);
|
||||
CHECK_EQ(*tm2->wantedType, *builtinTypes->numberType);
|
||||
CHECK_EQ(*tm2->givenType, *requireType("foo"));
|
||||
}
|
||||
|
||||
@ -802,7 +810,7 @@ local b: number = 1 or a
|
||||
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ(typeChecker.numberType, tm->wantedType);
|
||||
CHECK_EQ(builtinTypes->numberType, tm->wantedType);
|
||||
CHECK_EQ("number?", toString(tm->givenType));
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ TEST_CASE_FIXTURE(Fixture, "string_method")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(*requireType("p"), *typeChecker.numberType);
|
||||
CHECK_EQ(*requireType("p"), *builtinTypes->numberType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "string_function_indirect")
|
||||
@ -69,7 +69,7 @@ TEST_CASE_FIXTURE(Fixture, "string_function_indirect")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(*requireType("p"), *typeChecker.stringType);
|
||||
CHECK_EQ(*requireType("p"), *builtinTypes->stringType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "CheckMethodsOfNumber")
|
||||
|
@ -30,9 +30,8 @@ std::optional<WithPredicate<TypePackId>> magicFunctionInstanceIsA(
|
||||
if (!lvalue || !tfun)
|
||||
return std::nullopt;
|
||||
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
TypePackId booleanPack = typeChecker.globalTypes.addTypePack({typeChecker.booleanType});
|
||||
freeze(typeChecker.globalTypes);
|
||||
ModulePtr module = typeChecker.currentModule;
|
||||
TypePackId booleanPack = module->internalTypes.addTypePack({typeChecker.booleanType});
|
||||
return WithPredicate<TypePackId>{booleanPack, {IsAPredicate{std::move(*lvalue), expr.location, tfun->type}}};
|
||||
}
|
||||
|
||||
@ -62,47 +61,47 @@ struct RefinementClassFixture : BuiltinsFixture
|
||||
{
|
||||
RefinementClassFixture()
|
||||
{
|
||||
TypeArena& arena = typeChecker.globalTypes;
|
||||
NotNull<Scope> scope{typeChecker.globalScope.get()};
|
||||
TypeArena& arena = frontend.globals.globalTypes;
|
||||
NotNull<Scope> scope{frontend.globals.globalScope.get()};
|
||||
|
||||
std::optional<TypeId> rootSuper = FFlag::LuauNegatedClassTypes ? std::make_optional(typeChecker.builtinTypes->classType) : std::nullopt;
|
||||
std::optional<TypeId> rootSuper = FFlag::LuauNegatedClassTypes ? std::make_optional(builtinTypes->classType) : std::nullopt;
|
||||
|
||||
unfreeze(arena);
|
||||
TypeId vec3 = arena.addType(ClassType{"Vector3", {}, rootSuper, std::nullopt, {}, nullptr, "Test"});
|
||||
getMutable<ClassType>(vec3)->props = {
|
||||
{"X", Property{typeChecker.numberType}},
|
||||
{"Y", Property{typeChecker.numberType}},
|
||||
{"Z", Property{typeChecker.numberType}},
|
||||
{"X", Property{builtinTypes->numberType}},
|
||||
{"Y", Property{builtinTypes->numberType}},
|
||||
{"Z", Property{builtinTypes->numberType}},
|
||||
};
|
||||
|
||||
TypeId inst = arena.addType(ClassType{"Instance", {}, rootSuper, std::nullopt, {}, nullptr, "Test"});
|
||||
|
||||
TypePackId isAParams = arena.addTypePack({inst, typeChecker.stringType});
|
||||
TypePackId isARets = arena.addTypePack({typeChecker.booleanType});
|
||||
TypePackId isAParams = arena.addTypePack({inst, builtinTypes->stringType});
|
||||
TypePackId isARets = arena.addTypePack({builtinTypes->booleanType});
|
||||
TypeId isA = arena.addType(FunctionType{isAParams, isARets});
|
||||
getMutable<FunctionType>(isA)->magicFunction = magicFunctionInstanceIsA;
|
||||
getMutable<FunctionType>(isA)->dcrMagicRefinement = dcrMagicRefinementInstanceIsA;
|
||||
|
||||
getMutable<ClassType>(inst)->props = {
|
||||
{"Name", Property{typeChecker.stringType}},
|
||||
{"Name", Property{builtinTypes->stringType}},
|
||||
{"IsA", Property{isA}},
|
||||
};
|
||||
|
||||
TypeId folder = typeChecker.globalTypes.addType(ClassType{"Folder", {}, inst, std::nullopt, {}, nullptr, "Test"});
|
||||
TypeId part = typeChecker.globalTypes.addType(ClassType{"Part", {}, inst, std::nullopt, {}, nullptr, "Test"});
|
||||
TypeId folder = frontend.globals.globalTypes.addType(ClassType{"Folder", {}, inst, std::nullopt, {}, nullptr, "Test"});
|
||||
TypeId part = frontend.globals.globalTypes.addType(ClassType{"Part", {}, inst, std::nullopt, {}, nullptr, "Test"});
|
||||
getMutable<ClassType>(part)->props = {
|
||||
{"Position", Property{vec3}},
|
||||
};
|
||||
|
||||
typeChecker.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vec3};
|
||||
typeChecker.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, inst};
|
||||
typeChecker.globalScope->exportedTypeBindings["Folder"] = TypeFun{{}, folder};
|
||||
typeChecker.globalScope->exportedTypeBindings["Part"] = TypeFun{{}, part};
|
||||
frontend.globals.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vec3};
|
||||
frontend.globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, inst};
|
||||
frontend.globals.globalScope->exportedTypeBindings["Folder"] = TypeFun{{}, folder};
|
||||
frontend.globals.globalScope->exportedTypeBindings["Part"] = TypeFun{{}, part};
|
||||
|
||||
for (const auto& [name, ty] : typeChecker.globalScope->exportedTypeBindings)
|
||||
for (const auto& [name, ty] : frontend.globals.globalScope->exportedTypeBindings)
|
||||
persist(ty.type);
|
||||
|
||||
freeze(typeChecker.globalTypes);
|
||||
freeze(frontend.globals.globalTypes);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
@ -198,7 +198,7 @@ TEST_CASE_FIXTURE(Fixture, "call_method")
|
||||
CheckResult result = check("local T = {} T.x = 0 function T:method() return self.x end local a = T:method()");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("a"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("a"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "call_method_with_explicit_self_argument")
|
||||
@ -576,8 +576,8 @@ TEST_CASE_FIXTURE(Fixture, "infer_array")
|
||||
|
||||
REQUIRE(bool(ttv->indexer));
|
||||
|
||||
CHECK_EQ(*ttv->indexer->indexType, *typeChecker.numberType);
|
||||
CHECK_EQ(*ttv->indexer->indexResultType, *typeChecker.stringType);
|
||||
CHECK_EQ(*ttv->indexer->indexType, *builtinTypes->numberType);
|
||||
CHECK_EQ(*ttv->indexer->indexResultType, *builtinTypes->stringType);
|
||||
}
|
||||
|
||||
/* This is a bit weird.
|
||||
@ -685,8 +685,8 @@ TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_array_like_table")
|
||||
REQUIRE(bool(ttv->indexer));
|
||||
const TableIndexer& indexer = *ttv->indexer;
|
||||
|
||||
CHECK_EQ(*typeChecker.numberType, *indexer.indexType);
|
||||
CHECK_EQ(*typeChecker.stringType, *indexer.indexResultType);
|
||||
CHECK_EQ(*builtinTypes->numberType, *indexer.indexType);
|
||||
CHECK_EQ(*builtinTypes->stringType, *indexer.indexResultType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_value_property_in_literal")
|
||||
@ -740,8 +740,8 @@ TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_its_variable_type_and_unifiable")
|
||||
REQUIRE(tTy != nullptr);
|
||||
|
||||
REQUIRE(tTy->indexer);
|
||||
CHECK_EQ(*typeChecker.numberType, *tTy->indexer->indexType);
|
||||
CHECK_EQ(*typeChecker.stringType, *tTy->indexer->indexResultType);
|
||||
CHECK_EQ(*builtinTypes->numberType, *tTy->indexer->indexType);
|
||||
CHECK_EQ(*builtinTypes->stringType, *tTy->indexer->indexResultType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "indexer_mismatch")
|
||||
@ -844,7 +844,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_type_when_indexing_from_a_table_indexer")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(*typeChecker.stringType, *requireType("s"));
|
||||
CHECK_EQ(*builtinTypes->stringType, *requireType("s"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "indexing_from_a_table_should_prefer_properties_when_possible")
|
||||
@ -865,13 +865,13 @@ TEST_CASE_FIXTURE(Fixture, "indexing_from_a_table_should_prefer_properties_when_
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(*typeChecker.stringType, *requireType("a1"));
|
||||
CHECK_EQ(*typeChecker.stringType, *requireType("a2"));
|
||||
CHECK_EQ(*builtinTypes->stringType, *requireType("a1"));
|
||||
CHECK_EQ(*builtinTypes->stringType, *requireType("a2"));
|
||||
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("b1"));
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("b2"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("b1"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("b2"));
|
||||
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("c"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("c"));
|
||||
|
||||
CHECK_MESSAGE(nullptr != get<TypeMismatch>(result.errors[0]), "Expected a TypeMismatch but got " << result.errors[0]);
|
||||
}
|
||||
@ -932,7 +932,7 @@ TEST_CASE_FIXTURE(Fixture, "assigning_to_an_unsealed_table_with_string_literal_s
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK("string" == toString(*typeChecker.stringType));
|
||||
CHECK("string" == toString(*builtinTypes->stringType));
|
||||
|
||||
TableType* tableType = getMutable<TableType>(requireType("t"));
|
||||
REQUIRE(tableType != nullptr);
|
||||
@ -941,7 +941,7 @@ TEST_CASE_FIXTURE(Fixture, "assigning_to_an_unsealed_table_with_string_literal_s
|
||||
|
||||
TypeId propertyA = tableType->props["a"].type;
|
||||
REQUIRE(propertyA != nullptr);
|
||||
CHECK_EQ(*typeChecker.stringType, *propertyA);
|
||||
CHECK_EQ(*builtinTypes->stringType, *propertyA);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "oop_indexer_works")
|
||||
@ -964,7 +964,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oop_indexer_works")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(*typeChecker.stringType, *requireType("words"));
|
||||
CHECK_EQ(*builtinTypes->stringType, *requireType("words"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "indexer_table")
|
||||
@ -977,7 +977,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "indexer_table")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(*typeChecker.stringType, *requireType("b"));
|
||||
CHECK_EQ(*builtinTypes->stringType, *requireType("b"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "indexer_fn")
|
||||
@ -988,7 +988,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "indexer_fn")
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("b"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("b"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "meta_add")
|
||||
@ -1102,10 +1102,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oop_polymorphic")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(*typeChecker.booleanType, *requireType("alive"));
|
||||
CHECK_EQ(*typeChecker.stringType, *requireType("movement"));
|
||||
CHECK_EQ(*typeChecker.stringType, *requireType("name"));
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("speed"));
|
||||
CHECK_EQ(*builtinTypes->booleanType, *requireType("alive"));
|
||||
CHECK_EQ(*builtinTypes->stringType, *requireType("movement"));
|
||||
CHECK_EQ(*builtinTypes->stringType, *requireType("name"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("speed"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "user_defined_table_types_are_named")
|
||||
@ -2477,7 +2477,7 @@ TEST_CASE_FIXTURE(Fixture, "table_length")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK(nullptr != get<TableType>(requireType("t")));
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("s"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("s"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "nil_assign_doesnt_hit_indexer")
|
||||
@ -2498,8 +2498,8 @@ TEST_CASE_FIXTURE(Fixture, "wrong_assign_does_hit_indexer")
|
||||
CHECK((Location{Position{3, 15}, Position{3, 18}}) == result.errors[0].location);
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK(tm->wantedType == typeChecker.numberType);
|
||||
CHECK(tm->givenType == typeChecker.stringType);
|
||||
CHECK(tm->wantedType == builtinTypes->numberType);
|
||||
CHECK(tm->givenType == builtinTypes->stringType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "nil_assign_doesnt_hit_no_indexer")
|
||||
@ -2510,8 +2510,8 @@ TEST_CASE_FIXTURE(Fixture, "nil_assign_doesnt_hit_no_indexer")
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(result.errors[0], (TypeError{Location{Position{2, 17}, Position{2, 20}}, TypeMismatch{
|
||||
typeChecker.numberType,
|
||||
typeChecker.nilType,
|
||||
builtinTypes->numberType,
|
||||
builtinTypes->nilType,
|
||||
}}));
|
||||
}
|
||||
|
||||
@ -2709,7 +2709,7 @@ TEST_CASE_FIXTURE(Fixture, "setmetatable_cant_be_used_to_mutate_global_types")
|
||||
Fixture fix;
|
||||
|
||||
// inherit env from parent fixture checker
|
||||
fix.typeChecker.globalScope = typeChecker.globalScope;
|
||||
fix.frontend.globals.globalScope = frontend.globals.globalScope;
|
||||
|
||||
fix.check(R"(
|
||||
--!nonstrict
|
||||
@ -2723,7 +2723,7 @@ end
|
||||
// validate sharedEnv post-typecheck; valuable for debugging some typeck crashes but slows fuzzing down
|
||||
// note: it's important for typeck to be destroyed at this point!
|
||||
{
|
||||
for (auto& p : typeChecker.globalScope->bindings)
|
||||
for (auto& p : frontend.globals.globalScope->bindings)
|
||||
{
|
||||
toString(p.second.typeId); // toString walks the entire type, making sure ASAN catches access to destroyed type arenas
|
||||
}
|
||||
@ -3318,8 +3318,6 @@ caused by:
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compatible")
|
||||
{
|
||||
ScopedFastFlag luauScalarShapeUnifyToMtOwner{"LuauScalarShapeUnifyToMtOwner2", true}; // Changes argument from table type to primitive
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(s): string
|
||||
local foo = s:lower()
|
||||
@ -3350,8 +3348,6 @@ caused by:
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "a_free_shape_can_turn_into_a_scalar_directly")
|
||||
{
|
||||
ScopedFastFlag luauScalarShapeUnifyToMtOwner{"LuauScalarShapeUnifyToMtOwner2", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function stringByteList(str)
|
||||
local out = {}
|
||||
@ -3457,8 +3453,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tables_should_be_fully_populated")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzz_table_indexer_unification_can_bound_owner_to_string")
|
||||
{
|
||||
ScopedFastFlag luauScalarShapeUnifyToMtOwner{"LuauScalarShapeUnifyToMtOwner2", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
sin,_ = nil
|
||||
_ = _[_.sin][_._][_][_]._
|
||||
@ -3470,8 +3464,6 @@ _[_] = _
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_table_extra_prop_unification_can_bound_owner_to_string")
|
||||
{
|
||||
ScopedFastFlag luauScalarShapeUnifyToMtOwner{"LuauScalarShapeUnifyToMtOwner2", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
l0,_ = nil
|
||||
_ = _,_[_.n5]._[_][_][_]._
|
||||
@ -3483,8 +3475,6 @@ _._.foreach[_],_ = _[_],_._
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_typelevel_promote_on_changed_table_type")
|
||||
{
|
||||
ScopedFastFlag luauScalarShapeUnifyToMtOwner{"LuauScalarShapeUnifyToMtOwner2", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
_._,_ = nil
|
||||
_ = _.foreach[_]._,_[_.n5]._[_.foreach][_][_]._
|
||||
@ -3498,8 +3488,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_table_unify_instantiated_table")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauInstantiateInSubtyping", true},
|
||||
{"LuauScalarShapeUnifyToMtOwner2", true},
|
||||
{"LuauTableUnifyInstantiationFix", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -3517,8 +3505,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_table_unify_instantiated_table_with_prop_reallo
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauInstantiateInSubtyping", true},
|
||||
{"LuauScalarShapeUnifyToMtOwner2", true},
|
||||
{"LuauTableUnifyInstantiationFix", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -3537,12 +3523,6 @@ end)
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_table_unify_prop_realloc")
|
||||
{
|
||||
// For this test, we don't need LuauInstantiateInSubtyping
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauScalarShapeUnifyToMtOwner2", true},
|
||||
{"LuauTableUnifyInstantiationFix", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
n3,_ = nil
|
||||
_ = _[""]._,_[l0][_._][{[_]=_,_=_,}][_G].number
|
||||
|
@ -45,7 +45,8 @@ TEST_CASE_FIXTURE(Fixture, "tc_error")
|
||||
CheckResult result = check("local a = 7 local b = 'hi' a = b");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(result.errors[0], (TypeError{Location{Position{0, 35}, Position{0, 36}}, TypeMismatch{typeChecker.numberType, typeChecker.stringType}}));
|
||||
CHECK_EQ(
|
||||
result.errors[0], (TypeError{Location{Position{0, 35}, Position{0, 36}}, TypeMismatch{builtinTypes->numberType, builtinTypes->stringType}}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "tc_error_2")
|
||||
@ -55,7 +56,7 @@ TEST_CASE_FIXTURE(Fixture, "tc_error_2")
|
||||
|
||||
CHECK_EQ(result.errors[0], (TypeError{Location{Position{0, 18}, Position{0, 22}}, TypeMismatch{
|
||||
requireType("a"),
|
||||
typeChecker.stringType,
|
||||
builtinTypes->stringType,
|
||||
}}));
|
||||
}
|
||||
|
||||
@ -123,8 +124,8 @@ TEST_CASE_FIXTURE(Fixture, "if_statement")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(*typeChecker.stringType, *requireType("a"));
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("b"));
|
||||
CHECK_EQ(*builtinTypes->stringType, *requireType("a"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("b"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "statements_are_topologically_sorted")
|
||||
@ -256,6 +257,12 @@ TEST_CASE_FIXTURE(Fixture, "should_be_able_to_infer_this_without_stack_overflowi
|
||||
end
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(get<NormalizationTooComplex>(result.errors[0]));
|
||||
}
|
||||
else
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
@ -580,7 +587,7 @@ TEST_CASE_FIXTURE(Fixture, "stringify_nested_unions_with_optionals")
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ(typeChecker.numberType, tm->wantedType);
|
||||
CHECK_EQ(builtinTypes->numberType, tm->wantedType);
|
||||
CHECK_EQ("(boolean | number | string)?", toString(tm->givenType));
|
||||
}
|
||||
|
||||
@ -1150,8 +1157,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "it_is_ok_to_have_inconsistent_number_of_retu
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzz_free_table_type_change_during_index_check")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauScalarShapeUnifyToMtOwner2", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local _ = nil
|
||||
while _["" >= _] do
|
||||
@ -1175,4 +1180,24 @@ local b = typeof(foo) ~= 'nil'
|
||||
CHECK(toString(result.errors[1]) == "Unknown global 'foo'");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "dcr_delays_expansion_of_function_containing_blocked_parameter_type")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", true},
|
||||
{"LuauTinyUnifyNormalsFix", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local b: any
|
||||
|
||||
function f(x)
|
||||
local a = b[1] or 'Cn'
|
||||
local c = x[1]
|
||||
|
||||
if a:sub(1, #c) == c then
|
||||
end
|
||||
end
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -38,7 +38,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "primitives_unify")
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "compatible_functions_are_unified")
|
||||
{
|
||||
Type functionOne{
|
||||
TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({typeChecker.numberType}))}};
|
||||
TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({builtinTypes->numberType}))}};
|
||||
|
||||
Type functionTwo{TypeVariant{
|
||||
FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({arena.freshType(globalScope->level)}))}};
|
||||
@ -55,13 +55,13 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_functions_are_preserved")
|
||||
{
|
||||
TypePackVar argPackOne{TypePack{{arena.freshType(globalScope->level)}, std::nullopt}};
|
||||
Type functionOne{
|
||||
TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({typeChecker.numberType}))}};
|
||||
TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({builtinTypes->numberType}))}};
|
||||
|
||||
Type functionOneSaved = functionOne;
|
||||
|
||||
TypePackVar argPackTwo{TypePack{{arena.freshType(globalScope->level)}, std::nullopt}};
|
||||
Type functionTwo{
|
||||
TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({typeChecker.stringType}))}};
|
||||
TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({builtinTypes->stringType}))}};
|
||||
|
||||
Type functionTwoSaved = functionTwo;
|
||||
|
||||
@ -96,12 +96,12 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "tables_can_be_unified")
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_tables_are_preserved")
|
||||
{
|
||||
Type tableOne{TypeVariant{
|
||||
TableType{{{"foo", {arena.freshType(globalScope->level)}}, {"bar", {typeChecker.numberType}}}, std::nullopt, globalScope->level,
|
||||
TableType{{{"foo", {arena.freshType(globalScope->level)}}, {"bar", {builtinTypes->numberType}}}, std::nullopt, globalScope->level,
|
||||
TableState::Unsealed},
|
||||
}};
|
||||
|
||||
Type tableTwo{TypeVariant{
|
||||
TableType{{{"foo", {arena.freshType(globalScope->level)}}, {"bar", {typeChecker.stringType}}}, std::nullopt, globalScope->level,
|
||||
TableType{{{"foo", {arena.freshType(globalScope->level)}}, {"bar", {builtinTypes->stringType}}}, std::nullopt, globalScope->level,
|
||||
TableState::Unsealed},
|
||||
}};
|
||||
|
||||
@ -214,8 +214,8 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "typepack_unification_should_trim_free_tails"
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "variadic_type_pack_unification")
|
||||
{
|
||||
TypePackVar testPack{TypePack{{typeChecker.numberType, typeChecker.stringType}, std::nullopt}};
|
||||
TypePackVar variadicPack{VariadicTypePack{typeChecker.numberType}};
|
||||
TypePackVar testPack{TypePack{{builtinTypes->numberType, builtinTypes->stringType}, std::nullopt}};
|
||||
TypePackVar variadicPack{VariadicTypePack{builtinTypes->numberType}};
|
||||
|
||||
state.tryUnify(&testPack, &variadicPack);
|
||||
CHECK(!state.errors.empty());
|
||||
@ -223,9 +223,9 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "variadic_type_pack_unification")
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "variadic_tails_respect_progress")
|
||||
{
|
||||
TypePackVar variadicPack{VariadicTypePack{typeChecker.booleanType}};
|
||||
TypePackVar a{TypePack{{typeChecker.numberType, typeChecker.stringType, typeChecker.booleanType, typeChecker.booleanType}}};
|
||||
TypePackVar b{TypePack{{typeChecker.numberType, typeChecker.stringType}, &variadicPack}};
|
||||
TypePackVar variadicPack{VariadicTypePack{builtinTypes->booleanType}};
|
||||
TypePackVar a{TypePack{{builtinTypes->numberType, builtinTypes->stringType, builtinTypes->booleanType, builtinTypes->booleanType}}};
|
||||
TypePackVar b{TypePack{{builtinTypes->numberType, builtinTypes->stringType}, &variadicPack}};
|
||||
|
||||
state.tryUnify(&b, &a);
|
||||
CHECK(state.errors.empty());
|
||||
@ -266,8 +266,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_41095_concat_log_in_sealed_table_unifica
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "free_tail_is_grown_properly")
|
||||
{
|
||||
TypePackId threeNumbers = arena.addTypePack(TypePack{{typeChecker.numberType, typeChecker.numberType, typeChecker.numberType}, std::nullopt});
|
||||
TypePackId numberAndFreeTail = arena.addTypePack(TypePack{{typeChecker.numberType}, arena.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})});
|
||||
TypePackId threeNumbers =
|
||||
arena.addTypePack(TypePack{{builtinTypes->numberType, builtinTypes->numberType, builtinTypes->numberType}, std::nullopt});
|
||||
TypePackId numberAndFreeTail = arena.addTypePack(TypePack{{builtinTypes->numberType}, arena.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})});
|
||||
|
||||
ErrorVec unifyErrors = state.canUnify(numberAndFreeTail, threeNumbers);
|
||||
CHECK(unifyErrors.size() == 0);
|
||||
@ -279,7 +280,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "recursive_metatable_getmatchtag")
|
||||
Type table{TableType{}};
|
||||
Type metatable{MetatableType{&redirect, &table}};
|
||||
redirect = BoundType{&metatable}; // Now we have a metatable that is recursive on the table type
|
||||
Type variant{UnionType{{&metatable, typeChecker.numberType}}};
|
||||
Type variant{UnionType{{&metatable, builtinTypes->numberType}}};
|
||||
|
||||
state.tryUnify(&metatable, &variant);
|
||||
}
|
||||
@ -293,13 +294,13 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "cli_50320_follow_in_any_unification")
|
||||
|
||||
state.tryUnify(&free, &target);
|
||||
// Shouldn't assert or error.
|
||||
state.tryUnify(&func, typeChecker.anyType);
|
||||
state.tryUnify(&func, builtinTypes->anyType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "txnlog_preserves_type_owner")
|
||||
{
|
||||
TypeId a = arena.addType(Type{FreeType{TypeLevel{}}});
|
||||
TypeId b = typeChecker.numberType;
|
||||
TypeId b = builtinTypes->numberType;
|
||||
|
||||
state.tryUnify(a, b);
|
||||
state.log.commit();
|
||||
@ -310,7 +311,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "txnlog_preserves_type_owner")
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "txnlog_preserves_pack_owner")
|
||||
{
|
||||
TypePackId a = arena.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}});
|
||||
TypePackId b = typeChecker.anyTypePack;
|
||||
TypePackId b = builtinTypes->anyTypePack;
|
||||
|
||||
state.tryUnify(a, b);
|
||||
state.log.commit();
|
||||
@ -323,13 +324,13 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table
|
||||
ScopedFastFlag sff("DebugLuauDeferredConstraintResolution", true);
|
||||
|
||||
TableType::Props freeProps{
|
||||
{"foo", {typeChecker.numberType}},
|
||||
{"foo", {builtinTypes->numberType}},
|
||||
};
|
||||
|
||||
TypeId free = arena.addType(TableType{freeProps, std::nullopt, TypeLevel{}, TableState::Free});
|
||||
|
||||
TableType::Props indexProps{
|
||||
{"foo", {typeChecker.stringType}},
|
||||
{"foo", {builtinTypes->stringType}},
|
||||
};
|
||||
|
||||
TypeId index = arena.addType(TableType{indexProps, std::nullopt, TypeLevel{}, TableState::Sealed});
|
||||
@ -356,9 +357,9 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "fuzz_tail_unification_issue")
|
||||
{
|
||||
TypePackVar variadicAny{VariadicTypePack{typeChecker.anyType}};
|
||||
TypePackVar packTmp{TypePack{{typeChecker.anyType}, &variadicAny}};
|
||||
TypePackVar packSub{TypePack{{typeChecker.anyType, typeChecker.anyType}, &packTmp}};
|
||||
TypePackVar variadicAny{VariadicTypePack{builtinTypes->anyType}};
|
||||
TypePackVar packTmp{TypePack{{builtinTypes->anyType}, &variadicAny}};
|
||||
TypePackVar packSub{TypePack{{builtinTypes->anyType, builtinTypes->anyType}, &packTmp}};
|
||||
|
||||
Type freeTy{FreeType{TypeLevel{}}};
|
||||
TypePackVar freeTp{FreeTypePack{TypeLevel{}}};
|
||||
|
@ -27,8 +27,8 @@ TEST_CASE_FIXTURE(Fixture, "infer_multi_return")
|
||||
const auto& [returns, tail] = flatten(takeTwoType->retTypes);
|
||||
|
||||
CHECK_EQ(2, returns.size());
|
||||
CHECK_EQ(typeChecker.numberType, follow(returns[0]));
|
||||
CHECK_EQ(typeChecker.numberType, follow(returns[1]));
|
||||
CHECK_EQ(builtinTypes->numberType, follow(returns[0]));
|
||||
CHECK_EQ(builtinTypes->numberType, follow(returns[1]));
|
||||
|
||||
CHECK(!tail);
|
||||
}
|
||||
@ -74,9 +74,9 @@ TEST_CASE_FIXTURE(Fixture, "last_element_of_return_statement_can_itself_be_a_pac
|
||||
const auto& [rets, tail] = flatten(takeOneMoreType->retTypes);
|
||||
|
||||
REQUIRE_EQ(3, rets.size());
|
||||
CHECK_EQ(typeChecker.numberType, follow(rets[0]));
|
||||
CHECK_EQ(typeChecker.numberType, follow(rets[1]));
|
||||
CHECK_EQ(typeChecker.numberType, follow(rets[2]));
|
||||
CHECK_EQ(builtinTypes->numberType, follow(rets[0]));
|
||||
CHECK_EQ(builtinTypes->numberType, follow(rets[1]));
|
||||
CHECK_EQ(builtinTypes->numberType, follow(rets[2]));
|
||||
|
||||
CHECK(!tail);
|
||||
}
|
||||
@ -184,28 +184,28 @@ TEST_CASE_FIXTURE(Fixture, "parenthesized_varargs_returns_any")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "variadic_packs")
|
||||
{
|
||||
TypeArena& arena = typeChecker.globalTypes;
|
||||
TypeArena& arena = frontend.globals.globalTypes;
|
||||
|
||||
unfreeze(arena);
|
||||
|
||||
TypePackId listOfNumbers = arena.addTypePack(TypePackVar{VariadicTypePack{typeChecker.numberType}});
|
||||
TypePackId listOfStrings = arena.addTypePack(TypePackVar{VariadicTypePack{typeChecker.stringType}});
|
||||
TypePackId listOfNumbers = arena.addTypePack(TypePackVar{VariadicTypePack{builtinTypes->numberType}});
|
||||
TypePackId listOfStrings = arena.addTypePack(TypePackVar{VariadicTypePack{builtinTypes->stringType}});
|
||||
|
||||
// clang-format off
|
||||
addGlobalBinding(frontend, "foo",
|
||||
addGlobalBinding(frontend.globals, "foo",
|
||||
arena.addType(
|
||||
FunctionType{
|
||||
listOfNumbers,
|
||||
arena.addTypePack({typeChecker.numberType})
|
||||
arena.addTypePack({builtinTypes->numberType})
|
||||
}
|
||||
),
|
||||
"@test"
|
||||
);
|
||||
addGlobalBinding(frontend, "bar",
|
||||
addGlobalBinding(frontend.globals, "bar",
|
||||
arena.addType(
|
||||
FunctionType{
|
||||
arena.addTypePack({{typeChecker.numberType}, listOfStrings}),
|
||||
arena.addTypePack({typeChecker.numberType})
|
||||
arena.addTypePack({{builtinTypes->numberType}, listOfStrings}),
|
||||
arena.addTypePack({builtinTypes->numberType})
|
||||
}
|
||||
),
|
||||
"@test"
|
||||
@ -223,9 +223,11 @@ TEST_CASE_FIXTURE(Fixture, "variadic_packs")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
CHECK_EQ(result.errors[0], (TypeError{Location(Position{3, 21}, Position{3, 26}), TypeMismatch{typeChecker.numberType, typeChecker.stringType}}));
|
||||
CHECK_EQ(
|
||||
result.errors[0], (TypeError{Location(Position{3, 21}, Position{3, 26}), TypeMismatch{builtinTypes->numberType, builtinTypes->stringType}}));
|
||||
|
||||
CHECK_EQ(result.errors[1], (TypeError{Location(Position{4, 29}, Position{4, 30}), TypeMismatch{typeChecker.stringType, typeChecker.numberType}}));
|
||||
CHECK_EQ(
|
||||
result.errors[1], (TypeError{Location(Position{4, 29}, Position{4, 30}), TypeMismatch{builtinTypes->stringType, builtinTypes->numberType}}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "variadic_pack_syntax")
|
||||
|
@ -131,7 +131,7 @@ TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_property_guaranteed_to_ex
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("r"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("r"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_mixed_types")
|
||||
@ -211,7 +211,7 @@ TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_one_property_of_type_any"
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(*typeChecker.anyType, *requireType("r"));
|
||||
CHECK_EQ(*builtinTypes->anyType, *requireType("r"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_equality_comparisons")
|
||||
@ -245,7 +245,7 @@ local c = bf.a.y
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("c"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("c"));
|
||||
CHECK_EQ("Value of type 'A?' could be nil", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
@ -260,7 +260,7 @@ TEST_CASE_FIXTURE(Fixture, "optional_union_functions")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("c"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("c"));
|
||||
CHECK_EQ("Value of type 'A?' could be nil", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
@ -275,7 +275,7 @@ local c = b:foo(1, 2)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(*typeChecker.numberType, *requireType("c"));
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("c"));
|
||||
CHECK_EQ("Value of type 'A?' could be nil", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,12 @@ struct ReductionFixture : Fixture
|
||||
return *reducedTy;
|
||||
}
|
||||
|
||||
std::optional<TypeId> tryReduce(const std::string& annotation)
|
||||
{
|
||||
check("type _Res = " + annotation);
|
||||
return reduction.reduce(requireTypeAlias("_Res"));
|
||||
}
|
||||
|
||||
TypeId reductionof(const std::string& annotation)
|
||||
{
|
||||
check("type _Res = " + annotation);
|
||||
@ -1488,4 +1494,16 @@ TEST_CASE_FIXTURE(ReductionFixture, "cycles")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReductionFixture, "string_singletons")
|
||||
{
|
||||
TypeId ty = reductionof("(string & Not<\"A\">)?");
|
||||
CHECK("(string & ~\"A\")?" == toStringFull(ty));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReductionFixture, "string_singletons_2")
|
||||
{
|
||||
TypeId ty = reductionof("Not<\"A\"> & Not<\"B\"> & (string?)");
|
||||
CHECK("(string & ~\"A\" & ~\"B\")?" == toStringFull(ty));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -16,13 +16,13 @@ TEST_SUITE_BEGIN("TypeTests");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "primitives_are_equal")
|
||||
{
|
||||
REQUIRE_EQ(typeChecker.booleanType, typeChecker.booleanType);
|
||||
REQUIRE_EQ(builtinTypes->booleanType, builtinTypes->booleanType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "bound_type_is_equal_to_that_which_it_is_bound")
|
||||
{
|
||||
Type bound(BoundType(typeChecker.booleanType));
|
||||
REQUIRE_EQ(bound, *typeChecker.booleanType);
|
||||
Type bound(BoundType(builtinTypes->booleanType));
|
||||
REQUIRE_EQ(bound, *builtinTypes->booleanType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "equivalent_cyclic_tables_are_equal")
|
||||
@ -54,8 +54,8 @@ TEST_CASE_FIXTURE(Fixture, "different_cyclic_tables_are_not_equal")
|
||||
TEST_CASE_FIXTURE(Fixture, "return_type_of_function_is_not_parenthesized_if_just_one_value")
|
||||
{
|
||||
auto emptyArgumentPack = TypePackVar{TypePack{}};
|
||||
auto returnPack = TypePackVar{TypePack{{typeChecker.numberType}}};
|
||||
auto returnsTwo = Type(FunctionType(typeChecker.globalScope->level, &emptyArgumentPack, &returnPack));
|
||||
auto returnPack = TypePackVar{TypePack{{builtinTypes->numberType}}};
|
||||
auto returnsTwo = Type(FunctionType(frontend.globals.globalScope->level, &emptyArgumentPack, &returnPack));
|
||||
|
||||
std::string res = toString(&returnsTwo);
|
||||
CHECK_EQ("() -> number", res);
|
||||
@ -64,8 +64,8 @@ TEST_CASE_FIXTURE(Fixture, "return_type_of_function_is_not_parenthesized_if_just
|
||||
TEST_CASE_FIXTURE(Fixture, "return_type_of_function_is_parenthesized_if_not_just_one_value")
|
||||
{
|
||||
auto emptyArgumentPack = TypePackVar{TypePack{}};
|
||||
auto returnPack = TypePackVar{TypePack{{typeChecker.numberType, typeChecker.numberType}}};
|
||||
auto returnsTwo = Type(FunctionType(typeChecker.globalScope->level, &emptyArgumentPack, &returnPack));
|
||||
auto returnPack = TypePackVar{TypePack{{builtinTypes->numberType, builtinTypes->numberType}}};
|
||||
auto returnsTwo = Type(FunctionType(frontend.globals.globalScope->level, &emptyArgumentPack, &returnPack));
|
||||
|
||||
std::string res = toString(&returnsTwo);
|
||||
CHECK_EQ("() -> (number, number)", res);
|
||||
@ -76,8 +76,8 @@ TEST_CASE_FIXTURE(Fixture, "return_type_of_function_is_parenthesized_if_tail_is_
|
||||
auto emptyArgumentPack = TypePackVar{TypePack{}};
|
||||
auto free = Unifiable::Free(TypeLevel());
|
||||
auto freePack = TypePackVar{TypePackVariant{free}};
|
||||
auto returnPack = TypePackVar{TypePack{{typeChecker.numberType}, &freePack}};
|
||||
auto returnsTwo = Type(FunctionType(typeChecker.globalScope->level, &emptyArgumentPack, &returnPack));
|
||||
auto returnPack = TypePackVar{TypePack{{builtinTypes->numberType}, &freePack}};
|
||||
auto returnsTwo = Type(FunctionType(frontend.globals.globalScope->level, &emptyArgumentPack, &returnPack));
|
||||
|
||||
std::string res = toString(&returnsTwo);
|
||||
CHECK_EQ(res, "() -> (number, a...)");
|
||||
@ -86,9 +86,9 @@ TEST_CASE_FIXTURE(Fixture, "return_type_of_function_is_parenthesized_if_tail_is_
|
||||
TEST_CASE_FIXTURE(Fixture, "subset_check")
|
||||
{
|
||||
UnionType super, sub, notSub;
|
||||
super.options = {typeChecker.numberType, typeChecker.stringType, typeChecker.booleanType};
|
||||
sub.options = {typeChecker.numberType, typeChecker.stringType};
|
||||
notSub.options = {typeChecker.numberType, typeChecker.nilType};
|
||||
super.options = {builtinTypes->numberType, builtinTypes->stringType, builtinTypes->booleanType};
|
||||
sub.options = {builtinTypes->numberType, builtinTypes->stringType};
|
||||
notSub.options = {builtinTypes->numberType, builtinTypes->nilType};
|
||||
|
||||
CHECK(isSubset(super, sub));
|
||||
CHECK(!isSubset(super, notSub));
|
||||
@ -97,7 +97,7 @@ TEST_CASE_FIXTURE(Fixture, "subset_check")
|
||||
TEST_CASE_FIXTURE(Fixture, "iterate_over_UnionType")
|
||||
{
|
||||
UnionType utv;
|
||||
utv.options = {typeChecker.numberType, typeChecker.stringType, typeChecker.anyType};
|
||||
utv.options = {builtinTypes->numberType, builtinTypes->stringType, builtinTypes->anyType};
|
||||
|
||||
std::vector<TypeId> result;
|
||||
for (TypeId ty : &utv)
|
||||
@ -110,19 +110,19 @@ TEST_CASE_FIXTURE(Fixture, "iterating_over_nested_UnionTypes")
|
||||
{
|
||||
Type subunion{UnionType{}};
|
||||
UnionType* innerUtv = getMutable<UnionType>(&subunion);
|
||||
innerUtv->options = {typeChecker.numberType, typeChecker.stringType};
|
||||
innerUtv->options = {builtinTypes->numberType, builtinTypes->stringType};
|
||||
|
||||
UnionType utv;
|
||||
utv.options = {typeChecker.anyType, &subunion};
|
||||
utv.options = {builtinTypes->anyType, &subunion};
|
||||
|
||||
std::vector<TypeId> result;
|
||||
for (TypeId ty : &utv)
|
||||
result.push_back(ty);
|
||||
|
||||
REQUIRE_EQ(result.size(), 3);
|
||||
CHECK_EQ(result[0], typeChecker.anyType);
|
||||
CHECK_EQ(result[2], typeChecker.stringType);
|
||||
CHECK_EQ(result[1], typeChecker.numberType);
|
||||
CHECK_EQ(result[0], builtinTypes->anyType);
|
||||
CHECK_EQ(result[2], builtinTypes->stringType);
|
||||
CHECK_EQ(result[1], builtinTypes->numberType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "iterator_detects_cyclic_UnionTypes_and_skips_over_them")
|
||||
@ -132,8 +132,8 @@ TEST_CASE_FIXTURE(Fixture, "iterator_detects_cyclic_UnionTypes_and_skips_over_th
|
||||
|
||||
Type btv{UnionType{}};
|
||||
UnionType* utv2 = getMutable<UnionType>(&btv);
|
||||
utv2->options.push_back(typeChecker.numberType);
|
||||
utv2->options.push_back(typeChecker.stringType);
|
||||
utv2->options.push_back(builtinTypes->numberType);
|
||||
utv2->options.push_back(builtinTypes->stringType);
|
||||
utv2->options.push_back(&atv);
|
||||
|
||||
utv1->options.push_back(&btv);
|
||||
@ -143,14 +143,14 @@ TEST_CASE_FIXTURE(Fixture, "iterator_detects_cyclic_UnionTypes_and_skips_over_th
|
||||
result.push_back(ty);
|
||||
|
||||
REQUIRE_EQ(result.size(), 2);
|
||||
CHECK_EQ(result[0], typeChecker.numberType);
|
||||
CHECK_EQ(result[1], typeChecker.stringType);
|
||||
CHECK_EQ(result[0], builtinTypes->numberType);
|
||||
CHECK_EQ(result[1], builtinTypes->stringType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "iterator_descends_on_nested_in_first_operator*")
|
||||
{
|
||||
Type tv1{UnionType{{typeChecker.stringType, typeChecker.numberType}}};
|
||||
Type tv2{UnionType{{&tv1, typeChecker.booleanType}}};
|
||||
Type tv1{UnionType{{builtinTypes->stringType, builtinTypes->numberType}}};
|
||||
Type tv2{UnionType{{&tv1, builtinTypes->booleanType}}};
|
||||
auto utv = get<UnionType>(&tv2);
|
||||
|
||||
std::vector<TypeId> result;
|
||||
@ -158,19 +158,19 @@ TEST_CASE_FIXTURE(Fixture, "iterator_descends_on_nested_in_first_operator*")
|
||||
result.push_back(ty);
|
||||
|
||||
REQUIRE_EQ(result.size(), 3);
|
||||
CHECK_EQ(result[0], typeChecker.stringType);
|
||||
CHECK_EQ(result[1], typeChecker.numberType);
|
||||
CHECK_EQ(result[2], typeChecker.booleanType);
|
||||
CHECK_EQ(result[0], builtinTypes->stringType);
|
||||
CHECK_EQ(result[1], builtinTypes->numberType);
|
||||
CHECK_EQ(result[2], builtinTypes->booleanType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "UnionTypeIterator_with_vector_iter_ctor")
|
||||
{
|
||||
Type tv1{UnionType{{typeChecker.stringType, typeChecker.numberType}}};
|
||||
Type tv2{UnionType{{&tv1, typeChecker.booleanType}}};
|
||||
Type tv1{UnionType{{builtinTypes->stringType, builtinTypes->numberType}}};
|
||||
Type tv2{UnionType{{&tv1, builtinTypes->booleanType}}};
|
||||
auto utv = get<UnionType>(&tv2);
|
||||
|
||||
std::vector<TypeId> actual(begin(utv), end(utv));
|
||||
std::vector<TypeId> expected{typeChecker.stringType, typeChecker.numberType, typeChecker.booleanType};
|
||||
std::vector<TypeId> expected{builtinTypes->stringType, builtinTypes->numberType, builtinTypes->booleanType};
|
||||
CHECK_EQ(actual, expected);
|
||||
}
|
||||
|
||||
@ -273,10 +273,10 @@ TEST_CASE_FIXTURE(Fixture, "substitution_skip_failure")
|
||||
|
||||
TypeId root = &ttvTweenResult;
|
||||
|
||||
typeChecker.currentModule = std::make_shared<Module>();
|
||||
typeChecker.currentModule->scopes.emplace_back(Location{}, std::make_shared<Scope>(builtinTypes->anyTypePack));
|
||||
frontend.typeChecker.currentModule = std::make_shared<Module>();
|
||||
frontend.typeChecker.currentModule->scopes.emplace_back(Location{}, std::make_shared<Scope>(builtinTypes->anyTypePack));
|
||||
|
||||
TypeId result = typeChecker.anyify(typeChecker.globalScope, root, Location{});
|
||||
TypeId result = frontend.typeChecker.anyify(frontend.globals.globalScope, root, Location{});
|
||||
|
||||
CHECK_EQ("{| f: t1 |} where t1 = () -> {| f: () -> {| f: ({| f: t1 |}) -> (), signal: {| f: (any) -> () |} |} |}", toString(result));
|
||||
}
|
||||
|
@ -120,6 +120,8 @@ assert((function() local a a = nil local b = 2 b = a and b return b end)() == ni
|
||||
assert((function() local a a = 1 local b = 2 b = a or b return b end)() == 1)
|
||||
assert((function() local a a = nil local b = 2 b = a or b return b end)() == 2)
|
||||
|
||||
assert((function(a) return 12 % a end)(5) == 2)
|
||||
|
||||
-- binary arithmetics coerces strings to numbers (sadly)
|
||||
assert(1 + "2" == 3)
|
||||
assert(2 * "0xa" == 20)
|
||||
|
@ -281,6 +281,8 @@ assert(math.round(-0.4) == 0)
|
||||
assert(math.round(-0.5) == -1)
|
||||
assert(math.round(-3.5) == -4)
|
||||
assert(math.round(math.huge) == math.huge)
|
||||
assert(math.round(0.49999999999999994) == 0)
|
||||
assert(math.round(-0.49999999999999994) == 0)
|
||||
|
||||
-- fmod
|
||||
assert(math.fmod(3, 2) == 1)
|
||||
|
@ -2,6 +2,47 @@
|
||||
-- This file is based on Lua 5.x tests -- https://github.com/lua/lua/tree/master/testes
|
||||
print"testing sort"
|
||||
|
||||
function checksort(t, f, ...)
|
||||
assert(#t == select('#', ...))
|
||||
local copy = table.clone(t)
|
||||
table.sort(copy, f)
|
||||
for i=1,#t do assert(copy[i] == select(i, ...)) end
|
||||
end
|
||||
|
||||
-- basic edge cases
|
||||
checksort({}, nil)
|
||||
checksort({1}, nil, 1)
|
||||
|
||||
-- small inputs
|
||||
checksort({1, 2}, nil, 1, 2)
|
||||
checksort({2, 1}, nil, 1, 2)
|
||||
|
||||
checksort({1, 2, 3}, nil, 1, 2, 3)
|
||||
checksort({2, 1, 3}, nil, 1, 2, 3)
|
||||
checksort({1, 3, 2}, nil, 1, 2, 3)
|
||||
checksort({3, 2, 1}, nil, 1, 2, 3)
|
||||
checksort({3, 1, 2}, nil, 1, 2, 3)
|
||||
|
||||
-- "large" input
|
||||
checksort({3, 8, 1, 7, 10, 2, 5, 4, 9, 6}, nil, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
checksort({"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}, nil, "Apr", "Aug", "Dec", "Feb", "Jan", "Jul", "Jun", "Mar", "May", "Nov", "Oct", "Sep")
|
||||
|
||||
-- duplicates
|
||||
checksort({3, 1, 1, 7, 1, 3, 5, 1, 9, 3}, nil, 1, 1, 1, 1, 3, 3, 3, 5, 7, 9)
|
||||
|
||||
-- predicates
|
||||
checksort({3, 8, 1, 7, 10, 2, 5, 4, 9, 6}, function (a, b) return a > b end, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
|
||||
|
||||
-- can't sort readonly tables
|
||||
assert(pcall(table.sort, table.freeze({2, 1})) == false)
|
||||
|
||||
-- first argument must be a table, second argument must be nil or function
|
||||
assert(pcall(table.sort) == false)
|
||||
assert(pcall(table.sort, "abc") == false)
|
||||
assert(pcall(table.sort, {}, 42) == false)
|
||||
assert(pcall(table.sort, {}, {}) == false)
|
||||
|
||||
-- legacy Lua tests
|
||||
function check (a, f)
|
||||
f = f or function (x,y) return x<y end;
|
||||
for n=table.getn(a),2,-1 do
|
||||
@ -58,13 +99,13 @@ a = {"
|
||||
table.sort(a)
|
||||
check(a)
|
||||
|
||||
table.sort(a, function (x, y)
|
||||
-- TODO: assert that pcall returns false for new sort implementation (table is modified during sorting)
|
||||
pcall(table.sort, a, function (x, y)
|
||||
loadstring(string.format("a[%q] = ''", x))()
|
||||
collectgarbage()
|
||||
return x<y
|
||||
end)
|
||||
|
||||
|
||||
tt = {__lt = function (a,b) return a.val < b.val end}
|
||||
a = {}
|
||||
for i=1,10 do a[i] = {val=math.random(100)}; setmetatable(a[i], tt); end
|
||||
|
@ -85,7 +85,6 @@ RefinementTest.typeguard_in_assert_position
|
||||
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
|
||||
RuntimeLimits.typescript_port_of_Result_type
|
||||
TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
|
||||
TableTests.accidentally_checked_prop_in_opposite_branch
|
||||
TableTests.any_when_indexing_into_an_unsealed_table_with_no_indexer_in_nonstrict_mode
|
||||
TableTests.casting_tables_with_props_into_table_with_indexer3
|
||||
TableTests.casting_tables_with_props_into_table_with_indexer4
|
||||
@ -143,7 +142,6 @@ ToString.toStringNamedFunction_map
|
||||
TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
|
||||
TryUnifyTests.result_of_failed_typepack_unification_is_constrained
|
||||
TryUnifyTests.typepack_unification_should_trim_free_tails
|
||||
TypeAliases.cannot_create_cyclic_type_with_unknown_module
|
||||
TypeAliases.generic_param_remap
|
||||
TypeAliases.mismatched_generic_type_param
|
||||
TypeAliases.mutually_recursive_types_restriction_not_ok_1
|
||||
@ -153,6 +151,7 @@ TypeAliases.recursive_types_restriction_not_ok
|
||||
TypeAliases.report_shadowed_aliases
|
||||
TypeAliases.type_alias_local_mutation
|
||||
TypeAliases.type_alias_local_rename
|
||||
TypeAliases.type_alias_locations
|
||||
TypeAliases.type_alias_of_an_imported_recursive_generic_type
|
||||
TypeInfer.check_type_infer_recursion_count
|
||||
TypeInfer.checking_should_not_ice
|
||||
@ -172,14 +171,12 @@ TypeInferAnyError.for_in_loop_iterator_is_any2
|
||||
TypeInferClasses.class_type_mismatch_with_name_conflict
|
||||
TypeInferClasses.classes_without_overloaded_operators_cannot_be_added
|
||||
TypeInferClasses.index_instance_property
|
||||
TypeInferClasses.optional_class_field_access_error
|
||||
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties
|
||||
TypeInferClasses.warn_when_prop_almost_matches
|
||||
TypeInferFunctions.calling_function_with_anytypepack_doesnt_leak_free_types
|
||||
TypeInferFunctions.cannot_hoist_interior_defns_into_signature
|
||||
TypeInferFunctions.check_function_before_lambda_that_uses_it
|
||||
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.duplicate_functions_with_different_signatures_not_allowed_in_nonstrict
|
||||
TypeInferFunctions.function_cast_error_uses_correct_language
|
||||
TypeInferFunctions.function_decl_non_self_sealed_overwrite_2
|
||||
@ -215,13 +212,10 @@ TypeInferModules.custom_require_global
|
||||
TypeInferModules.do_not_modify_imported_types_5
|
||||
TypeInferModules.module_type_conflict
|
||||
TypeInferModules.module_type_conflict_instantiated
|
||||
TypeInferModules.type_error_of_unknown_qualified_type
|
||||
TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
|
||||
TypeInferOOP.methods_are_topologically_sorted
|
||||
TypeInferOperators.CallAndOrOfFunctions
|
||||
TypeInferOperators.CallOrOfFunctions
|
||||
TypeInferOperators.cannot_indirectly_compare_types_that_do_not_have_a_metatable
|
||||
TypeInferOperators.cannot_indirectly_compare_types_that_do_not_offer_overloaded_ordering_operators
|
||||
TypeInferOperators.cli_38355_recursive_union
|
||||
TypeInferOperators.compound_assign_metatable
|
||||
TypeInferOperators.compound_assign_mismatch_metatable
|
||||
@ -259,13 +253,8 @@ TypeSingletons.widening_happens_almost_everywhere
|
||||
UnionTypes.index_on_a_union_type_with_missing_property
|
||||
UnionTypes.optional_assignment_errors
|
||||
UnionTypes.optional_call_error
|
||||
UnionTypes.optional_field_access_error
|
||||
UnionTypes.optional_index_error
|
||||
UnionTypes.optional_iteration
|
||||
UnionTypes.optional_length_error
|
||||
UnionTypes.optional_missing_key_error_details
|
||||
UnionTypes.optional_union_follow
|
||||
UnionTypes.optional_union_functions
|
||||
UnionTypes.optional_union_members
|
||||
UnionTypes.optional_union_methods
|
||||
UnionTypes.table_union_write_indirect
|
||||
|
Loading…
Reference in New Issue
Block a user