Sync to upstream/release/590

This commit is contained in:
Vyacheslav Egorov 2023-08-11 15:55:30 +03:00
parent 25cc75b096
commit 089da9e924
82 changed files with 2356 additions and 1283 deletions

1
.gitignore vendored
View File

@ -10,4 +10,5 @@
/luau
/luau-tests
/luau-analyze
/luau-compile
__pycache__

View File

@ -16,6 +16,8 @@ using SeenTypePacks = std::unordered_map<TypePackId, TypePackId>;
struct CloneState
{
NotNull<BuiltinTypes> builtinTypes;
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;

View File

@ -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>

View File

@ -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.

View File

@ -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

View File

@ -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);

View 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

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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;

View File

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

View File

@ -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();

View File

@ -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);

View File

@ -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);
}

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -5,6 +5,7 @@
#include "ByteUtils.h"
#include <stdarg.h>
#include <stdio.h>
namespace Luau
{

View File

@ -5,7 +5,6 @@
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
namespace Luau
{

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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++)

View File

@ -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);

View File

@ -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"

View File

@ -25,7 +25,7 @@ struct ModuleHelpers
Label exitContinueVm;
Label exitNoContinueVm;
Label exitContinueVmClearNativeFlag;
Label updatePcAndContinueInVm;
Label updatePcAndContinueInVm; // no reentry
Label return_;
Label interrupt;

View File

@ -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)

View File

@ -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);

View File

@ -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.

View File

@ -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)
{

View File

@ -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;
}
}

View File

@ -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)});
}
}

View File

@ -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);

View File

@ -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)

View File

@ -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);

View File

@ -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}
{
}

View File

@ -112,6 +112,7 @@ using GateFn = int (*)(lua_State*, Proto*, uintptr_t, NativeContext*);
struct NativeState
{
NativeState();
NativeState(AllocationCallback* allocationCallback, void* allocationCallbackContext);
~NativeState();
CodeAllocator codeAllocator;

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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
{

View File

@ -2,6 +2,7 @@
#pragma once
#include "Luau/Ast.h"
#include "Luau/DenseHash.h"
#include <string>

View File

@ -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;
}

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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 '*':

View File

@ -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);
}

View File

@ -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!

View File

@ -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

View File

@ -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;

View File

@ -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 }

View File

@ -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())

View File

@ -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();

View File

@ -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)

View File

@ -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

View File

@ -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);

View File

@ -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();

View File

@ -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();

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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,

View File

@ -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")

View File

@ -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();

View File

@ -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 = {}

View File

@ -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);
}

View File

@ -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")));
}

View File

@ -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'

View File

@ -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")

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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')

View File

@ -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