mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
Sync to upstream/release/590
This commit is contained in:
parent
25cc75b096
commit
089da9e924
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,4 +10,5 @@
|
||||
/luau
|
||||
/luau-tests
|
||||
/luau-analyze
|
||||
/luau-compile
|
||||
__pycache__
|
||||
|
@ -16,6 +16,8 @@ using SeenTypePacks = std::unordered_map<TypePackId, TypePackId>;
|
||||
|
||||
struct CloneState
|
||||
{
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
|
||||
|
@ -1,7 +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/Linter.h"
|
||||
#include "Luau/LinterConfig.h"
|
||||
#include "Luau/ParseOptions.h"
|
||||
|
||||
#include <string>
|
||||
|
@ -128,10 +128,10 @@ struct DiffError
|
||||
checkValidInitialization(left, right);
|
||||
}
|
||||
|
||||
std::string toString() const;
|
||||
std::string toString(bool multiLine = false) const;
|
||||
|
||||
private:
|
||||
std::string toStringALeaf(std::string rootName, const DiffPathNodeLeaf& leaf, const DiffPathNodeLeaf& otherLeaf) const;
|
||||
std::string toStringALeaf(std::string rootName, const DiffPathNodeLeaf& leaf, const DiffPathNodeLeaf& otherLeaf, bool multiLine) const;
|
||||
void checkValidInitialization(const DiffPathNodeLeaf& left, const DiffPathNodeLeaf& right);
|
||||
void checkNonMissingPropertyLeavesHaveNulloptTableProperty() const;
|
||||
};
|
||||
@ -152,12 +152,17 @@ struct DifferEnvironment
|
||||
{
|
||||
TypeId rootLeft;
|
||||
TypeId rootRight;
|
||||
std::optional<std::string> externalSymbolLeft;
|
||||
std::optional<std::string> externalSymbolRight;
|
||||
DenseHashMap<TypeId, TypeId> genericMatchedPairs;
|
||||
DenseHashMap<TypePackId, TypePackId> genericTpMatchedPairs;
|
||||
|
||||
DifferEnvironment(TypeId rootLeft, TypeId rootRight)
|
||||
DifferEnvironment(
|
||||
TypeId rootLeft, TypeId rootRight, std::optional<std::string> externalSymbolLeft, std::optional<std::string> externalSymbolRight)
|
||||
: rootLeft(rootLeft)
|
||||
, rootRight(rootRight)
|
||||
, externalSymbolLeft(externalSymbolLeft)
|
||||
, externalSymbolRight(externalSymbolRight)
|
||||
, genericMatchedPairs(nullptr)
|
||||
, genericTpMatchedPairs(nullptr)
|
||||
{
|
||||
@ -170,6 +175,8 @@ struct DifferEnvironment
|
||||
void popVisiting();
|
||||
std::vector<std::pair<TypeId, TypeId>>::const_reverse_iterator visitingBegin() const;
|
||||
std::vector<std::pair<TypeId, TypeId>>::const_reverse_iterator visitingEnd() const;
|
||||
std::string getDevFixFriendlyNameLeft() const;
|
||||
std::string getDevFixFriendlyNameRight() const;
|
||||
|
||||
private:
|
||||
// TODO: consider using DenseHashSet
|
||||
@ -179,6 +186,7 @@ private:
|
||||
std::vector<std::pair<TypeId, TypeId>> visitingStack;
|
||||
};
|
||||
DifferResult diff(TypeId ty1, TypeId ty2);
|
||||
DifferResult diffWithSymbols(TypeId ty1, TypeId ty2, std::optional<std::string> symbol1, std::optional<std::string> symbol2);
|
||||
|
||||
/**
|
||||
* True if ty is a "simple" type, i.e. cannot contain types.
|
||||
|
@ -2,6 +2,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
@ -432,7 +433,7 @@ std::string toString(const TypeError& error, TypeErrorToStringOptions options);
|
||||
bool containsParseErrorName(const TypeError& error);
|
||||
|
||||
// Copy any types named in the error into destArena.
|
||||
void copyErrors(ErrorVec& errors, struct TypeArena& destArena);
|
||||
void copyErrors(ErrorVec& errors, struct TypeArena& destArena, NotNull<BuiltinTypes> builtinTypes);
|
||||
|
||||
// Internal Compiler Error
|
||||
struct InternalErrorReporter
|
||||
|
@ -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/LinterConfig.h"
|
||||
#include "Luau/Location.h"
|
||||
|
||||
#include <memory>
|
||||
@ -15,86 +16,15 @@ class AstStat;
|
||||
class AstNameTable;
|
||||
struct TypeChecker;
|
||||
struct Module;
|
||||
struct HotComment;
|
||||
|
||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||
|
||||
struct LintWarning
|
||||
{
|
||||
// Make sure any new lint codes are documented here: https://luau-lang.org/lint
|
||||
// Note that in Studio, the active set of lint warnings is determined by FStringStudioLuauLints
|
||||
enum Code
|
||||
{
|
||||
Code_Unknown = 0,
|
||||
|
||||
Code_UnknownGlobal = 1, // superseded by type checker
|
||||
Code_DeprecatedGlobal = 2,
|
||||
Code_GlobalUsedAsLocal = 3,
|
||||
Code_LocalShadow = 4, // disabled in Studio
|
||||
Code_SameLineStatement = 5, // disabled in Studio
|
||||
Code_MultiLineStatement = 6,
|
||||
Code_LocalUnused = 7, // disabled in Studio
|
||||
Code_FunctionUnused = 8, // disabled in Studio
|
||||
Code_ImportUnused = 9, // disabled in Studio
|
||||
Code_BuiltinGlobalWrite = 10,
|
||||
Code_PlaceholderRead = 11,
|
||||
Code_UnreachableCode = 12,
|
||||
Code_UnknownType = 13,
|
||||
Code_ForRange = 14,
|
||||
Code_UnbalancedAssignment = 15,
|
||||
Code_ImplicitReturn = 16, // disabled in Studio, superseded by type checker in strict mode
|
||||
Code_DuplicateLocal = 17,
|
||||
Code_FormatString = 18,
|
||||
Code_TableLiteral = 19,
|
||||
Code_UninitializedLocal = 20,
|
||||
Code_DuplicateFunction = 21,
|
||||
Code_DeprecatedApi = 22,
|
||||
Code_TableOperations = 23,
|
||||
Code_DuplicateCondition = 24,
|
||||
Code_MisleadingAndOr = 25,
|
||||
Code_CommentDirective = 26,
|
||||
Code_IntegerParsing = 27,
|
||||
Code_ComparisonPrecedence = 28,
|
||||
|
||||
Code__Count
|
||||
};
|
||||
|
||||
Code code;
|
||||
Location location;
|
||||
std::string text;
|
||||
|
||||
static const char* getName(Code code);
|
||||
static Code parseName(const char* name);
|
||||
static uint64_t parseMask(const std::vector<HotComment>& hotcomments);
|
||||
};
|
||||
|
||||
struct LintResult
|
||||
{
|
||||
std::vector<LintWarning> errors;
|
||||
std::vector<LintWarning> warnings;
|
||||
};
|
||||
|
||||
struct LintOptions
|
||||
{
|
||||
uint64_t warningMask = 0;
|
||||
|
||||
void enableWarning(LintWarning::Code code)
|
||||
{
|
||||
warningMask |= 1ull << code;
|
||||
}
|
||||
void disableWarning(LintWarning::Code code)
|
||||
{
|
||||
warningMask &= ~(1ull << code);
|
||||
}
|
||||
|
||||
bool isEnabled(LintWarning::Code code) const
|
||||
{
|
||||
return 0 != (warningMask & (1ull << code));
|
||||
}
|
||||
|
||||
void setDefaults();
|
||||
};
|
||||
|
||||
std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module,
|
||||
const std::vector<HotComment>& hotcomments, const LintOptions& options);
|
||||
|
||||
|
121
Analysis/include/Luau/LinterConfig.h
Normal file
121
Analysis/include/Luau/LinterConfig.h
Normal file
@ -0,0 +1,121 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Location.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct HotComment;
|
||||
|
||||
struct LintWarning
|
||||
{
|
||||
// Make sure any new lint codes are documented here: https://luau-lang.org/lint
|
||||
// Note that in Studio, the active set of lint warnings is determined by FStringStudioLuauLints
|
||||
enum Code
|
||||
{
|
||||
Code_Unknown = 0,
|
||||
|
||||
Code_UnknownGlobal = 1, // superseded by type checker
|
||||
Code_DeprecatedGlobal = 2,
|
||||
Code_GlobalUsedAsLocal = 3,
|
||||
Code_LocalShadow = 4, // disabled in Studio
|
||||
Code_SameLineStatement = 5, // disabled in Studio
|
||||
Code_MultiLineStatement = 6,
|
||||
Code_LocalUnused = 7, // disabled in Studio
|
||||
Code_FunctionUnused = 8, // disabled in Studio
|
||||
Code_ImportUnused = 9, // disabled in Studio
|
||||
Code_BuiltinGlobalWrite = 10,
|
||||
Code_PlaceholderRead = 11,
|
||||
Code_UnreachableCode = 12,
|
||||
Code_UnknownType = 13,
|
||||
Code_ForRange = 14,
|
||||
Code_UnbalancedAssignment = 15,
|
||||
Code_ImplicitReturn = 16, // disabled in Studio, superseded by type checker in strict mode
|
||||
Code_DuplicateLocal = 17,
|
||||
Code_FormatString = 18,
|
||||
Code_TableLiteral = 19,
|
||||
Code_UninitializedLocal = 20,
|
||||
Code_DuplicateFunction = 21,
|
||||
Code_DeprecatedApi = 22,
|
||||
Code_TableOperations = 23,
|
||||
Code_DuplicateCondition = 24,
|
||||
Code_MisleadingAndOr = 25,
|
||||
Code_CommentDirective = 26,
|
||||
Code_IntegerParsing = 27,
|
||||
Code_ComparisonPrecedence = 28,
|
||||
|
||||
Code__Count
|
||||
};
|
||||
|
||||
Code code;
|
||||
Location location;
|
||||
std::string text;
|
||||
|
||||
static const char* getName(Code code);
|
||||
static Code parseName(const char* name);
|
||||
static uint64_t parseMask(const std::vector<HotComment>& hotcomments);
|
||||
};
|
||||
|
||||
struct LintOptions
|
||||
{
|
||||
uint64_t warningMask = 0;
|
||||
|
||||
void enableWarning(LintWarning::Code code)
|
||||
{
|
||||
warningMask |= 1ull << code;
|
||||
}
|
||||
void disableWarning(LintWarning::Code code)
|
||||
{
|
||||
warningMask &= ~(1ull << code);
|
||||
}
|
||||
|
||||
bool isEnabled(LintWarning::Code code) const
|
||||
{
|
||||
return 0 != (warningMask & (1ull << code));
|
||||
}
|
||||
|
||||
void setDefaults();
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
static const char* kWarningNames[] = {
|
||||
"Unknown",
|
||||
|
||||
"UnknownGlobal",
|
||||
"DeprecatedGlobal",
|
||||
"GlobalUsedAsLocal",
|
||||
"LocalShadow",
|
||||
"SameLineStatement",
|
||||
"MultiLineStatement",
|
||||
"LocalUnused",
|
||||
"FunctionUnused",
|
||||
"ImportUnused",
|
||||
"BuiltinGlobalWrite",
|
||||
"PlaceholderRead",
|
||||
"UnreachableCode",
|
||||
"UnknownType",
|
||||
"ForRange",
|
||||
"UnbalancedAssignment",
|
||||
"ImplicitReturn",
|
||||
"DuplicateLocal",
|
||||
"FormatString",
|
||||
"TableLiteral",
|
||||
"UninitializedLocal",
|
||||
"DuplicateFunction",
|
||||
"DeprecatedApi",
|
||||
"TableOperations",
|
||||
"DuplicateCondition",
|
||||
"MisleadingAndOr",
|
||||
"CommentDirective",
|
||||
"IntegerParsing",
|
||||
"ComparisonPrecedence",
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static_assert(std::size(kWarningNames) == unsigned(LintWarning::Code__Count), "did you forget to add warning to the list?");
|
||||
|
||||
} // namespace Luau
|
@ -147,9 +147,6 @@ struct Tarjan
|
||||
void visitEdge(int index, int parentIndex);
|
||||
void visitSCC(int index);
|
||||
|
||||
TarjanResult loop_DEPRECATED();
|
||||
void visitSCC_DEPRECATED(int index);
|
||||
|
||||
// Each subclass can decide to ignore some nodes.
|
||||
virtual bool ignoreChildren(TypeId ty)
|
||||
{
|
||||
@ -178,13 +175,6 @@ struct Tarjan
|
||||
virtual bool isDirty(TypePackId tp) = 0;
|
||||
virtual void foundDirty(TypeId ty) = 0;
|
||||
virtual void foundDirty(TypePackId tp) = 0;
|
||||
|
||||
// TODO: remove with FFlagLuauTarjanSingleArr
|
||||
std::vector<TypeId> indexToType;
|
||||
std::vector<TypePackId> indexToPack;
|
||||
std::vector<bool> onStack;
|
||||
std::vector<int> lowlink;
|
||||
std::vector<bool> dirty;
|
||||
};
|
||||
|
||||
// And finally substitution, which finds all the reachable dirty vertices
|
||||
|
@ -1,6 +1,7 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Clone.h"
|
||||
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypePack.h"
|
||||
@ -13,12 +14,424 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCloneCyclicUnions, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauStacklessTypeClone, false)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using Kind = Variant<TypeId, TypePackId>;
|
||||
|
||||
template<typename T>
|
||||
const T* get(const Kind& kind)
|
||||
{
|
||||
return get_if<T>(&kind);
|
||||
}
|
||||
|
||||
class TypeCloner2
|
||||
{
|
||||
NotNull<TypeArena> arena;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
|
||||
// A queue of kinds where we cloned it, but whose interior types hasn't
|
||||
// been updated to point to new clones. Once all of its interior types
|
||||
// has been updated, it gets removed from the queue.
|
||||
std::vector<Kind> queue;
|
||||
|
||||
NotNull<SeenTypes> types;
|
||||
NotNull<SeenTypePacks> packs;
|
||||
|
||||
int steps = 0;
|
||||
|
||||
public:
|
||||
TypeCloner2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<SeenTypes> types, NotNull<SeenTypePacks> packs)
|
||||
: arena(arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
, types(types)
|
||||
, packs(packs)
|
||||
{
|
||||
}
|
||||
|
||||
TypeId clone(TypeId ty)
|
||||
{
|
||||
shallowClone(ty);
|
||||
run();
|
||||
|
||||
if (hasExceededIterationLimit())
|
||||
{
|
||||
TypeId error = builtinTypes->errorRecoveryType();
|
||||
(*types)[ty] = error;
|
||||
return error;
|
||||
}
|
||||
|
||||
return find(ty).value_or(builtinTypes->errorRecoveryType());
|
||||
}
|
||||
|
||||
TypePackId clone(TypePackId tp)
|
||||
{
|
||||
shallowClone(tp);
|
||||
run();
|
||||
|
||||
if (hasExceededIterationLimit())
|
||||
{
|
||||
TypePackId error = builtinTypes->errorRecoveryTypePack();
|
||||
(*packs)[tp] = error;
|
||||
return error;
|
||||
}
|
||||
|
||||
return find(tp).value_or(builtinTypes->errorRecoveryTypePack());
|
||||
}
|
||||
|
||||
private:
|
||||
bool hasExceededIterationLimit() const
|
||||
{
|
||||
if (FInt::LuauTypeCloneIterationLimit == 0)
|
||||
return false;
|
||||
|
||||
return steps + queue.size() >= size_t(FInt::LuauTypeCloneIterationLimit);
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
while (!queue.empty())
|
||||
{
|
||||
++steps;
|
||||
|
||||
if (hasExceededIterationLimit())
|
||||
break;
|
||||
|
||||
Kind kind = queue.back();
|
||||
queue.pop_back();
|
||||
|
||||
if (find(kind))
|
||||
continue;
|
||||
|
||||
cloneChildren(kind);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TypeId> find(TypeId ty) const
|
||||
{
|
||||
if (auto it = types->find(ty); it != types->end())
|
||||
return it->second;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypePackId> find(TypePackId tp) const
|
||||
{
|
||||
if (auto it = packs->find(tp); it != packs->end())
|
||||
return it->second;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<Kind> find(Kind kind) const
|
||||
{
|
||||
if (auto ty = get<TypeId>(kind))
|
||||
return find(*ty);
|
||||
else if (auto tp = get<TypePackId>(kind))
|
||||
return find(*tp);
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"Unknown kind?");
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
TypeId shallowClone(TypeId ty)
|
||||
{
|
||||
if (auto clone = find(ty))
|
||||
return *clone;
|
||||
else if (ty->persistent)
|
||||
return ty;
|
||||
|
||||
// We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s.
|
||||
TypeId target = nullptr;
|
||||
if (auto bt = get<BoundType>(ty))
|
||||
target = bt->boundTo;
|
||||
else if (auto tt = get<TableType>(ty); tt && tt->boundTo)
|
||||
target = *tt->boundTo;
|
||||
else
|
||||
{
|
||||
target = arena->addType(ty->ty);
|
||||
asMutable(target)->documentationSymbol = ty->documentationSymbol;
|
||||
}
|
||||
|
||||
LUAU_ASSERT(target);
|
||||
(*types)[ty] = target;
|
||||
queue.push_back(target);
|
||||
return target;
|
||||
}
|
||||
|
||||
TypePackId shallowClone(TypePackId tp)
|
||||
{
|
||||
if (auto clone = find(tp))
|
||||
return *clone;
|
||||
else if (tp->persistent)
|
||||
return tp;
|
||||
|
||||
TypePackId target;
|
||||
if (auto btp = get<BoundTypePack>(tp))
|
||||
target = btp->boundTo;
|
||||
else
|
||||
target = arena->addTypePack(tp->ty);
|
||||
|
||||
LUAU_ASSERT(target);
|
||||
(*packs)[tp] = target;
|
||||
queue.push_back(target);
|
||||
return target;
|
||||
}
|
||||
|
||||
Property shallowClone(const Property& p)
|
||||
{
|
||||
if (FFlag::DebugLuauReadWriteProperties)
|
||||
{
|
||||
std::optional<TypeId> cloneReadTy;
|
||||
if (auto ty = p.readType())
|
||||
cloneReadTy = shallowClone(*ty);
|
||||
|
||||
std::optional<TypeId> cloneWriteTy;
|
||||
if (auto ty = p.writeType())
|
||||
cloneWriteTy = shallowClone(*ty);
|
||||
|
||||
std::optional<Property> cloned = Property::create(cloneReadTy, cloneWriteTy);
|
||||
LUAU_ASSERT(cloned);
|
||||
cloned->deprecated = p.deprecated;
|
||||
cloned->deprecatedSuggestion = p.deprecatedSuggestion;
|
||||
cloned->location = p.location;
|
||||
cloned->tags = p.tags;
|
||||
cloned->documentationSymbol = p.documentationSymbol;
|
||||
return *cloned;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Property{
|
||||
shallowClone(p.type()),
|
||||
p.deprecated,
|
||||
p.deprecatedSuggestion,
|
||||
p.location,
|
||||
p.tags,
|
||||
p.documentationSymbol,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void cloneChildren(TypeId ty)
|
||||
{
|
||||
return visit(
|
||||
[&](auto&& t) {
|
||||
return cloneChildren(&t);
|
||||
},
|
||||
asMutable(ty)->ty);
|
||||
}
|
||||
|
||||
void cloneChildren(TypePackId tp)
|
||||
{
|
||||
return visit(
|
||||
[&](auto&& t) {
|
||||
return cloneChildren(&t);
|
||||
},
|
||||
asMutable(tp)->ty);
|
||||
}
|
||||
|
||||
void cloneChildren(Kind kind)
|
||||
{
|
||||
if (auto ty = get<TypeId>(kind))
|
||||
return cloneChildren(*ty);
|
||||
else if (auto tp = get<TypePackId>(kind))
|
||||
return cloneChildren(*tp);
|
||||
else
|
||||
LUAU_ASSERT(!"Item holds neither TypeId nor TypePackId when enqueuing its children?");
|
||||
}
|
||||
|
||||
// ErrorType and ErrorTypePack is an alias to this type.
|
||||
void cloneChildren(Unifiable::Error* t)
|
||||
{
|
||||
// noop.
|
||||
}
|
||||
|
||||
void cloneChildren(BoundType* t)
|
||||
{
|
||||
t->boundTo = shallowClone(t->boundTo);
|
||||
}
|
||||
|
||||
void cloneChildren(FreeType* t)
|
||||
{
|
||||
// TODO: clone lower and upper bounds.
|
||||
// TODO: In the new solver, we should ice.
|
||||
}
|
||||
|
||||
void cloneChildren(GenericType* t)
|
||||
{
|
||||
// TOOD: clone upper bounds.
|
||||
}
|
||||
|
||||
void cloneChildren(PrimitiveType* t)
|
||||
{
|
||||
// noop.
|
||||
}
|
||||
|
||||
void cloneChildren(BlockedType* t)
|
||||
{
|
||||
// TODO: In the new solver, we should ice.
|
||||
}
|
||||
|
||||
void cloneChildren(PendingExpansionType* t)
|
||||
{
|
||||
// TODO: In the new solver, we should ice.
|
||||
}
|
||||
|
||||
void cloneChildren(SingletonType* t)
|
||||
{
|
||||
// noop.
|
||||
}
|
||||
|
||||
void cloneChildren(FunctionType* t)
|
||||
{
|
||||
for (TypeId& g : t->generics)
|
||||
g = shallowClone(g);
|
||||
|
||||
for (TypePackId& gp : t->genericPacks)
|
||||
gp = shallowClone(gp);
|
||||
|
||||
t->argTypes = shallowClone(t->argTypes);
|
||||
t->retTypes = shallowClone(t->retTypes);
|
||||
}
|
||||
|
||||
void cloneChildren(TableType* t)
|
||||
{
|
||||
if (t->indexer)
|
||||
{
|
||||
t->indexer->indexType = shallowClone(t->indexer->indexType);
|
||||
t->indexer->indexResultType = shallowClone(t->indexer->indexResultType);
|
||||
}
|
||||
|
||||
for (auto& [_, p] : t->props)
|
||||
p = shallowClone(p);
|
||||
|
||||
for (TypeId& ty : t->instantiatedTypeParams)
|
||||
ty = shallowClone(ty);
|
||||
|
||||
for (TypePackId& tp : t->instantiatedTypePackParams)
|
||||
tp = shallowClone(tp);
|
||||
}
|
||||
|
||||
void cloneChildren(MetatableType* t)
|
||||
{
|
||||
t->table = shallowClone(t->table);
|
||||
t->metatable = shallowClone(t->metatable);
|
||||
}
|
||||
|
||||
void cloneChildren(ClassType* t)
|
||||
{
|
||||
for (auto& [_, p] : t->props)
|
||||
p = shallowClone(p);
|
||||
|
||||
if (t->parent)
|
||||
t->parent = shallowClone(*t->parent);
|
||||
|
||||
if (t->metatable)
|
||||
t->metatable = shallowClone(*t->metatable);
|
||||
|
||||
if (t->indexer)
|
||||
{
|
||||
t->indexer->indexType = shallowClone(t->indexer->indexType);
|
||||
t->indexer->indexResultType = shallowClone(t->indexer->indexResultType);
|
||||
}
|
||||
}
|
||||
|
||||
void cloneChildren(AnyType* t)
|
||||
{
|
||||
// noop.
|
||||
}
|
||||
|
||||
void cloneChildren(UnionType* t)
|
||||
{
|
||||
for (TypeId& ty : t->options)
|
||||
ty = shallowClone(ty);
|
||||
}
|
||||
|
||||
void cloneChildren(IntersectionType* t)
|
||||
{
|
||||
for (TypeId& ty : t->parts)
|
||||
ty = shallowClone(ty);
|
||||
}
|
||||
|
||||
void cloneChildren(LazyType* t)
|
||||
{
|
||||
if (auto unwrapped = t->unwrapped.load())
|
||||
t->unwrapped.store(shallowClone(unwrapped));
|
||||
}
|
||||
|
||||
void cloneChildren(UnknownType* t)
|
||||
{
|
||||
// noop.
|
||||
}
|
||||
|
||||
void cloneChildren(NeverType* t)
|
||||
{
|
||||
// noop.
|
||||
}
|
||||
|
||||
void cloneChildren(NegationType* t)
|
||||
{
|
||||
t->ty = shallowClone(t->ty);
|
||||
}
|
||||
|
||||
void cloneChildren(TypeFamilyInstanceType* t)
|
||||
{
|
||||
// TODO: In the new solver, we should ice.
|
||||
}
|
||||
|
||||
void cloneChildren(FreeTypePack* t)
|
||||
{
|
||||
// TODO: clone lower and upper bounds.
|
||||
// TODO: In the new solver, we should ice.
|
||||
}
|
||||
|
||||
void cloneChildren(GenericTypePack* t)
|
||||
{
|
||||
// TOOD: clone upper bounds.
|
||||
}
|
||||
|
||||
void cloneChildren(BlockedTypePack* t)
|
||||
{
|
||||
// TODO: In the new solver, we should ice.
|
||||
}
|
||||
|
||||
void cloneChildren(BoundTypePack* t)
|
||||
{
|
||||
t->boundTo = shallowClone(t->boundTo);
|
||||
}
|
||||
|
||||
void cloneChildren(VariadicTypePack* t)
|
||||
{
|
||||
t->ty = shallowClone(t->ty);
|
||||
}
|
||||
|
||||
void cloneChildren(TypePack* t)
|
||||
{
|
||||
for (TypeId& ty : t->head)
|
||||
ty = shallowClone(ty);
|
||||
|
||||
if (t->tail)
|
||||
t->tail = shallowClone(*t->tail);
|
||||
}
|
||||
|
||||
void cloneChildren(TypeFamilyInstanceTypePack* t)
|
||||
{
|
||||
// TODO: In the new solver, we should ice.
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
Property clone(const Property& prop, TypeArena& dest, CloneState& cloneState)
|
||||
{
|
||||
if (FFlag::DebugLuauReadWriteProperties)
|
||||
@ -470,17 +883,25 @@ TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
|
||||
if (tp->persistent)
|
||||
return tp;
|
||||
|
||||
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
|
||||
|
||||
TypePackId& res = cloneState.seenTypePacks[tp];
|
||||
|
||||
if (res == nullptr)
|
||||
if (FFlag::LuauStacklessTypeClone)
|
||||
{
|
||||
TypePackCloner cloner{dest, tp, cloneState};
|
||||
Luau::visit(cloner, tp->ty); // Mutates the storage that 'res' points into.
|
||||
TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
|
||||
return cloner.clone(tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
|
||||
|
||||
return res;
|
||||
TypePackId& res = cloneState.seenTypePacks[tp];
|
||||
|
||||
if (res == nullptr)
|
||||
{
|
||||
TypePackCloner cloner{dest, tp, cloneState};
|
||||
Luau::visit(cloner, tp->ty); // Mutates the storage that 'res' points into.
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
|
||||
@ -488,54 +909,91 @@ TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
|
||||
if (typeId->persistent)
|
||||
return typeId;
|
||||
|
||||
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
|
||||
|
||||
TypeId& res = cloneState.seenTypes[typeId];
|
||||
|
||||
if (res == nullptr)
|
||||
if (FFlag::LuauStacklessTypeClone)
|
||||
{
|
||||
TypeCloner cloner{dest, typeId, cloneState};
|
||||
Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into.
|
||||
|
||||
// Persistent types are not being cloned and we get the original type back which might be read-only
|
||||
if (!res->persistent)
|
||||
{
|
||||
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
|
||||
}
|
||||
TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
|
||||
return cloner.clone(typeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
|
||||
|
||||
return res;
|
||||
TypeId& res = cloneState.seenTypes[typeId];
|
||||
|
||||
if (res == nullptr)
|
||||
{
|
||||
TypeCloner cloner{dest, typeId, cloneState};
|
||||
Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into.
|
||||
|
||||
// Persistent types are not being cloned and we get the original type back which might be read-only
|
||||
if (!res->persistent)
|
||||
{
|
||||
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState)
|
||||
{
|
||||
TypeFun result;
|
||||
|
||||
for (auto param : typeFun.typeParams)
|
||||
if (FFlag::LuauStacklessTypeClone)
|
||||
{
|
||||
TypeId ty = clone(param.ty, dest, cloneState);
|
||||
std::optional<TypeId> defaultValue;
|
||||
TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
|
||||
|
||||
if (param.defaultValue)
|
||||
defaultValue = clone(*param.defaultValue, dest, cloneState);
|
||||
TypeFun copy = typeFun;
|
||||
|
||||
result.typeParams.push_back({ty, defaultValue});
|
||||
for (auto& param : copy.typeParams)
|
||||
{
|
||||
param.ty = cloner.clone(param.ty);
|
||||
|
||||
if (param.defaultValue)
|
||||
param.defaultValue = cloner.clone(*param.defaultValue);
|
||||
}
|
||||
|
||||
for (auto& param : copy.typePackParams)
|
||||
{
|
||||
param.tp = cloner.clone(param.tp);
|
||||
|
||||
if (param.defaultValue)
|
||||
param.defaultValue = cloner.clone(*param.defaultValue);
|
||||
}
|
||||
|
||||
copy.type = cloner.clone(copy.type);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
for (auto param : typeFun.typePackParams)
|
||||
else
|
||||
{
|
||||
TypePackId tp = clone(param.tp, dest, cloneState);
|
||||
std::optional<TypePackId> defaultValue;
|
||||
TypeFun result;
|
||||
|
||||
if (param.defaultValue)
|
||||
defaultValue = clone(*param.defaultValue, dest, cloneState);
|
||||
for (auto param : typeFun.typeParams)
|
||||
{
|
||||
TypeId ty = clone(param.ty, dest, cloneState);
|
||||
std::optional<TypeId> defaultValue;
|
||||
|
||||
result.typePackParams.push_back({tp, defaultValue});
|
||||
if (param.defaultValue)
|
||||
defaultValue = clone(*param.defaultValue, dest, cloneState);
|
||||
|
||||
result.typeParams.push_back({ty, defaultValue});
|
||||
}
|
||||
|
||||
for (auto param : typeFun.typePackParams)
|
||||
{
|
||||
TypePackId tp = clone(param.tp, dest, cloneState);
|
||||
std::optional<TypePackId> defaultValue;
|
||||
|
||||
if (param.defaultValue)
|
||||
defaultValue = clone(*param.defaultValue, dest, cloneState);
|
||||
|
||||
result.typePackParams.push_back({tp, defaultValue});
|
||||
}
|
||||
|
||||
result.type = clone(typeFun.type, dest, cloneState);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
result.type = clone(typeFun.type, dest, cloneState);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -107,15 +107,19 @@ std::string DiffPath::toString(bool prependDot) const
|
||||
}
|
||||
return pathStr;
|
||||
}
|
||||
std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLeaf& leaf, const DiffPathNodeLeaf& otherLeaf) const
|
||||
std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLeaf& leaf, const DiffPathNodeLeaf& otherLeaf, bool multiLine) const
|
||||
{
|
||||
std::string conditionalNewline = multiLine ? "\n" : " ";
|
||||
std::string conditionalIndent = multiLine ? " " : "";
|
||||
std::string pathStr{rootName + diffPath.toString(true)};
|
||||
switch (kind)
|
||||
{
|
||||
case DiffError::Kind::Normal:
|
||||
{
|
||||
checkNonMissingPropertyLeavesHaveNulloptTableProperty();
|
||||
return pathStr + " has type " + Luau::toString(*leaf.ty);
|
||||
return pathStr + conditionalNewline
|
||||
+ "has type" + conditionalNewline
|
||||
+ conditionalIndent + Luau::toString(*leaf.ty);
|
||||
}
|
||||
case DiffError::Kind::MissingTableProperty:
|
||||
{
|
||||
@ -123,13 +127,17 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea
|
||||
{
|
||||
if (!leaf.tableProperty.has_value())
|
||||
throw InternalCompilerError{"leaf.tableProperty is nullopt"};
|
||||
return pathStr + "." + *leaf.tableProperty + " has type " + Luau::toString(*leaf.ty);
|
||||
return pathStr + "." + *leaf.tableProperty + conditionalNewline
|
||||
+ "has type" + conditionalNewline
|
||||
+ conditionalIndent + Luau::toString(*leaf.ty);
|
||||
}
|
||||
else if (otherLeaf.ty.has_value())
|
||||
{
|
||||
if (!otherLeaf.tableProperty.has_value())
|
||||
throw InternalCompilerError{"otherLeaf.tableProperty is nullopt"};
|
||||
return pathStr + " is missing the property " + *otherLeaf.tableProperty;
|
||||
return pathStr + conditionalNewline
|
||||
+ "is missing the property" + conditionalNewline
|
||||
+ conditionalIndent + *otherLeaf.tableProperty;
|
||||
}
|
||||
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
|
||||
}
|
||||
@ -140,11 +148,15 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea
|
||||
{
|
||||
if (!leaf.unionIndex.has_value())
|
||||
throw InternalCompilerError{"leaf.unionIndex is nullopt"};
|
||||
return pathStr + " is a union containing type " + Luau::toString(*leaf.ty);
|
||||
return pathStr + conditionalNewline
|
||||
+ "is a union containing type" + conditionalNewline
|
||||
+ conditionalIndent + Luau::toString(*leaf.ty);
|
||||
}
|
||||
else if (otherLeaf.ty.has_value())
|
||||
{
|
||||
return pathStr + " is a union missing type " + Luau::toString(*otherLeaf.ty);
|
||||
return pathStr + conditionalNewline
|
||||
+ "is a union missing type" + conditionalNewline
|
||||
+ conditionalIndent + Luau::toString(*otherLeaf.ty);
|
||||
}
|
||||
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
|
||||
}
|
||||
@ -157,11 +169,15 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea
|
||||
{
|
||||
if (!leaf.unionIndex.has_value())
|
||||
throw InternalCompilerError{"leaf.unionIndex is nullopt"};
|
||||
return pathStr + " is an intersection containing type " + Luau::toString(*leaf.ty);
|
||||
return pathStr + conditionalNewline
|
||||
+ "is an intersection containing type" + conditionalNewline
|
||||
+ conditionalIndent + Luau::toString(*leaf.ty);
|
||||
}
|
||||
else if (otherLeaf.ty.has_value())
|
||||
{
|
||||
return pathStr + " is an intersection missing type " + Luau::toString(*otherLeaf.ty);
|
||||
return pathStr + conditionalNewline
|
||||
+ "is an intersection missing type" + conditionalNewline
|
||||
+ conditionalIndent + Luau::toString(*otherLeaf.ty);
|
||||
}
|
||||
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
|
||||
}
|
||||
@ -169,13 +185,15 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea
|
||||
{
|
||||
if (!leaf.minLength.has_value())
|
||||
throw InternalCompilerError{"leaf.minLength is nullopt"};
|
||||
return pathStr + " takes " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " arguments";
|
||||
return pathStr + conditionalNewline
|
||||
+ "takes " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " arguments";
|
||||
}
|
||||
case DiffError::Kind::LengthMismatchInFnRets:
|
||||
{
|
||||
if (!leaf.minLength.has_value())
|
||||
throw InternalCompilerError{"leaf.minLength is nullopt"};
|
||||
return pathStr + " returns " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " values";
|
||||
return pathStr + conditionalNewline
|
||||
+ "returns " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " values";
|
||||
}
|
||||
default:
|
||||
{
|
||||
@ -190,8 +208,11 @@ void DiffError::checkNonMissingPropertyLeavesHaveNulloptTableProperty() const
|
||||
throw InternalCompilerError{"Non-MissingProperty DiffError should have nullopt tableProperty in both leaves"};
|
||||
}
|
||||
|
||||
std::string getDevFixFriendlyName(TypeId ty)
|
||||
std::string getDevFixFriendlyName(const std::optional<std::string>& maybeSymbol, TypeId ty)
|
||||
{
|
||||
if (maybeSymbol.has_value())
|
||||
return *maybeSymbol;
|
||||
|
||||
if (auto table = get<TableType>(ty))
|
||||
{
|
||||
if (table->name.has_value())
|
||||
@ -206,27 +227,39 @@ std::string getDevFixFriendlyName(TypeId ty)
|
||||
return *metatable->syntheticName;
|
||||
}
|
||||
}
|
||||
// else if (auto primitive = get<PrimitiveType>(ty))
|
||||
//{
|
||||
// return "<unlabeled-symbol>";
|
||||
//}
|
||||
return "<unlabeled-symbol>";
|
||||
}
|
||||
|
||||
std::string DiffError::toString() const
|
||||
std::string DifferEnvironment::getDevFixFriendlyNameLeft() const
|
||||
{
|
||||
return getDevFixFriendlyName(externalSymbolLeft, rootLeft);
|
||||
}
|
||||
|
||||
std::string DifferEnvironment::getDevFixFriendlyNameRight() const
|
||||
{
|
||||
return getDevFixFriendlyName(externalSymbolRight, rootRight);
|
||||
}
|
||||
|
||||
std::string DiffError::toString(bool multiLine) const
|
||||
{
|
||||
std::string conditionalNewline = multiLine ? "\n" : " ";
|
||||
std::string conditionalIndent = multiLine ? " " : "";
|
||||
switch (kind)
|
||||
{
|
||||
case DiffError::Kind::IncompatibleGeneric:
|
||||
{
|
||||
std::string diffPathStr{diffPath.toString(true)};
|
||||
return "DiffError: these two types are not equal because the left generic at " + leftRootName + diffPathStr +
|
||||
" cannot be the same type parameter as the right generic at " + rightRootName + diffPathStr;
|
||||
return "DiffError: these two types are not equal because the left generic at" + conditionalNewline
|
||||
+ conditionalIndent + leftRootName + diffPathStr + conditionalNewline
|
||||
+ "cannot be the same type parameter as the right generic at" + conditionalNewline
|
||||
+ conditionalIndent + rightRootName + diffPathStr;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return "DiffError: these two types are not equal because the left type at " + toStringALeaf(leftRootName, left, right) +
|
||||
", while the right type at " + toStringALeaf(rightRootName, right, left);
|
||||
return "DiffError: these two types are not equal because the left type at" + conditionalNewline
|
||||
+ conditionalIndent + toStringALeaf(leftRootName, left, right, multiLine) + "," + conditionalNewline +
|
||||
"while the right type at" + conditionalNewline
|
||||
+ conditionalIndent + toStringALeaf(rightRootName, right, left, multiLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -296,8 +329,8 @@ static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right)
|
||||
DiffError::Kind::MissingTableProperty,
|
||||
DiffPathNodeLeaf::detailsTableProperty(value.type(), field),
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
env.getDevFixFriendlyNameLeft(),
|
||||
env.getDevFixFriendlyNameRight(),
|
||||
}};
|
||||
}
|
||||
}
|
||||
@ -307,8 +340,7 @@ static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right)
|
||||
{
|
||||
// right has a field the left doesn't
|
||||
return DifferResult{DiffError{DiffError::Kind::MissingTableProperty, DiffPathNodeLeaf::nullopts(),
|
||||
DiffPathNodeLeaf::detailsTableProperty(value.type(), field), getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight)}};
|
||||
DiffPathNodeLeaf::detailsTableProperty(value.type(), field), env.getDevFixFriendlyNameLeft(), env.getDevFixFriendlyNameRight()}};
|
||||
}
|
||||
}
|
||||
// left and right have the same set of keys
|
||||
@ -360,8 +392,8 @@ static DifferResult diffPrimitive(DifferEnvironment& env, TypeId left, TypeId ri
|
||||
DiffError::Kind::Normal,
|
||||
DiffPathNodeLeaf::detailsNormal(left),
|
||||
DiffPathNodeLeaf::detailsNormal(right),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
env.getDevFixFriendlyNameLeft(),
|
||||
env.getDevFixFriendlyNameRight(),
|
||||
}};
|
||||
}
|
||||
return DifferResult{};
|
||||
@ -380,8 +412,8 @@ static DifferResult diffSingleton(DifferEnvironment& env, TypeId left, TypeId ri
|
||||
DiffError::Kind::Normal,
|
||||
DiffPathNodeLeaf::detailsNormal(left),
|
||||
DiffPathNodeLeaf::detailsNormal(right),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
env.getDevFixFriendlyNameLeft(),
|
||||
env.getDevFixFriendlyNameRight(),
|
||||
}};
|
||||
}
|
||||
return DifferResult{};
|
||||
@ -419,8 +451,8 @@ static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId righ
|
||||
DiffError::Kind::IncompatibleGeneric,
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
env.getDevFixFriendlyNameLeft(),
|
||||
env.getDevFixFriendlyNameRight(),
|
||||
}};
|
||||
}
|
||||
|
||||
@ -432,8 +464,8 @@ static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId righ
|
||||
DiffError::Kind::IncompatibleGeneric,
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
env.getDevFixFriendlyNameLeft(),
|
||||
env.getDevFixFriendlyNameRight(),
|
||||
}};
|
||||
}
|
||||
|
||||
@ -468,8 +500,8 @@ static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right)
|
||||
DiffError::Kind::Normal,
|
||||
DiffPathNodeLeaf::detailsNormal(left),
|
||||
DiffPathNodeLeaf::detailsNormal(right),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
env.getDevFixFriendlyNameLeft(),
|
||||
env.getDevFixFriendlyNameRight(),
|
||||
}};
|
||||
}
|
||||
|
||||
@ -521,16 +553,16 @@ static DifferResult diffUnion(DifferEnvironment& env, TypeId left, TypeId right)
|
||||
DiffError::Kind::MissingUnionMember,
|
||||
DiffPathNodeLeaf::detailsUnionIndex(leftUnion->options[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx),
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
env.getDevFixFriendlyNameLeft(),
|
||||
env.getDevFixFriendlyNameRight(),
|
||||
}};
|
||||
else
|
||||
return DifferResult{DiffError{
|
||||
DiffError::Kind::MissingUnionMember,
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
DiffPathNodeLeaf::detailsUnionIndex(rightUnion->options[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
env.getDevFixFriendlyNameLeft(),
|
||||
env.getDevFixFriendlyNameRight(),
|
||||
}};
|
||||
}
|
||||
|
||||
@ -554,16 +586,16 @@ static DifferResult diffIntersection(DifferEnvironment& env, TypeId left, TypeId
|
||||
DiffError::Kind::MissingIntersectionMember,
|
||||
DiffPathNodeLeaf::detailsUnionIndex(leftIntersection->parts[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx),
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
env.getDevFixFriendlyNameLeft(),
|
||||
env.getDevFixFriendlyNameRight(),
|
||||
}};
|
||||
else
|
||||
return DifferResult{DiffError{
|
||||
DiffError::Kind::MissingIntersectionMember,
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
DiffPathNodeLeaf::detailsUnionIndex(rightIntersection->parts[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
env.getDevFixFriendlyNameLeft(),
|
||||
env.getDevFixFriendlyNameRight(),
|
||||
}};
|
||||
}
|
||||
|
||||
@ -583,8 +615,8 @@ static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId rig
|
||||
DiffError::Kind::Normal,
|
||||
DiffPathNodeLeaf::detailsNormal(left),
|
||||
DiffPathNodeLeaf::detailsNormal(right),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
env.getDevFixFriendlyNameLeft(),
|
||||
env.getDevFixFriendlyNameRight(),
|
||||
}};
|
||||
}
|
||||
|
||||
@ -753,8 +785,8 @@ static DifferResult diffCanonicalTpShape(DifferEnvironment& env, DiffError::Kind
|
||||
possibleNonNormalErrorKind,
|
||||
DiffPathNodeLeaf::detailsLength(int(left.first.size()), left.second.has_value()),
|
||||
DiffPathNodeLeaf::detailsLength(int(right.first.size()), right.second.has_value()),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
env.getDevFixFriendlyNameLeft(),
|
||||
env.getDevFixFriendlyNameRight(),
|
||||
}};
|
||||
}
|
||||
|
||||
@ -769,8 +801,8 @@ static DifferResult diffHandleFlattenedTail(DifferEnvironment& env, DiffError::K
|
||||
DiffError::Kind::Normal,
|
||||
DiffPathNodeLeaf::detailsNormal(env.visitingBegin()->first),
|
||||
DiffPathNodeLeaf::detailsNormal(env.visitingBegin()->second),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
env.getDevFixFriendlyNameLeft(),
|
||||
env.getDevFixFriendlyNameRight(),
|
||||
}};
|
||||
}
|
||||
|
||||
@ -847,8 +879,8 @@ static DifferResult diffGenericTp(DifferEnvironment& env, TypePackId left, TypeP
|
||||
DiffError::Kind::IncompatibleGeneric,
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
env.getDevFixFriendlyNameLeft(),
|
||||
env.getDevFixFriendlyNameRight(),
|
||||
}};
|
||||
}
|
||||
|
||||
@ -860,8 +892,8 @@ static DifferResult diffGenericTp(DifferEnvironment& env, TypePackId left, TypeP
|
||||
DiffError::Kind::IncompatibleGeneric,
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
env.getDevFixFriendlyNameLeft(),
|
||||
env.getDevFixFriendlyNameRight(),
|
||||
}};
|
||||
}
|
||||
|
||||
@ -910,7 +942,13 @@ std::vector<std::pair<TypeId, TypeId>>::const_reverse_iterator DifferEnvironment
|
||||
|
||||
DifferResult diff(TypeId ty1, TypeId ty2)
|
||||
{
|
||||
DifferEnvironment differEnv{ty1, ty2};
|
||||
DifferEnvironment differEnv{ty1, ty2, std::nullopt, std::nullopt};
|
||||
return diffUsingEnv(differEnv, ty1, ty2);
|
||||
}
|
||||
|
||||
DifferResult diffWithSymbols(TypeId ty1, TypeId ty2, std::optional<std::string> symbol1, std::optional<std::string> symbol2)
|
||||
{
|
||||
DifferEnvironment differEnv{ty1, ty2, symbol1, symbol2};
|
||||
return diffUsingEnv(differEnv, ty1, ty2);
|
||||
}
|
||||
|
||||
|
@ -4,12 +4,16 @@
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/ToString.h"
|
||||
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauIndentTypeMismatch, false)
|
||||
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
|
||||
|
||||
static std::string wrongNumberOfArgsString(
|
||||
size_t expectedCount, std::optional<size_t> maximumCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
|
||||
@ -66,6 +70,20 @@ struct ErrorConverter
|
||||
|
||||
std::string result;
|
||||
|
||||
auto quote = [&](std::string s) {
|
||||
return "'" + s + "'";
|
||||
};
|
||||
|
||||
auto constructErrorMessage = [&](std::string givenType, std::string wantedType, std::optional<std::string> givenModule,
|
||||
std::optional<std::string> wantedModule) -> std::string {
|
||||
std::string given = givenModule ? quote(givenType) + " from " + quote(*givenModule) : quote(givenType);
|
||||
std::string wanted = wantedModule ? quote(wantedType) + " from " + quote(*wantedModule) : quote(wantedType);
|
||||
size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength);
|
||||
if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength)
|
||||
return "Type " + given + " could not be converted into " + wanted;
|
||||
return "Type\n " + given + "\ncould not be converted into\n " + wanted;
|
||||
};
|
||||
|
||||
if (givenTypeName == wantedTypeName)
|
||||
{
|
||||
if (auto givenDefinitionModule = getDefinitionModuleName(tm.givenType))
|
||||
@ -76,20 +94,31 @@ struct ErrorConverter
|
||||
{
|
||||
std::string givenModuleName = fileResolver->getHumanReadableModuleName(*givenDefinitionModule);
|
||||
std::string wantedModuleName = fileResolver->getHumanReadableModuleName(*wantedDefinitionModule);
|
||||
result = "Type '" + givenTypeName + "' from '" + givenModuleName + "' could not be converted into '" + wantedTypeName +
|
||||
"' from '" + wantedModuleName + "'";
|
||||
if (FFlag::LuauIndentTypeMismatch)
|
||||
result = constructErrorMessage(givenTypeName, wantedTypeName, givenModuleName, wantedModuleName);
|
||||
else
|
||||
result = "Type '" + givenTypeName + "' from '" + givenModuleName + "' could not be converted into '" + wantedTypeName +
|
||||
"' from '" + wantedModuleName + "'";
|
||||
}
|
||||
else
|
||||
{
|
||||
result = "Type '" + givenTypeName + "' from '" + *givenDefinitionModule + "' could not be converted into '" + wantedTypeName +
|
||||
"' from '" + *wantedDefinitionModule + "'";
|
||||
if (FFlag::LuauIndentTypeMismatch)
|
||||
result = constructErrorMessage(givenTypeName, wantedTypeName, *givenDefinitionModule, *wantedDefinitionModule);
|
||||
else
|
||||
result = "Type '" + givenTypeName + "' from '" + *givenDefinitionModule + "' could not be converted into '" +
|
||||
wantedTypeName + "' from '" + *wantedDefinitionModule + "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.empty())
|
||||
result = "Type '" + givenTypeName + "' could not be converted into '" + wantedTypeName + "'";
|
||||
{
|
||||
if (FFlag::LuauIndentTypeMismatch)
|
||||
result = constructErrorMessage(givenTypeName, wantedTypeName, std::nullopt, std::nullopt);
|
||||
else
|
||||
result = "Type '" + givenTypeName + "' could not be converted into '" + wantedTypeName + "'";
|
||||
}
|
||||
|
||||
|
||||
if (tm.error)
|
||||
@ -97,7 +126,7 @@ struct ErrorConverter
|
||||
result += "\ncaused by:\n ";
|
||||
|
||||
if (!tm.reason.empty())
|
||||
result += tm.reason + " ";
|
||||
result += tm.reason + (FFlag::LuauIndentTypeMismatch ? " \n" : " ");
|
||||
|
||||
result += Luau::toString(*tm.error, TypeErrorToStringOptions{fileResolver});
|
||||
}
|
||||
@ -845,7 +874,7 @@ bool containsParseErrorName(const TypeError& error)
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void copyError(T& e, TypeArena& destArena, CloneState cloneState)
|
||||
void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
|
||||
{
|
||||
auto clone = [&](auto&& ty) {
|
||||
return ::Luau::clone(ty, destArena, cloneState);
|
||||
@ -998,9 +1027,9 @@ void copyError(T& e, TypeArena& destArena, CloneState cloneState)
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
||||
void copyErrors(ErrorVec& errors, TypeArena& destArena)
|
||||
void copyErrors(ErrorVec& errors, TypeArena& destArena, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
CloneState cloneState;
|
||||
CloneState cloneState{builtinTypes};
|
||||
|
||||
auto visitErrorData = [&](auto&& e) {
|
||||
copyError(e, destArena, cloneState);
|
||||
|
@ -35,7 +35,6 @@ LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckCancellation, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -126,7 +125,7 @@ static ParseResult parseSourceForModule(std::string_view source, Luau::SourceMod
|
||||
|
||||
static void persistCheckedTypes(ModulePtr checkedModule, GlobalTypes& globals, ScopePtr targetScope, const std::string& packageName)
|
||||
{
|
||||
CloneState cloneState;
|
||||
CloneState cloneState{globals.builtinTypes};
|
||||
|
||||
std::vector<TypeId> typesToPersist;
|
||||
typesToPersist.reserve(checkedModule->declaredGlobals.size() + checkedModule->exportedTypeBindings.size());
|
||||
@ -462,7 +461,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||
checkResult.timeoutHits.push_back(item.name);
|
||||
|
||||
// If check was manually cancelled, do not return partial results
|
||||
if (FFlag::LuauTypecheckCancellation && item.module->cancelled)
|
||||
if (item.module->cancelled)
|
||||
return {};
|
||||
|
||||
checkResult.errors.insert(checkResult.errors.end(), item.module->errors.begin(), item.module->errors.end());
|
||||
@ -635,7 +634,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptio
|
||||
if (item.exception)
|
||||
itemWithException = i;
|
||||
|
||||
if (FFlag::LuauTypecheckCancellation && item.module && item.module->cancelled)
|
||||
if (item.module && item.module->cancelled)
|
||||
cancelled = true;
|
||||
|
||||
if (itemWithException || cancelled)
|
||||
@ -677,7 +676,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptio
|
||||
if (remaining != 0 && processing == 0)
|
||||
{
|
||||
// Typechecking might have been cancelled by user, don't return partial results
|
||||
if (FFlag::LuauTypecheckCancellation && cancelled)
|
||||
if (cancelled)
|
||||
return {};
|
||||
|
||||
// We might have stopped because of a pending exception
|
||||
@ -910,8 +909,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
|
||||
else
|
||||
typeCheckLimits.unifierIterationLimit = std::nullopt;
|
||||
|
||||
if (FFlag::LuauTypecheckCancellation)
|
||||
typeCheckLimits.cancellationToken = item.options.cancellationToken;
|
||||
typeCheckLimits.cancellationToken = item.options.cancellationToken;
|
||||
|
||||
ModulePtr moduleForAutocomplete = check(sourceModule, Mode::Strict, requireCycles, environmentScope, /*forAutocomplete*/ true,
|
||||
/*recordJsonLog*/ false, typeCheckLimits);
|
||||
@ -932,8 +930,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
|
||||
|
||||
TypeCheckLimits typeCheckLimits;
|
||||
|
||||
if (FFlag::LuauTypecheckCancellation)
|
||||
typeCheckLimits.cancellationToken = item.options.cancellationToken;
|
||||
typeCheckLimits.cancellationToken = item.options.cancellationToken;
|
||||
|
||||
ModulePtr module = check(sourceModule, mode, requireCycles, environmentScope, /*forAutocomplete*/ false, item.recordJsonLog, typeCheckLimits);
|
||||
|
||||
@ -969,7 +966,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
|
||||
// copyErrors needs to allocate into interfaceTypes as it copies
|
||||
// types out of internalTypes, so we unfreeze it here.
|
||||
unfreeze(module->interfaceTypes);
|
||||
copyErrors(module->errors, module->interfaceTypes);
|
||||
copyErrors(module->errors, module->interfaceTypes, builtinTypes);
|
||||
freeze(module->interfaceTypes);
|
||||
|
||||
module->internalTypes.clear();
|
||||
@ -1014,7 +1011,7 @@ void Frontend::checkBuildQueueItems(std::vector<BuildQueueItem>& items)
|
||||
{
|
||||
checkBuildQueueItem(item);
|
||||
|
||||
if (FFlag::LuauTypecheckCancellation && item.module && item.module->cancelled)
|
||||
if (item.module && item.module->cancelled)
|
||||
break;
|
||||
|
||||
recordItemResult(item);
|
||||
@ -1295,9 +1292,7 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vect
|
||||
typeChecker.finishTime = typeCheckLimits.finishTime;
|
||||
typeChecker.instantiationChildLimit = typeCheckLimits.instantiationChildLimit;
|
||||
typeChecker.unifierIterationLimit = typeCheckLimits.unifierIterationLimit;
|
||||
|
||||
if (FFlag::LuauTypecheckCancellation)
|
||||
typeChecker.cancellationToken = typeCheckLimits.cancellationToken;
|
||||
typeChecker.cancellationToken = typeCheckLimits.cancellationToken;
|
||||
|
||||
return typeChecker.check(sourceModule, mode, environmentScope);
|
||||
}
|
||||
|
@ -14,48 +14,9 @@
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauLintNativeComment, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
// clang-format off
|
||||
static const char* kWarningNames[] = {
|
||||
"Unknown",
|
||||
|
||||
"UnknownGlobal",
|
||||
"DeprecatedGlobal",
|
||||
"GlobalUsedAsLocal",
|
||||
"LocalShadow",
|
||||
"SameLineStatement",
|
||||
"MultiLineStatement",
|
||||
"LocalUnused",
|
||||
"FunctionUnused",
|
||||
"ImportUnused",
|
||||
"BuiltinGlobalWrite",
|
||||
"PlaceholderRead",
|
||||
"UnreachableCode",
|
||||
"UnknownType",
|
||||
"ForRange",
|
||||
"UnbalancedAssignment",
|
||||
"ImplicitReturn",
|
||||
"DuplicateLocal",
|
||||
"FormatString",
|
||||
"TableLiteral",
|
||||
"UninitializedLocal",
|
||||
"DuplicateFunction",
|
||||
"DeprecatedApi",
|
||||
"TableOperations",
|
||||
"DuplicateCondition",
|
||||
"MisleadingAndOr",
|
||||
"CommentDirective",
|
||||
"IntegerParsing",
|
||||
"ComparisonPrecedence",
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static_assert(std::size(kWarningNames) == unsigned(LintWarning::Code__Count), "did you forget to add warning to the list?");
|
||||
|
||||
struct LintContext
|
||||
{
|
||||
struct Global
|
||||
@ -2827,7 +2788,7 @@ static void lintComments(LintContext& context, const std::vector<HotComment>& ho
|
||||
"optimize directive uses unknown optimization level '%s', 0..2 expected", level);
|
||||
}
|
||||
}
|
||||
else if (FFlag::LuauLintNativeComment && first == "native")
|
||||
else if (first == "native")
|
||||
{
|
||||
if (space != std::string::npos)
|
||||
emitWarning(context, LintWarning::Code_CommentDirective, hc.location,
|
||||
@ -2855,12 +2816,6 @@ static void lintComments(LintContext& context, const std::vector<HotComment>& ho
|
||||
}
|
||||
}
|
||||
|
||||
void LintOptions::setDefaults()
|
||||
{
|
||||
// By default, we enable all warnings
|
||||
warningMask = ~0ull;
|
||||
}
|
||||
|
||||
std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module,
|
||||
const std::vector<HotComment>& hotcomments, const LintOptions& options)
|
||||
{
|
||||
@ -2952,54 +2907,6 @@ std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const Sc
|
||||
return context.result;
|
||||
}
|
||||
|
||||
const char* LintWarning::getName(Code code)
|
||||
{
|
||||
LUAU_ASSERT(unsigned(code) < Code__Count);
|
||||
|
||||
return kWarningNames[code];
|
||||
}
|
||||
|
||||
LintWarning::Code LintWarning::parseName(const char* name)
|
||||
{
|
||||
for (int code = Code_Unknown; code < Code__Count; ++code)
|
||||
if (strcmp(name, getName(Code(code))) == 0)
|
||||
return Code(code);
|
||||
|
||||
return Code_Unknown;
|
||||
}
|
||||
|
||||
uint64_t LintWarning::parseMask(const std::vector<HotComment>& hotcomments)
|
||||
{
|
||||
uint64_t result = 0;
|
||||
|
||||
for (const HotComment& hc : hotcomments)
|
||||
{
|
||||
if (!hc.header)
|
||||
continue;
|
||||
|
||||
if (hc.content.compare(0, 6, "nolint") != 0)
|
||||
continue;
|
||||
|
||||
size_t name = hc.content.find_first_not_of(" \t", 6);
|
||||
|
||||
// --!nolint disables everything
|
||||
if (name == std::string::npos)
|
||||
return ~0ull;
|
||||
|
||||
// --!nolint needs to be followed by a whitespace character
|
||||
if (name == 6)
|
||||
continue;
|
||||
|
||||
// --!nolint name disables the specific lint
|
||||
LintWarning::Code code = LintWarning::parseName(hc.content.c_str() + name);
|
||||
|
||||
if (code != LintWarning::Code_Unknown)
|
||||
result |= 1ull << int(code);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<AstName> getDeprecatedGlobals(const AstNameTable& names)
|
||||
{
|
||||
LintContext context;
|
||||
|
63
Analysis/src/LinterConfig.cpp
Normal file
63
Analysis/src/LinterConfig.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/LinterConfig.h"
|
||||
|
||||
#include "Luau/ParseResult.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
void LintOptions::setDefaults()
|
||||
{
|
||||
// By default, we enable all warnings
|
||||
warningMask = ~0ull;
|
||||
}
|
||||
|
||||
const char* LintWarning::getName(Code code)
|
||||
{
|
||||
LUAU_ASSERT(unsigned(code) < Code__Count);
|
||||
|
||||
return kWarningNames[code];
|
||||
}
|
||||
|
||||
LintWarning::Code LintWarning::parseName(const char* name)
|
||||
{
|
||||
for (int code = Code_Unknown; code < Code__Count; ++code)
|
||||
if (strcmp(name, getName(Code(code))) == 0)
|
||||
return Code(code);
|
||||
|
||||
return Code_Unknown;
|
||||
}
|
||||
|
||||
uint64_t LintWarning::parseMask(const std::vector<HotComment>& hotcomments)
|
||||
{
|
||||
uint64_t result = 0;
|
||||
|
||||
for (const HotComment& hc : hotcomments)
|
||||
{
|
||||
if (!hc.header)
|
||||
continue;
|
||||
|
||||
if (hc.content.compare(0, 6, "nolint") != 0)
|
||||
continue;
|
||||
|
||||
size_t name = hc.content.find_first_not_of(" \t", 6);
|
||||
|
||||
// --!nolint disables everything
|
||||
if (name == std::string::npos)
|
||||
return ~0ull;
|
||||
|
||||
// --!nolint needs to be followed by a whitespace character
|
||||
if (name == 6)
|
||||
continue;
|
||||
|
||||
// --!nolint name disables the specific lint
|
||||
LintWarning::Code code = LintWarning::parseName(hc.content.c_str() + name);
|
||||
|
||||
if (code != LintWarning::Code_Unknown)
|
||||
result |= 1ull << int(code);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
@ -199,7 +199,7 @@ void Module::clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalEr
|
||||
LUAU_ASSERT(interfaceTypes.types.empty());
|
||||
LUAU_ASSERT(interfaceTypes.typePacks.empty());
|
||||
|
||||
CloneState cloneState;
|
||||
CloneState cloneState{builtinTypes};
|
||||
|
||||
ScopePtr moduleScope = getModuleScope();
|
||||
|
||||
|
@ -10,7 +10,6 @@
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTarjanSingleArr, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -269,67 +268,30 @@ std::pair<int, bool> Tarjan::indexify(TypeId ty)
|
||||
{
|
||||
ty = log->follow(ty);
|
||||
|
||||
if (FFlag::LuauTarjanSingleArr)
|
||||
auto [index, fresh] = typeToIndex.try_insert(ty, false);
|
||||
|
||||
if (fresh)
|
||||
{
|
||||
auto [index, fresh] = typeToIndex.try_insert(ty, false);
|
||||
|
||||
if (fresh)
|
||||
{
|
||||
index = int(nodes.size());
|
||||
nodes.push_back({ty, nullptr, false, false, index});
|
||||
}
|
||||
|
||||
return {index, fresh};
|
||||
index = int(nodes.size());
|
||||
nodes.push_back({ty, nullptr, false, false, index});
|
||||
}
|
||||
else
|
||||
{
|
||||
bool fresh = !typeToIndex.contains(ty);
|
||||
int& index = typeToIndex[ty];
|
||||
|
||||
if (fresh)
|
||||
{
|
||||
index = int(indexToType.size());
|
||||
indexToType.push_back(ty);
|
||||
indexToPack.push_back(nullptr);
|
||||
onStack.push_back(false);
|
||||
lowlink.push_back(index);
|
||||
}
|
||||
return {index, fresh};
|
||||
}
|
||||
return {index, fresh};
|
||||
}
|
||||
|
||||
std::pair<int, bool> Tarjan::indexify(TypePackId tp)
|
||||
{
|
||||
tp = log->follow(tp);
|
||||
|
||||
if (FFlag::LuauTarjanSingleArr)
|
||||
auto [index, fresh] = packToIndex.try_insert(tp, false);
|
||||
|
||||
if (fresh)
|
||||
{
|
||||
auto [index, fresh] = packToIndex.try_insert(tp, false);
|
||||
|
||||
if (fresh)
|
||||
{
|
||||
index = int(nodes.size());
|
||||
nodes.push_back({nullptr, tp, false, false, index});
|
||||
}
|
||||
|
||||
return {index, fresh};
|
||||
index = int(nodes.size());
|
||||
nodes.push_back({nullptr, tp, false, false, index});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
bool fresh = !packToIndex.contains(tp);
|
||||
int& index = packToIndex[tp];
|
||||
|
||||
if (fresh)
|
||||
{
|
||||
index = int(indexToPack.size());
|
||||
indexToType.push_back(nullptr);
|
||||
indexToPack.push_back(tp);
|
||||
onStack.push_back(false);
|
||||
lowlink.push_back(index);
|
||||
}
|
||||
return {index, fresh};
|
||||
}
|
||||
return {index, fresh};
|
||||
}
|
||||
|
||||
void Tarjan::visitChild(TypeId ty)
|
||||
@ -350,9 +312,6 @@ void Tarjan::visitChild(TypePackId tp)
|
||||
|
||||
TarjanResult Tarjan::loop()
|
||||
{
|
||||
if (!FFlag::LuauTarjanSingleArr)
|
||||
return loop_DEPRECATED();
|
||||
|
||||
// Normally Tarjan is presented recursively, but this is a hot loop, so worth optimizing
|
||||
while (!worklist.empty())
|
||||
{
|
||||
@ -476,27 +435,11 @@ TarjanResult Tarjan::visitRoot(TypePackId tp)
|
||||
|
||||
void Tarjan::clearTarjan()
|
||||
{
|
||||
if (FFlag::LuauTarjanSingleArr)
|
||||
{
|
||||
typeToIndex.clear();
|
||||
packToIndex.clear();
|
||||
nodes.clear();
|
||||
typeToIndex.clear();
|
||||
packToIndex.clear();
|
||||
nodes.clear();
|
||||
|
||||
stack.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
dirty.clear();
|
||||
|
||||
typeToIndex.clear();
|
||||
packToIndex.clear();
|
||||
indexToType.clear();
|
||||
indexToPack.clear();
|
||||
|
||||
stack.clear();
|
||||
onStack.clear();
|
||||
lowlink.clear();
|
||||
}
|
||||
stack.clear();
|
||||
|
||||
edgesTy.clear();
|
||||
edgesTp.clear();
|
||||
@ -505,32 +448,14 @@ void Tarjan::clearTarjan()
|
||||
|
||||
bool Tarjan::getDirty(int index)
|
||||
{
|
||||
if (FFlag::LuauTarjanSingleArr)
|
||||
{
|
||||
LUAU_ASSERT(size_t(index) < nodes.size());
|
||||
return nodes[index].dirty;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dirty.size() <= size_t(index))
|
||||
dirty.resize(index + 1, false);
|
||||
return dirty[index];
|
||||
}
|
||||
LUAU_ASSERT(size_t(index) < nodes.size());
|
||||
return nodes[index].dirty;
|
||||
}
|
||||
|
||||
void Tarjan::setDirty(int index, bool d)
|
||||
{
|
||||
if (FFlag::LuauTarjanSingleArr)
|
||||
{
|
||||
LUAU_ASSERT(size_t(index) < nodes.size());
|
||||
nodes[index].dirty = d;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dirty.size() <= size_t(index))
|
||||
dirty.resize(index + 1, false);
|
||||
dirty[index] = d;
|
||||
}
|
||||
LUAU_ASSERT(size_t(index) < nodes.size());
|
||||
nodes[index].dirty = d;
|
||||
}
|
||||
|
||||
void Tarjan::visitEdge(int index, int parentIndex)
|
||||
@ -541,9 +466,6 @@ void Tarjan::visitEdge(int index, int parentIndex)
|
||||
|
||||
void Tarjan::visitSCC(int index)
|
||||
{
|
||||
if (!FFlag::LuauTarjanSingleArr)
|
||||
return visitSCC_DEPRECATED(index);
|
||||
|
||||
bool d = getDirty(index);
|
||||
|
||||
for (auto it = stack.rbegin(); !d && it != stack.rend(); it++)
|
||||
@ -588,132 +510,6 @@ TarjanResult Tarjan::findDirty(TypePackId tp)
|
||||
return visitRoot(tp);
|
||||
}
|
||||
|
||||
TarjanResult Tarjan::loop_DEPRECATED()
|
||||
{
|
||||
// Normally Tarjan is presented recursively, but this is a hot loop, so worth optimizing
|
||||
while (!worklist.empty())
|
||||
{
|
||||
auto [index, currEdge, lastEdge] = worklist.back();
|
||||
|
||||
// First visit
|
||||
if (currEdge == -1)
|
||||
{
|
||||
++childCount;
|
||||
if (childLimit > 0 && childLimit <= childCount)
|
||||
return TarjanResult::TooManyChildren;
|
||||
|
||||
stack.push_back(index);
|
||||
onStack[index] = true;
|
||||
|
||||
currEdge = int(edgesTy.size());
|
||||
|
||||
// Fill in edge list of this vertex
|
||||
if (TypeId ty = indexToType[index])
|
||||
visitChildren(ty, index);
|
||||
else if (TypePackId tp = indexToPack[index])
|
||||
visitChildren(tp, index);
|
||||
|
||||
lastEdge = int(edgesTy.size());
|
||||
}
|
||||
|
||||
// Visit children
|
||||
bool foundFresh = false;
|
||||
|
||||
for (; currEdge < lastEdge; currEdge++)
|
||||
{
|
||||
int childIndex = -1;
|
||||
bool fresh = false;
|
||||
|
||||
if (auto ty = edgesTy[currEdge])
|
||||
std::tie(childIndex, fresh) = indexify(ty);
|
||||
else if (auto tp = edgesTp[currEdge])
|
||||
std::tie(childIndex, fresh) = indexify(tp);
|
||||
else
|
||||
LUAU_ASSERT(false);
|
||||
|
||||
if (fresh)
|
||||
{
|
||||
// Original recursion point, update the parent continuation point and start the new element
|
||||
worklist.back() = {index, currEdge + 1, lastEdge};
|
||||
worklist.push_back({childIndex, -1, -1});
|
||||
|
||||
// We need to continue the top-level loop from the start with the new worklist element
|
||||
foundFresh = true;
|
||||
break;
|
||||
}
|
||||
else if (onStack[childIndex])
|
||||
{
|
||||
lowlink[index] = std::min(lowlink[index], childIndex);
|
||||
}
|
||||
|
||||
visitEdge(childIndex, index);
|
||||
}
|
||||
|
||||
if (foundFresh)
|
||||
continue;
|
||||
|
||||
if (lowlink[index] == index)
|
||||
{
|
||||
visitSCC(index);
|
||||
while (!stack.empty())
|
||||
{
|
||||
int popped = stack.back();
|
||||
stack.pop_back();
|
||||
onStack[popped] = false;
|
||||
if (popped == index)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
worklist.pop_back();
|
||||
|
||||
// Original return from recursion into a child
|
||||
if (!worklist.empty())
|
||||
{
|
||||
auto [parentIndex, _, parentEndEdge] = worklist.back();
|
||||
|
||||
// No need to keep child edges around
|
||||
edgesTy.resize(parentEndEdge);
|
||||
edgesTp.resize(parentEndEdge);
|
||||
|
||||
lowlink[parentIndex] = std::min(lowlink[parentIndex], lowlink[index]);
|
||||
visitEdge(index, parentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return TarjanResult::Ok;
|
||||
}
|
||||
|
||||
|
||||
void Tarjan::visitSCC_DEPRECATED(int index)
|
||||
{
|
||||
bool d = getDirty(index);
|
||||
|
||||
for (auto it = stack.rbegin(); !d && it != stack.rend(); it++)
|
||||
{
|
||||
if (TypeId ty = indexToType[*it])
|
||||
d = isDirty(ty);
|
||||
else if (TypePackId tp = indexToPack[*it])
|
||||
d = isDirty(tp);
|
||||
if (*it == index)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!d)
|
||||
return;
|
||||
|
||||
for (auto it = stack.rbegin(); it != stack.rend(); it++)
|
||||
{
|
||||
setDirty(*it, true);
|
||||
if (TypeId ty = indexToType[*it])
|
||||
foundDirty(ty);
|
||||
else if (TypePackId tp = indexToPack[*it])
|
||||
foundDirty(tp);
|
||||
if (*it == index)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TypeId> Substitution::substitute(TypeId ty)
|
||||
{
|
||||
ty = log->follow(ty);
|
||||
|
@ -2688,7 +2688,7 @@ void check(
|
||||
typeChecker.visit(sourceModule.root);
|
||||
|
||||
unfreeze(module->interfaceTypes);
|
||||
copyErrors(module->errors, module->interfaceTypes);
|
||||
copyErrors(module->errors, module->interfaceTypes, builtinTypes);
|
||||
freeze(module->interfaceTypes);
|
||||
}
|
||||
|
||||
|
@ -38,11 +38,9 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixCyclicModuleExports, false)
|
||||
LUAU_FASTFLAG(LuauOccursIsntAlwaysFailure)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckTypeguards, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false)
|
||||
LUAU_FASTFLAG(LuauParseDeclareClassIndexer)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIndexTableIntersectionStringExpr, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -3110,22 +3108,13 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
|
||||
}
|
||||
else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe)
|
||||
{
|
||||
if (!FFlag::LuauTypecheckTypeguards)
|
||||
{
|
||||
if (auto predicate = tryGetTypeGuardPredicate(expr))
|
||||
return {booleanType, {std::move(*predicate)}};
|
||||
}
|
||||
|
||||
// For these, passing expectedType is worse than simply forcing them, because their implementation
|
||||
// may inadvertently check if expectedTypes exist first and use it, instead of forceSingleton first.
|
||||
WithPredicate<TypeId> lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/true);
|
||||
WithPredicate<TypeId> rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/true);
|
||||
|
||||
if (FFlag::LuauTypecheckTypeguards)
|
||||
{
|
||||
if (auto predicate = tryGetTypeGuardPredicate(expr))
|
||||
return {booleanType, {std::move(*predicate)}};
|
||||
}
|
||||
if (auto predicate = tryGetTypeGuardPredicate(expr))
|
||||
return {booleanType, {std::move(*predicate)}};
|
||||
|
||||
PredicateVec predicates;
|
||||
|
||||
@ -3405,7 +3394,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
|
||||
reportError(TypeError{expr.location, UnknownProperty{exprType, value->value.data}});
|
||||
return errorRecoveryType(scope);
|
||||
}
|
||||
else if (FFlag::LuauIndexTableIntersectionStringExpr && get<IntersectionType>(exprType))
|
||||
else if (get<IntersectionType>(exprType))
|
||||
{
|
||||
Name name = std::string(value->value.data, value->value.size);
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/CodeGen.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <stddef.h>
|
||||
@ -16,6 +18,7 @@ constexpr uint32_t kCodeAlignment = 32;
|
||||
struct CodeAllocator
|
||||
{
|
||||
CodeAllocator(size_t blockSize, size_t maxTotalSize);
|
||||
CodeAllocator(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext);
|
||||
~CodeAllocator();
|
||||
|
||||
// Places data and code into the executable page area
|
||||
@ -24,7 +27,7 @@ struct CodeAllocator
|
||||
bool allocate(
|
||||
const uint8_t* data, size_t dataSize, const uint8_t* code, size_t codeSize, uint8_t*& result, size_t& resultSize, uint8_t*& resultCodeStart);
|
||||
|
||||
// Provided to callbacks
|
||||
// Provided to unwind info callbacks
|
||||
void* context = nullptr;
|
||||
|
||||
// Called when new block is created to create and setup the unwinding information for all the code in the block
|
||||
@ -34,12 +37,16 @@ struct CodeAllocator
|
||||
// Called to destroy unwinding information returned by 'createBlockUnwindInfo'
|
||||
void (*destroyBlockUnwindInfo)(void* context, void* unwindData) = nullptr;
|
||||
|
||||
private:
|
||||
// Unwind information can be placed inside the block with some implementation-specific reservations at the beginning
|
||||
// But to simplify block space checks, we limit the max size of all that data
|
||||
static const size_t kMaxReservedDataSize = 256;
|
||||
|
||||
bool allocateNewBlock(size_t& unwindInfoSize);
|
||||
|
||||
uint8_t* allocatePages(size_t size) const;
|
||||
void freePages(uint8_t* mem, size_t size) const;
|
||||
|
||||
// Current block we use for allocations
|
||||
uint8_t* blockPos = nullptr;
|
||||
uint8_t* blockEnd = nullptr;
|
||||
@ -50,6 +57,9 @@ struct CodeAllocator
|
||||
|
||||
size_t blockSize = 0;
|
||||
size_t maxTotalSize = 0;
|
||||
|
||||
AllocationCallback* allocationCallback = nullptr;
|
||||
void* allocationCallbackContext = nullptr;
|
||||
};
|
||||
|
||||
} // namespace CodeGen
|
||||
|
@ -18,12 +18,25 @@ enum CodeGenFlags
|
||||
CodeGen_OnlyNativeModules = 1 << 0,
|
||||
};
|
||||
|
||||
struct CompilationStats
|
||||
{
|
||||
size_t bytecodeSizeBytes = 0;
|
||||
size_t nativeCodeSizeBytes = 0;
|
||||
size_t nativeDataSizeBytes = 0;
|
||||
size_t nativeMetadataSizeBytes = 0;
|
||||
|
||||
uint32_t functionsCompiled = 0;
|
||||
};
|
||||
|
||||
using AllocationCallback = void(void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize);
|
||||
|
||||
bool isSupported();
|
||||
|
||||
void create(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext);
|
||||
void create(lua_State* L);
|
||||
|
||||
// Builds target function and all inner functions
|
||||
void compile(lua_State* L, int idx, unsigned int flags = 0);
|
||||
void compile(lua_State* L, int idx, unsigned int flags = 0, CompilationStats* stats = nullptr);
|
||||
|
||||
using AnnotatorFn = void (*)(void* context, std::string& result, int fid, int instpos);
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
@ -43,5 +45,68 @@ enum class ConditionX64 : uint8_t
|
||||
Count
|
||||
};
|
||||
|
||||
inline ConditionX64 getReverseCondition(ConditionX64 cond)
|
||||
{
|
||||
switch (cond)
|
||||
{
|
||||
case ConditionX64::Overflow:
|
||||
return ConditionX64::NoOverflow;
|
||||
case ConditionX64::NoOverflow:
|
||||
return ConditionX64::Overflow;
|
||||
case ConditionX64::Carry:
|
||||
return ConditionX64::NoCarry;
|
||||
case ConditionX64::NoCarry:
|
||||
return ConditionX64::Carry;
|
||||
case ConditionX64::Below:
|
||||
return ConditionX64::NotBelow;
|
||||
case ConditionX64::BelowEqual:
|
||||
return ConditionX64::NotBelowEqual;
|
||||
case ConditionX64::Above:
|
||||
return ConditionX64::NotAbove;
|
||||
case ConditionX64::AboveEqual:
|
||||
return ConditionX64::NotAboveEqual;
|
||||
case ConditionX64::Equal:
|
||||
return ConditionX64::NotEqual;
|
||||
case ConditionX64::Less:
|
||||
return ConditionX64::NotLess;
|
||||
case ConditionX64::LessEqual:
|
||||
return ConditionX64::NotLessEqual;
|
||||
case ConditionX64::Greater:
|
||||
return ConditionX64::NotGreater;
|
||||
case ConditionX64::GreaterEqual:
|
||||
return ConditionX64::NotGreaterEqual;
|
||||
case ConditionX64::NotBelow:
|
||||
return ConditionX64::Below;
|
||||
case ConditionX64::NotBelowEqual:
|
||||
return ConditionX64::BelowEqual;
|
||||
case ConditionX64::NotAbove:
|
||||
return ConditionX64::Above;
|
||||
case ConditionX64::NotAboveEqual:
|
||||
return ConditionX64::AboveEqual;
|
||||
case ConditionX64::NotEqual:
|
||||
return ConditionX64::Equal;
|
||||
case ConditionX64::NotLess:
|
||||
return ConditionX64::Less;
|
||||
case ConditionX64::NotLessEqual:
|
||||
return ConditionX64::LessEqual;
|
||||
case ConditionX64::NotGreater:
|
||||
return ConditionX64::Greater;
|
||||
case ConditionX64::NotGreaterEqual:
|
||||
return ConditionX64::GreaterEqual;
|
||||
case ConditionX64::Zero:
|
||||
return ConditionX64::NotZero;
|
||||
case ConditionX64::NotZero:
|
||||
return ConditionX64::Zero;
|
||||
case ConditionX64::Parity:
|
||||
return ConditionX64::NotParity;
|
||||
case ConditionX64::NotParity:
|
||||
return ConditionX64::Parity;
|
||||
case ConditionX64::Count:
|
||||
LUAU_ASSERT(!"invalid ConditionX64 value");
|
||||
}
|
||||
|
||||
return ConditionX64::Count;
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
||||
|
@ -176,7 +176,7 @@ enum class IrCmd : uint8_t
|
||||
CMP_ANY,
|
||||
|
||||
// Unconditional jump
|
||||
// A: block/vmexit
|
||||
// A: block/vmexit/undef
|
||||
JUMP,
|
||||
|
||||
// Jump if TValue is truthy
|
||||
@ -369,10 +369,8 @@ enum class IrCmd : uint8_t
|
||||
// Guard against tag mismatch
|
||||
// A, B: tag
|
||||
// C: block/vmexit/undef
|
||||
// D: bool (finish execution in VM on failure)
|
||||
// In final x64 lowering, A can also be Rn
|
||||
// When undef is specified instead of a block, execution is aborted on check failure; if D is true, execution is continued in VM interpreter
|
||||
// instead.
|
||||
// When undef is specified instead of a block, execution is aborted on check failure
|
||||
CHECK_TAG,
|
||||
|
||||
// Guard against a falsy tag+value
|
||||
@ -689,6 +687,10 @@ enum class IrOpKind : uint32_t
|
||||
VmExit,
|
||||
};
|
||||
|
||||
// VmExit uses a special value to indicate that pcpos update should be skipped
|
||||
// This is only used during type checking at function entry
|
||||
constexpr uint32_t kVmExitEntryGuardPc = (1u << 28) - 1;
|
||||
|
||||
struct IrOp
|
||||
{
|
||||
IrOpKind kind : 4;
|
||||
@ -851,6 +853,8 @@ struct IrFunction
|
||||
std::vector<IrConst> constants;
|
||||
|
||||
std::vector<BytecodeMapping> bcMapping;
|
||||
uint32_t entryBlock = 0;
|
||||
uint32_t entryLocation = 0;
|
||||
|
||||
// For each instruction, an operand that can be used to recompute the value
|
||||
std::vector<IrOp> valueRestoreOps;
|
||||
@ -1037,5 +1041,11 @@ inline int vmUpvalueOp(IrOp op)
|
||||
return op.index;
|
||||
}
|
||||
|
||||
inline uint32_t vmExitOp(IrOp op)
|
||||
{
|
||||
LUAU_ASSERT(op.kind == IrOpKind::VmExit);
|
||||
return op.index;
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "ByteUtils.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -33,13 +33,17 @@ static size_t alignToPageSize(size_t size)
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
static uint8_t* allocatePages(size_t size)
|
||||
static uint8_t* allocatePagesImpl(size_t size)
|
||||
{
|
||||
return (uint8_t*)VirtualAlloc(nullptr, alignToPageSize(size), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
||||
LUAU_ASSERT(size == alignToPageSize(size));
|
||||
|
||||
return (uint8_t*)VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
||||
}
|
||||
|
||||
static void freePages(uint8_t* mem, size_t size)
|
||||
static void freePagesImpl(uint8_t* mem, size_t size)
|
||||
{
|
||||
LUAU_ASSERT(size == alignToPageSize(size));
|
||||
|
||||
if (VirtualFree(mem, 0, MEM_RELEASE) == 0)
|
||||
LUAU_ASSERT(!"failed to deallocate block memory");
|
||||
}
|
||||
@ -62,14 +66,22 @@ static void flushInstructionCache(uint8_t* mem, size_t size)
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
static uint8_t* allocatePages(size_t size)
|
||||
static uint8_t* allocatePagesImpl(size_t size)
|
||||
{
|
||||
return (uint8_t*)mmap(nullptr, alignToPageSize(size), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
LUAU_ASSERT(size == alignToPageSize(size));
|
||||
|
||||
#ifdef __APPLE__
|
||||
return (uint8_t*)mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_JIT, -1, 0);
|
||||
#else
|
||||
return (uint8_t*)mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void freePages(uint8_t* mem, size_t size)
|
||||
static void freePagesImpl(uint8_t* mem, size_t size)
|
||||
{
|
||||
if (munmap(mem, alignToPageSize(size)) != 0)
|
||||
LUAU_ASSERT(size == alignToPageSize(size));
|
||||
|
||||
if (munmap(mem, size) != 0)
|
||||
LUAU_ASSERT(!"Failed to deallocate block memory");
|
||||
}
|
||||
|
||||
@ -94,8 +106,15 @@ namespace CodeGen
|
||||
{
|
||||
|
||||
CodeAllocator::CodeAllocator(size_t blockSize, size_t maxTotalSize)
|
||||
: blockSize(blockSize)
|
||||
, maxTotalSize(maxTotalSize)
|
||||
: CodeAllocator(blockSize, maxTotalSize, nullptr, nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
CodeAllocator::CodeAllocator(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext)
|
||||
: blockSize{blockSize}
|
||||
, maxTotalSize{maxTotalSize}
|
||||
, allocationCallback{allocationCallback}
|
||||
, allocationCallbackContext{allocationCallbackContext}
|
||||
{
|
||||
LUAU_ASSERT(blockSize > kMaxReservedDataSize);
|
||||
LUAU_ASSERT(maxTotalSize >= blockSize);
|
||||
@ -207,5 +226,29 @@ bool CodeAllocator::allocateNewBlock(size_t& unwindInfoSize)
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t* CodeAllocator::allocatePages(size_t size) const
|
||||
{
|
||||
const size_t pageAlignedSize = alignToPageSize(size);
|
||||
|
||||
uint8_t* const mem = allocatePagesImpl(pageAlignedSize);
|
||||
if (mem == nullptr)
|
||||
return nullptr;
|
||||
|
||||
if (allocationCallback)
|
||||
allocationCallback(allocationCallbackContext, nullptr, 0, mem, pageAlignedSize);
|
||||
|
||||
return mem;
|
||||
}
|
||||
|
||||
void CodeAllocator::freePages(uint8_t* mem, size_t size) const
|
||||
{
|
||||
const size_t pageAlignedSize = alignToPageSize(size);
|
||||
|
||||
if (allocationCallback)
|
||||
allocationCallback(allocationCallbackContext, mem, pageAlignedSize, nullptr, 0);
|
||||
|
||||
freePagesImpl(mem, pageAlignedSize);
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
||||
|
@ -65,7 +65,7 @@ static NativeProto createNativeProto(Proto* proto, const IrBuilder& ir)
|
||||
int sizecode = proto->sizecode;
|
||||
|
||||
uint32_t* instOffsets = new uint32_t[sizecode];
|
||||
uint32_t instTarget = ir.function.bcMapping[0].asmLocation;
|
||||
uint32_t instTarget = ir.function.entryLocation;
|
||||
|
||||
for (int i = 0; i < sizecode; i++)
|
||||
{
|
||||
@ -74,6 +74,9 @@ static NativeProto createNativeProto(Proto* proto, const IrBuilder& ir)
|
||||
instOffsets[i] = ir.function.bcMapping[i].asmLocation - instTarget;
|
||||
}
|
||||
|
||||
// Set first instruction offset to 0 so that entering this function still executes any generated entry code.
|
||||
instOffsets[0] = 0;
|
||||
|
||||
// entry target will be relocated when assembly is finalized
|
||||
return {proto, instOffsets, instTarget};
|
||||
}
|
||||
@ -202,11 +205,11 @@ bool isSupported()
|
||||
#endif
|
||||
}
|
||||
|
||||
void create(lua_State* L)
|
||||
void create(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext)
|
||||
{
|
||||
LUAU_ASSERT(isSupported());
|
||||
|
||||
std::unique_ptr<NativeState> data = std::make_unique<NativeState>();
|
||||
std::unique_ptr<NativeState> data = std::make_unique<NativeState>(allocationCallback, allocationCallbackContext);
|
||||
|
||||
#if defined(_WIN32)
|
||||
data->unwindBuilder = std::make_unique<UnwindBuilderWin>();
|
||||
@ -239,7 +242,12 @@ void create(lua_State* L)
|
||||
ecb->enter = onEnter;
|
||||
}
|
||||
|
||||
void compile(lua_State* L, int idx, unsigned int flags)
|
||||
void create(lua_State* L)
|
||||
{
|
||||
create(L, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void compile(lua_State* L, int idx, unsigned int flags, CompilationStats* stats)
|
||||
{
|
||||
LUAU_ASSERT(lua_isLfunction(L, idx));
|
||||
const TValue* func = luaA_toobject(L, idx);
|
||||
@ -318,13 +326,28 @@ void compile(lua_State* L, int idx, unsigned int flags)
|
||||
}
|
||||
}
|
||||
|
||||
for (NativeProto result : results)
|
||||
for (const NativeProto& result : results)
|
||||
{
|
||||
// the memory is now managed by VM and will be freed via onDestroyFunction
|
||||
result.p->execdata = result.execdata;
|
||||
result.p->exectarget = uintptr_t(codeStart) + result.exectarget;
|
||||
result.p->codeentry = &kCodeEntryInsn;
|
||||
}
|
||||
|
||||
if (stats != nullptr)
|
||||
{
|
||||
for (const NativeProto& result : results)
|
||||
{
|
||||
stats->bytecodeSizeBytes += result.p->sizecode * sizeof(Instruction);
|
||||
|
||||
// Account for the native -> bytecode instruction offsets mapping:
|
||||
stats->nativeMetadataSizeBytes += result.p->sizecode * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
stats->functionsCompiled += uint32_t(results.size());
|
||||
stats->nativeCodeSizeBytes += build.code.size();
|
||||
stats->nativeDataSizeBytes += build.data.size();
|
||||
}
|
||||
}
|
||||
|
||||
void setPerfLog(void* context, PerfLogFn logFn)
|
||||
|
@ -24,15 +24,6 @@ struct EntryLocations
|
||||
Label epilogueStart;
|
||||
};
|
||||
|
||||
static void emitClearNativeFlag(AssemblyBuilderA64& build)
|
||||
{
|
||||
build.ldr(x0, mem(rState, offsetof(lua_State, ci)));
|
||||
build.ldr(w1, mem(x0, offsetof(CallInfo, flags)));
|
||||
build.mov(w2, ~LUA_CALLINFO_NATIVE);
|
||||
build.and_(w1, w1, w2);
|
||||
build.str(w1, mem(x0, offsetof(CallInfo, flags)));
|
||||
}
|
||||
|
||||
static void emitExit(AssemblyBuilderA64& build, bool continueInVm)
|
||||
{
|
||||
build.mov(x0, continueInVm);
|
||||
@ -40,14 +31,21 @@ static void emitExit(AssemblyBuilderA64& build, bool continueInVm)
|
||||
build.br(x1);
|
||||
}
|
||||
|
||||
static void emitUpdatePcAndContinueInVm(AssemblyBuilderA64& build)
|
||||
static void emitUpdatePcForExit(AssemblyBuilderA64& build)
|
||||
{
|
||||
// x0 = pcpos * sizeof(Instruction)
|
||||
build.add(x0, rCode, x0);
|
||||
build.ldr(x1, mem(rState, offsetof(lua_State, ci)));
|
||||
build.str(x0, mem(x1, offsetof(CallInfo, savedpc)));
|
||||
}
|
||||
|
||||
emitExit(build, /* continueInVm */ true);
|
||||
static void emitClearNativeFlag(AssemblyBuilderA64& build)
|
||||
{
|
||||
build.ldr(x0, mem(rState, offsetof(lua_State, ci)));
|
||||
build.ldr(w1, mem(x0, offsetof(CallInfo, flags)));
|
||||
build.mov(w2, ~LUA_CALLINFO_NATIVE);
|
||||
build.and_(w1, w1, w2);
|
||||
build.str(w1, mem(x0, offsetof(CallInfo, flags)));
|
||||
}
|
||||
|
||||
static void emitInterrupt(AssemblyBuilderA64& build)
|
||||
@ -305,6 +303,11 @@ bool initHeaderFunctions(NativeState& data)
|
||||
|
||||
void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers)
|
||||
{
|
||||
if (build.logText)
|
||||
build.logAppend("; updatePcAndContinueInVm\n");
|
||||
build.setLabel(helpers.updatePcAndContinueInVm);
|
||||
emitUpdatePcForExit(build);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; exitContinueVmClearNativeFlag\n");
|
||||
build.setLabel(helpers.exitContinueVmClearNativeFlag);
|
||||
@ -320,11 +323,6 @@ void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers)
|
||||
build.setLabel(helpers.exitNoContinueVm);
|
||||
emitExit(build, /* continueInVm */ false);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; updatePcAndContinueInVm\n");
|
||||
build.setLabel(helpers.updatePcAndContinueInVm);
|
||||
emitUpdatePcAndContinueInVm(build);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; reentry\n");
|
||||
build.setLabel(helpers.reentry);
|
||||
|
@ -130,6 +130,11 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
|
||||
|
||||
build.setLabel(block.label);
|
||||
|
||||
if (blockIndex == function.entryBlock)
|
||||
{
|
||||
function.entryLocation = build.getLabelOffset(block.label);
|
||||
}
|
||||
|
||||
IrBlock& nextBlock = getNextBlock(function, sortedBlocks, dummy, i);
|
||||
|
||||
for (uint32_t index = block.start; index <= block.finish; index++)
|
||||
|
@ -180,6 +180,11 @@ bool initHeaderFunctions(NativeState& data)
|
||||
|
||||
void assembleHelpers(X64::AssemblyBuilderX64& build, ModuleHelpers& helpers)
|
||||
{
|
||||
if (build.logText)
|
||||
build.logAppend("; updatePcAndContinueInVm\n");
|
||||
build.setLabel(helpers.updatePcAndContinueInVm);
|
||||
emitUpdatePcForExit(build);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; exitContinueVmClearNativeFlag\n");
|
||||
build.setLabel(helpers.exitContinueVmClearNativeFlag);
|
||||
@ -195,11 +200,6 @@ void assembleHelpers(X64::AssemblyBuilderX64& build, ModuleHelpers& helpers)
|
||||
build.setLabel(helpers.exitNoContinueVm);
|
||||
emitExit(build, /* continueInVm */ false);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; updatePcAndContinueInVm\n");
|
||||
build.setLabel(helpers.updatePcAndContinueInVm);
|
||||
emitUpdatePcAndContinueInVm(build);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; continueCallInVm\n");
|
||||
build.setLabel(helpers.continueCallInVm);
|
||||
|
@ -1,8 +1,9 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "EmitBuiltinsX64.h"
|
||||
|
||||
#include "Luau/AssemblyBuilderX64.h"
|
||||
#include "Luau/Bytecode.h"
|
||||
|
||||
#include "Luau/AssemblyBuilderX64.h"
|
||||
#include "Luau/IrCallWrapperX64.h"
|
||||
#include "Luau/IrRegAllocX64.h"
|
||||
|
||||
|
@ -25,7 +25,7 @@ struct ModuleHelpers
|
||||
Label exitContinueVm;
|
||||
Label exitNoContinueVm;
|
||||
Label exitContinueVmClearNativeFlag;
|
||||
Label updatePcAndContinueInVm;
|
||||
Label updatePcAndContinueInVm; // no reentry
|
||||
Label return_;
|
||||
Label interrupt;
|
||||
|
||||
|
@ -328,14 +328,12 @@ void emitFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int offset, in
|
||||
emitUpdateBase(build);
|
||||
}
|
||||
|
||||
void emitUpdatePcAndContinueInVm(AssemblyBuilderX64& build)
|
||||
void emitUpdatePcForExit(AssemblyBuilderX64& build)
|
||||
{
|
||||
// edx = pcpos * sizeof(Instruction)
|
||||
build.add(rdx, sCode);
|
||||
build.mov(rax, qword[rState + offsetof(lua_State, ci)]);
|
||||
build.mov(qword[rax + offsetof(CallInfo, savedpc)], rdx);
|
||||
|
||||
emitExit(build, /* continueInVm */ true);
|
||||
}
|
||||
|
||||
void emitContinueCallInVm(AssemblyBuilderX64& build)
|
||||
|
@ -180,7 +180,7 @@ void emitUpdateBase(AssemblyBuilderX64& build);
|
||||
void emitInterrupt(AssemblyBuilderX64& build);
|
||||
void emitFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int offset, int pcpos);
|
||||
|
||||
void emitUpdatePcAndContinueInVm(AssemblyBuilderX64& build);
|
||||
void emitUpdatePcForExit(AssemblyBuilderX64& build);
|
||||
void emitContinueCallInVm(AssemblyBuilderX64& build);
|
||||
|
||||
void emitReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers);
|
||||
|
@ -186,75 +186,12 @@ void requireVariadicSequence(RegisterSet& sourceRs, const RegisterSet& defRs, ui
|
||||
}
|
||||
}
|
||||
|
||||
static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& block, RegisterSet& defRs, std::bitset<256>& capturedRegs)
|
||||
template<typename T>
|
||||
static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrBlock& block)
|
||||
{
|
||||
RegisterSet inRs;
|
||||
|
||||
auto def = [&](IrOp op, int offset = 0) {
|
||||
defRs.regs.set(vmRegOp(op) + offset, true);
|
||||
};
|
||||
|
||||
auto use = [&](IrOp op, int offset = 0) {
|
||||
if (!defRs.regs.test(vmRegOp(op) + offset))
|
||||
inRs.regs.set(vmRegOp(op) + offset, true);
|
||||
};
|
||||
|
||||
auto maybeDef = [&](IrOp op) {
|
||||
if (op.kind == IrOpKind::VmReg)
|
||||
defRs.regs.set(vmRegOp(op), true);
|
||||
};
|
||||
|
||||
auto maybeUse = [&](IrOp op) {
|
||||
if (op.kind == IrOpKind::VmReg)
|
||||
{
|
||||
if (!defRs.regs.test(vmRegOp(op)))
|
||||
inRs.regs.set(vmRegOp(op), 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];
|
||||
IrInst& inst = function.instructions[instIdx];
|
||||
|
||||
// For correct analysis, all instruction uses must be handled before handling the definitions
|
||||
switch (inst.cmd)
|
||||
@ -264,7 +201,7 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
||||
case IrCmd::LOAD_DOUBLE:
|
||||
case IrCmd::LOAD_INT:
|
||||
case IrCmd::LOAD_TVALUE:
|
||||
maybeUse(inst.a); // Argument can also be a VmConst
|
||||
visitor.maybeUse(inst.a); // Argument can also be a VmConst
|
||||
break;
|
||||
case IrCmd::STORE_TAG:
|
||||
case IrCmd::STORE_POINTER:
|
||||
@ -272,63 +209,63 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
||||
case IrCmd::STORE_INT:
|
||||
case IrCmd::STORE_VECTOR:
|
||||
case IrCmd::STORE_TVALUE:
|
||||
maybeDef(inst.a); // Argument can also be a pointer value
|
||||
visitor.maybeDef(inst.a); // Argument can also be a pointer value
|
||||
break;
|
||||
case IrCmd::CMP_ANY:
|
||||
use(inst.a);
|
||||
use(inst.b);
|
||||
visitor.use(inst.a);
|
||||
visitor.use(inst.b);
|
||||
break;
|
||||
case IrCmd::JUMP_IF_TRUTHY:
|
||||
case IrCmd::JUMP_IF_FALSY:
|
||||
use(inst.a);
|
||||
visitor.use(inst.a);
|
||||
break;
|
||||
// A <- B, C
|
||||
case IrCmd::DO_ARITH:
|
||||
case IrCmd::GET_TABLE:
|
||||
use(inst.b);
|
||||
maybeUse(inst.c); // Argument can also be a VmConst
|
||||
visitor.use(inst.b);
|
||||
visitor.maybeUse(inst.c); // Argument can also be a VmConst
|
||||
|
||||
def(inst.a);
|
||||
visitor.def(inst.a);
|
||||
break;
|
||||
case IrCmd::SET_TABLE:
|
||||
use(inst.a);
|
||||
use(inst.b);
|
||||
maybeUse(inst.c); // Argument can also be a VmConst
|
||||
visitor.use(inst.a);
|
||||
visitor.use(inst.b);
|
||||
visitor.maybeUse(inst.c); // Argument can also be a VmConst
|
||||
break;
|
||||
// A <- B
|
||||
case IrCmd::DO_LEN:
|
||||
use(inst.b);
|
||||
visitor.use(inst.b);
|
||||
|
||||
def(inst.a);
|
||||
visitor.def(inst.a);
|
||||
break;
|
||||
case IrCmd::GET_IMPORT:
|
||||
def(inst.a);
|
||||
visitor.def(inst.a);
|
||||
break;
|
||||
case IrCmd::CONCAT:
|
||||
useRange(vmRegOp(inst.a), function.uintOp(inst.b));
|
||||
visitor.useRange(vmRegOp(inst.a), function.uintOp(inst.b));
|
||||
|
||||
defRange(vmRegOp(inst.a), function.uintOp(inst.b));
|
||||
visitor.defRange(vmRegOp(inst.a), function.uintOp(inst.b));
|
||||
break;
|
||||
case IrCmd::GET_UPVALUE:
|
||||
def(inst.a);
|
||||
visitor.def(inst.a);
|
||||
break;
|
||||
case IrCmd::SET_UPVALUE:
|
||||
use(inst.b);
|
||||
visitor.use(inst.b);
|
||||
break;
|
||||
case IrCmd::PREPARE_FORN:
|
||||
use(inst.a);
|
||||
use(inst.b);
|
||||
use(inst.c);
|
||||
visitor.use(inst.a);
|
||||
visitor.use(inst.b);
|
||||
visitor.use(inst.c);
|
||||
|
||||
def(inst.a);
|
||||
def(inst.b);
|
||||
def(inst.c);
|
||||
visitor.def(inst.a);
|
||||
visitor.def(inst.b);
|
||||
visitor.def(inst.c);
|
||||
break;
|
||||
case IrCmd::INTERRUPT:
|
||||
break;
|
||||
case IrCmd::BARRIER_OBJ:
|
||||
case IrCmd::BARRIER_TABLE_FORWARD:
|
||||
use(inst.b);
|
||||
visitor.use(inst.b);
|
||||
break;
|
||||
case IrCmd::CLOSE_UPVALS:
|
||||
// Closing an upvalue should be counted as a register use (it copies the fresh register value)
|
||||
@ -336,23 +273,23 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
||||
// 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);
|
||||
visitor.maybeUse(inst.a);
|
||||
|
||||
if (function.uintOp(inst.b) == 1)
|
||||
capturedRegs.set(vmRegOp(inst.a), true);
|
||||
visitor.capture(vmRegOp(inst.a));
|
||||
break;
|
||||
case IrCmd::SETLIST:
|
||||
use(inst.b);
|
||||
useRange(vmRegOp(inst.c), function.intOp(inst.d));
|
||||
visitor.use(inst.b);
|
||||
visitor.useRange(vmRegOp(inst.c), function.intOp(inst.d));
|
||||
break;
|
||||
case IrCmd::CALL:
|
||||
use(inst.a);
|
||||
useRange(vmRegOp(inst.a) + 1, function.intOp(inst.b));
|
||||
visitor.use(inst.a);
|
||||
visitor.useRange(vmRegOp(inst.a) + 1, function.intOp(inst.b));
|
||||
|
||||
defRange(vmRegOp(inst.a), function.intOp(inst.c));
|
||||
visitor.defRange(vmRegOp(inst.a), function.intOp(inst.c));
|
||||
break;
|
||||
case IrCmd::RETURN:
|
||||
useRange(vmRegOp(inst.a), function.intOp(inst.b));
|
||||
visitor.useRange(vmRegOp(inst.a), function.intOp(inst.b));
|
||||
break;
|
||||
|
||||
// TODO: FASTCALL is more restrictive than INVOKE_FASTCALL; we should either determine the exact semantics, or rework it
|
||||
@ -364,89 +301,89 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
||||
{
|
||||
LUAU_ASSERT(inst.d.kind == IrOpKind::VmReg && vmRegOp(inst.d) == vmRegOp(inst.c) + 1);
|
||||
|
||||
useRange(vmRegOp(inst.c), count);
|
||||
visitor.useRange(vmRegOp(inst.c), count);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (count >= 1)
|
||||
use(inst.c);
|
||||
visitor.use(inst.c);
|
||||
|
||||
if (count >= 2)
|
||||
maybeUse(inst.d); // Argument can also be a VmConst
|
||||
visitor.maybeUse(inst.d); // Argument can also be a VmConst
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
useVarargs(vmRegOp(inst.c));
|
||||
visitor.useVarargs(vmRegOp(inst.c));
|
||||
}
|
||||
|
||||
// Multiple return sequences (count == -1) are defined by ADJUST_STACK_TO_REG
|
||||
if (int count = function.intOp(inst.f); count != -1)
|
||||
defRange(vmRegOp(inst.b), count);
|
||||
visitor.defRange(vmRegOp(inst.b), count);
|
||||
break;
|
||||
case IrCmd::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);
|
||||
visitor.use(inst.a, 1);
|
||||
visitor.use(inst.a, 2);
|
||||
|
||||
def(inst.a, 2);
|
||||
defRange(vmRegOp(inst.a) + 3, function.intOp(inst.b));
|
||||
visitor.def(inst.a, 2);
|
||||
visitor.defRange(vmRegOp(inst.a) + 3, function.intOp(inst.b));
|
||||
break;
|
||||
case IrCmd::FORGLOOP_FALLBACK:
|
||||
useRange(vmRegOp(inst.a), 3);
|
||||
visitor.useRange(vmRegOp(inst.a), 3);
|
||||
|
||||
def(inst.a, 2);
|
||||
defRange(vmRegOp(inst.a) + 3, uint8_t(function.intOp(inst.b))); // ignore most significant bit
|
||||
visitor.def(inst.a, 2);
|
||||
visitor.defRange(vmRegOp(inst.a) + 3, uint8_t(function.intOp(inst.b))); // ignore most significant bit
|
||||
break;
|
||||
case IrCmd::FORGPREP_XNEXT_FALLBACK:
|
||||
use(inst.b);
|
||||
visitor.use(inst.b);
|
||||
break;
|
||||
case IrCmd::FALLBACK_GETGLOBAL:
|
||||
def(inst.b);
|
||||
visitor.def(inst.b);
|
||||
break;
|
||||
case IrCmd::FALLBACK_SETGLOBAL:
|
||||
use(inst.b);
|
||||
visitor.use(inst.b);
|
||||
break;
|
||||
case IrCmd::FALLBACK_GETTABLEKS:
|
||||
use(inst.c);
|
||||
visitor.use(inst.c);
|
||||
|
||||
def(inst.b);
|
||||
visitor.def(inst.b);
|
||||
break;
|
||||
case IrCmd::FALLBACK_SETTABLEKS:
|
||||
use(inst.b);
|
||||
use(inst.c);
|
||||
visitor.use(inst.b);
|
||||
visitor.use(inst.c);
|
||||
break;
|
||||
case IrCmd::FALLBACK_NAMECALL:
|
||||
use(inst.c);
|
||||
visitor.use(inst.c);
|
||||
|
||||
defRange(vmRegOp(inst.b), 2);
|
||||
visitor.defRange(vmRegOp(inst.b), 2);
|
||||
break;
|
||||
case IrCmd::FALLBACK_PREPVARARGS:
|
||||
// No effect on explicitly referenced registers
|
||||
break;
|
||||
case IrCmd::FALLBACK_GETVARARGS:
|
||||
defRange(vmRegOp(inst.b), function.intOp(inst.c));
|
||||
visitor.defRange(vmRegOp(inst.b), function.intOp(inst.c));
|
||||
break;
|
||||
case IrCmd::FALLBACK_DUPCLOSURE:
|
||||
def(inst.b);
|
||||
visitor.def(inst.b);
|
||||
break;
|
||||
case IrCmd::FALLBACK_FORGPREP:
|
||||
use(inst.b);
|
||||
visitor.use(inst.b);
|
||||
|
||||
defRange(vmRegOp(inst.b), 3);
|
||||
visitor.defRange(vmRegOp(inst.b), 3);
|
||||
break;
|
||||
case IrCmd::ADJUST_STACK_TO_REG:
|
||||
defRange(vmRegOp(inst.a), -1);
|
||||
visitor.defRange(vmRegOp(inst.a), -1);
|
||||
break;
|
||||
case IrCmd::ADJUST_STACK_TO_TOP:
|
||||
// While this can be considered to be a vararg consumer, it is already handled in fastcall instructions
|
||||
break;
|
||||
case IrCmd::GET_TYPEOF:
|
||||
use(inst.a);
|
||||
visitor.use(inst.a);
|
||||
break;
|
||||
|
||||
case IrCmd::FINDUPVAL:
|
||||
use(inst.a);
|
||||
visitor.use(inst.a);
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -460,8 +397,102 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inRs;
|
||||
struct BlockVmRegLiveInComputation
|
||||
{
|
||||
BlockVmRegLiveInComputation(RegisterSet& defRs, std::bitset<256>& capturedRegs)
|
||||
: defRs(defRs)
|
||||
, capturedRegs(capturedRegs)
|
||||
{
|
||||
}
|
||||
|
||||
RegisterSet& defRs;
|
||||
std::bitset<256>& capturedRegs;
|
||||
|
||||
RegisterSet inRs;
|
||||
|
||||
void def(IrOp op, int offset = 0)
|
||||
{
|
||||
defRs.regs.set(vmRegOp(op) + offset, true);
|
||||
}
|
||||
|
||||
void use(IrOp op, int offset = 0)
|
||||
{
|
||||
if (!defRs.regs.test(vmRegOp(op) + offset))
|
||||
inRs.regs.set(vmRegOp(op) + offset, true);
|
||||
}
|
||||
|
||||
void maybeDef(IrOp op)
|
||||
{
|
||||
if (op.kind == IrOpKind::VmReg)
|
||||
defRs.regs.set(vmRegOp(op), true);
|
||||
}
|
||||
|
||||
void maybeUse(IrOp op)
|
||||
{
|
||||
if (op.kind == IrOpKind::VmReg)
|
||||
{
|
||||
if (!defRs.regs.test(vmRegOp(op)))
|
||||
inRs.regs.set(vmRegOp(op), true);
|
||||
}
|
||||
}
|
||||
|
||||
void defVarargs(uint8_t varargStart)
|
||||
{
|
||||
defRs.varargSeq = true;
|
||||
defRs.varargStart = varargStart;
|
||||
}
|
||||
|
||||
void useVarargs(uint8_t varargStart)
|
||||
{
|
||||
requireVariadicSequence(inRs, defRs, varargStart);
|
||||
|
||||
// Variadic sequence has been consumed
|
||||
defRs.varargSeq = false;
|
||||
defRs.varargStart = 0;
|
||||
}
|
||||
|
||||
void defRange(int start, int count)
|
||||
{
|
||||
if (count == -1)
|
||||
{
|
||||
defVarargs(start);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = start; i < start + count; i++)
|
||||
defRs.regs.set(i, true);
|
||||
}
|
||||
}
|
||||
|
||||
void 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void capture(int reg)
|
||||
{
|
||||
capturedRegs.set(reg, true);
|
||||
}
|
||||
};
|
||||
|
||||
static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& block, RegisterSet& defRs, std::bitset<256>& capturedRegs)
|
||||
{
|
||||
BlockVmRegLiveInComputation visitor(defRs, capturedRegs);
|
||||
visitVmRegDefsUses(visitor, function, block);
|
||||
return visitor.inRs;
|
||||
}
|
||||
|
||||
// The algorithm used here is commonly known as backwards data-flow analysis.
|
||||
|
@ -1,7 +1,7 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/IrBuilder.h"
|
||||
|
||||
#include "Luau/IrAnalysis.h"
|
||||
#include "Luau/IrData.h"
|
||||
#include "Luau/IrUtils.h"
|
||||
|
||||
#include "IrTranslation.h"
|
||||
@ -22,10 +22,14 @@ IrBuilder::IrBuilder()
|
||||
{
|
||||
}
|
||||
|
||||
static bool hasTypedParameters(Proto* proto)
|
||||
{
|
||||
return proto->typeinfo && proto->numparams != 0;
|
||||
}
|
||||
|
||||
static void buildArgumentTypeChecks(IrBuilder& build, Proto* proto)
|
||||
{
|
||||
if (!proto->typeinfo || proto->numparams == 0)
|
||||
return;
|
||||
LUAU_ASSERT(hasTypedParameters(proto));
|
||||
|
||||
for (int i = 0; i < proto->numparams; ++i)
|
||||
{
|
||||
@ -53,31 +57,31 @@ static void buildArgumentTypeChecks(IrBuilder& build, Proto* proto)
|
||||
switch (tag)
|
||||
{
|
||||
case LBC_TYPE_NIL:
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TNIL), build.undef(), build.constInt(1));
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TNIL), build.vmExit(kVmExitEntryGuardPc));
|
||||
break;
|
||||
case LBC_TYPE_BOOLEAN:
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TBOOLEAN), build.undef(), build.constInt(1));
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TBOOLEAN), build.vmExit(kVmExitEntryGuardPc));
|
||||
break;
|
||||
case LBC_TYPE_NUMBER:
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TNUMBER), build.undef(), build.constInt(1));
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TNUMBER), build.vmExit(kVmExitEntryGuardPc));
|
||||
break;
|
||||
case LBC_TYPE_STRING:
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TSTRING), build.undef(), build.constInt(1));
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TSTRING), build.vmExit(kVmExitEntryGuardPc));
|
||||
break;
|
||||
case LBC_TYPE_TABLE:
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TTABLE), build.undef(), build.constInt(1));
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TTABLE), build.vmExit(kVmExitEntryGuardPc));
|
||||
break;
|
||||
case LBC_TYPE_FUNCTION:
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TFUNCTION), build.undef(), build.constInt(1));
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TFUNCTION), build.vmExit(kVmExitEntryGuardPc));
|
||||
break;
|
||||
case LBC_TYPE_THREAD:
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TTHREAD), build.undef(), build.constInt(1));
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TTHREAD), build.vmExit(kVmExitEntryGuardPc));
|
||||
break;
|
||||
case LBC_TYPE_USERDATA:
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TUSERDATA), build.undef(), build.constInt(1));
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TUSERDATA), build.vmExit(kVmExitEntryGuardPc));
|
||||
break;
|
||||
case LBC_TYPE_VECTOR:
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TVECTOR), build.undef(), build.constInt(1));
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TVECTOR), build.vmExit(kVmExitEntryGuardPc));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -103,11 +107,28 @@ void IrBuilder::buildFunctionIr(Proto* proto)
|
||||
function.proto = proto;
|
||||
function.variadic = proto->is_vararg != 0;
|
||||
|
||||
// Reserve entry block
|
||||
bool generateTypeChecks = hasTypedParameters(proto);
|
||||
IrOp entry = generateTypeChecks ? block(IrBlockKind::Internal) : IrOp{};
|
||||
|
||||
// Rebuild original control flow blocks
|
||||
rebuildBytecodeBasicBlocks(proto);
|
||||
|
||||
function.bcMapping.resize(proto->sizecode, {~0u, ~0u});
|
||||
|
||||
if (generateTypeChecks)
|
||||
{
|
||||
beginBlock(entry);
|
||||
buildArgumentTypeChecks(*this, proto);
|
||||
inst(IrCmd::JUMP, blockAtInst(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
entry = blockAtInst(0);
|
||||
}
|
||||
|
||||
function.entryBlock = entry.index;
|
||||
|
||||
// Translate all instructions to IR inside blocks
|
||||
for (int i = 0; i < proto->sizecode;)
|
||||
{
|
||||
@ -123,9 +144,6 @@ void IrBuilder::buildFunctionIr(Proto* proto)
|
||||
if (instIndexToBlock[i] != kNoAssociatedBlockIndex)
|
||||
beginBlock(blockAtInst(i));
|
||||
|
||||
if (i == 0)
|
||||
buildArgumentTypeChecks(*this, proto);
|
||||
|
||||
// We skip dead bytecode instructions when they appear after block was already terminated
|
||||
if (!inTerminatedBlock)
|
||||
{
|
||||
|
@ -402,7 +402,7 @@ void toString(IrToStringContext& ctx, IrOp op)
|
||||
append(ctx.result, "U%d", vmUpvalueOp(op));
|
||||
break;
|
||||
case IrOpKind::VmExit:
|
||||
append(ctx.result, "exit(%d)", op.index);
|
||||
append(ctx.result, "exit(%d)", vmExitOp(op));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "IrLoweringA64.h"
|
||||
|
||||
#include "Luau/CodeGen.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/IrAnalysis.h"
|
||||
#include "Luau/IrDump.h"
|
||||
#include "Luau/IrData.h"
|
||||
#include "Luau/IrUtils.h"
|
||||
|
||||
#include "EmitCommonA64.h"
|
||||
@ -189,7 +187,7 @@ IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers,
|
||||
});
|
||||
}
|
||||
|
||||
void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
|
||||
{
|
||||
valueTracker.beforeInstLowering(inst);
|
||||
|
||||
@ -566,7 +564,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
break;
|
||||
}
|
||||
case IrCmd::JUMP:
|
||||
if (inst.a.kind == IrOpKind::VmExit)
|
||||
if (inst.a.kind == IrOpKind::Undef || inst.a.kind == IrOpKind::VmExit)
|
||||
{
|
||||
Label fresh;
|
||||
build.b(getTargetLabel(inst.a, fresh));
|
||||
@ -1047,9 +1045,8 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
break;
|
||||
case IrCmd::CHECK_TAG:
|
||||
{
|
||||
bool continueInVm = (inst.d.kind == IrOpKind::Constant && intOp(inst.d));
|
||||
Label fresh; // used when guard aborts execution or jumps to a VM exit
|
||||
Label& fail = continueInVm ? helpers.exitContinueVmClearNativeFlag : getTargetLabel(inst.c, fresh);
|
||||
Label& fail = getTargetLabel(inst.c, fresh);
|
||||
|
||||
// To support DebugLuauAbortingChecks, CHECK_TAG with VmReg has to be handled
|
||||
RegisterA64 tag = inst.a.kind == IrOpKind::VmReg ? regs.allocTemp(KindA64::w) : regOp(inst.a);
|
||||
@ -1066,8 +1063,8 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
build.cmp(tag, tagOp(inst.b));
|
||||
build.b(ConditionA64::NotEqual, fail);
|
||||
}
|
||||
if (!continueInVm)
|
||||
finalizeTargetLabel(inst.c, fresh);
|
||||
|
||||
finalizeTargetLabel(inst.c, fresh);
|
||||
break;
|
||||
}
|
||||
case IrCmd::CHECK_TRUTHY:
|
||||
@ -1862,7 +1859,10 @@ void IrLoweringA64::finishFunction()
|
||||
|
||||
for (ExitHandler& handler : exitHandlers)
|
||||
{
|
||||
LUAU_ASSERT(handler.pcpos != kVmExitEntryGuardPc);
|
||||
|
||||
build.setLabel(handler.self);
|
||||
|
||||
build.mov(x0, handler.pcpos * sizeof(Instruction));
|
||||
build.b(helpers.updatePcAndContinueInVm);
|
||||
}
|
||||
@ -1873,12 +1873,12 @@ bool IrLoweringA64::hasError() const
|
||||
return error || regs.error;
|
||||
}
|
||||
|
||||
bool IrLoweringA64::isFallthroughBlock(IrBlock target, IrBlock next)
|
||||
bool IrLoweringA64::isFallthroughBlock(const IrBlock& target, const IrBlock& next)
|
||||
{
|
||||
return target.start == next.start;
|
||||
}
|
||||
|
||||
void IrLoweringA64::jumpOrFallthrough(IrBlock& target, IrBlock& next)
|
||||
void IrLoweringA64::jumpOrFallthrough(IrBlock& target, const IrBlock& next)
|
||||
{
|
||||
if (!isFallthroughBlock(target, next))
|
||||
build.b(target.label);
|
||||
@ -1891,7 +1891,11 @@ Label& IrLoweringA64::getTargetLabel(IrOp op, Label& fresh)
|
||||
|
||||
if (op.kind == IrOpKind::VmExit)
|
||||
{
|
||||
if (uint32_t* index = exitHandlerMap.find(op.index))
|
||||
// Special exit case that doesn't have to update pcpos
|
||||
if (vmExitOp(op) == kVmExitEntryGuardPc)
|
||||
return helpers.exitContinueVmClearNativeFlag;
|
||||
|
||||
if (uint32_t* index = exitHandlerMap.find(vmExitOp(op)))
|
||||
return exitHandlers[*index].self;
|
||||
|
||||
return fresh;
|
||||
@ -1906,10 +1910,10 @@ void IrLoweringA64::finalizeTargetLabel(IrOp op, Label& fresh)
|
||||
{
|
||||
emitAbort(build, fresh);
|
||||
}
|
||||
else if (op.kind == IrOpKind::VmExit && fresh.id != 0)
|
||||
else if (op.kind == IrOpKind::VmExit && fresh.id != 0 && fresh.id != helpers.exitContinueVmClearNativeFlag.id)
|
||||
{
|
||||
exitHandlerMap[op.index] = uint32_t(exitHandlers.size());
|
||||
exitHandlers.push_back({fresh, op.index});
|
||||
exitHandlerMap[vmExitOp(op)] = uint32_t(exitHandlers.size());
|
||||
exitHandlers.push_back({fresh, vmExitOp(op)});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,14 +25,14 @@ struct IrLoweringA64
|
||||
{
|
||||
IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, IrFunction& function);
|
||||
|
||||
void lowerInst(IrInst& inst, uint32_t index, IrBlock& next);
|
||||
void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next);
|
||||
void finishBlock();
|
||||
void finishFunction();
|
||||
|
||||
bool hasError() const;
|
||||
|
||||
bool isFallthroughBlock(IrBlock target, IrBlock next);
|
||||
void jumpOrFallthrough(IrBlock& target, IrBlock& next);
|
||||
bool isFallthroughBlock(const IrBlock& target, const IrBlock& next);
|
||||
void jumpOrFallthrough(IrBlock& target, const IrBlock& next);
|
||||
|
||||
Label& getTargetLabel(IrOp op, Label& fresh);
|
||||
void finalizeTargetLabel(IrOp op, Label& fresh);
|
||||
|
@ -1,19 +1,19 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "IrLoweringX64.h"
|
||||
|
||||
#include "Luau/CodeGen.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/IrAnalysis.h"
|
||||
#include "Luau/IrCallWrapperX64.h"
|
||||
#include "Luau/IrDump.h"
|
||||
#include "Luau/IrData.h"
|
||||
#include "Luau/IrUtils.h"
|
||||
|
||||
#include "Luau/IrCallWrapperX64.h"
|
||||
|
||||
#include "EmitBuiltinsX64.h"
|
||||
#include "EmitCommonX64.h"
|
||||
#include "EmitInstructionX64.h"
|
||||
#include "NativeState.h"
|
||||
|
||||
#include "lstate.h"
|
||||
#include "lgc.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -59,7 +59,7 @@ void IrLoweringX64::storeDoubleAsFloat(OperandX64 dst, IrOp src)
|
||||
build.vmovss(dst, tmp.reg);
|
||||
}
|
||||
|
||||
void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
|
||||
{
|
||||
regs.currInstIdx = index;
|
||||
|
||||
@ -565,24 +565,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
break;
|
||||
}
|
||||
case IrCmd::JUMP:
|
||||
if (inst.a.kind == IrOpKind::VmExit)
|
||||
{
|
||||
if (uint32_t* index = exitHandlerMap.find(inst.a.index))
|
||||
{
|
||||
build.jmp(exitHandlers[*index].self);
|
||||
}
|
||||
else
|
||||
{
|
||||
Label self;
|
||||
build.jmp(self);
|
||||
exitHandlerMap[inst.a.index] = uint32_t(exitHandlers.size());
|
||||
exitHandlers.push_back({self, inst.a.index});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
jumpOrFallthrough(blockOp(inst.a), next);
|
||||
}
|
||||
jumpOrAbortOnUndef(inst.a, next);
|
||||
break;
|
||||
case IrCmd::JUMP_IF_TRUTHY:
|
||||
jumpIfTruthy(build, vmRegOp(inst.a), labelOp(inst.b), labelOp(inst.c));
|
||||
@ -975,12 +958,9 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
callPrepareForN(regs, build, vmRegOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c));
|
||||
break;
|
||||
case IrCmd::CHECK_TAG:
|
||||
{
|
||||
bool continueInVm = (inst.d.kind == IrOpKind::Constant && intOp(inst.d));
|
||||
build.cmp(memRegTagOp(inst.a), tagOp(inst.b));
|
||||
jumpOrAbortOnUndef(ConditionX64::NotEqual, ConditionX64::Equal, inst.c, continueInVm);
|
||||
jumpOrAbortOnUndef(ConditionX64::NotEqual, inst.c, next);
|
||||
break;
|
||||
}
|
||||
case IrCmd::CHECK_TRUTHY:
|
||||
{
|
||||
// Constant tags which don't require boolean value check should've been removed in constant folding
|
||||
@ -992,7 +972,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
{
|
||||
// Fail to fallback on 'nil' (falsy)
|
||||
build.cmp(memRegTagOp(inst.a), LUA_TNIL);
|
||||
jumpOrAbortOnUndef(ConditionX64::Equal, ConditionX64::NotEqual, inst.c);
|
||||
jumpOrAbortOnUndef(ConditionX64::Equal, inst.c, next);
|
||||
|
||||
// Skip value test if it's not a boolean (truthy)
|
||||
build.cmp(memRegTagOp(inst.a), LUA_TBOOLEAN);
|
||||
@ -1001,7 +981,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
|
||||
// fail to fallback on 'false' boolean value (falsy)
|
||||
build.cmp(memRegUintOp(inst.b), 0);
|
||||
jumpOrAbortOnUndef(ConditionX64::Equal, ConditionX64::NotEqual, inst.c);
|
||||
jumpOrAbortOnUndef(ConditionX64::Equal, inst.c, next);
|
||||
|
||||
if (inst.a.kind != IrOpKind::Constant)
|
||||
build.setLabel(skip);
|
||||
@ -1009,11 +989,11 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
}
|
||||
case IrCmd::CHECK_READONLY:
|
||||
build.cmp(byte[regOp(inst.a) + offsetof(Table, readonly)], 0);
|
||||
jumpOrAbortOnUndef(ConditionX64::NotEqual, ConditionX64::Equal, inst.b);
|
||||
jumpOrAbortOnUndef(ConditionX64::NotEqual, inst.b, next);
|
||||
break;
|
||||
case IrCmd::CHECK_NO_METATABLE:
|
||||
build.cmp(qword[regOp(inst.a) + offsetof(Table, metatable)], 0);
|
||||
jumpOrAbortOnUndef(ConditionX64::NotEqual, ConditionX64::Equal, inst.b);
|
||||
jumpOrAbortOnUndef(ConditionX64::NotEqual, inst.b, next);
|
||||
break;
|
||||
case IrCmd::CHECK_SAFE_ENV:
|
||||
{
|
||||
@ -1023,7 +1003,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
build.mov(tmp.reg, qword[tmp.reg + offsetof(Closure, env)]);
|
||||
build.cmp(byte[tmp.reg + offsetof(Table, safeenv)], 0);
|
||||
|
||||
jumpOrAbortOnUndef(ConditionX64::Equal, ConditionX64::NotEqual, inst.a);
|
||||
jumpOrAbortOnUndef(ConditionX64::Equal, inst.a, next);
|
||||
break;
|
||||
}
|
||||
case IrCmd::CHECK_ARRAY_SIZE:
|
||||
@ -1034,7 +1014,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
else
|
||||
LUAU_ASSERT(!"Unsupported instruction form");
|
||||
|
||||
jumpOrAbortOnUndef(ConditionX64::BelowEqual, ConditionX64::NotBelowEqual, inst.c);
|
||||
jumpOrAbortOnUndef(ConditionX64::BelowEqual, inst.c, next);
|
||||
break;
|
||||
case IrCmd::JUMP_SLOT_MATCH:
|
||||
case IrCmd::CHECK_SLOT_MATCH:
|
||||
@ -1080,7 +1060,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||
|
||||
build.mov(tmp.reg, dword[regOp(inst.a) + offsetof(LuaNode, key) + kOffsetOfTKeyTagNext]);
|
||||
build.shr(tmp.reg, kTKeyTagBits);
|
||||
jumpOrAbortOnUndef(ConditionX64::NotZero, ConditionX64::Zero, inst.b);
|
||||
jumpOrAbortOnUndef(ConditionX64::NotZero, inst.b, next);
|
||||
break;
|
||||
}
|
||||
case IrCmd::INTERRUPT:
|
||||
@ -1583,7 +1563,10 @@ void IrLoweringX64::finishFunction()
|
||||
|
||||
for (ExitHandler& handler : exitHandlers)
|
||||
{
|
||||
LUAU_ASSERT(handler.pcpos != kVmExitEntryGuardPc);
|
||||
|
||||
build.setLabel(handler.self);
|
||||
|
||||
build.mov(edx, handler.pcpos * sizeof(Instruction));
|
||||
build.jmp(helpers.updatePcAndContinueInVm);
|
||||
}
|
||||
@ -1598,50 +1581,81 @@ bool IrLoweringX64::hasError() const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IrLoweringX64::isFallthroughBlock(IrBlock target, IrBlock next)
|
||||
bool IrLoweringX64::isFallthroughBlock(const IrBlock& target, const IrBlock& next)
|
||||
{
|
||||
return target.start == next.start;
|
||||
}
|
||||
|
||||
void IrLoweringX64::jumpOrFallthrough(IrBlock& target, IrBlock& next)
|
||||
Label& IrLoweringX64::getTargetLabel(IrOp op, Label& fresh)
|
||||
{
|
||||
if (op.kind == IrOpKind::Undef)
|
||||
return fresh;
|
||||
|
||||
if (op.kind == IrOpKind::VmExit)
|
||||
{
|
||||
// Special exit case that doesn't have to update pcpos
|
||||
if (vmExitOp(op) == kVmExitEntryGuardPc)
|
||||
return helpers.exitContinueVmClearNativeFlag;
|
||||
|
||||
if (uint32_t* index = exitHandlerMap.find(vmExitOp(op)))
|
||||
return exitHandlers[*index].self;
|
||||
|
||||
return fresh;
|
||||
}
|
||||
|
||||
return labelOp(op);
|
||||
}
|
||||
|
||||
void IrLoweringX64::finalizeTargetLabel(IrOp op, Label& fresh)
|
||||
{
|
||||
if (op.kind == IrOpKind::VmExit && fresh.id != 0 && fresh.id != helpers.exitContinueVmClearNativeFlag.id)
|
||||
{
|
||||
exitHandlerMap[vmExitOp(op)] = uint32_t(exitHandlers.size());
|
||||
exitHandlers.push_back({fresh, vmExitOp(op)});
|
||||
}
|
||||
}
|
||||
|
||||
void IrLoweringX64::jumpOrFallthrough(IrBlock& target, const IrBlock& next)
|
||||
{
|
||||
if (!isFallthroughBlock(target, next))
|
||||
build.jmp(target.label);
|
||||
}
|
||||
|
||||
void IrLoweringX64::jumpOrAbortOnUndef(ConditionX64 cond, ConditionX64 condInverse, IrOp targetOrUndef, bool continueInVm)
|
||||
void IrLoweringX64::jumpOrAbortOnUndef(ConditionX64 cond, IrOp target, const IrBlock& next)
|
||||
{
|
||||
if (targetOrUndef.kind == IrOpKind::Undef)
|
||||
{
|
||||
if (continueInVm)
|
||||
{
|
||||
build.jcc(cond, helpers.exitContinueVmClearNativeFlag);
|
||||
return;
|
||||
}
|
||||
Label fresh;
|
||||
Label& label = getTargetLabel(target, fresh);
|
||||
|
||||
Label skip;
|
||||
build.jcc(condInverse, skip);
|
||||
build.ud2();
|
||||
build.setLabel(skip);
|
||||
}
|
||||
else if (targetOrUndef.kind == IrOpKind::VmExit)
|
||||
if (target.kind == IrOpKind::Undef)
|
||||
{
|
||||
if (uint32_t* index = exitHandlerMap.find(targetOrUndef.index))
|
||||
if (cond == ConditionX64::Count)
|
||||
{
|
||||
build.jcc(cond, exitHandlers[*index].self);
|
||||
build.ud2(); // Unconditional jump to abort is just an abort
|
||||
}
|
||||
else
|
||||
{
|
||||
Label self;
|
||||
build.jcc(cond, self);
|
||||
exitHandlerMap[targetOrUndef.index] = uint32_t(exitHandlers.size());
|
||||
exitHandlers.push_back({self, targetOrUndef.index});
|
||||
build.jcc(getReverseCondition(cond), label);
|
||||
build.ud2();
|
||||
build.setLabel(label);
|
||||
}
|
||||
}
|
||||
else if (cond == ConditionX64::Count)
|
||||
{
|
||||
// Unconditional jump can be skipped if it's a fallthrough
|
||||
if (target.kind == IrOpKind::VmExit || !isFallthroughBlock(blockOp(target), next))
|
||||
build.jmp(label);
|
||||
}
|
||||
else
|
||||
{
|
||||
build.jcc(cond, labelOp(targetOrUndef));
|
||||
build.jcc(cond, label);
|
||||
}
|
||||
|
||||
finalizeTargetLabel(target, fresh);
|
||||
}
|
||||
|
||||
void IrLoweringX64::jumpOrAbortOnUndef(IrOp target, const IrBlock& next)
|
||||
{
|
||||
jumpOrAbortOnUndef(ConditionX64::Count, target, next);
|
||||
}
|
||||
|
||||
OperandX64 IrLoweringX64::memRegDoubleOp(IrOp op)
|
||||
|
@ -27,15 +27,20 @@ struct IrLoweringX64
|
||||
{
|
||||
IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function);
|
||||
|
||||
void lowerInst(IrInst& inst, uint32_t index, IrBlock& next);
|
||||
void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next);
|
||||
void finishBlock();
|
||||
void finishFunction();
|
||||
|
||||
bool hasError() const;
|
||||
|
||||
bool isFallthroughBlock(IrBlock target, IrBlock next);
|
||||
void jumpOrFallthrough(IrBlock& target, IrBlock& next);
|
||||
void jumpOrAbortOnUndef(ConditionX64 cond, ConditionX64 condInverse, IrOp targetOrUndef, bool continueInVm = false);
|
||||
bool isFallthroughBlock(const IrBlock& target, const IrBlock& next);
|
||||
void jumpOrFallthrough(IrBlock& target, const IrBlock& next);
|
||||
|
||||
Label& getTargetLabel(IrOp op, Label& fresh);
|
||||
void finalizeTargetLabel(IrOp op, Label& fresh);
|
||||
|
||||
void jumpOrAbortOnUndef(ConditionX64 cond, IrOp target, const IrBlock& next);
|
||||
void jumpOrAbortOnUndef(IrOp target, const IrBlock& next);
|
||||
|
||||
void storeDoubleAsFloat(OperandX64 dst, IrOp src);
|
||||
|
||||
|
@ -23,7 +23,12 @@ constexpr unsigned kBlockSize = 4 * 1024 * 1024;
|
||||
constexpr unsigned kMaxTotalSize = 256 * 1024 * 1024;
|
||||
|
||||
NativeState::NativeState()
|
||||
: codeAllocator(kBlockSize, kMaxTotalSize)
|
||||
: NativeState(nullptr, nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
NativeState::NativeState(AllocationCallback* allocationCallback, void* allocationCallbackContext)
|
||||
: codeAllocator{kBlockSize, kMaxTotalSize, allocationCallback, allocationCallbackContext}
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -112,6 +112,7 @@ using GateFn = int (*)(lua_State*, Proto*, uintptr_t, NativeContext*);
|
||||
struct NativeState
|
||||
{
|
||||
NativeState();
|
||||
NativeState(AllocationCallback* allocationCallback, void* allocationCallbackContext);
|
||||
~NativeState();
|
||||
|
||||
CodeAllocator codeAllocator;
|
||||
|
@ -2,7 +2,7 @@
|
||||
#include "Luau/OptimizeConstProp.h"
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/IrAnalysis.h"
|
||||
#include "Luau/IrData.h"
|
||||
#include "Luau/IrBuilder.h"
|
||||
#include "Luau/IrUtils.h"
|
||||
|
||||
|
@ -39,7 +39,7 @@ struct CompileOptions
|
||||
const char* vectorType = nullptr;
|
||||
|
||||
// null-terminated array of globals that are mutable; disables the import optimization for fields accessed through these
|
||||
const char** mutableGlobals = nullptr;
|
||||
const char* const* mutableGlobals = nullptr;
|
||||
};
|
||||
|
||||
class CompileError : public std::exception
|
||||
|
@ -35,7 +35,7 @@ struct lua_CompileOptions
|
||||
const char* vectorType;
|
||||
|
||||
// null-terminated array of globals that are mutable; disables the import optimization for fields accessed through these
|
||||
const char** mutableGlobals;
|
||||
const char* const* mutableGlobals;
|
||||
};
|
||||
|
||||
// compile source to bytecode; when source compilation fails, the resulting bytecode contains the encoded error. use free() to destroy
|
||||
|
@ -26,9 +26,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
|
||||
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
||||
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileFunctionType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileNativeComment, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileFixBuiltinArity, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileFoldMathK, false)
|
||||
@ -209,12 +206,9 @@ struct Compiler
|
||||
|
||||
setDebugLine(func);
|
||||
|
||||
if (FFlag::LuauCompileFunctionType)
|
||||
{
|
||||
// note: we move types out of typeMap which is safe because compileFunction is only called once per function
|
||||
if (std::string* funcType = typeMap.find(func))
|
||||
bytecode.setFunctionTypeInfo(std::move(*funcType));
|
||||
}
|
||||
// note: we move types out of typeMap which is safe because compileFunction is only called once per function
|
||||
if (std::string* funcType = typeMap.find(func))
|
||||
bytecode.setFunctionTypeInfo(std::move(*funcType));
|
||||
|
||||
if (func->vararg)
|
||||
bytecode.emitABC(LOP_PREPVARARGS, uint8_t(self + func->args.size), 0, 0);
|
||||
@ -3620,9 +3614,8 @@ struct Compiler
|
||||
{
|
||||
node->body->visit(this);
|
||||
|
||||
if (FFlag::LuauCompileFunctionType)
|
||||
for (AstLocal* arg : node->args)
|
||||
hasTypes |= arg->annotation != nullptr;
|
||||
for (AstLocal* arg : node->args)
|
||||
hasTypes |= arg->annotation != nullptr;
|
||||
|
||||
// this makes sure all functions that are used when compiling this one have been already added to the vector
|
||||
functions.push_back(node);
|
||||
@ -3863,7 +3856,7 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
|
||||
if (hc.header && hc.content.compare(0, 9, "optimize ") == 0)
|
||||
options.optimizationLevel = std::max(0, std::min(2, atoi(hc.content.c_str() + 9)));
|
||||
|
||||
if (FFlag::LuauCompileNativeComment && hc.header && hc.content == "native")
|
||||
if (hc.header && hc.content == "native")
|
||||
{
|
||||
mainFlags |= LPF_NATIVE_MODULE;
|
||||
options.optimizationLevel = 2; // note: this might be removed in the future in favor of --!optimize
|
||||
@ -3916,7 +3909,7 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
|
||||
root->visit(&functionVisitor);
|
||||
|
||||
// computes type information for all functions based on type annotations
|
||||
if (FFlag::LuauCompileFunctionType && functionVisitor.hasTypes)
|
||||
if (functionVisitor.hasTypes)
|
||||
buildTypeMap(compiler.typeMap, root, options.vectorType);
|
||||
|
||||
for (AstExprFunction* expr : functions)
|
||||
|
@ -1,8 +1,8 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/BytecodeBuilder.h"
|
||||
|
||||
#include "Types.h"
|
||||
|
||||
#include "Luau/BytecodeBuilder.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
@ -82,13 +82,13 @@ struct ValueVisitor : AstVisitor
|
||||
}
|
||||
};
|
||||
|
||||
void assignMutable(DenseHashMap<AstName, Global>& globals, const AstNameTable& names, const char** mutableGlobals)
|
||||
void assignMutable(DenseHashMap<AstName, Global>& globals, const AstNameTable& names, const char* const* mutableGlobals)
|
||||
{
|
||||
if (AstName name = names.get("_G"); name.value)
|
||||
globals[name] = Global::Mutable;
|
||||
|
||||
if (mutableGlobals)
|
||||
for (const char** ptr = mutableGlobals; *ptr; ++ptr)
|
||||
for (const char* const* ptr = mutableGlobals; *ptr; ++ptr)
|
||||
if (AstName name = names.get(*ptr); name.value)
|
||||
globals[name] = Global::Mutable;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ struct Variable
|
||||
bool constant = false; // is the variable's value a compile-time constant? filled by constantFold
|
||||
};
|
||||
|
||||
void assignMutable(DenseHashMap<AstName, Global>& globals, const AstNameTable& names, const char** mutableGlobals);
|
||||
void assignMutable(DenseHashMap<AstName, Global>& globals, const AstNameTable& names, const char* const* mutableGlobals);
|
||||
void trackValues(DenseHashMap<AstName, Global>& globals, DenseHashMap<AstLocal*, Variable>& variables, AstNode* root);
|
||||
|
||||
inline Global getGlobalState(const DenseHashMap<AstName, Global>& globals, AstName name)
|
||||
|
@ -163,6 +163,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/include/Luau/IostreamHelpers.h
|
||||
Analysis/include/Luau/JsonEmitter.h
|
||||
Analysis/include/Luau/Linter.h
|
||||
Analysis/include/Luau/LinterConfig.h
|
||||
Analysis/include/Luau/LValue.h
|
||||
Analysis/include/Luau/Metamethods.h
|
||||
Analysis/include/Luau/Module.h
|
||||
@ -221,6 +222,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/src/IostreamHelpers.cpp
|
||||
Analysis/src/JsonEmitter.cpp
|
||||
Analysis/src/Linter.cpp
|
||||
Analysis/src/LinterConfig.cpp
|
||||
Analysis/src/LValue.cpp
|
||||
Analysis/src/Module.cpp
|
||||
Analysis/src/Normalize.cpp
|
||||
|
@ -7,8 +7,6 @@
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauFasterNoise, false)
|
||||
|
||||
#undef PI
|
||||
#define PI (3.14159265358979323846)
|
||||
#define RADIANS_PER_DEGREE (PI / 180.0)
|
||||
@ -277,27 +275,6 @@ static int math_randomseed(lua_State* L)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: Delete with LuauFasterNoise
|
||||
static const unsigned char kPerlin[512] = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99,
|
||||
37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174,
|
||||
20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41,
|
||||
55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86,
|
||||
164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17,
|
||||
182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110,
|
||||
79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14,
|
||||
239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24,
|
||||
72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180,
|
||||
|
||||
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247,
|
||||
120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74,
|
||||
165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65,
|
||||
25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52,
|
||||
217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213,
|
||||
119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112,
|
||||
104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199,
|
||||
106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61,
|
||||
156, 180};
|
||||
|
||||
static const unsigned char kPerlinHash[257] = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99,
|
||||
37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174,
|
||||
20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41,
|
||||
@ -321,61 +298,14 @@ static float perlin_lerp(float t, float a, float b)
|
||||
return a + t * (b - a);
|
||||
}
|
||||
|
||||
static float grad(unsigned char hash, float x, float y, float z)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauFasterNoise);
|
||||
unsigned char h = hash & 15;
|
||||
float u = (h < 8) ? x : y;
|
||||
float v = (h < 4) ? y : (h == 12 || h == 14) ? x : z;
|
||||
|
||||
return (h & 1 ? -u : u) + (h & 2 ? -v : v);
|
||||
}
|
||||
|
||||
static float perlin_grad(int hash, float x, float y, float z)
|
||||
{
|
||||
const float* g = kPerlinGrad[hash & 15];
|
||||
return g[0] * x + g[1] * y + g[2] * z;
|
||||
}
|
||||
|
||||
static float perlin_dep(float x, float y, float z)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauFasterNoise);
|
||||
float xflr = floorf(x);
|
||||
float yflr = floorf(y);
|
||||
float zflr = floorf(z);
|
||||
|
||||
int xi = int(xflr) & 255;
|
||||
int yi = int(yflr) & 255;
|
||||
int zi = int(zflr) & 255;
|
||||
|
||||
float xf = x - xflr;
|
||||
float yf = y - yflr;
|
||||
float zf = z - zflr;
|
||||
|
||||
float u = perlin_fade(xf);
|
||||
float v = perlin_fade(yf);
|
||||
float w = perlin_fade(zf);
|
||||
|
||||
const unsigned char* p = kPerlin;
|
||||
|
||||
int a = p[xi] + yi;
|
||||
int aa = p[a] + zi;
|
||||
int ab = p[a + 1] + zi;
|
||||
|
||||
int b = p[xi + 1] + yi;
|
||||
int ba = p[b] + zi;
|
||||
int bb = p[b + 1] + zi;
|
||||
|
||||
return perlin_lerp(w,
|
||||
perlin_lerp(v, perlin_lerp(u, grad(p[aa], xf, yf, zf), grad(p[ba], xf - 1, yf, zf)),
|
||||
perlin_lerp(u, grad(p[ab], xf, yf - 1, zf), grad(p[bb], xf - 1, yf - 1, zf))),
|
||||
perlin_lerp(v, perlin_lerp(u, grad(p[aa + 1], xf, yf, zf - 1), grad(p[ba + 1], xf - 1, yf, zf - 1)),
|
||||
perlin_lerp(u, grad(p[ab + 1], xf, yf - 1, zf - 1), grad(p[bb + 1], xf - 1, yf - 1, zf - 1))));
|
||||
}
|
||||
|
||||
static float perlin(float x, float y, float z)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauFasterNoise);
|
||||
float xflr = floorf(x);
|
||||
float yflr = floorf(y);
|
||||
float zflr = floorf(z);
|
||||
@ -412,33 +342,19 @@ static float perlin(float x, float y, float z)
|
||||
|
||||
static int math_noise(lua_State* L)
|
||||
{
|
||||
if (FFlag::LuauFasterNoise)
|
||||
{
|
||||
int nx, ny, nz;
|
||||
double x = lua_tonumberx(L, 1, &nx);
|
||||
double y = lua_tonumberx(L, 2, &ny);
|
||||
double z = lua_tonumberx(L, 3, &nz);
|
||||
int nx, ny, nz;
|
||||
double x = lua_tonumberx(L, 1, &nx);
|
||||
double y = lua_tonumberx(L, 2, &ny);
|
||||
double z = lua_tonumberx(L, 3, &nz);
|
||||
|
||||
luaL_argexpected(L, nx, 1, "number");
|
||||
luaL_argexpected(L, ny || lua_isnoneornil(L, 2), 2, "number");
|
||||
luaL_argexpected(L, nz || lua_isnoneornil(L, 3), 3, "number");
|
||||
luaL_argexpected(L, nx, 1, "number");
|
||||
luaL_argexpected(L, ny || lua_isnoneornil(L, 2), 2, "number");
|
||||
luaL_argexpected(L, nz || lua_isnoneornil(L, 3), 3, "number");
|
||||
|
||||
double r = perlin((float)x, (float)y, (float)z);
|
||||
double r = perlin((float)x, (float)y, (float)z);
|
||||
|
||||
lua_pushnumber(L, r);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
double x = luaL_checknumber(L, 1);
|
||||
double y = luaL_optnumber(L, 2, 0.0);
|
||||
double z = luaL_optnumber(L, 3, 0.0);
|
||||
|
||||
double r = perlin_dep((float)x, (float)y, (float)z);
|
||||
|
||||
lua_pushnumber(L, r);
|
||||
return 1;
|
||||
}
|
||||
lua_pushnumber(L, r);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int math_clamp(lua_State* L)
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauFasterInterp, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFasterFormatS, false)
|
||||
|
||||
// macro to `unsign' a character
|
||||
#define uchar(c) ((unsigned char)(c))
|
||||
@ -1028,18 +1029,35 @@ static int str_format(lua_State* L)
|
||||
{
|
||||
size_t l;
|
||||
const char* s = luaL_checklstring(L, arg, &l);
|
||||
if (!strchr(form, '.') && l >= 100)
|
||||
if (FFlag::LuauFasterFormatS)
|
||||
{
|
||||
/* no precision and string is too long to be formatted;
|
||||
keep original string */
|
||||
lua_pushvalue(L, arg);
|
||||
luaL_addvalue(&b);
|
||||
continue; // skip the `luaL_addlstring' at the end
|
||||
// no precision and string is too long to be formatted, or no format necessary to begin with
|
||||
if (form[2] == '\0' || (!strchr(form, '.') && l >= 100))
|
||||
{
|
||||
luaL_addlstring(&b, s, l, -1);
|
||||
continue; // skip the `luaL_addlstring' at the end
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(buff, sizeof(buff), form, s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(buff, sizeof(buff), form, s);
|
||||
break;
|
||||
if (!strchr(form, '.') && l >= 100)
|
||||
{
|
||||
/* no precision and string is too long to be formatted;
|
||||
keep original string */
|
||||
lua_pushvalue(L, arg);
|
||||
luaL_addvalue(&b);
|
||||
continue; // skip the `luaL_addlstring' at the end
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(buff, sizeof(buff), form, s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
case '*':
|
||||
|
@ -10,8 +10,6 @@
|
||||
#include "ldebug.h"
|
||||
#include "lvm.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauFasterTableConcat, false)
|
||||
|
||||
static int foreachi(lua_State* L)
|
||||
{
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
@ -222,7 +220,7 @@ static int tmove(lua_State* L)
|
||||
static void addfield(lua_State* L, luaL_Buffer* b, int i)
|
||||
{
|
||||
int tt = lua_rawgeti(L, 1, i);
|
||||
if (FFlag::LuauFasterTableConcat ? (tt != LUA_TSTRING && tt != LUA_TNUMBER) : !lua_isstring(L, -1))
|
||||
if (tt != LUA_TSTRING && tt != LUA_TNUMBER)
|
||||
luaL_error(L, "invalid value (%s) at index %d in table for 'concat'", luaL_typename(L, -1), i);
|
||||
luaL_addvalue(b);
|
||||
}
|
||||
|
@ -13,8 +13,6 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauLoadCheckGC, false)
|
||||
|
||||
// TODO: RAII deallocation doesn't work for longjmp builds if a memory error happens
|
||||
template<typename T>
|
||||
struct TempBuffer
|
||||
@ -181,8 +179,7 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
|
||||
}
|
||||
|
||||
// we will allocate a fair amount of memory so check GC before we do
|
||||
if (FFlag::LuauLoadCheckGC)
|
||||
luaC_checkGC(L);
|
||||
luaC_checkGC(L);
|
||||
|
||||
// pause GC for the duration of deserialization - some objects we're creating aren't rooted
|
||||
// TODO: if an allocation error happens mid-load, we do not unpause GC!
|
||||
|
@ -36,6 +36,13 @@ bench.runCode(function()
|
||||
end
|
||||
end, "interp: interp number")
|
||||
|
||||
bench.runCode(function()
|
||||
local ok = "hello!"
|
||||
for j=1,1e6 do
|
||||
local _ = string.format("j=%s", ok)
|
||||
end
|
||||
end, "interp: %s format")
|
||||
|
||||
bench.runCode(function()
|
||||
local ok = "hello!"
|
||||
for j=1,1e6 do
|
||||
|
@ -47,6 +47,56 @@ TEST_CASE("CodeAllocation")
|
||||
CHECK(nativeEntry == nativeData + kCodeAlignment);
|
||||
}
|
||||
|
||||
TEST_CASE("CodeAllocationCallbacks")
|
||||
{
|
||||
struct AllocationData
|
||||
{
|
||||
size_t bytesAllocated = 0;
|
||||
size_t bytesFreed = 0;
|
||||
};
|
||||
|
||||
AllocationData allocationData{};
|
||||
|
||||
const auto allocationCallback = [](void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize)
|
||||
{
|
||||
AllocationData& allocationData = *static_cast<AllocationData*>(context);
|
||||
if (oldPointer != nullptr)
|
||||
{
|
||||
CHECK(oldSize != 0);
|
||||
|
||||
allocationData.bytesFreed += oldSize;
|
||||
}
|
||||
|
||||
if (newPointer != nullptr)
|
||||
{
|
||||
CHECK(newSize != 0);
|
||||
|
||||
allocationData.bytesAllocated += newSize;
|
||||
}
|
||||
};
|
||||
|
||||
const size_t blockSize = 1024 * 1024;
|
||||
const size_t maxTotalSize = 1024 * 1024;
|
||||
|
||||
{
|
||||
CodeAllocator allocator(blockSize, maxTotalSize, allocationCallback, &allocationData);
|
||||
|
||||
uint8_t* nativeData = nullptr;
|
||||
size_t sizeNativeData = 0;
|
||||
uint8_t* nativeEntry = nullptr;
|
||||
|
||||
std::vector<uint8_t> code;
|
||||
code.resize(128);
|
||||
|
||||
REQUIRE(allocator.allocate(nullptr, 0, code.data(), code.size(), nativeData, sizeNativeData, nativeEntry));
|
||||
CHECK(allocationData.bytesAllocated == blockSize);
|
||||
CHECK(allocationData.bytesFreed == 0);
|
||||
}
|
||||
|
||||
CHECK(allocationData.bytesAllocated == blockSize);
|
||||
CHECK(allocationData.bytesFreed == blockSize);
|
||||
}
|
||||
|
||||
TEST_CASE("CodeAllocationFailure")
|
||||
{
|
||||
size_t blockSize = 3000;
|
||||
|
@ -4213,7 +4213,7 @@ RETURN R0 0
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
|
||||
Luau::CompileOptions options;
|
||||
const char* mutableGlobals[] = {"Game", "Workspace", "game", "plugin", "script", "shared", "workspace", NULL};
|
||||
options.mutableGlobals = &mutableGlobals[0];
|
||||
options.mutableGlobals = mutableGlobals;
|
||||
Luau::compileOrThrow(bcb, source, options);
|
||||
|
||||
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
|
||||
@ -7100,8 +7100,6 @@ L1: RETURN R3 1
|
||||
|
||||
TEST_CASE("EncodedTypeTable")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileFunctionType", true);
|
||||
|
||||
CHECK_EQ("\n" + compileTypeTable(R"(
|
||||
function myfunc(test: string, num: number)
|
||||
print(test)
|
||||
@ -7153,8 +7151,6 @@ Str:test(234)
|
||||
|
||||
TEST_CASE("HostTypesAreUserdata")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileFunctionType", true);
|
||||
|
||||
CHECK_EQ("\n" + compileTypeTable(R"(
|
||||
function myfunc(test: string, num: number)
|
||||
print(test)
|
||||
@ -7181,8 +7177,6 @@ end
|
||||
|
||||
TEST_CASE("HostTypesVector")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileFunctionType", true);
|
||||
|
||||
CHECK_EQ("\n" + compileTypeTable(R"(
|
||||
function myfunc(test: Instance, pos: Vector3)
|
||||
end
|
||||
@ -7206,8 +7200,6 @@ end
|
||||
|
||||
TEST_CASE("TypeAliasScoping")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileFunctionType", true);
|
||||
|
||||
CHECK_EQ("\n" + compileTypeTable(R"(
|
||||
do
|
||||
type Part = number
|
||||
@ -7242,8 +7234,6 @@ type Instance = string
|
||||
|
||||
TEST_CASE("TypeAliasResolve")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileFunctionType", true);
|
||||
|
||||
CHECK_EQ("\n" + compileTypeTable(R"(
|
||||
type Foo1 = number
|
||||
type Foo2 = { number }
|
||||
|
@ -266,6 +266,12 @@ static void* limitedRealloc(void* ud, void* ptr, size_t osize, size_t nsize)
|
||||
|
||||
TEST_SUITE_BEGIN("Conformance");
|
||||
|
||||
TEST_CASE("CodegenSupported")
|
||||
{
|
||||
if (codegen && !luau_codegen_supported())
|
||||
MESSAGE("Native code generation is not supported by the current configuration and will be disabled");
|
||||
}
|
||||
|
||||
TEST_CASE("Assert")
|
||||
{
|
||||
runConformance("assert.lua");
|
||||
@ -1726,7 +1732,6 @@ TEST_CASE("Native")
|
||||
TEST_CASE("NativeTypeAnnotations")
|
||||
{
|
||||
ScopedFastFlag bytecodeVersion4("BytecodeVersion4", true);
|
||||
ScopedFastFlag luauCompileFunctionType("LuauCompileFunctionType", true);
|
||||
|
||||
// This tests requires code to run natively, otherwise all 'is_native' checks will fail
|
||||
if (!codegen || !luau_codegen_supported())
|
||||
|
@ -1528,4 +1528,43 @@ TEST_CASE_FIXTURE(DifferFixture, "equal_generictp_cyclic")
|
||||
compareTypesEq("foo", "almostFoo");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "symbol_forward")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local foo = 5
|
||||
local almostFoo = "five"
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
INFO(Luau::toString(requireType("foo")));
|
||||
INFO(Luau::toString(requireType("almostFoo")));
|
||||
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at foo has type number, while the right type at almostFoo has type string)",
|
||||
true);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "newlines")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local foo = 5
|
||||
local almostFoo = "five"
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
INFO(Luau::toString(requireType("foo")));
|
||||
INFO(Luau::toString(requireType("almostFoo")));
|
||||
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at
|
||||
foo
|
||||
has type
|
||||
number,
|
||||
while the right type at
|
||||
almostFoo
|
||||
has type
|
||||
string)",
|
||||
true, true);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -160,14 +160,37 @@ void createSomeClasses(Frontend* frontend);
|
||||
template<typename BaseFixture>
|
||||
struct DifferFixtureGeneric : BaseFixture
|
||||
{
|
||||
void compareNe(TypeId left, TypeId right, const std::string& expectedMessage)
|
||||
std::string normalizeWhitespace(std::string msg)
|
||||
{
|
||||
std::string normalizedMsg = "";
|
||||
bool wasWhitespace = true;
|
||||
for (char c : msg)
|
||||
{
|
||||
bool isWhitespace = c == ' ' || c == '\n';
|
||||
if (wasWhitespace && isWhitespace)
|
||||
continue;
|
||||
normalizedMsg += isWhitespace ? ' ' : c;
|
||||
wasWhitespace = isWhitespace;
|
||||
}
|
||||
if (wasWhitespace)
|
||||
normalizedMsg.pop_back();
|
||||
return normalizedMsg;
|
||||
}
|
||||
|
||||
void compareNe(TypeId left, TypeId right, const std::string& expectedMessage, bool multiLine)
|
||||
{
|
||||
compareNe(left, std::nullopt, right, std::nullopt, expectedMessage, multiLine);
|
||||
}
|
||||
|
||||
void compareNe(TypeId left, std::optional<std::string> symbolLeft, TypeId right, std::optional<std::string> symbolRight,
|
||||
const std::string& expectedMessage, bool multiLine)
|
||||
{
|
||||
std::string diffMessage;
|
||||
try
|
||||
{
|
||||
DifferResult diffRes = diff(left, right);
|
||||
DifferResult diffRes = diffWithSymbols(left, right, symbolLeft, symbolRight);
|
||||
REQUIRE_MESSAGE(diffRes.diffError.has_value(), "Differ did not report type error, even though types are unequal");
|
||||
diffMessage = diffRes.diffError->toString();
|
||||
diffMessage = diffRes.diffError->toString(multiLine);
|
||||
}
|
||||
catch (const InternalCompilerError& e)
|
||||
{
|
||||
@ -176,9 +199,19 @@ struct DifferFixtureGeneric : BaseFixture
|
||||
CHECK_EQ(expectedMessage, diffMessage);
|
||||
}
|
||||
|
||||
void compareTypesNe(const std::string& leftSymbol, const std::string& rightSymbol, const std::string& expectedMessage)
|
||||
void compareTypesNe(const std::string& leftSymbol, const std::string& rightSymbol, const std::string& expectedMessage, bool forwardSymbol = false,
|
||||
bool multiLine = false)
|
||||
{
|
||||
compareNe(BaseFixture::requireType(leftSymbol), BaseFixture::requireType(rightSymbol), expectedMessage);
|
||||
if (forwardSymbol)
|
||||
{
|
||||
compareNe(
|
||||
BaseFixture::requireType(leftSymbol), leftSymbol, BaseFixture::requireType(rightSymbol), rightSymbol, expectedMessage, multiLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
compareNe(
|
||||
BaseFixture::requireType(leftSymbol), std::nullopt, BaseFixture::requireType(rightSymbol), std::nullopt, expectedMessage, multiLine);
|
||||
}
|
||||
}
|
||||
|
||||
void compareEq(TypeId left, TypeId right)
|
||||
|
@ -1657,8 +1657,6 @@ _ = (math.random() < 0.5 and false) or 42 -- currently ignored
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "WrongComment")
|
||||
{
|
||||
ScopedFastFlag sff("LuauLintNativeComment", true);
|
||||
|
||||
LintResult result = lint(R"(
|
||||
--!strict
|
||||
--!struct
|
||||
|
@ -1,5 +1,6 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
@ -7,11 +8,13 @@
|
||||
|
||||
#include "Fixture.h"
|
||||
|
||||
#include "ScopedFlags.h"
|
||||
#include "doctest.h"
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauStacklessTypeClone)
|
||||
|
||||
TEST_SUITE_BEGIN("ModuleTests");
|
||||
|
||||
@ -78,7 +81,7 @@ TEST_CASE_FIXTURE(Fixture, "is_within_comment_parse_result")
|
||||
TEST_CASE_FIXTURE(Fixture, "dont_clone_persistent_primitive")
|
||||
{
|
||||
TypeArena dest;
|
||||
CloneState cloneState;
|
||||
CloneState cloneState{builtinTypes};
|
||||
|
||||
// numberType is persistent. We leave it as-is.
|
||||
TypeId newNumber = clone(builtinTypes->numberType, dest, cloneState);
|
||||
@ -88,7 +91,7 @@ TEST_CASE_FIXTURE(Fixture, "dont_clone_persistent_primitive")
|
||||
TEST_CASE_FIXTURE(Fixture, "deepClone_non_persistent_primitive")
|
||||
{
|
||||
TypeArena dest;
|
||||
CloneState cloneState;
|
||||
CloneState cloneState{builtinTypes};
|
||||
|
||||
// Create a new number type that isn't persistent
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
@ -129,7 +132,7 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table")
|
||||
TypeId ty = requireType("Cyclic");
|
||||
|
||||
TypeArena dest;
|
||||
CloneState cloneState;
|
||||
CloneState cloneState{builtinTypes};
|
||||
TypeId cloneTy = clone(ty, dest, cloneState);
|
||||
|
||||
TableType* ttv = getMutable<TableType>(cloneTy);
|
||||
@ -147,8 +150,8 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table")
|
||||
REQUIRE(methodReturnType);
|
||||
|
||||
CHECK_MESSAGE(methodReturnType == cloneTy, toString(methodType, {true}) << " should be pointer identical to " << toString(cloneTy, {true}));
|
||||
CHECK_EQ(2, dest.typePacks.size()); // one for the function args, and another for its return type
|
||||
CHECK_EQ(2, dest.types.size()); // One table and one function
|
||||
CHECK_EQ(FFlag::LuauStacklessTypeClone ? 1 : 2, dest.typePacks.size()); // one for the function args, and another for its return type
|
||||
CHECK_EQ(2, dest.types.size()); // One table and one function
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table_2")
|
||||
@ -165,7 +168,7 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table_2")
|
||||
|
||||
TypeArena dest;
|
||||
|
||||
CloneState cloneState;
|
||||
CloneState cloneState{builtinTypes};
|
||||
TypeId cloneTy = clone(tableTy, dest, cloneState);
|
||||
TableType* ctt = getMutable<TableType>(cloneTy);
|
||||
REQUIRE(ctt);
|
||||
@ -209,7 +212,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_types_point_into_globalTypes_arena")
|
||||
TEST_CASE_FIXTURE(Fixture, "deepClone_union")
|
||||
{
|
||||
TypeArena dest;
|
||||
CloneState cloneState;
|
||||
CloneState cloneState{builtinTypes};
|
||||
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
TypeId oldUnion = frontend.globals.globalTypes.addType(UnionType{{builtinTypes->numberType, builtinTypes->stringType}});
|
||||
@ -224,7 +227,7 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_union")
|
||||
TEST_CASE_FIXTURE(Fixture, "deepClone_intersection")
|
||||
{
|
||||
TypeArena dest;
|
||||
CloneState cloneState;
|
||||
CloneState cloneState{builtinTypes};
|
||||
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
TypeId oldIntersection = frontend.globals.globalTypes.addType(IntersectionType{{builtinTypes->numberType, builtinTypes->stringType}});
|
||||
@ -251,7 +254,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_class")
|
||||
std::nullopt, &exampleMetaClass, {}, {}, "Test"}};
|
||||
|
||||
TypeArena dest;
|
||||
CloneState cloneState;
|
||||
CloneState cloneState{builtinTypes};
|
||||
|
||||
TypeId cloned = clone(&exampleClass, dest, cloneState);
|
||||
const ClassType* ctv = get<ClassType>(cloned);
|
||||
@ -274,12 +277,12 @@ TEST_CASE_FIXTURE(Fixture, "clone_free_types")
|
||||
TypePackVar freeTp(FreeTypePack{TypeLevel{}});
|
||||
|
||||
TypeArena dest;
|
||||
CloneState cloneState;
|
||||
CloneState cloneState{builtinTypes};
|
||||
|
||||
TypeId clonedTy = clone(freeTy, dest, cloneState);
|
||||
CHECK(get<FreeType>(clonedTy));
|
||||
|
||||
cloneState = {};
|
||||
cloneState = {builtinTypes};
|
||||
TypePackId clonedTp = clone(&freeTp, dest, cloneState);
|
||||
CHECK(get<FreeTypePack>(clonedTp));
|
||||
}
|
||||
@ -291,7 +294,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_free_tables")
|
||||
ttv->state = TableState::Free;
|
||||
|
||||
TypeArena dest;
|
||||
CloneState cloneState;
|
||||
CloneState cloneState{builtinTypes};
|
||||
|
||||
TypeId cloned = clone(&tableTy, dest, cloneState);
|
||||
const TableType* clonedTtv = get<TableType>(cloned);
|
||||
@ -332,6 +335,8 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
|
||||
#else
|
||||
int limit = 400;
|
||||
#endif
|
||||
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone", false};
|
||||
ScopedFastInt luauTypeCloneRecursionLimit{"LuauTypeCloneRecursionLimit", limit};
|
||||
|
||||
TypeArena src;
|
||||
@ -348,11 +353,39 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
|
||||
}
|
||||
|
||||
TypeArena dest;
|
||||
CloneState cloneState;
|
||||
CloneState cloneState{builtinTypes};
|
||||
|
||||
CHECK_THROWS_AS(clone(table, dest, cloneState), RecursionLimitException);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "clone_iteration_limit")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone", true};
|
||||
ScopedFastInt sfi{"LuauTypeCloneIterationLimit", 500};
|
||||
|
||||
TypeArena src;
|
||||
|
||||
TypeId table = src.addType(TableType{});
|
||||
TypeId nested = table;
|
||||
|
||||
for (int i = 0; i < 2500; i++)
|
||||
{
|
||||
TableType* ttv = getMutable<TableType>(nested);
|
||||
ttv->props["a"].setType(src.addType(TableType{}));
|
||||
nested = ttv->props["a"].type();
|
||||
}
|
||||
|
||||
TypeArena dest;
|
||||
CloneState cloneState{builtinTypes};
|
||||
|
||||
TypeId ty = clone(table, dest, cloneState);
|
||||
CHECK(get<ErrorType>(ty));
|
||||
|
||||
// Cloning it again is an important test.
|
||||
TypeId ty2 = clone(table, dest, cloneState);
|
||||
CHECK(get<ErrorType>(ty2));
|
||||
}
|
||||
|
||||
// Unions should never be cyclic, but we should clone them correctly even if
|
||||
// they are.
|
||||
TEST_CASE_FIXTURE(Fixture, "clone_cyclic_union")
|
||||
@ -368,7 +401,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_cyclic_union")
|
||||
uu->options.push_back(u);
|
||||
|
||||
TypeArena dest;
|
||||
CloneState cloneState;
|
||||
CloneState cloneState{builtinTypes};
|
||||
|
||||
TypeId cloned = clone(u, dest, cloneState);
|
||||
REQUIRE(cloned);
|
||||
|
@ -831,4 +831,39 @@ TEST_CASE_FIXTURE(Fixture, "tostring_unsee_ttv_if_array")
|
||||
CHECK(toString(requireType("y")) == "({string}, {string}) -> ()");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauIndentTypeMismatch", true},
|
||||
};
|
||||
|
||||
ScopedFastInt sfi[] = {
|
||||
{"LuauIndentTypeMismatchMaxTypeLength", 10},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
function f1() : {a : number, b : string, c : { d : number}}
|
||||
return { a = 1, b = "a", c = {d = "a"}}
|
||||
end
|
||||
|
||||
)");
|
||||
std::string expected = R"(Type
|
||||
'{ a: number, b: string, c: { d: string } }'
|
||||
could not be converted into
|
||||
'{| a: number, b: string, c: {| d: number |} |}'
|
||||
caused by:
|
||||
Property 'c' is not compatible.
|
||||
Type
|
||||
'{ d: string }'
|
||||
could not be converted into
|
||||
'{| d: number |}'
|
||||
caused by:
|
||||
Property 'd' is not compatible.
|
||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
||||
std::string actual = toString(result.errors[0]);
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(expected == actual);
|
||||
}
|
||||
TEST_SUITE_END();
|
||||
|
@ -187,8 +187,11 @@ TEST_CASE_FIXTURE(Fixture, "mutually_recursive_aliases")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_aliases")
|
||||
{
|
||||
ScopedFastFlag sff_DebugLuauDeferredConstraintResolution{"DebugLuauDeferredConstraintResolution", true};
|
||||
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", true},
|
||||
{"LuauIndentTypeMismatch", true},
|
||||
};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
CheckResult result = check(R"(
|
||||
type T<a> = { v: a }
|
||||
local x: T<number> = { v = 123 }
|
||||
@ -197,18 +200,22 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
const char* expectedError = "Type 'bad' could not be converted into 'T<number>'\n"
|
||||
"caused by:\n"
|
||||
" Property 'v' is not compatible. Type 'string' could not be converted into 'number' in an invariant context";
|
||||
|
||||
const std::string expected = R"(Type 'bad' could not be converted into 'T<number>'
|
||||
caused by:
|
||||
Property 'v' is not compatible.
|
||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
||||
CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}});
|
||||
CHECK(toString(result.errors[0]) == expectedError);
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
|
||||
{
|
||||
ScopedFastFlag sff_DebugLuauDeferredConstraintResolution{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", true},
|
||||
{"LuauIndentTypeMismatch", true},
|
||||
};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type T<a> = { v: a }
|
||||
@ -218,15 +225,16 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
std::string expectedError = "Type 'bad' could not be converted into 'U<number>'\n"
|
||||
"caused by:\n"
|
||||
" Property 't' is not compatible. Type '{ v: string }' could not be converted into 'T<number>'\n"
|
||||
"caused by:\n"
|
||||
" Property 'v' is not compatible. Type 'string' could not be converted into 'number' in an invariant context";
|
||||
const std::string expected = R"(Type 'bad' could not be converted into 'U<number>'
|
||||
caused by:
|
||||
Property 't' is not compatible.
|
||||
Type '{ v: string }' could not be converted into 'T<number>'
|
||||
caused by:
|
||||
Property 'v' is not compatible.
|
||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
||||
|
||||
CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}});
|
||||
CHECK(toString(result.errors[0]) == expectedError);
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases")
|
||||
@ -261,7 +269,7 @@ TEST_CASE_FIXTURE(Fixture, "mutually_recursive_types_errors")
|
||||
// We had a UAF in this example caused by not cloning type function arguments
|
||||
ModulePtr module = frontend.moduleResolver.getModule("MainModule");
|
||||
unfreeze(module->interfaceTypes);
|
||||
copyErrors(module->errors, module->interfaceTypes);
|
||||
copyErrors(module->errors, module->interfaceTypes, builtinTypes);
|
||||
freeze(module->interfaceTypes);
|
||||
module->internalTypes.clear();
|
||||
module->astTypes.clear();
|
||||
|
@ -132,7 +132,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_predicate")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauAlwaysCommitInferencesOfFunctionCalls", true};
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauAlwaysCommitInferencesOfFunctionCalls", true},
|
||||
{"LuauIndentTypeMismatch", true},
|
||||
};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
@ -142,12 +146,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(R"(Type '(number, number) -> boolean' could not be converted into '((string, string) -> boolean)?'
|
||||
const std::string expected = R"(Type
|
||||
'(number, number) -> boolean'
|
||||
could not be converted into
|
||||
'((string, string) -> boolean)?'
|
||||
caused by:
|
||||
None of the union options are compatible. For example: Type '(number, number) -> boolean' could not be converted into '(string, string) -> boolean'
|
||||
None of the union options are compatible. For example:
|
||||
Type
|
||||
'(number, number) -> boolean'
|
||||
could not be converted into
|
||||
'(string, string) -> boolean'
|
||||
caused by:
|
||||
Argument #1 type is not compatible. Type 'string' could not be converted into 'number')",
|
||||
toString(result.errors[0]));
|
||||
Argument #1 type is not compatible.
|
||||
Type 'string' could not be converted into 'number')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "strings_have_methods")
|
||||
|
@ -367,8 +367,11 @@ b.X = 2 -- real Vector2.X is also read-only
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "detailed_class_unification_error")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauAlwaysCommitInferencesOfFunctionCalls", true};
|
||||
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauAlwaysCommitInferencesOfFunctionCalls", true},
|
||||
{"LuauIndentTypeMismatch", true},
|
||||
};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
CheckResult result = check(R"(
|
||||
local function foo(v)
|
||||
return v.X :: number + string.len(v.Y)
|
||||
@ -380,10 +383,11 @@ b(a)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'Vector2' could not be converted into '{- X: number, Y: string -}'
|
||||
const std::string expected = R"(Type 'Vector2' could not be converted into '{- X: number, Y: string -}'
|
||||
caused by:
|
||||
Property 'Y' is not compatible. Type 'number' could not be converted into 'string')");
|
||||
Property 'Y' is not compatible.
|
||||
Type 'number' could not be converted into 'string')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "class_type_mismatch_with_name_conflict")
|
||||
@ -453,6 +457,8 @@ TEST_CASE_FIXTURE(ClassFixture, "index_instance_property_nonstrict")
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "type_mismatch_invariance_required_for_error")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
CheckResult result = check(R"(
|
||||
type A = { x: ChildClass }
|
||||
type B = { x: BaseClass }
|
||||
@ -462,9 +468,11 @@ local b: B = a
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
const std::string expected = R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property 'x' is not compatible. Type 'ChildClass' could not be converted into 'BaseClass' in an invariant context)");
|
||||
Property 'x' is not compatible.
|
||||
Type 'ChildClass' could not be converted into 'BaseClass' in an invariant context)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "callable_classes")
|
||||
|
@ -1095,6 +1095,9 @@ TEST_CASE_FIXTURE(Fixture, "return_type_by_overload")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "infer_anonymous_function_arguments")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
// Simple direct arg to arg propagation
|
||||
CheckResult result = check(R"(
|
||||
type Table = { x: number, y: number }
|
||||
@ -1150,129 +1153,27 @@ f(function(a, b, c, ...) return a + b end)
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
std::string expected;
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
CHECK_EQ(R"(Type '<a>(number, number, a) -> number' could not be converted into '(number, number) -> number'
|
||||
expected = R"(Type
|
||||
'<a>(number, number, a) -> number'
|
||||
could not be converted into
|
||||
'(number, number) -> number'
|
||||
caused by:
|
||||
Argument count mismatch. Function expects 3 arguments, but only 2 are specified)",
|
||||
toString(result.errors[0]));
|
||||
Argument count mismatch. Function expects 3 arguments, but only 2 are specified)";
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(R"(Type '(number, number, a) -> number' could not be converted into '(number, number) -> number'
|
||||
expected = R"(Type
|
||||
'(number, number, a) -> number'
|
||||
could not be converted into
|
||||
'(number, number) -> number'
|
||||
caused by:
|
||||
Argument count mismatch. Function expects 3 arguments, but only 2 are specified)",
|
||||
toString(result.errors[0]));
|
||||
Argument count mismatch. Function expects 3 arguments, but only 2 are specified)";
|
||||
}
|
||||
|
||||
// Infer from variadic packs into elements
|
||||
result = check(R"(
|
||||
function f(a: (...number) -> number) return a(1, 2) end
|
||||
f(function(a, b) return a + b end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// Infer from variadic packs into variadic packs
|
||||
result = check(R"(
|
||||
type Table = { x: number, y: number }
|
||||
function f(a: (...Table) -> number) return a({x = 1, y = 2}, {x = 3, y = 4}) end
|
||||
f(function(a, ...) local b = ... return b.z end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ("Key 'z' not found in table 'Table'", toString(result.errors[0]));
|
||||
|
||||
// Return type inference
|
||||
result = check(R"(
|
||||
type Table = { x: number, y: number }
|
||||
function f(a: (number) -> Table) return a(4) end
|
||||
f(function(x) return x * 2 end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ("Type 'number' could not be converted into 'Table'", toString(result.errors[0]));
|
||||
|
||||
// Return type doesn't inference 'nil'
|
||||
result = check(R"(
|
||||
function f(a: (number) -> nil) return a(4) end
|
||||
f(function(x) print(x) end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "infer_anonymous_function_arguments")
|
||||
{
|
||||
// Simple direct arg to arg propagation
|
||||
CheckResult result = check(R"(
|
||||
type Table = { x: number, y: number }
|
||||
local function f(a: (Table) -> number) return a({x = 1, y = 2}) end
|
||||
f(function(a) return a.x + a.y end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// An optional function is accepted, but since we already provide a function, nil can be ignored
|
||||
result = check(R"(
|
||||
type Table = { x: number, y: number }
|
||||
local function f(a: ((Table) -> number)?) if a then return a({x = 1, y = 2}) else return 0 end end
|
||||
f(function(a) return a.x + a.y end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// Make sure self calls match correct index
|
||||
result = check(R"(
|
||||
type Table = { x: number, y: number }
|
||||
local x = {}
|
||||
x.b = {x = 1, y = 2}
|
||||
function x:f(a: (Table) -> number) return a(self.b) end
|
||||
x:f(function(a) return a.x + a.y end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// Mix inferred and explicit argument types
|
||||
result = check(R"(
|
||||
function f(a: (a: number, b: number, c: boolean) -> number) return a(1, 2, true) end
|
||||
f(function(a: number, b, c) return c and a + b or b - a end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// Anonymous function has a variadic pack
|
||||
result = check(R"(
|
||||
type Table = { x: number, y: number }
|
||||
local function f(a: (Table) -> number) return a({x = 1, y = 2}) end
|
||||
f(function(...) return select(1, ...).z end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ("Key 'z' not found in table 'Table'", toString(result.errors[0]));
|
||||
|
||||
// Can't accept more arguments than provided
|
||||
result = check(R"(
|
||||
function f(a: (a: number, b: number) -> number) return a(1, 2) end
|
||||
f(function(a, b, c, ...) return a + b end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
CHECK_EQ(R"(Type '<a>(number, number, a) -> number' could not be converted into '(number, number) -> number'
|
||||
caused by:
|
||||
Argument count mismatch. Function expects 3 arguments, but only 2 are specified)",
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(R"(Type '(number, number, a) -> number' could not be converted into '(number, number) -> number'
|
||||
caused by:
|
||||
Argument count mismatch. Function expects 3 arguments, but only 2 are specified)",
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
|
||||
// Infer from variadic packs into elements
|
||||
result = check(R"(
|
||||
@ -1376,6 +1277,8 @@ end
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_arg_count")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
CheckResult result = check(R"(
|
||||
type A = (number, number) -> string
|
||||
type B = (number) -> string
|
||||
@ -1385,13 +1288,20 @@ local b: B = a
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type '(number, number) -> string' could not be converted into '(number) -> string'
|
||||
const std::string expected = R"(Type
|
||||
'(number, number) -> string'
|
||||
could not be converted into
|
||||
'(number) -> string'
|
||||
caused by:
|
||||
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)");
|
||||
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_arg")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type A = (number, number) -> string
|
||||
type B = (number, string) -> string
|
||||
@ -1401,13 +1311,21 @@ local b: B = a
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type '(number, number) -> string' could not be converted into '(number, string) -> string'
|
||||
const std::string expected = R"(Type
|
||||
'(number, number) -> string'
|
||||
could not be converted into
|
||||
'(number, string) -> string'
|
||||
caused by:
|
||||
Argument #2 type is not compatible. Type 'string' could not be converted into 'number')");
|
||||
Argument #2 type is not compatible.
|
||||
Type 'string' could not be converted into 'number')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret_count")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type A = (number, number) -> (number)
|
||||
type B = (number, number) -> (number, boolean)
|
||||
@ -1417,13 +1335,20 @@ local b: B = a
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type '(number, number) -> number' could not be converted into '(number, number) -> (number, boolean)'
|
||||
const std::string expected = R"(Type
|
||||
'(number, number) -> number'
|
||||
could not be converted into
|
||||
'(number, number) -> (number, boolean)'
|
||||
caused by:
|
||||
Function only returns 1 value, but 2 are required here)");
|
||||
Function only returns 1 value, but 2 are required here)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type A = (number, number) -> string
|
||||
type B = (number, number) -> number
|
||||
@ -1433,13 +1358,21 @@ local b: B = a
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type '(number, number) -> string' could not be converted into '(number, number) -> number'
|
||||
const std::string expected = R"(Type
|
||||
'(number, number) -> string'
|
||||
could not be converted into
|
||||
'(number, number) -> number'
|
||||
caused by:
|
||||
Return type is not compatible. Type 'string' could not be converted into 'number')");
|
||||
Return type is not compatible.
|
||||
Type 'string' could not be converted into 'number')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret_mult")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type A = (number, number) -> (number, string)
|
||||
type B = (number, number) -> (number, boolean)
|
||||
@ -1449,10 +1382,14 @@ local b: B = a
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
R"(Type '(number, number) -> (number, string)' could not be converted into '(number, number) -> (number, boolean)'
|
||||
const std::string expected = R"(Type
|
||||
'(number, number) -> (number, string)'
|
||||
could not be converted into
|
||||
'(number, number) -> (number, boolean)'
|
||||
caused by:
|
||||
Return #2 type is not compatible. Type 'string' could not be converted into 'boolean')");
|
||||
Return #2 type is not compatible.
|
||||
Type 'string' could not be converted into 'boolean')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_quantify_right_type")
|
||||
@ -1561,6 +1498,9 @@ TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_th
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_non_self_unsealed_overwrite")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local t = { f = nil :: ((x: number) -> number)? }
|
||||
|
||||
@ -1575,11 +1515,19 @@ end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type '(string) -> string' could not be converted into '((number) -> number)?'
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type
|
||||
'(string) -> string'
|
||||
could not be converted into
|
||||
'((number) -> number)?'
|
||||
caused by:
|
||||
None of the union options are compatible. For example: Type '(string) -> string' could not be converted into '(number) -> number'
|
||||
None of the union options are compatible. For example:
|
||||
Type
|
||||
'(string) -> string'
|
||||
could not be converted into
|
||||
'(number) -> number'
|
||||
caused by:
|
||||
Argument #1 type is not compatible. Type 'number' could not be converted into 'string')");
|
||||
Argument #1 type is not compatible.
|
||||
Type 'number' could not be converted into 'string')");
|
||||
CHECK_EQ(toString(result.errors[1]), R"(Type 'string' could not be converted into 'number')");
|
||||
}
|
||||
|
||||
@ -1595,6 +1543,9 @@ TEST_CASE_FIXTURE(Fixture, "strict_mode_ok_with_missing_arguments")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "function_statement_sealed_table_assignment_through_indexer")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local t: {[string]: () -> number} = {}
|
||||
|
||||
@ -1603,7 +1554,10 @@ function t:b() return 2 end -- not OK
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(R"(Type '(*error-type*) -> number' could not be converted into '() -> number'
|
||||
CHECK_EQ(R"(Type
|
||||
'(*error-type*) -> number'
|
||||
could not be converted into
|
||||
'() -> number'
|
||||
caused by:
|
||||
Argument count mismatch. Function expects 1 argument, but none are specified)",
|
||||
toString(result.errors[0]));
|
||||
@ -1800,6 +1754,9 @@ foo(string.find("hello", "e"))
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "luau_subtyping_is_np_hard")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
|
||||
@ -1832,11 +1789,11 @@ z = y -- Not OK, so the line is colorable
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '((\"blue\" | \"red\") -> (\"blue\" | \"red\") -> (\"blue\" | \"red\") -> boolean) & ((\"blue\" | \"red\") -> (\"blue\") -> (\"blue\") "
|
||||
"-> false) & ((\"blue\" | \"red\") -> (\"red\") -> (\"red\") -> false) & ((\"blue\") -> (\"blue\") -> (\"blue\" | \"red\") -> false) & "
|
||||
"((\"red\") -> (\"red\") -> (\"blue\" | \"red\") -> false)' could not be converted into '(\"blue\" | \"red\") -> (\"blue\" | \"red\") -> "
|
||||
"(\"blue\" | \"red\") -> false'; none of the intersection parts are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'(("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> boolean) & (("blue" | "red") -> ("blue") -> ("blue") -> false) & (("blue" | "red") -> ("red") -> ("red") -> false) & (("blue") -> ("blue") -> ("blue" | "red") -> false) & (("red") -> ("red") -> ("blue" | "red") -> false)'
|
||||
could not be converted into
|
||||
'("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> false'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "function_is_supertype_of_concrete_functions")
|
||||
@ -1994,7 +1951,11 @@ TEST_CASE_FIXTURE(Fixture, "function_exprs_are_generalized_at_signature_scope_no
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauAlwaysCommitInferencesOfFunctionCalls", true};
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauIndentTypeMismatch", true},
|
||||
{"LuauAlwaysCommitInferencesOfFunctionCalls", true},
|
||||
};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function foo<a>(x: a, y: a?)
|
||||
@ -2038,11 +1999,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_bu
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type '{ x: number }' could not be converted into 'vec2?'
|
||||
const std::string expected = R"(Type '{ x: number }' could not be converted into 'vec2?'
|
||||
caused by:
|
||||
None of the union options are compatible. For example: Table type '{ x: number }' not compatible with type 'vec2' because the former is missing field 'y')");
|
||||
|
||||
CHECK_EQ(toString(result.errors[1]), "Type 'vec2' could not be converted into 'number'");
|
||||
None of the union options are compatible. For example:
|
||||
Table type '{ x: number }' not compatible with type 'vec2' because the former is missing field 'y')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ("Type 'vec2' could not be converted into 'number'", toString(result.errors[1]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2")
|
||||
|
@ -713,6 +713,9 @@ end
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
-- At one point this produced a UAF
|
||||
@ -725,12 +728,14 @@ y.a.c = y
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
R"(Type 'y' could not be converted into 'T<string>'
|
||||
const std::string expected = R"(Type 'y' could not be converted into 'T<string>'
|
||||
caused by:
|
||||
Property 'a' is not compatible. Type '{ c: T<string>?, d: number }' could not be converted into 'U<string>'
|
||||
Property 'a' is not compatible.
|
||||
Type '{ c: T<string>?, d: number }' could not be converted into 'U<string>'
|
||||
caused by:
|
||||
Property 'd' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)");
|
||||
Property 'd' is not compatible.
|
||||
Type 'number' could not be converted into 'string' in an invariant context)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification1")
|
||||
|
@ -317,6 +317,9 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type X = { x: (number) -> number }
|
||||
type Y = { y: (string) -> string }
|
||||
@ -333,9 +336,13 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type '(string, number) -> string' could not be converted into '(string) -> string'
|
||||
const std::string expected = R"(Type
|
||||
'(string, number) -> string'
|
||||
could not be converted into
|
||||
'(string) -> string'
|
||||
caused by:
|
||||
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)");
|
||||
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'X & Y'");
|
||||
CHECK_EQ(toString(result.errors[2]), "Type 'number' could not be converted into 'string'");
|
||||
CHECK_EQ(toString(result.errors[3]), "Cannot add property 'w' to table 'X & Y'");
|
||||
@ -343,6 +350,9 @@ caused by:
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
// After normalization, previous 'table_intersection_write_sealed_indirect' is identical to this one
|
||||
CheckResult result = check(R"(
|
||||
type XY = { x: (number) -> number, y: (string) -> string }
|
||||
@ -357,9 +367,14 @@ TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type '(string, number) -> string' could not be converted into '(string) -> string'
|
||||
const std::string expected = R"(Type
|
||||
'(string, number) -> string'
|
||||
could not be converted into
|
||||
'(string) -> string'
|
||||
caused by:
|
||||
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)");
|
||||
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
|
||||
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'XY'");
|
||||
CHECK_EQ(toString(result.errors[2]), "Type 'number' could not be converted into 'string'");
|
||||
CHECK_EQ(toString(result.errors[3]), "Cannot add property 'w' to table 'XY'");
|
||||
@ -377,6 +392,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_intersection_setmetatable")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_part")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type X = { x: number }
|
||||
type Y = { y: number }
|
||||
@ -386,9 +404,11 @@ local a: XYZ = 3
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'number' could not be converted into 'X & Y & Z'
|
||||
const std::string expected = R"(Type 'number' could not be converted into 'X & Y & Z'
|
||||
caused by:
|
||||
Not all intersection parts are compatible. Type 'number' could not be converted into 'X')");
|
||||
Not all intersection parts are compatible.
|
||||
Type 'number' could not be converted into 'X')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_all")
|
||||
@ -462,6 +482,9 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : ((number?) -> number?) & ((string?) -> string?)
|
||||
local y : (nil) -> nil = x -- OK
|
||||
@ -469,12 +492,18 @@ TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> number?) & ((string?) -> string?)' could not be converted into '(number) -> number'; "
|
||||
"none of the intersection parts are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'((number?) -> number?) & ((string?) -> string?)'
|
||||
could not be converted into
|
||||
'(number) -> number'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : ((number) -> number) & ((string) -> string)
|
||||
local y : ((number | string) -> (number | string)) = x -- OK
|
||||
@ -482,12 +511,18 @@ TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number) -> number) & ((string) -> string)' could not be converted into '(boolean | number) -> "
|
||||
"boolean | number'; none of the intersection parts are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'((number) -> number) & ((string) -> string)'
|
||||
could not be converted into
|
||||
'(boolean | number) -> boolean | number'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : { p : number?, q : string? } & { p : number?, q : number?, r : number? }
|
||||
local y : { p : number?, q : nil, r : number? } = x -- OK
|
||||
@ -495,12 +530,18 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}' could not be converted into "
|
||||
"'{| p: nil |}'; none of the intersection parts are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}'
|
||||
could not be converted into
|
||||
'{| p: nil |}'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : { p : number?, q : any } & { p : unknown, q : string? }
|
||||
local y : { p : number?, q : string? } = x -- OK
|
||||
@ -532,9 +573,11 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '{| p: number?, q: any |} & {| p: unknown, q: string? |}' could not be converted into '{| p: string?, "
|
||||
"q: number? |}'; none of the intersection parts are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'{| p: number?, q: any |} & {| p: unknown, q: string? |}'
|
||||
could not be converted into
|
||||
'{| p: string?, q: number? |}'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
@ -551,6 +594,9 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : ((number?) -> ({ p : number } & { q : number })) & ((string?) -> ({ p : number } & { r : number }))
|
||||
local y : (nil) -> { p : number, q : number, r : number} = x -- OK
|
||||
@ -558,13 +604,18 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})' could not be converted into "
|
||||
"'(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})'
|
||||
could not be converted into
|
||||
'(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a>()
|
||||
local x : ((number?) -> (a | number)) & ((string?) -> (a | string))
|
||||
@ -574,12 +625,18 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> a | number) & ((string?) -> a | string)' could not be converted into '(number?) -> a'; "
|
||||
"none of the intersection parts are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'((number?) -> a | number) & ((string?) -> a | string)'
|
||||
could not be converted into
|
||||
'(number?) -> a'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a,b,c>()
|
||||
local x : ((a?) -> (a | b)) & ((c?) -> (b | c))
|
||||
@ -589,12 +646,18 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '((a?) -> a | b) & ((c?) -> b | c)' could not be converted into '(a?) -> (a & c) | b'; none of the intersection parts are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'((a?) -> a | b) & ((c?) -> b | c)'
|
||||
could not be converted into
|
||||
'(a?) -> (a & c) | b'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))
|
||||
@ -604,12 +667,18 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))' could not be converted "
|
||||
"into '(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))'
|
||||
could not be converted into
|
||||
'(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : ((number) -> number) & ((nil) -> unknown)
|
||||
@ -619,12 +688,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((nil) -> unknown) & ((number) -> number)' could not be converted into '(number?) -> number?'; none "
|
||||
"of the intersection parts are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'((nil) -> unknown) & ((number) -> number)'
|
||||
could not be converted into
|
||||
'(number?) -> number?'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : ((number) -> number?) & ((unknown) -> string?)
|
||||
@ -634,12 +709,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number) -> number?) & ((unknown) -> string?)' could not be converted into '(number?) -> nil'; none "
|
||||
"of the intersection parts are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'((number) -> number?) & ((unknown) -> string?)'
|
||||
could not be converted into
|
||||
'(number?) -> nil'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : ((number) -> number) & ((nil) -> never)
|
||||
@ -649,12 +730,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((nil) -> never) & ((number) -> number)' could not be converted into '(number?) -> never'; none of "
|
||||
"the intersection parts are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'((nil) -> never) & ((number) -> number)'
|
||||
could not be converted into
|
||||
'(number?) -> never'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : ((number) -> number?) & ((never) -> string?)
|
||||
@ -664,12 +751,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((never) -> string?) & ((number) -> number?)' could not be converted into '(number?) -> nil'; none "
|
||||
"of the intersection parts are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'((never) -> string?) & ((number) -> number?)'
|
||||
could not be converted into
|
||||
'(number?) -> nil'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_variadics")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : ((string?) -> (string | number)) & ((number?) -> ...number)
|
||||
local y : ((nil) -> (number, number?)) = x -- OK
|
||||
@ -677,8 +770,11 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> (...number)) & ((string?) -> number | string)' could not be converted into '(number | "
|
||||
"string) -> (number, number?)'; none of the intersection parts are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'((number?) -> (...number)) & ((string?) -> number | string)'
|
||||
could not be converted into
|
||||
'(number | string) -> (number, number?)'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1")
|
||||
@ -713,6 +809,9 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a...>()
|
||||
local x : (() -> a...) & (() -> (number?,a...))
|
||||
@ -722,12 +821,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '(() -> (a...)) & (() -> (number?, a...))' could not be converted into '() -> number'; none of the intersection parts are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'(() -> (a...)) & (() -> (number?, a...))'
|
||||
could not be converted into
|
||||
'() -> number'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a...>()
|
||||
local x : ((a...) -> ()) & ((number,a...) -> number)
|
||||
@ -737,8 +842,11 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '((a...) -> ()) & ((number, a...) -> number)' could not be converted into '(number?) -> ()'; none of "
|
||||
"the intersection parts are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'((a...) -> ()) & ((number, a...) -> number)'
|
||||
could not be converted into
|
||||
'(number?) -> ()'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables")
|
||||
@ -897,8 +1005,6 @@ local y = x.Bar
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "index_property_table_intersection_2")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndexTableIntersectionStringExpr", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Foo = {
|
||||
Bar: string,
|
||||
|
@ -389,6 +389,9 @@ type Table = typeof(tbl)
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "module_type_conflict")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
export type T = { x: number }
|
||||
return {}
|
||||
@ -407,15 +410,19 @@ local b: B.T = a
|
||||
)";
|
||||
|
||||
CheckResult result = frontend.check("game/C");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'
|
||||
const std::string expected = R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'
|
||||
caused by:
|
||||
Property 'x' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)");
|
||||
Property 'x' is not compatible.
|
||||
Type 'number' could not be converted into 'string' in an invariant context)";
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "module_type_conflict_instantiated")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
export type Wrap<T> = { x: T }
|
||||
return {}
|
||||
@ -441,11 +448,12 @@ local b: B.T = a
|
||||
)";
|
||||
|
||||
CheckResult result = frontend.check("game/D");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'
|
||||
const std::string expected = R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'
|
||||
caused by:
|
||||
Property 'x' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)");
|
||||
Property 'x' is not compatible.
|
||||
Type 'number' could not be converted into 'string' in an invariant context)";
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "constrained_anyification_clone_immutable_types")
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include "Fixture.h"
|
||||
|
||||
#include "ScopedFlags.h"
|
||||
#include "doctest.h"
|
||||
|
||||
using namespace Luau;
|
||||
@ -404,4 +405,92 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cycle_between_object_constructor_and_alias")
|
||||
CHECK_MESSAGE(get<MetatableType>(follow(aliasType)), "Expected metatable type but got: " << toString(aliasType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "promise_type_error_too_complex")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone", true};
|
||||
|
||||
frontend.options.retainFullTypeGraphs = false;
|
||||
|
||||
// Used `luau-reduce` tool to extract a minimal reproduction.
|
||||
// Credit: https://github.com/evaera/roblox-lua-promise/blob/v4.0.0/lib/init.lua
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
|
||||
local Promise = {}
|
||||
Promise.prototype = {}
|
||||
Promise.__index = Promise.prototype
|
||||
|
||||
function Promise._new(traceback, callback, parent)
|
||||
if parent ~= nil and not Promise.is(parent)then
|
||||
end
|
||||
|
||||
local self = {
|
||||
_parent = parent,
|
||||
}
|
||||
|
||||
parent._consumers[self] = true
|
||||
setmetatable(self, Promise)
|
||||
self:_reject()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Promise.resolve(...)
|
||||
return Promise._new(debug.traceback(nil, 2), function(resolve)
|
||||
end)
|
||||
end
|
||||
|
||||
function Promise.reject(...)
|
||||
return Promise._new(debug.traceback(nil, 2), function(_, reject)
|
||||
end)
|
||||
end
|
||||
|
||||
function Promise._try(traceback, callback, ...)
|
||||
return Promise._new(traceback, function(resolve)
|
||||
end)
|
||||
end
|
||||
|
||||
function Promise.try(callback, ...)
|
||||
return Promise._try(debug.traceback(nil, 2), callback, ...)
|
||||
end
|
||||
|
||||
function Promise._all(traceback, promises, amount)
|
||||
if #promises == 0 or amount == 0 then
|
||||
return Promise.resolve({})
|
||||
end
|
||||
return Promise._new(traceback, function(resolve, reject, onCancel)
|
||||
end)
|
||||
end
|
||||
|
||||
function Promise.all(promises)
|
||||
return Promise._all(debug.traceback(nil, 2), promises)
|
||||
end
|
||||
|
||||
function Promise.allSettled(promises)
|
||||
return Promise.resolve({})
|
||||
end
|
||||
|
||||
function Promise.race(promises)
|
||||
return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel)
|
||||
end)
|
||||
end
|
||||
|
||||
function Promise.each(list, predicate)
|
||||
return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel)
|
||||
local predicatePromise = Promise.resolve(predicate(value, index))
|
||||
local success, result = predicatePromise:await()
|
||||
end)
|
||||
end
|
||||
|
||||
function Promise.is(object)
|
||||
end
|
||||
|
||||
function Promise.prototype:_reject(...)
|
||||
self:_finalize()
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -787,6 +787,9 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "functions_with_mismatching_arity_but_any_is
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_type_is_illegal")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local t: {x: number?} = {x = nil}
|
||||
|
||||
@ -796,11 +799,14 @@ TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_ty
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(R"(Type '{| x: number? |}' could not be converted into '{| x: number |}'
|
||||
const std::string expected = R"(Type
|
||||
'{| x: number? |}'
|
||||
could not be converted into
|
||||
'{| x: number |}'
|
||||
caused by:
|
||||
Property 'x' is not compatible. Type 'number?' could not be converted into 'number' in an invariant context)",
|
||||
toString(result.errors[0]));
|
||||
Property 'x' is not compatible.
|
||||
Type 'number?' could not be converted into 'number' in an invariant context)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument")
|
||||
@ -1005,6 +1011,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_unification_infinite_recursion")
|
||||
{
|
||||
ScopedFastFlag luauTableUnifyRecursionLimit{"LuauTableUnifyRecursionLimit", true};
|
||||
|
||||
#if defined(_NOOPT) || defined(_DEBUG)
|
||||
ScopedFastInt LuauTypeInferRecursionLimit{"LuauTypeInferRecursionLimit", 100};
|
||||
#endif
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
local tbl = {}
|
||||
|
||||
|
@ -316,6 +316,9 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_string")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Cat = { tag: 'cat', catfood: string }
|
||||
type Dog = { tag: 'dog', dogfood: string }
|
||||
@ -325,14 +328,18 @@ local a: Animal = { tag = 'cat', cafood = 'something' }
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(R"(Type 'a' could not be converted into 'Cat | Dog'
|
||||
const std::string expected = R"(Type 'a' could not be converted into 'Cat | Dog'
|
||||
caused by:
|
||||
None of the union options are compatible. For example: Table type 'a' not compatible with type 'Cat' because the former is missing field 'catfood')",
|
||||
toString(result.errors[0]));
|
||||
None of the union options are compatible. For example:
|
||||
Table type 'a' not compatible with type 'Cat' because the former is missing field 'catfood')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_bool")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Good = { success: true, result: string }
|
||||
type Bad = { success: false, error: string }
|
||||
@ -342,18 +349,20 @@ local a: Result = { success = false, result = 'something' }
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(R"(Type 'a' could not be converted into 'Bad | Good'
|
||||
const std::string expected = R"(Type 'a' could not be converted into 'Bad | Good'
|
||||
caused by:
|
||||
None of the union options are compatible. For example: Table type 'a' not compatible with type 'Bad' because the former is missing field 'error')",
|
||||
toString(result.errors[0]));
|
||||
None of the union options are compatible. For example:
|
||||
Table type 'a' not compatible with type 'Bad' because the former is missing field 'error')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", true},
|
||||
{"LuauIndentTypeMismatch", true},
|
||||
};
|
||||
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
CheckResult result = check(R"(
|
||||
type Ok<T> = {success: true, result: T}
|
||||
type Err<T> = {success: false, error: T}
|
||||
@ -365,10 +374,10 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
const std::string expectedError = "Type 'a' could not be converted into 'Err<number> | Ok<string>'\n"
|
||||
"caused by:\n"
|
||||
" None of the union options are compatible. For example: Table type 'a'"
|
||||
" not compatible with type 'Err<number>' because the former is missing field 'error'";
|
||||
const std::string expectedError = R"(Type 'a' could not be converted into 'Err<number> | Ok<string>'
|
||||
caused by:
|
||||
None of the union options are compatible. For example:
|
||||
Table type 'a' not compatible with type 'Err<number>' because the former is missing field 'error')";
|
||||
|
||||
CHECK(toString(result.errors[0]) == expectedError);
|
||||
}
|
||||
|
@ -2083,6 +2083,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_should_cope_with_optional_prope
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_prop")
|
||||
{
|
||||
ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}};
|
||||
ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}};
|
||||
CheckResult result = check(R"(
|
||||
type A = { x: number, y: number }
|
||||
type B = { x: number, y: string }
|
||||
@ -2092,13 +2094,17 @@ local b: B = a
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
const std::string expected = R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property 'y' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)");
|
||||
Property 'y' is not compatible.
|
||||
Type 'number' could not be converted into 'string' in an invariant context)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_prop_nested")
|
||||
{
|
||||
ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}};
|
||||
ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}};
|
||||
CheckResult result = check(R"(
|
||||
type AS = { x: number, y: number }
|
||||
type BS = { x: number, y: string }
|
||||
@ -2111,15 +2117,21 @@ local b: B = a
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
const std::string expected = R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property 'b' is not compatible. Type 'AS' could not be converted into 'BS'
|
||||
Property 'b' is not compatible.
|
||||
Type 'AS' could not be converted into 'BS'
|
||||
caused by:
|
||||
Property 'y' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)");
|
||||
Property 'y' is not compatible.
|
||||
Type 'number' could not be converted into 'string' in an invariant context)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "error_detailed_metatable_prop")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a1 = setmetatable({ x = 2, y = 3 }, { __call = function(s) end });
|
||||
local b1 = setmetatable({ x = 2, y = "hello" }, { __call = function(s) end });
|
||||
@ -2130,33 +2142,68 @@ local b2 = setmetatable({ x = 2, y = 4 }, { __call = function(s, t) end });
|
||||
local c2: typeof(a2) = b2
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'b1' could not be converted into 'a1'
|
||||
const std::string expected1 = R"(Type 'b1' could not be converted into 'a1'
|
||||
caused by:
|
||||
Type '{ x: number, y: string }' could not be converted into '{ x: number, y: number }'
|
||||
Type
|
||||
'{ x: number, y: string }'
|
||||
could not be converted into
|
||||
'{ x: number, y: number }'
|
||||
caused by:
|
||||
Property 'y' is not compatible. Type 'string' could not be converted into 'number' in an invariant context)");
|
||||
Property 'y' is not compatible.
|
||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
||||
const std::string expected2 = R"(Type 'b2' could not be converted into 'a2'
|
||||
caused by:
|
||||
Type
|
||||
'{ __call: <a, b>(a, b) -> () }'
|
||||
could not be converted into
|
||||
'{ __call: <a>(a) -> () }'
|
||||
caused by:
|
||||
Property '__call' is not compatible.
|
||||
Type
|
||||
'<a, b>(a, b) -> ()'
|
||||
could not be converted into
|
||||
'<a>(a) -> ()'; different number of generic type parameters)";
|
||||
const std::string expected3 = R"(Type 'b2' could not be converted into 'a2'
|
||||
caused by:
|
||||
Type
|
||||
'{ __call: <a, b>(a, b) -> () }'
|
||||
could not be converted into
|
||||
'{ __call: <a>(a) -> () }'
|
||||
caused by:
|
||||
Property '__call' is not compatible.
|
||||
Type
|
||||
'<a, b>(a, b) -> ()'
|
||||
could not be converted into
|
||||
'<a>(a) -> ()'; different number of generic type parameters)";
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK_EQ(expected1, toString(result.errors[0]));
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
CHECK_EQ(toString(result.errors[1]), R"(Type 'b2' could not be converted into 'a2'
|
||||
caused by:
|
||||
Type '{ __call: <a, b>(a, b) -> () }' could not be converted into '{ __call: <a>(a) -> () }'
|
||||
caused by:
|
||||
Property '__call' is not compatible. Type '<a, b>(a, b) -> ()' could not be converted into '<a>(a) -> ()'; different number of generic type parameters)");
|
||||
CHECK_EQ(expected2, toString(result.errors[1]));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(result.errors[1]), R"(Type 'b2' could not be converted into 'a2'
|
||||
std::string expected3 = R"(Type 'b2' could not be converted into 'a2'
|
||||
caused by:
|
||||
Type '{ __call: (a, b) -> () }' could not be converted into '{ __call: <a>(a) -> () }'
|
||||
Type
|
||||
'{ __call: (a, b) -> () }'
|
||||
could not be converted into
|
||||
'{ __call: <a>(a) -> () }'
|
||||
caused by:
|
||||
Property '__call' is not compatible. Type '(a, b) -> ()' could not be converted into '<a>(a) -> ()'; different number of generic type parameters)");
|
||||
Property '__call' is not compatible.
|
||||
Type
|
||||
'(a, b) -> ()'
|
||||
could not be converted into
|
||||
'<a>(a) -> ()'; different number of generic type parameters)";
|
||||
CHECK_EQ(expected3, toString(result.errors[1]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key")
|
||||
{
|
||||
ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}};
|
||||
ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}};
|
||||
CheckResult result = check(R"(
|
||||
type A = { [number]: string }
|
||||
type B = { [string]: string }
|
||||
@ -2166,13 +2213,17 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
const std::string expected = R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property '[indexer key]' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)");
|
||||
Property '[indexer key]' is not compatible.
|
||||
Type 'number' could not be converted into 'string' in an invariant context)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value")
|
||||
{
|
||||
ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}};
|
||||
ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}};
|
||||
CheckResult result = check(R"(
|
||||
type A = { [number]: number }
|
||||
type B = { [number]: string }
|
||||
@ -2182,9 +2233,11 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
const std::string expected = R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property '[indexer value]' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)");
|
||||
Property '[indexer value]' is not compatible.
|
||||
Type 'number' could not be converted into 'string' in an invariant context)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table")
|
||||
@ -2204,6 +2257,8 @@ a.p = { x = 9 }
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_error")
|
||||
{
|
||||
ScopedFastFlag sff[] = {{"LuauIndentTypeMismatch", true}};
|
||||
ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}};
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
type Super = { x : number }
|
||||
@ -2218,9 +2273,11 @@ local y: number = tmp.p.y
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'tmp' could not be converted into 'HasSuper'
|
||||
const std::string expected = R"(Type 'tmp' could not be converted into 'HasSuper'
|
||||
caused by:
|
||||
Property 'p' is not compatible. Table type '{ x: number, y: number }' not compatible with type 'Super' because the former has extra field 'y')");
|
||||
Property 'p' is not compatible.
|
||||
Table type '{ x: number, y: number }' not compatible with type 'Super' because the former has extra field 'y')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_with_indexer")
|
||||
@ -3302,7 +3359,13 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_a_subtype_of_a_compatible_polymorphic_shap
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauAlwaysCommitInferencesOfFunctionCalls", true};
|
||||
ScopedFastInt sfi[] = {{"LuauIndentTypeMismatchMaxTypeLength", 10}};
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauAlwaysCommitInferencesOfFunctionCalls", true},
|
||||
{"LuauIndentTypeMismatch", true},
|
||||
};
|
||||
|
||||
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(s)
|
||||
@ -3316,22 +3379,32 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
|
||||
CHECK_EQ(R"(Type 'string' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
const std::string expected1 =
|
||||
R"(Type 'string' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')",
|
||||
toString(result.errors[0]));
|
||||
The former's metatable does not satisfy the requirements.
|
||||
Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')";
|
||||
CHECK_EQ(expected1, toString(result.errors[0]));
|
||||
|
||||
CHECK_EQ(R"(Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')",
|
||||
toString(result.errors[1]));
|
||||
|
||||
CHECK_EQ(R"(Type '"bar" | "baz"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
const std::string expected2 =
|
||||
R"(Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
Not all union options are compatible. Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
The former's metatable does not satisfy the requirements.
|
||||
Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')";
|
||||
CHECK_EQ(expected2, toString(result.errors[1]));
|
||||
|
||||
const std::string expected3 = R"(Type
|
||||
'"bar" | "baz"'
|
||||
could not be converted into
|
||||
't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')",
|
||||
toString(result.errors[2]));
|
||||
Not all union options are compatible.
|
||||
Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements.
|
||||
Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')";
|
||||
CHECK_EQ(expected3, toString(result.errors[2]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compatible")
|
||||
@ -3349,6 +3422,9 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compati
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(s): string
|
||||
local foo = s:absolutely_no_scalar_has_this_method()
|
||||
@ -3356,11 +3432,13 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(R"(Type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' could not be converted into 'string'
|
||||
const std::string expected =
|
||||
R"(Type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' could not be converted into 'string'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' because the former is missing field 'absolutely_no_scalar_has_this_method')",
|
||||
toString(result.errors[0]));
|
||||
The former's metatable does not satisfy the requirements.
|
||||
Table type 'typeof(string)' not compatible with type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' because the former is missing field 'absolutely_no_scalar_has_this_method')";
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ("<a, b...>(t1) -> string where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}", toString(requireType("f")));
|
||||
}
|
||||
|
||||
|
@ -992,6 +992,9 @@ end
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
--!nolint
|
||||
@ -1028,16 +1031,23 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
|
||||
// unsound.
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(
|
||||
R"(Type 'Policies' from 'MainModule' could not be converted into 'Policies' from 'MainModule'
|
||||
const std::string expected = R"(Type 'Policies' from 'MainModule' could not be converted into 'Policies' from 'MainModule'
|
||||
caused by:
|
||||
Property 'getStoreFieldName' is not compatible. Type '(Policies, FieldSpecifier & {| from: number? |}) -> (a, b...)' could not be converted into '(Policies, FieldSpecifier) -> string'
|
||||
Property 'getStoreFieldName' is not compatible.
|
||||
Type
|
||||
'(Policies, FieldSpecifier & {| from: number? |}) -> (a, b...)'
|
||||
could not be converted into
|
||||
'(Policies, FieldSpecifier) -> string'
|
||||
caused by:
|
||||
Argument #2 type is not compatible. Type 'FieldSpecifier' could not be converted into 'FieldSpecifier & {| from: number? |}'
|
||||
Argument #2 type is not compatible.
|
||||
Type
|
||||
'FieldSpecifier'
|
||||
could not be converted into
|
||||
'FieldSpecifier & {| from: number? |}'
|
||||
caused by:
|
||||
Not all intersection parts are compatible. Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName')",
|
||||
toString(result.errors[0]));
|
||||
Not all intersection parts are compatible.
|
||||
Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1205,8 +1215,6 @@ end
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typechecking_in_type_guards")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTypecheckTypeguards", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a = type(foo) == 'nil'
|
||||
local b = typeof(foo) ~= 'nil'
|
||||
|
@ -345,7 +345,9 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauTransitiveSubtyping", true},
|
||||
{"LuauIndentTypeMismatch", true},
|
||||
};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
TableType::Props freeProps{
|
||||
{"foo", {builtinTypes->numberType}},
|
||||
@ -373,11 +375,13 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table
|
||||
state.log.commit();
|
||||
|
||||
REQUIRE_EQ(state.errors.size(), 1);
|
||||
|
||||
std::string expected = "Type '{ @metatable {| __index: {| foo: string |} |}, { } }' could not be converted into '{- foo: number -}'\n"
|
||||
"caused by:\n"
|
||||
" Type 'number' could not be converted into 'string'";
|
||||
CHECK_EQ(toString(state.errors[0]), expected);
|
||||
const std::string expected = R"(Type
|
||||
'{ @metatable {| __index: {| foo: string |} |}, { } }'
|
||||
could not be converted into
|
||||
'{- foo: number -}'
|
||||
caused by:
|
||||
Type 'number' could not be converted into 'string')";
|
||||
CHECK_EQ(expected, toString(state.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "fuzz_tail_unification_issue")
|
||||
|
@ -872,6 +872,9 @@ type R = { m: F<R> }
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "pack_tail_unification_check")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a: () -> (number, ...string)
|
||||
local b: () -> (number, ...boolean)
|
||||
@ -879,9 +882,13 @@ a = b
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type '() -> (number, ...boolean)' could not be converted into '() -> (number, ...string)'
|
||||
const std::string expected = R"(Type
|
||||
'() -> (number, ...boolean)'
|
||||
could not be converted into
|
||||
'() -> (number, ...string)'
|
||||
caused by:
|
||||
Type 'boolean' could not be converted into 'string')");
|
||||
Type 'boolean' could not be converted into 'string')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
// TODO: File a Jira about this
|
||||
|
@ -459,6 +459,8 @@ local oh : boolean = t.y
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_union_part")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
CheckResult result = check(R"(
|
||||
type X = { x: number }
|
||||
type Y = { y: number }
|
||||
@ -471,9 +473,11 @@ local b: { w: number } = a
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'X | Y | Z' could not be converted into '{| w: number |}'
|
||||
const std::string expected = R"(Type 'X | Y | Z' could not be converted into '{| w: number |}'
|
||||
caused by:
|
||||
Not all union options are compatible. Table type 'X' not compatible with type '{| w: number |}' because the former is missing field 'w')");
|
||||
Not all union options are compatible.
|
||||
Table type 'X' not compatible with type '{| w: number |}' because the former is missing field 'w')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_union_all")
|
||||
@ -494,6 +498,8 @@ local a: XYZ = { w = 4 }
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_optional")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
CheckResult result = check(R"(
|
||||
type X = { x: number }
|
||||
|
||||
@ -501,9 +507,11 @@ local a: X? = { w = 4 }
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'a' could not be converted into 'X?'
|
||||
const std::string expected = R"(Type 'a' could not be converted into 'X?'
|
||||
caused by:
|
||||
None of the union options are compatible. For example: Table type 'a' not compatible with type 'X' because the former is missing field 'x')");
|
||||
None of the union options are compatible. For example:
|
||||
Table type 'a' not compatible with type 'X' because the former is missing field 'x')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
// We had a bug where a cyclic union caused a stack overflow.
|
||||
@ -524,6 +532,9 @@ TEST_CASE_FIXTURE(Fixture, "dont_allow_cyclic_unions_to_be_inferred")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type A = { x: number, y: (number) -> string } | { z: number, y: (number) -> string }
|
||||
|
||||
@ -540,8 +551,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
// NOTE: union normalization will improve this message
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
R"(Type '(string) -> number' could not be converted into '((number) -> string) | ((number) -> string)'; none of the union options are compatible)");
|
||||
const std::string expected = R"(Type
|
||||
'(string) -> number'
|
||||
could not be converted into
|
||||
'((number) -> string) | ((number) -> string)'; none of the union options are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_true_and_false")
|
||||
@ -606,6 +620,8 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
CheckResult result = check(R"(
|
||||
function f<a...>()
|
||||
local x : (number, a...) -> (number?, a...)
|
||||
@ -615,12 +631,17 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(number, a...) -> (number?, a...)' could not be converted into '((number) -> number) | ((number?, "
|
||||
"a...) -> (number?, a...))'; none of the union options are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'(number, a...) -> (number?, a...)'
|
||||
could not be converted into
|
||||
'((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
CheckResult result = check(R"(
|
||||
local x : (number) -> number?
|
||||
local y : ((number?) -> number) | ((number | string) -> nil) = x -- OK
|
||||
@ -628,12 +649,18 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(number) -> number?' could not be converted into '((number) -> nil) | ((number, string?) -> "
|
||||
"number)'; none of the union options are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'(number) -> number?'
|
||||
could not be converted into
|
||||
'((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : () -> (number | string)
|
||||
local y : (() -> number) | (() -> string) = x -- OK
|
||||
@ -641,12 +668,18 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '() -> number | string' could not be converted into '(() -> (string, string)) | (() -> number)'; none "
|
||||
"of the union options are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'() -> number | string'
|
||||
could not be converted into
|
||||
'(() -> (string, string)) | (() -> number)'; none of the union options are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : (...nil) -> (...number?)
|
||||
local y : ((...string?) -> (...number)) | ((...number?) -> nil) = x -- OK
|
||||
@ -654,12 +687,18 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '(...nil) -> (...number?)' could not be converted into '((...string?) -> (...number)) | ((...string?) "
|
||||
"-> nil)'; none of the union options are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'(...nil) -> (...number?)'
|
||||
could not be converted into
|
||||
'((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : (number) -> ()
|
||||
local y : ((number?) -> ()) | ((...number) -> ()) = x -- OK
|
||||
@ -667,12 +706,18 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type '(number) -> ()' could not be converted into '((...number?) -> ()) | ((number?) -> ())'; none of the union options are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'(number) -> ()'
|
||||
could not be converted into
|
||||
'((...number?) -> ()) | ((number?) -> ())'; none of the union options are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIndentTypeMismatch", true};
|
||||
ScopedFastInt sfi{"LuauIndentTypeMismatchMaxTypeLength", 10};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : () -> (number?, ...number)
|
||||
local y : (() -> (...number)) | (() -> nil) = x -- OK
|
||||
@ -680,8 +725,11 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '() -> (number?, ...number)' could not be converted into '(() -> (...number)) | (() -> number)'; none "
|
||||
"of the union options are compatible");
|
||||
const std::string expected = R"(Type
|
||||
'() -> (number?, ...number)'
|
||||
could not be converted into
|
||||
'(() -> (...number)) | (() -> number)'; none of the union options are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types")
|
||||
|
@ -14,6 +14,17 @@ assert((function(x, y)
|
||||
return c, b, t, t1, t2
|
||||
end)(5, 10) == 50)
|
||||
|
||||
assert((function(x)
|
||||
local oops -- split to prevent inlining
|
||||
function oops()
|
||||
end
|
||||
|
||||
-- x is checked to be a number here; we can not execute a reentry from oops() because optimizer assumes this holds until return
|
||||
local y = math.abs(x)
|
||||
oops()
|
||||
return y * x
|
||||
end)("42") == 1764)
|
||||
|
||||
local function fuzzfail1(...)
|
||||
repeat
|
||||
_ = nil
|
||||
|
@ -68,5 +68,16 @@ ecall(checkuserdata, 2)
|
||||
call(checkvector, vector(1, 2, 3))
|
||||
ecall(checkvector, 2)
|
||||
|
||||
local function mutation_causes_bad_exit(a: number, count: number, sum: number)
|
||||
repeat
|
||||
a = 's'
|
||||
sum += count
|
||||
pcall(function() end)
|
||||
count -= 1
|
||||
until count == 0
|
||||
return sum
|
||||
end
|
||||
|
||||
assert(call(mutation_causes_bad_exit, 5, 10, 0) == 55)
|
||||
|
||||
return('OK')
|
||||
|
@ -161,8 +161,10 @@ struct BoostLikeReporter : doctest::IReporter
|
||||
}
|
||||
|
||||
void log_message(const doctest::MessageData& md) override
|
||||
{ //
|
||||
printf("%s(%d): ERROR: %s\n", md.m_file, md.m_line, md.m_string.c_str());
|
||||
{
|
||||
const char* severity = (md.m_severity & doctest::assertType::is_warn) ? "WARNING" : "ERROR";
|
||||
|
||||
printf("%s(%d): %s: %s\n", md.m_file, md.m_line, severity, md.m_string.c_str());
|
||||
}
|
||||
|
||||
// called when a test case is skipped either because it doesn't pass the filters, has a skip decorator
|
||||
|
Loading…
Reference in New Issue
Block a user