mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
Sync to upstream/release/535
This commit is contained in:
parent
8f040862b1
commit
4a95f2201e
@ -63,6 +63,7 @@ private:
|
||||
AstLocal* local = nullptr;
|
||||
};
|
||||
|
||||
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(const SourceModule& source, Position pos);
|
||||
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos);
|
||||
AstNode* findNodeAtPosition(const SourceModule& source, Position pos);
|
||||
AstExpr* findExprAtPosition(const SourceModule& source, Position pos);
|
||||
|
@ -153,7 +153,7 @@ struct TypeChecker
|
||||
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
|
||||
TypeId checkBinaryOperation(
|
||||
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprBinary& expr);
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr);
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprError& expr);
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||
@ -180,8 +180,12 @@ struct TypeChecker
|
||||
const ScopePtr& scope, Unifier& state, TypePackId paramPack, TypePackId argPack, const std::vector<Location>& argLocations);
|
||||
|
||||
WithPredicate<TypePackId> checkExprPack(const ScopePtr& scope, const AstExpr& expr);
|
||||
WithPredicate<TypePackId> checkExprPack(const ScopePtr& scope, const AstExprCall& expr);
|
||||
|
||||
WithPredicate<TypePackId> checkExprPackHelper(const ScopePtr& scope, const AstExpr& expr);
|
||||
WithPredicate<TypePackId> checkExprPackHelper(const ScopePtr& scope, const AstExprCall& expr);
|
||||
|
||||
std::vector<std::optional<TypeId>> getExpectedTypesForCall(const std::vector<TypeId>& overloads, size_t argumentCount, bool selfCall);
|
||||
|
||||
std::optional<WithPredicate<TypePackId>> checkCallOverload(const ScopePtr& scope, const AstExprCall& expr, TypeId fn, TypePackId retPack,
|
||||
TypePackId argPack, TypePack* args, const std::vector<Location>* argLocations, const WithPredicate<TypePackId>& argListResult,
|
||||
std::vector<TypeId>& overloadsThatMatchArgCount, std::vector<TypeId>& overloadsThatDont, std::vector<OverloadErrorEntry>& errors);
|
||||
@ -236,10 +240,11 @@ struct TypeChecker
|
||||
|
||||
void unifyLowerBound(TypePackId subTy, TypePackId superTy, TypeLevel demotedLevel, const Location& location);
|
||||
|
||||
std::optional<TypeId> findMetatableEntry(TypeId type, std::string entry, const Location& location);
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location);
|
||||
std::optional<TypeId> findMetatableEntry(TypeId type, std::string entry, const Location& location, bool addErrors);
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location, bool addErrors);
|
||||
|
||||
std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, TypeId type, const Name& name, const Location& location, bool addErrors);
|
||||
std::optional<TypeId> getIndexTypeFromTypeImpl(const ScopePtr& scope, TypeId type, const Name& name, const Location& location, bool addErrors);
|
||||
|
||||
// Reduces the union to its simplest possible shape.
|
||||
// (A | B) | B | C yields A | B | C
|
||||
@ -316,11 +321,12 @@ private:
|
||||
|
||||
TypeIdPredicate mkTruthyPredicate(bool sense);
|
||||
|
||||
// Returns nullopt if the predicate filters down the TypeId to 0 options.
|
||||
std::optional<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);
|
||||
// TODO: Return TypeId only.
|
||||
std::optional<TypeId> filterMapImpl(TypeId type, TypeIdPredicate predicate);
|
||||
std::pair<std::optional<TypeId>, bool> filterMap(TypeId type, TypeIdPredicate predicate);
|
||||
|
||||
public:
|
||||
std::optional<TypeId> pickTypesFromSense(TypeId type, bool sense);
|
||||
std::pair<std::optional<TypeId>, bool> pickTypesFromSense(TypeId type, bool sense);
|
||||
|
||||
private:
|
||||
TypeId unionOfTypes(TypeId a, TypeId b, const Location& location, bool unifyFreeTypes = true);
|
||||
@ -345,6 +351,7 @@ private:
|
||||
TypePackId freshTypePack(TypeLevel level);
|
||||
|
||||
TypeId resolveType(const ScopePtr& scope, const AstType& annotation);
|
||||
TypeId resolveTypeWorker(const ScopePtr& scope, const AstType& annotation);
|
||||
TypePackId resolveTypePack(const ScopePtr& scope, const AstTypeList& types);
|
||||
TypePackId resolveTypePack(const ScopePtr& scope, const AstTypePack& annotation);
|
||||
TypeId instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, const std::vector<TypeId>& typeParams,
|
||||
@ -412,8 +419,12 @@ public:
|
||||
const TypeId booleanType;
|
||||
const TypeId threadType;
|
||||
const TypeId anyType;
|
||||
const TypeId unknownType;
|
||||
const TypeId neverType;
|
||||
|
||||
const TypePackId anyTypePack;
|
||||
const TypePackId neverTypePack;
|
||||
const TypePackId uninhabitableTypePack;
|
||||
|
||||
private:
|
||||
int checkRecursionCount = 0;
|
||||
|
@ -173,5 +173,6 @@ std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp,
|
||||
bool isVariadic(TypePackId tp);
|
||||
bool isVariadic(TypePackId tp, const TxnLog& log);
|
||||
|
||||
bool containsNever(TypePackId tp);
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -460,10 +460,18 @@ struct LazyTypeVar
|
||||
std::function<TypeId()> thunk;
|
||||
};
|
||||
|
||||
struct UnknownTypeVar
|
||||
{
|
||||
};
|
||||
|
||||
struct NeverTypeVar
|
||||
{
|
||||
};
|
||||
|
||||
using ErrorTypeVar = Unifiable::Error;
|
||||
|
||||
using TypeVariant = Unifiable::Variant<TypeId, PrimitiveTypeVar, ConstrainedTypeVar, BlockedTypeVar, SingletonTypeVar, FunctionTypeVar, TableTypeVar,
|
||||
MetatableTypeVar, ClassTypeVar, AnyTypeVar, UnionTypeVar, IntersectionTypeVar, LazyTypeVar>;
|
||||
MetatableTypeVar, ClassTypeVar, AnyTypeVar, UnionTypeVar, IntersectionTypeVar, LazyTypeVar, UnknownTypeVar, NeverTypeVar>;
|
||||
|
||||
struct TypeVar final
|
||||
{
|
||||
@ -575,8 +583,12 @@ struct SingletonTypes
|
||||
const TypeId trueType;
|
||||
const TypeId falseType;
|
||||
const TypeId anyType;
|
||||
const TypeId unknownType;
|
||||
const TypeId neverType;
|
||||
|
||||
const TypePackId anyTypePack;
|
||||
const TypePackId neverTypePack;
|
||||
const TypePackId uninhabitableTypePack;
|
||||
|
||||
SingletonTypes();
|
||||
~SingletonTypes();
|
||||
@ -632,12 +644,30 @@ T* getMutable(TypeId tv)
|
||||
return get_if<T>(&asMutable(tv)->ty);
|
||||
}
|
||||
|
||||
/* Traverses the UnionTypeVar yielding each TypeId.
|
||||
* If the iterator encounters a nested UnionTypeVar, it will instead yield each TypeId within.
|
||||
*
|
||||
* Beware: the iterator does not currently filter for unique TypeIds. This may change in the future.
|
||||
const std::vector<TypeId>& getTypes(const UnionTypeVar* utv);
|
||||
const std::vector<TypeId>& getTypes(const IntersectionTypeVar* itv);
|
||||
const std::vector<TypeId>& getTypes(const ConstrainedTypeVar* ctv);
|
||||
|
||||
template<typename T>
|
||||
struct TypeIterator;
|
||||
|
||||
using UnionTypeVarIterator = TypeIterator<UnionTypeVar>;
|
||||
UnionTypeVarIterator begin(const UnionTypeVar* utv);
|
||||
UnionTypeVarIterator end(const UnionTypeVar* utv);
|
||||
|
||||
using IntersectionTypeVarIterator = TypeIterator<IntersectionTypeVar>;
|
||||
IntersectionTypeVarIterator begin(const IntersectionTypeVar* itv);
|
||||
IntersectionTypeVarIterator end(const IntersectionTypeVar* itv);
|
||||
|
||||
using ConstrainedTypeVarIterator = TypeIterator<ConstrainedTypeVar>;
|
||||
ConstrainedTypeVarIterator begin(const ConstrainedTypeVar* ctv);
|
||||
ConstrainedTypeVarIterator end(const ConstrainedTypeVar* ctv);
|
||||
|
||||
/* Traverses the type T yielding each TypeId.
|
||||
* If the iterator encounters a nested type T, it will instead yield each TypeId within.
|
||||
*/
|
||||
struct UnionTypeVarIterator
|
||||
template<typename T>
|
||||
struct TypeIterator
|
||||
{
|
||||
using value_type = Luau::TypeId;
|
||||
using pointer = value_type*;
|
||||
@ -645,33 +675,116 @@ struct UnionTypeVarIterator
|
||||
using difference_type = size_t;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
explicit UnionTypeVarIterator(const UnionTypeVar* utv);
|
||||
explicit TypeIterator(const T* t)
|
||||
{
|
||||
LUAU_ASSERT(t);
|
||||
|
||||
UnionTypeVarIterator& operator++();
|
||||
UnionTypeVarIterator operator++(int);
|
||||
bool operator!=(const UnionTypeVarIterator& rhs);
|
||||
bool operator==(const UnionTypeVarIterator& rhs);
|
||||
const std::vector<TypeId>& types = getTypes(t);
|
||||
if (!types.empty())
|
||||
stack.push_front({t, 0});
|
||||
|
||||
const TypeId& operator*();
|
||||
seen.insert(t);
|
||||
}
|
||||
|
||||
friend UnionTypeVarIterator end(const UnionTypeVar* utv);
|
||||
TypeIterator<T>& operator++()
|
||||
{
|
||||
advance();
|
||||
descend();
|
||||
return *this;
|
||||
}
|
||||
|
||||
TypeIterator<T> operator++(int)
|
||||
{
|
||||
TypeIterator<T> copy = *this;
|
||||
++copy;
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool operator==(const TypeIterator<T>& rhs) const
|
||||
{
|
||||
if (!stack.empty() && !rhs.stack.empty())
|
||||
return stack.front() == rhs.stack.front();
|
||||
|
||||
return stack.empty() && rhs.stack.empty();
|
||||
}
|
||||
|
||||
bool operator!=(const TypeIterator<T>& rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
const TypeId& operator*()
|
||||
{
|
||||
LUAU_ASSERT(!stack.empty());
|
||||
|
||||
descend();
|
||||
|
||||
auto [t, currentIndex] = stack.front();
|
||||
LUAU_ASSERT(t);
|
||||
const std::vector<TypeId>& types = getTypes(t);
|
||||
LUAU_ASSERT(currentIndex < types.size());
|
||||
|
||||
const TypeId& ty = types[currentIndex];
|
||||
LUAU_ASSERT(!get<T>(follow(ty)));
|
||||
return ty;
|
||||
}
|
||||
|
||||
// Normally, we'd have `begin` and `end` be a template but there's too much trouble
|
||||
// with templates portability in this area, so not worth it. Thanks MSVC.
|
||||
friend UnionTypeVarIterator end(const UnionTypeVar*);
|
||||
friend IntersectionTypeVarIterator end(const IntersectionTypeVar*);
|
||||
friend ConstrainedTypeVarIterator end(const ConstrainedTypeVar*);
|
||||
|
||||
private:
|
||||
UnionTypeVarIterator() = default;
|
||||
TypeIterator() = default;
|
||||
|
||||
// (UnionTypeVar* utv, size_t currentIndex)
|
||||
using SavedIterInfo = std::pair<const UnionTypeVar*, size_t>;
|
||||
// (T* t, size_t currentIndex)
|
||||
using SavedIterInfo = std::pair<const T*, size_t>;
|
||||
|
||||
std::deque<SavedIterInfo> stack;
|
||||
std::unordered_set<const UnionTypeVar*> seen; // Only needed to protect the iterator from hanging the thread.
|
||||
std::unordered_set<const T*> seen; // Only needed to protect the iterator from hanging the thread.
|
||||
|
||||
void advance();
|
||||
void descend();
|
||||
void advance()
|
||||
{
|
||||
while (!stack.empty())
|
||||
{
|
||||
auto& [t, currentIndex] = stack.front();
|
||||
++currentIndex;
|
||||
|
||||
const std::vector<TypeId>& types = getTypes(t);
|
||||
if (currentIndex >= types.size())
|
||||
stack.pop_front();
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void descend()
|
||||
{
|
||||
while (!stack.empty())
|
||||
{
|
||||
auto [current, currentIndex] = stack.front();
|
||||
const std::vector<TypeId>& types = getTypes(current);
|
||||
if (auto inner = get<T>(follow(types[currentIndex])))
|
||||
{
|
||||
// If we're about to descend into a cyclic type, we should skip over this.
|
||||
// Ideally this should never happen, but alas it does from time to time. :(
|
||||
if (seen.find(inner) != seen.end())
|
||||
advance();
|
||||
else
|
||||
{
|
||||
seen.insert(inner);
|
||||
stack.push_front({inner, 0});
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
UnionTypeVarIterator begin(const UnionTypeVar* utv);
|
||||
UnionTypeVarIterator end(const UnionTypeVar* utv);
|
||||
|
||||
using TypeIdPredicate = std::function<std::optional<TypeId>(TypeId)>;
|
||||
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);
|
||||
|
||||
|
@ -129,6 +129,14 @@ struct GenericTypeVarVisitor
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const UnknownTypeVar& atv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const NeverTypeVar& atv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const UnionTypeVar& utv)
|
||||
{
|
||||
return visit(ty);
|
||||
|
@ -17,6 +17,104 @@ namespace Luau
|
||||
namespace
|
||||
{
|
||||
|
||||
|
||||
struct AutocompleteNodeFinder : public AstVisitor
|
||||
{
|
||||
const Position pos;
|
||||
std::vector<AstNode*> ancestry;
|
||||
|
||||
explicit AutocompleteNodeFinder(Position pos, AstNode* root)
|
||||
: pos(pos)
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(AstExpr* expr) override
|
||||
{
|
||||
if (expr->location.begin < pos && pos <= expr->location.end)
|
||||
{
|
||||
ancestry.push_back(expr);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstStat* stat) override
|
||||
{
|
||||
if (stat->location.begin < pos && pos <= stat->location.end)
|
||||
{
|
||||
ancestry.push_back(stat);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstType* type) override
|
||||
{
|
||||
if (type->location.begin < pos && pos <= type->location.end)
|
||||
{
|
||||
ancestry.push_back(type);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstTypeError* type) override
|
||||
{
|
||||
// For a missing type, match the whole range including the start position
|
||||
if (type->isMissing && type->location.containsClosed(pos))
|
||||
{
|
||||
ancestry.push_back(type);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(class AstTypePack* typePack) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstStatBlock* block) override
|
||||
{
|
||||
// If ancestry is empty, we are inspecting the root of the AST. Its extent is considered to be infinite.
|
||||
if (ancestry.empty())
|
||||
{
|
||||
ancestry.push_back(block);
|
||||
return true;
|
||||
}
|
||||
|
||||
// AstExprIndexName nodes are nested outside-in, so we want the outermost node in the case of nested nodes.
|
||||
// ex foo.bar.baz is represented in the AST as IndexName{ IndexName {foo, bar}, baz}
|
||||
if (!ancestry.empty() && ancestry.back()->is<AstExprIndexName>())
|
||||
return false;
|
||||
|
||||
// Type annotation error might intersect the block statement when the function header is being written,
|
||||
// annotation takes priority
|
||||
if (!ancestry.empty() && ancestry.back()->is<AstTypeError>())
|
||||
return false;
|
||||
|
||||
// If the cursor is at the end of an expression or type and simultaneously at the beginning of a block,
|
||||
// the expression or type wins out.
|
||||
// The exception to this is if we are in a block under an AstExprFunction. In this case, we consider the position to
|
||||
// be within the block.
|
||||
if (block->location.begin == pos && !ancestry.empty())
|
||||
{
|
||||
if (ancestry.back()->asExpr() && !ancestry.back()->is<AstExprFunction>())
|
||||
return false;
|
||||
|
||||
if (ancestry.back()->asType())
|
||||
return false;
|
||||
}
|
||||
|
||||
if (block->location.begin <= pos && pos <= block->location.end)
|
||||
{
|
||||
ancestry.push_back(block);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct FindNode : public AstVisitor
|
||||
{
|
||||
const Position pos;
|
||||
@ -102,6 +200,13 @@ struct FindFullAncestry final : public AstVisitor
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(const SourceModule& source, Position pos)
|
||||
{
|
||||
AutocompleteNodeFinder finder{pos, source.root};
|
||||
source.root->visit(&finder);
|
||||
return finder.ancestry;
|
||||
}
|
||||
|
||||
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos)
|
||||
{
|
||||
const Position end = source.root->location.end;
|
||||
@ -110,7 +215,7 @@ std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Posi
|
||||
|
||||
FindFullAncestry finder(pos, end);
|
||||
source.root->visit(&finder);
|
||||
return std::move(finder.nodes);
|
||||
return finder.nodes;
|
||||
}
|
||||
|
||||
AstNode* findNodeAtPosition(const SourceModule& source, Position pos)
|
||||
|
@ -21,102 +21,6 @@ static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct NodeFinder : public AstVisitor
|
||||
{
|
||||
const Position pos;
|
||||
std::vector<AstNode*> ancestry;
|
||||
|
||||
explicit NodeFinder(Position pos, AstNode* root)
|
||||
: pos(pos)
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(AstExpr* expr) override
|
||||
{
|
||||
if (expr->location.begin < pos && pos <= expr->location.end)
|
||||
{
|
||||
ancestry.push_back(expr);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstStat* stat) override
|
||||
{
|
||||
if (stat->location.begin < pos && pos <= stat->location.end)
|
||||
{
|
||||
ancestry.push_back(stat);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstType* type) override
|
||||
{
|
||||
if (type->location.begin < pos && pos <= type->location.end)
|
||||
{
|
||||
ancestry.push_back(type);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstTypeError* type) override
|
||||
{
|
||||
// For a missing type, match the whole range including the start position
|
||||
if (type->isMissing && type->location.containsClosed(pos))
|
||||
{
|
||||
ancestry.push_back(type);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(class AstTypePack* typePack) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstStatBlock* block) override
|
||||
{
|
||||
// If ancestry is empty, we are inspecting the root of the AST. Its extent is considered to be infinite.
|
||||
if (ancestry.empty())
|
||||
{
|
||||
ancestry.push_back(block);
|
||||
return true;
|
||||
}
|
||||
|
||||
// AstExprIndexName nodes are nested outside-in, so we want the outermost node in the case of nested nodes.
|
||||
// ex foo.bar.baz is represented in the AST as IndexName{ IndexName {foo, bar}, baz}
|
||||
if (!ancestry.empty() && ancestry.back()->is<AstExprIndexName>())
|
||||
return false;
|
||||
|
||||
// Type annotation error might intersect the block statement when the function header is being written,
|
||||
// annotation takes priority
|
||||
if (!ancestry.empty() && ancestry.back()->is<AstTypeError>())
|
||||
return false;
|
||||
|
||||
// If the cursor is at the end of an expression or type and simultaneously at the beginning of a block,
|
||||
// the expression or type wins out.
|
||||
// The exception to this is if we are in a block under an AstExprFunction. In this case, we consider the position to
|
||||
// be within the block.
|
||||
if (block->location.begin == pos && !ancestry.empty())
|
||||
{
|
||||
if (ancestry.back()->asExpr() && !ancestry.back()->is<AstExprFunction>())
|
||||
return false;
|
||||
|
||||
if (ancestry.back()->asType())
|
||||
return false;
|
||||
}
|
||||
|
||||
if (block->location.begin <= pos && pos <= block->location.end)
|
||||
{
|
||||
ancestry.push_back(block);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
static bool alreadyHasParens(const std::vector<AstNode*>& nodes)
|
||||
{
|
||||
@ -905,7 +809,7 @@ AutocompleteEntryMap autocompleteTypeNames(const Module& module, Position positi
|
||||
}
|
||||
|
||||
AstNode* parent = nullptr;
|
||||
AstType* topType = nullptr;
|
||||
AstType* topType = nullptr; // TODO: rename?
|
||||
|
||||
for (auto it = ancestry.rbegin(), e = ancestry.rend(); it != e; ++it)
|
||||
{
|
||||
@ -1477,21 +1381,20 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
if (isWithinComment(sourceModule, position))
|
||||
return {};
|
||||
|
||||
NodeFinder finder{position, sourceModule.root};
|
||||
sourceModule.root->visit(&finder);
|
||||
LUAU_ASSERT(!finder.ancestry.empty());
|
||||
AstNode* node = finder.ancestry.back();
|
||||
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(sourceModule, position);
|
||||
LUAU_ASSERT(!ancestry.empty());
|
||||
AstNode* node = ancestry.back();
|
||||
|
||||
AstExprConstantNil dummy{Location{}};
|
||||
AstNode* parent = finder.ancestry.size() >= 2 ? finder.ancestry.rbegin()[1] : &dummy;
|
||||
AstNode* parent = ancestry.size() >= 2 ? ancestry.rbegin()[1] : &dummy;
|
||||
|
||||
// If we are inside a body of a function that doesn't have a completed argument list, ignore the body node
|
||||
if (auto exprFunction = parent->as<AstExprFunction>(); exprFunction && !exprFunction->argLocation && node == exprFunction->body)
|
||||
{
|
||||
finder.ancestry.pop_back();
|
||||
ancestry.pop_back();
|
||||
|
||||
node = finder.ancestry.back();
|
||||
parent = finder.ancestry.size() >= 2 ? finder.ancestry.rbegin()[1] : &dummy;
|
||||
node = ancestry.back();
|
||||
parent = ancestry.size() >= 2 ? ancestry.rbegin()[1] : &dummy;
|
||||
}
|
||||
|
||||
if (auto indexName = node->as<AstExprIndexName>())
|
||||
@ -1504,47 +1407,47 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point;
|
||||
|
||||
if (!FFlag::LuauSelfCallAutocompleteFix2 && isString(ty))
|
||||
return {autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, finder.ancestry),
|
||||
finder.ancestry};
|
||||
return {autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry),
|
||||
ancestry};
|
||||
else
|
||||
return {autocompleteProps(*module, typeArena, ty, indexType, finder.ancestry), finder.ancestry};
|
||||
return {autocompleteProps(*module, typeArena, ty, indexType, ancestry), ancestry};
|
||||
}
|
||||
else if (auto typeReference = node->as<AstTypeReference>())
|
||||
{
|
||||
if (typeReference->prefix)
|
||||
return {autocompleteModuleTypes(*module, position, typeReference->prefix->value), finder.ancestry};
|
||||
return {autocompleteModuleTypes(*module, position, typeReference->prefix->value), ancestry};
|
||||
else
|
||||
return {autocompleteTypeNames(*module, position, finder.ancestry), finder.ancestry};
|
||||
return {autocompleteTypeNames(*module, position, ancestry), ancestry};
|
||||
}
|
||||
else if (node->is<AstTypeError>())
|
||||
{
|
||||
return {autocompleteTypeNames(*module, position, finder.ancestry), finder.ancestry};
|
||||
return {autocompleteTypeNames(*module, position, ancestry), ancestry};
|
||||
}
|
||||
else if (AstStatLocal* statLocal = node->as<AstStatLocal>())
|
||||
{
|
||||
if (statLocal->vars.size == 1 && (!statLocal->equalsSignLocation || position < statLocal->equalsSignLocation->begin))
|
||||
return {{{"function", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
|
||||
return {{{"function", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
|
||||
else if (statLocal->equalsSignLocation && position >= statLocal->equalsSignLocation->end)
|
||||
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry};
|
||||
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry};
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
else if (AstStatFor* statFor = extractStat<AstStatFor>(finder.ancestry))
|
||||
else if (AstStatFor* statFor = extractStat<AstStatFor>(ancestry))
|
||||
{
|
||||
if (!statFor->hasDo || position < statFor->doLocation.begin)
|
||||
{
|
||||
if (!statFor->from->is<AstExprError>() && !statFor->to->is<AstExprError>() && (!statFor->step || !statFor->step->is<AstExprError>()))
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
|
||||
|
||||
if (statFor->from->location.containsClosed(position) || statFor->to->location.containsClosed(position) ||
|
||||
(statFor->step && statFor->step->location.containsClosed(position)))
|
||||
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry};
|
||||
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
return {autocompleteStatement(sourceModule, *module, finder.ancestry, position), finder.ancestry};
|
||||
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry};
|
||||
}
|
||||
|
||||
else if (AstStatForIn* statForIn = parent->as<AstStatForIn>(); statForIn && (node->is<AstStatBlock>() || isIdentifier(node)))
|
||||
@ -1560,7 +1463,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
return {};
|
||||
}
|
||||
|
||||
return {{{"in", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
|
||||
return {{{"in", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
|
||||
}
|
||||
|
||||
if (!statForIn->hasDo || position <= statForIn->doLocation.begin)
|
||||
@ -1569,58 +1472,58 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
AstExpr* lastExpr = statForIn->values.data[statForIn->values.size - 1];
|
||||
|
||||
if (lastExpr->location.containsClosed(position))
|
||||
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry};
|
||||
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry};
|
||||
|
||||
if (position > lastExpr->location.end)
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
|
||||
|
||||
return {}; // Not sure what this means
|
||||
}
|
||||
}
|
||||
else if (AstStatForIn* statForIn = extractStat<AstStatForIn>(finder.ancestry))
|
||||
else if (AstStatForIn* statForIn = extractStat<AstStatForIn>(ancestry))
|
||||
{
|
||||
// The AST looks a bit differently if the cursor is at a position where only the "do" keyword is allowed.
|
||||
// ex "for f in f do"
|
||||
if (!statForIn->hasDo)
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
|
||||
|
||||
return {autocompleteStatement(sourceModule, *module, finder.ancestry, position), finder.ancestry};
|
||||
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry};
|
||||
}
|
||||
|
||||
else if (AstStatWhile* statWhile = parent->as<AstStatWhile>(); node->is<AstStatBlock>() && statWhile)
|
||||
{
|
||||
if (!statWhile->hasDo && !statWhile->condition->is<AstStatError>() && position > statWhile->condition->location.end)
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
|
||||
|
||||
if (!statWhile->hasDo || position < statWhile->doLocation.begin)
|
||||
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry};
|
||||
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry};
|
||||
|
||||
if (statWhile->hasDo && position > statWhile->doLocation.end)
|
||||
return {autocompleteStatement(sourceModule, *module, finder.ancestry, position), finder.ancestry};
|
||||
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry};
|
||||
}
|
||||
|
||||
else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(finder.ancestry); statWhile && !statWhile->hasDo)
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
|
||||
else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(ancestry); statWhile && !statWhile->hasDo)
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
|
||||
|
||||
else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->elseLocation.has_value())
|
||||
{
|
||||
return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}},
|
||||
finder.ancestry};
|
||||
ancestry};
|
||||
}
|
||||
else if (AstStatIf* statIf = parent->as<AstStatIf>(); statIf && node->is<AstStatBlock>())
|
||||
{
|
||||
if (statIf->condition->is<AstExprError>())
|
||||
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry};
|
||||
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry};
|
||||
else if (!statIf->thenLocation || statIf->thenLocation->containsClosed(position))
|
||||
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
|
||||
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
|
||||
}
|
||||
else if (AstStatIf* statIf = extractStat<AstStatIf>(finder.ancestry);
|
||||
else if (AstStatIf* statIf = extractStat<AstStatIf>(ancestry);
|
||||
statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position)))
|
||||
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
|
||||
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
|
||||
else if (AstStatRepeat* statRepeat = node->as<AstStatRepeat>(); statRepeat && statRepeat->condition->is<AstExprError>())
|
||||
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry};
|
||||
else if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(finder.ancestry); statRepeat)
|
||||
return {autocompleteStatement(sourceModule, *module, finder.ancestry, position), finder.ancestry};
|
||||
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry};
|
||||
else if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat)
|
||||
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry};
|
||||
else if (AstExprTable* exprTable = parent->as<AstExprTable>(); exprTable && (node->is<AstExprGlobal>() || node->is<AstExprConstantString>()))
|
||||
{
|
||||
for (const auto& [kind, key, value] : exprTable->items)
|
||||
@ -1630,7 +1533,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
{
|
||||
if (auto it = module->astExpectedTypes.find(exprTable))
|
||||
{
|
||||
auto result = autocompleteProps(*module, typeArena, *it, PropIndexType::Key, finder.ancestry);
|
||||
auto result = autocompleteProps(*module, typeArena, *it, PropIndexType::Key, ancestry);
|
||||
|
||||
// Remove keys that are already completed
|
||||
for (const auto& item : exprTable->items)
|
||||
@ -1644,9 +1547,9 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
|
||||
// If we know for sure that a key is being written, do not offer general expression suggestions
|
||||
if (!key)
|
||||
autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position, result);
|
||||
autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position, result);
|
||||
|
||||
return {result, finder.ancestry};
|
||||
return {result, ancestry};
|
||||
}
|
||||
|
||||
break;
|
||||
@ -1654,11 +1557,11 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
}
|
||||
}
|
||||
else if (isIdentifier(node) && (parent->is<AstStatExpr>() || parent->is<AstStatError>()))
|
||||
return {autocompleteStatement(sourceModule, *module, finder.ancestry, position), finder.ancestry};
|
||||
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry};
|
||||
|
||||
if (std::optional<AutocompleteEntryMap> ret = autocompleteStringParams(sourceModule, module, finder.ancestry, position, callback))
|
||||
if (std::optional<AutocompleteEntryMap> ret = autocompleteStringParams(sourceModule, module, ancestry, position, callback))
|
||||
{
|
||||
return {*ret, finder.ancestry};
|
||||
return {*ret, ancestry};
|
||||
}
|
||||
else if (node->is<AstExprConstantString>())
|
||||
{
|
||||
@ -1667,14 +1570,14 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
if (auto it = module->astExpectedTypes.find(node->asExpr()))
|
||||
autocompleteStringSingleton(*it, false, result);
|
||||
|
||||
if (finder.ancestry.size() >= 2)
|
||||
if (ancestry.size() >= 2)
|
||||
{
|
||||
if (auto idxExpr = finder.ancestry.at(finder.ancestry.size() - 2)->as<AstExprIndexExpr>())
|
||||
if (auto idxExpr = ancestry.at(ancestry.size() - 2)->as<AstExprIndexExpr>())
|
||||
{
|
||||
if (auto it = module->astTypes.find(idxExpr->expr))
|
||||
autocompleteProps(*module, typeArena, follow(*it), PropIndexType::Point, finder.ancestry, result);
|
||||
autocompleteProps(*module, typeArena, follow(*it), PropIndexType::Point, ancestry, result);
|
||||
}
|
||||
else if (auto binExpr = finder.ancestry.at(finder.ancestry.size() - 2)->as<AstExprBinary>())
|
||||
else if (auto binExpr = ancestry.at(ancestry.size() - 2)->as<AstExprBinary>())
|
||||
{
|
||||
if (binExpr->op == AstExprBinary::CompareEq || binExpr->op == AstExprBinary::CompareNe)
|
||||
{
|
||||
@ -1684,7 +1587,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
}
|
||||
}
|
||||
|
||||
return {result, finder.ancestry};
|
||||
return {result, ancestry};
|
||||
}
|
||||
|
||||
if (node->is<AstExprConstantNumber>())
|
||||
@ -1693,9 +1596,9 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
}
|
||||
|
||||
if (node->asExpr())
|
||||
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry};
|
||||
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry};
|
||||
else if (node->asStat())
|
||||
return {autocompleteStatement(sourceModule, *module, finder.ancestry, position), finder.ancestry};
|
||||
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false)
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
|
||||
/** FIXME: Many of these type definitions are not quite completely accurate.
|
||||
*
|
||||
@ -222,14 +223,14 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
|
||||
|
||||
addGlobalBinding(typeChecker, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau");
|
||||
|
||||
// setmetatable<MT>({ @metatable MT }, MT) -> { @metatable MT }
|
||||
// clang-format off
|
||||
// setmetatable<T: {}, MT>(T, MT) -> { @metatable MT, T }
|
||||
addGlobalBinding(typeChecker, "setmetatable",
|
||||
arena.addType(
|
||||
FunctionTypeVar{
|
||||
{genericMT},
|
||||
{},
|
||||
arena.addTypePack(TypePack{{tableMetaMT, genericMT}}),
|
||||
arena.addTypePack(TypePack{{FFlag::LuauUnknownAndNeverType ? tabTy : tableMetaMT, genericMT}}),
|
||||
arena.addTypePack(TypePack{{tableMetaMT}})
|
||||
}
|
||||
), "@luau"
|
||||
@ -309,6 +310,12 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
|
||||
{
|
||||
auto [paramPack, _predicates] = withPredicate;
|
||||
|
||||
if (FFlag::LuauUnknownAndNeverType)
|
||||
{
|
||||
if (size(paramPack) < 2 && finite(paramPack))
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TypeArena& arena = typechecker.currentModule->internalTypes;
|
||||
|
||||
std::vector<TypeId> expectedArgs = typechecker.unTypePack(scope, paramPack, 2, expr.location);
|
||||
@ -316,6 +323,12 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
|
||||
TypeId target = follow(expectedArgs[0]);
|
||||
TypeId mt = follow(expectedArgs[1]);
|
||||
|
||||
if (FFlag::LuauUnknownAndNeverType)
|
||||
{
|
||||
typechecker.tablify(target);
|
||||
typechecker.tablify(mt);
|
||||
}
|
||||
|
||||
if (const auto& tab = get<TableTypeVar>(target))
|
||||
{
|
||||
if (target->persistent)
|
||||
@ -324,7 +337,8 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
|
||||
}
|
||||
else
|
||||
{
|
||||
typechecker.tablify(mt);
|
||||
if (!FFlag::LuauUnknownAndNeverType)
|
||||
typechecker.tablify(mt);
|
||||
|
||||
const TableTypeVar* mtTtv = get<TableTypeVar>(mt);
|
||||
MetatableTypeVar mtv{target, mt};
|
||||
@ -343,7 +357,10 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
|
||||
|
||||
if (FFlag::LuauSetMetaTableArgsCheck && expr.args.size < 1)
|
||||
{
|
||||
return WithPredicate<TypePackId>{};
|
||||
if (FFlag::LuauUnknownAndNeverType)
|
||||
return std::nullopt;
|
||||
else
|
||||
return WithPredicate<TypePackId>{};
|
||||
}
|
||||
|
||||
if (!FFlag::LuauSetMetaTableArgsCheck || !expr.self)
|
||||
@ -390,11 +407,21 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionAssert(
|
||||
|
||||
if (head.size() > 0)
|
||||
{
|
||||
std::optional<TypeId> newhead = typechecker.pickTypesFromSense(head[0], true);
|
||||
if (!newhead)
|
||||
head = {typechecker.nilType};
|
||||
auto [ty, ok] = typechecker.pickTypesFromSense(head[0], true);
|
||||
if (FFlag::LuauUnknownAndNeverType)
|
||||
{
|
||||
if (get<NeverTypeVar>(*ty))
|
||||
head = {*ty};
|
||||
else
|
||||
head[0] = *ty;
|
||||
}
|
||||
else
|
||||
head[0] = *newhead;
|
||||
{
|
||||
if (!ty)
|
||||
head = {typechecker.nilType};
|
||||
else
|
||||
head[0] = *ty;
|
||||
}
|
||||
}
|
||||
|
||||
return WithPredicate<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
|
||||
|
@ -59,6 +59,8 @@ struct TypeCloner
|
||||
void operator()(const UnionTypeVar& t);
|
||||
void operator()(const IntersectionTypeVar& t);
|
||||
void operator()(const LazyTypeVar& t);
|
||||
void operator()(const UnknownTypeVar& t);
|
||||
void operator()(const NeverTypeVar& t);
|
||||
};
|
||||
|
||||
struct TypePackCloner
|
||||
@ -310,6 +312,16 @@ void TypeCloner::operator()(const LazyTypeVar& t)
|
||||
defaultClone(t);
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const UnknownTypeVar& t)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const NeverTypeVar& t)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
|
||||
|
@ -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/BuiltinDefinitions.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAG(LuauCheckLenMT)
|
||||
|
||||
namespace Luau
|
||||
@ -116,8 +117,6 @@ declare function typeof<T>(value: T): string
|
||||
-- `assert` has a magic function attached that will give more detailed type information
|
||||
declare function assert<T>(value: T, errorMessage: string?): T
|
||||
|
||||
declare function error<T>(message: T, level: number?)
|
||||
|
||||
declare function tostring<T>(value: T): string
|
||||
declare function tonumber<T>(value: T, radix: number?): number?
|
||||
|
||||
@ -204,12 +203,18 @@ declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
|
||||
|
||||
std::string getBuiltinDefinitionSource()
|
||||
{
|
||||
|
||||
std::string result = kBuiltinDefinitionLuaSrc;
|
||||
|
||||
// TODO: move this into kBuiltinDefinitionLuaSrc
|
||||
if (FFlag::LuauCheckLenMT)
|
||||
result += "declare function rawlen<K, V>(obj: {[K]: V} | string): number\n";
|
||||
|
||||
if (FFlag::LuauUnknownAndNeverType)
|
||||
result += "declare function error<T>(message: T, level: number?): never\n";
|
||||
else
|
||||
result += "declare function error<T>(message: T, level: number?)\n";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -496,6 +496,8 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||
module->astTypes.clear();
|
||||
module->astExpectedTypes.clear();
|
||||
module->astOriginalCallTypes.clear();
|
||||
module->astResolvedTypes.clear();
|
||||
module->astResolvedTypePacks.clear();
|
||||
module->scopes.resize(1);
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCopyBeforeNormalizing, false)
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeFlagIsConservative, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineEqFix, false);
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAG(LuauQuantifyConstrained)
|
||||
|
||||
namespace Luau
|
||||
@ -182,7 +182,6 @@ struct Normalize final : TypeVarVisitor
|
||||
{
|
||||
if (!ty->normal)
|
||||
asMutable(ty)->normal = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -193,6 +192,20 @@ struct Normalize final : TypeVarVisitor
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const UnknownTypeVar&) override
|
||||
{
|
||||
if (!ty->normal)
|
||||
asMutable(ty)->normal = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const NeverTypeVar&) override
|
||||
{
|
||||
if (!ty->normal)
|
||||
asMutable(ty)->normal = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const ConstrainedTypeVar& ctvRef) override
|
||||
{
|
||||
CHECK_ITERATION_LIMIT(false);
|
||||
@ -416,7 +429,13 @@ struct Normalize final : TypeVarVisitor
|
||||
std::vector<TypeId> result;
|
||||
|
||||
for (TypeId part : options)
|
||||
{
|
||||
// AnyTypeVar always win the battle no matter what we do, so we're done.
|
||||
if (FFlag::LuauUnknownAndNeverType && get<AnyTypeVar>(follow(part)))
|
||||
return {part};
|
||||
|
||||
combineIntoUnion(result, part);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -427,7 +446,17 @@ struct Normalize final : TypeVarVisitor
|
||||
if (auto utv = get<UnionTypeVar>(ty))
|
||||
{
|
||||
for (TypeId t : utv)
|
||||
{
|
||||
// AnyTypeVar always win the battle no matter what we do, so we're done.
|
||||
if (FFlag::LuauUnknownAndNeverType && get<AnyTypeVar>(t))
|
||||
{
|
||||
result = {t};
|
||||
return;
|
||||
}
|
||||
|
||||
combineIntoUnion(result, t);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -571,8 +600,7 @@ struct Normalize final : TypeVarVisitor
|
||||
*/
|
||||
TypeId combine(Replacer& replacer, TypeId a, TypeId b)
|
||||
{
|
||||
if (FFlag::LuauNormalizeCombineEqFix)
|
||||
b = follow(b);
|
||||
b = follow(b);
|
||||
|
||||
if (FFlag::LuauNormalizeCombineTableFix && a == b)
|
||||
return a;
|
||||
@ -592,7 +620,7 @@ struct Normalize final : TypeVarVisitor
|
||||
}
|
||||
else if (auto ttv = getMutable<TableTypeVar>(a))
|
||||
{
|
||||
if (FFlag::LuauNormalizeCombineTableFix && !get<TableTypeVar>(FFlag::LuauNormalizeCombineEqFix ? b : follow(b)))
|
||||
if (FFlag::LuauNormalizeCombineTableFix && !get<TableTypeVar>(b))
|
||||
return arena.addType(IntersectionTypeVar{{a, b}});
|
||||
combineIntoTable(replacer, ttv, b);
|
||||
return a;
|
||||
|
@ -8,8 +8,10 @@
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauAnyificationMustClone, false)
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -154,7 +156,7 @@ TarjanResult Tarjan::loop()
|
||||
if (currEdge == -1)
|
||||
{
|
||||
++childCount;
|
||||
if (childLimit > 0 && childLimit < childCount)
|
||||
if (childLimit > 0 && (FFlag::LuauUnknownAndNeverType ? childLimit <= childCount : childLimit < childCount))
|
||||
return TarjanResult::TooManyChildren;
|
||||
|
||||
stack.push_back(index);
|
||||
@ -439,6 +441,9 @@ void Substitution::replaceChildren(TypeId ty)
|
||||
if (ignoreChildren(ty))
|
||||
return;
|
||||
|
||||
if (FFlag::LuauAnyificationMustClone && ty->owningArena != arena)
|
||||
return;
|
||||
|
||||
if (FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty))
|
||||
{
|
||||
ftv->argTypes = replace(ftv->argTypes);
|
||||
@ -490,6 +495,9 @@ void Substitution::replaceChildren(TypePackId tp)
|
||||
if (ignoreChildren(tp))
|
||||
return;
|
||||
|
||||
if (FFlag::LuauAnyificationMustClone && tp->owningArena != arena)
|
||||
return;
|
||||
|
||||
if (TypePack* tpp = getMutable<TypePack>(tp))
|
||||
{
|
||||
for (TypeId& tv : tpp->head)
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
|
||||
/*
|
||||
* Prefix generic typenames with gen-
|
||||
@ -699,6 +700,12 @@ struct TypeVarStringifier
|
||||
void operator()(TypeId, const MetatableTypeVar& mtv)
|
||||
{
|
||||
state.result.invalid = true;
|
||||
if (!state.exhaustive && mtv.syntheticName)
|
||||
{
|
||||
state.emit(*mtv.syntheticName);
|
||||
return;
|
||||
}
|
||||
|
||||
state.emit("{ @metatable ");
|
||||
stringify(mtv.metatable);
|
||||
state.emit(",");
|
||||
@ -834,7 +841,7 @@ struct TypeVarStringifier
|
||||
void operator()(TypeId, const ErrorTypeVar& tv)
|
||||
{
|
||||
state.result.error = true;
|
||||
state.emit("*unknown*");
|
||||
state.emit(FFlag::LuauUnknownAndNeverType ? "<error-type>" : "*unknown*");
|
||||
}
|
||||
|
||||
void operator()(TypeId, const LazyTypeVar& ltv)
|
||||
@ -843,7 +850,17 @@ struct TypeVarStringifier
|
||||
state.emit("lazy?");
|
||||
}
|
||||
|
||||
}; // namespace
|
||||
void operator()(TypeId, const UnknownTypeVar& ttv)
|
||||
{
|
||||
state.emit("unknown");
|
||||
}
|
||||
|
||||
void operator()(TypeId, const NeverTypeVar& ttv)
|
||||
{
|
||||
state.emit("never");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct TypePackStringifier
|
||||
{
|
||||
@ -947,7 +964,7 @@ struct TypePackStringifier
|
||||
void operator()(TypePackId, const Unifiable::Error& error)
|
||||
{
|
||||
state.result.error = true;
|
||||
state.emit("*unknown*");
|
||||
state.emit(FFlag::LuauUnknownAndNeverType ? "<error-type>" : "*unknown*");
|
||||
}
|
||||
|
||||
void operator()(TypePackId, const VariadicTypePack& pack)
|
||||
|
@ -205,20 +205,6 @@ struct Printer
|
||||
}
|
||||
}
|
||||
|
||||
void visualizeWithSelf(AstExpr& expr, bool self)
|
||||
{
|
||||
if (!self)
|
||||
return visualize(expr);
|
||||
|
||||
AstExprIndexName* func = expr.as<AstExprIndexName>();
|
||||
LUAU_ASSERT(func);
|
||||
|
||||
visualize(*func->expr);
|
||||
writer.symbol(":");
|
||||
advance(func->indexLocation.begin);
|
||||
writer.identifier(func->index.value);
|
||||
}
|
||||
|
||||
void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg)
|
||||
{
|
||||
advance(annotation.location.begin);
|
||||
@ -366,7 +352,7 @@ struct Printer
|
||||
}
|
||||
else if (const auto& a = expr.as<AstExprCall>())
|
||||
{
|
||||
visualizeWithSelf(*a->func, a->self);
|
||||
visualize(*a->func);
|
||||
writer.symbol("(");
|
||||
|
||||
bool first = true;
|
||||
@ -385,7 +371,7 @@ struct Printer
|
||||
else if (const auto& a = expr.as<AstExprIndexName>())
|
||||
{
|
||||
visualize(*a->expr);
|
||||
writer.symbol(".");
|
||||
writer.symbol(std::string(1, a->op));
|
||||
writer.write(a->index.value);
|
||||
}
|
||||
else if (const auto& a = expr.as<AstExprIndexExpr>())
|
||||
@ -766,7 +752,7 @@ struct Printer
|
||||
else if (const auto& a = program.as<AstStatFunction>())
|
||||
{
|
||||
writer.keyword("function");
|
||||
visualizeWithSelf(*a->name, a->func->self != nullptr);
|
||||
visualize(*a->name);
|
||||
visualizeFunctionBody(*a->func);
|
||||
}
|
||||
else if (const auto& a = program.as<AstStatLocalFunction>())
|
||||
|
@ -7,7 +7,7 @@
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(LuauNonCopyableTypeVarFields)
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -81,34 +81,10 @@ void TxnLog::concat(TxnLog rhs)
|
||||
void TxnLog::commit()
|
||||
{
|
||||
for (auto& [ty, rep] : typeVarChanges)
|
||||
{
|
||||
if (FFlag::LuauNonCopyableTypeVarFields)
|
||||
{
|
||||
asMutable(ty)->reassign(rep.get()->pending);
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeArena* owningArena = ty->owningArena;
|
||||
TypeVar* mtv = asMutable(ty);
|
||||
*mtv = rep.get()->pending;
|
||||
mtv->owningArena = owningArena;
|
||||
}
|
||||
}
|
||||
asMutable(ty)->reassign(rep.get()->pending);
|
||||
|
||||
for (auto& [tp, rep] : typePackChanges)
|
||||
{
|
||||
if (FFlag::LuauNonCopyableTypeVarFields)
|
||||
{
|
||||
asMutable(tp)->reassign(rep.get()->pending);
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeArena* owningArena = tp->owningArena;
|
||||
TypePackVar* mpv = asMutable(tp);
|
||||
*mpv = rep.get()->pending;
|
||||
mpv->owningArena = owningArena;
|
||||
}
|
||||
}
|
||||
asMutable(tp)->reassign(rep.get()->pending);
|
||||
|
||||
clear();
|
||||
}
|
||||
@ -196,9 +172,7 @@ PendingType* TxnLog::queue(TypeId ty)
|
||||
if (!pending)
|
||||
{
|
||||
pending = std::make_unique<PendingType>(*ty);
|
||||
|
||||
if (FFlag::LuauNonCopyableTypeVarFields)
|
||||
pending->pending.owningArena = nullptr;
|
||||
pending->pending.owningArena = nullptr;
|
||||
}
|
||||
|
||||
return pending.get();
|
||||
@ -214,9 +188,7 @@ PendingTypePack* TxnLog::queue(TypePackId tp)
|
||||
if (!pending)
|
||||
{
|
||||
pending = std::make_unique<PendingTypePack>(*tp);
|
||||
|
||||
if (FFlag::LuauNonCopyableTypeVarFields)
|
||||
pending->pending.owningArena = nullptr;
|
||||
pending->pending.owningArena = nullptr;
|
||||
}
|
||||
|
||||
return pending.get();
|
||||
@ -255,24 +227,14 @@ PendingTypePack* TxnLog::pending(TypePackId tp) const
|
||||
PendingType* TxnLog::replace(TypeId ty, TypeVar replacement)
|
||||
{
|
||||
PendingType* newTy = queue(ty);
|
||||
|
||||
if (FFlag::LuauNonCopyableTypeVarFields)
|
||||
newTy->pending.reassign(replacement);
|
||||
else
|
||||
newTy->pending = replacement;
|
||||
|
||||
newTy->pending.reassign(replacement);
|
||||
return newTy;
|
||||
}
|
||||
|
||||
PendingTypePack* TxnLog::replace(TypePackId tp, TypePackVar replacement)
|
||||
{
|
||||
PendingTypePack* newTp = queue(tp);
|
||||
|
||||
if (FFlag::LuauNonCopyableTypeVarFields)
|
||||
newTp->pending.reassign(replacement);
|
||||
else
|
||||
newTp->pending = replacement;
|
||||
|
||||
newTp->pending.reassign(replacement);
|
||||
return newTp;
|
||||
}
|
||||
|
||||
@ -289,7 +251,7 @@ PendingType* TxnLog::bindTable(TypeId ty, std::optional<TypeId> newBoundTo)
|
||||
|
||||
PendingType* TxnLog::changeLevel(TypeId ty, TypeLevel newLevel)
|
||||
{
|
||||
LUAU_ASSERT(get<FreeTypeVar>(ty) || get<TableTypeVar>(ty) || get<FunctionTypeVar>(ty));
|
||||
LUAU_ASSERT(get<FreeTypeVar>(ty) || get<TableTypeVar>(ty) || get<FunctionTypeVar>(ty) || get<ConstrainedTypeVar>(ty));
|
||||
|
||||
PendingType* newTy = queue(ty);
|
||||
if (FreeTypeVar* ftv = Luau::getMutable<FreeTypeVar>(newTy))
|
||||
@ -305,6 +267,11 @@ PendingType* TxnLog::changeLevel(TypeId ty, TypeLevel newLevel)
|
||||
{
|
||||
ftv->level = newLevel;
|
||||
}
|
||||
else if (ConstrainedTypeVar* ctv = Luau::getMutable<ConstrainedTypeVar>(newTy))
|
||||
{
|
||||
if (FFlag::LuauUnknownAndNeverType)
|
||||
ctv->level = newLevel;
|
||||
}
|
||||
|
||||
return newTy;
|
||||
}
|
||||
|
@ -335,6 +335,14 @@ public:
|
||||
{
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("<Lazy?>"));
|
||||
}
|
||||
AstType* operator()(const UnknownTypeVar& ttv)
|
||||
{
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName{"unknown"});
|
||||
}
|
||||
AstType* operator()(const NeverTypeVar& ttv)
|
||||
{
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName{"never"});
|
||||
}
|
||||
|
||||
private:
|
||||
Allocator* allocator;
|
||||
|
@ -31,6 +31,7 @@ LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300)
|
||||
LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
|
||||
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIndexSilenceErrors, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix2, false)
|
||||
@ -41,10 +42,12 @@ LUAU_FASTFLAGVARIABLE(LuauReturnTypeInferenceInNonstrict, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAlwaysQuantify, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauReportErrorsOnIndexerKeyMismatch, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnknownAndNeverType, false)
|
||||
LUAU_FASTFLAG(LuauQuantifyConstrained)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFalsyPredicateReturnsNilInstead, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNonCopyableTypeVarFields, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCheckLenMT, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCheckGenericHOFTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -258,7 +261,11 @@ TypeChecker::TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHan
|
||||
, booleanType(getSingletonTypes().booleanType)
|
||||
, threadType(getSingletonTypes().threadType)
|
||||
, anyType(getSingletonTypes().anyType)
|
||||
, unknownType(getSingletonTypes().unknownType)
|
||||
, neverType(getSingletonTypes().neverType)
|
||||
, anyTypePack(getSingletonTypes().anyTypePack)
|
||||
, neverTypePack(getSingletonTypes().neverTypePack)
|
||||
, uninhabitableTypePack(getSingletonTypes().uninhabitableTypePack)
|
||||
, duplicateTypeAliases{{false, {}}}
|
||||
{
|
||||
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
|
||||
@ -269,6 +276,11 @@ TypeChecker::TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHan
|
||||
globalScope->exportedTypeBindings["string"] = TypeFun{{}, stringType};
|
||||
globalScope->exportedTypeBindings["boolean"] = TypeFun{{}, booleanType};
|
||||
globalScope->exportedTypeBindings["thread"] = TypeFun{{}, threadType};
|
||||
if (FFlag::LuauUnknownAndNeverType)
|
||||
{
|
||||
globalScope->exportedTypeBindings["unknown"] = TypeFun{{}, unknownType};
|
||||
globalScope->exportedTypeBindings["never"] = TypeFun{{}, neverType};
|
||||
}
|
||||
}
|
||||
|
||||
ModulePtr TypeChecker::check(const SourceModule& module, Mode mode, std::optional<ScopePtr> environmentScope)
|
||||
@ -456,6 +468,59 @@ void TypeChecker::checkBlock(const ScopePtr& scope, const AstStatBlock& block)
|
||||
}
|
||||
}
|
||||
|
||||
struct InplaceDemoter : TypeVarOnceVisitor
|
||||
{
|
||||
TypeLevel newLevel;
|
||||
TypeArena* arena;
|
||||
|
||||
InplaceDemoter(TypeLevel level, TypeArena* arena)
|
||||
: newLevel(level)
|
||||
, arena(arena)
|
||||
{
|
||||
}
|
||||
|
||||
bool demote(TypeId ty)
|
||||
{
|
||||
if (auto level = getMutableLevel(ty))
|
||||
{
|
||||
if (level->subsumesStrict(newLevel))
|
||||
{
|
||||
*level = newLevel;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const BoundTypeVar& btyRef) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
if (ty->owningArena != arena)
|
||||
return false;
|
||||
return demote(ty);
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp, const FreeTypePack& ftpRef) override
|
||||
{
|
||||
if (tp->owningArena != arena)
|
||||
return false;
|
||||
|
||||
FreeTypePack* ftp = &const_cast<FreeTypePack&>(ftpRef);
|
||||
if (ftp->level.subsumesStrict(newLevel))
|
||||
{
|
||||
ftp->level = newLevel;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
void TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& block)
|
||||
{
|
||||
int subLevel = 0;
|
||||
@ -559,7 +624,7 @@ void TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, const A
|
||||
tablify(baseTy);
|
||||
|
||||
if (!fun->func->self)
|
||||
expectedType = getIndexTypeFromType(scope, baseTy, name->index.value, name->indexLocation, false);
|
||||
expectedType = getIndexTypeFromType(scope, baseTy, name->index.value, name->indexLocation, /* addErrors= */ false);
|
||||
else if (auto ttv = getMutableTableType(baseTy))
|
||||
{
|
||||
if (!baseTy->persistent && ttv->state != TableState::Sealed && !ttv->selfTy)
|
||||
@ -579,7 +644,7 @@ void TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, const A
|
||||
if (auto name = fun->name->as<AstExprIndexName>())
|
||||
{
|
||||
TypeId exprTy = checkExpr(scope, *name->expr).type;
|
||||
expectedType = getIndexTypeFromType(scope, exprTy, name->index.value, name->indexLocation, false);
|
||||
expectedType = getIndexTypeFromType(scope, exprTy, name->index.value, name->indexLocation, /* addErrors= */ false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -634,15 +699,8 @@ LUAU_NOINLINE void TypeChecker::checkBlockTypeAliases(const ScopePtr& scope, std
|
||||
TypeId type = bindings[name].type;
|
||||
if (get<FreeTypeVar>(follow(type)))
|
||||
{
|
||||
if (FFlag::LuauNonCopyableTypeVarFields)
|
||||
{
|
||||
TypeVar* mty = asMutable(follow(type));
|
||||
mty->reassign(*errorRecoveryType(anyType));
|
||||
}
|
||||
else
|
||||
{
|
||||
*asMutable(type) = *errorRecoveryType(anyType);
|
||||
}
|
||||
TypeVar* mty = asMutable(follow(type));
|
||||
mty->reassign(*errorRecoveryType(anyType));
|
||||
|
||||
reportError(TypeError{typealias->location, OccursCheckFailed{}});
|
||||
}
|
||||
@ -1206,7 +1264,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
|
||||
iterTy = instantiate(scope, checkExpr(scope, *firstValue).type, firstValue->location);
|
||||
}
|
||||
|
||||
if (std::optional<TypeId> iterMM = findMetatableEntry(iterTy, "__iter", firstValue->location))
|
||||
if (std::optional<TypeId> iterMM = findMetatableEntry(iterTy, "__iter", firstValue->location, /* addErrors= */ true))
|
||||
{
|
||||
// if __iter metamethod is present, it will be called and the results are going to be called as if they are functions
|
||||
// TODO: this needs to typecheck all returned values by __iter as if they were for loop arguments
|
||||
@ -1253,7 +1311,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
|
||||
for (TypeId var : varTypes)
|
||||
unify(varTy, var, forin.location);
|
||||
|
||||
if (!get<ErrorTypeVar>(iterTy) && !get<AnyTypeVar>(iterTy) && !get<FreeTypeVar>(iterTy))
|
||||
if (!get<ErrorTypeVar>(iterTy) && !get<AnyTypeVar>(iterTy) && !get<FreeTypeVar>(iterTy) && !get<NeverTypeVar>(iterTy))
|
||||
reportError(firstValue->location, CannotCallNonFunction{iterTy});
|
||||
|
||||
return check(loopScope, *forin.body);
|
||||
@ -1350,7 +1408,7 @@ void TypeChecker::check(const ScopePtr& scope, TypeId ty, const ScopePtr& funSco
|
||||
TypeId exprTy = checkExpr(scope, *name->expr).type;
|
||||
TableTypeVar* ttv = getMutableTableType(exprTy);
|
||||
|
||||
if (!getIndexTypeFromType(scope, exprTy, name->index.value, name->indexLocation, false))
|
||||
if (!getIndexTypeFromType(scope, exprTy, name->index.value, name->indexLocation, /* addErrors= */ false))
|
||||
{
|
||||
if (ttv || isTableIntersection(exprTy))
|
||||
reportError(TypeError{function.location, CannotExtendTable{exprTy, CannotExtendTable::Property, name->index.value}});
|
||||
@ -1376,6 +1434,12 @@ void TypeChecker::check(const ScopePtr& scope, TypeId ty, const ScopePtr& funSco
|
||||
|
||||
checkFunctionBody(funScope, ty, *function.func);
|
||||
|
||||
if (FFlag::LuauUnknownAndNeverType)
|
||||
{
|
||||
InplaceDemoter demoter{funScope->level, ¤tModule->internalTypes};
|
||||
demoter.traverse(ty);
|
||||
}
|
||||
|
||||
if (ttv && ttv->state != TableState::Sealed)
|
||||
ttv->props[name->index.value] = {follow(quantify(funScope, ty, name->indexLocation)), /* deprecated */ false, {}, name->indexLocation};
|
||||
}
|
||||
@ -1729,7 +1793,7 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
|
||||
else if (auto a = expr.as<AstExprUnary>())
|
||||
result = checkExpr(scope, *a);
|
||||
else if (auto a = expr.as<AstExprBinary>())
|
||||
result = checkExpr(scope, *a);
|
||||
result = checkExpr(scope, *a, FFlag::LuauBinaryNeedsExpectedTypesToo ? expectedType : std::nullopt);
|
||||
else if (auto a = expr.as<AstExprTypeAssertion>())
|
||||
result = checkExpr(scope, *a);
|
||||
else if (auto a = expr.as<AstExprError>())
|
||||
@ -1851,41 +1915,56 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
|
||||
|
||||
lhsType = stripFromNilAndReport(lhsType, expr.expr->location);
|
||||
|
||||
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, lhsType, name, expr.location, true))
|
||||
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, lhsType, name, expr.location, /* addErrors= */ true))
|
||||
return {*ty};
|
||||
|
||||
return {errorRecoveryType(scope)};
|
||||
}
|
||||
|
||||
std::optional<TypeId> TypeChecker::findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location)
|
||||
std::optional<TypeId> TypeChecker::findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location, bool addErrors)
|
||||
{
|
||||
ErrorVec errors;
|
||||
auto result = Luau::findTablePropertyRespectingMeta(errors, lhsType, name, location);
|
||||
reportErrors(errors);
|
||||
if (!FFlag::LuauIndexSilenceErrors || addErrors)
|
||||
reportErrors(errors);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<TypeId> TypeChecker::findMetatableEntry(TypeId type, std::string entry, const Location& location)
|
||||
std::optional<TypeId> TypeChecker::findMetatableEntry(TypeId type, std::string entry, const Location& location, bool addErrors)
|
||||
{
|
||||
ErrorVec errors;
|
||||
auto result = Luau::findMetatableEntry(errors, type, entry, location);
|
||||
reportErrors(errors);
|
||||
if (!FFlag::LuauIndexSilenceErrors || addErrors)
|
||||
reportErrors(errors);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
||||
const ScopePtr& scope, TypeId type, const std::string& name, const Location& location, bool addErrors)
|
||||
const ScopePtr& scope, TypeId type, const Name& name, const Location& location, bool addErrors)
|
||||
{
|
||||
size_t errorCount = currentModule->errors.size();
|
||||
|
||||
std::optional<TypeId> result = getIndexTypeFromTypeImpl(scope, type, name, location, addErrors);
|
||||
|
||||
if (FFlag::LuauIndexSilenceErrors && !addErrors)
|
||||
LUAU_ASSERT(errorCount == currentModule->errors.size());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
|
||||
const ScopePtr& scope, TypeId type, const Name& name, const Location& location, bool addErrors)
|
||||
{
|
||||
type = follow(type);
|
||||
|
||||
if (get<ErrorTypeVar>(type) || get<AnyTypeVar>(type))
|
||||
if (get<ErrorTypeVar>(type) || get<AnyTypeVar>(type) || get<NeverTypeVar>(type))
|
||||
return type;
|
||||
|
||||
tablify(type);
|
||||
|
||||
if (isString(type))
|
||||
{
|
||||
std::optional<TypeId> mtIndex = findMetatableEntry(stringType, "__index", location);
|
||||
std::optional<TypeId> mtIndex = findMetatableEntry(stringType, "__index", location, addErrors);
|
||||
LUAU_ASSERT(mtIndex);
|
||||
type = *mtIndex;
|
||||
}
|
||||
@ -1919,7 +1998,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
||||
return result;
|
||||
}
|
||||
|
||||
if (auto found = findTablePropertyRespectingMeta(type, name, location))
|
||||
if (auto found = findTablePropertyRespectingMeta(type, name, location, addErrors))
|
||||
return *found;
|
||||
}
|
||||
else if (const ClassTypeVar* cls = get<ClassTypeVar>(type))
|
||||
@ -1941,7 +2020,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
||||
if (get<AnyTypeVar>(follow(t)))
|
||||
return t;
|
||||
|
||||
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, t, name, location, false))
|
||||
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, t, name, location, /* addErrors= */ false))
|
||||
goodOptions.push_back(*ty);
|
||||
else
|
||||
badOptions.push_back(t);
|
||||
@ -1972,6 +2051,8 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> result = reduceUnion(goodOptions);
|
||||
if (FFlag::LuauUnknownAndNeverType && result.empty())
|
||||
return neverType;
|
||||
|
||||
if (result.size() == 1)
|
||||
return result[0];
|
||||
@ -1987,7 +2068,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
||||
{
|
||||
RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
|
||||
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, t, name, location, false))
|
||||
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, t, name, location, /* addErrors= */ false))
|
||||
parts.push_back(*ty);
|
||||
}
|
||||
|
||||
@ -2017,6 +2098,9 @@ std::vector<TypeId> TypeChecker::reduceUnion(const std::vector<TypeId>& types)
|
||||
for (TypeId t : types)
|
||||
{
|
||||
t = follow(t);
|
||||
if (get<NeverTypeVar>(t))
|
||||
continue;
|
||||
|
||||
if (get<ErrorTypeVar>(t) || get<AnyTypeVar>(t))
|
||||
return {t};
|
||||
|
||||
@ -2028,6 +2112,8 @@ std::vector<TypeId> TypeChecker::reduceUnion(const std::vector<TypeId>& types)
|
||||
{
|
||||
if (FFlag::LuauNormalizeFlagIsConservative)
|
||||
ty = follow(ty);
|
||||
if (get<NeverTypeVar>(ty))
|
||||
continue;
|
||||
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
|
||||
return {ty};
|
||||
|
||||
@ -2041,6 +2127,8 @@ std::vector<TypeId> TypeChecker::reduceUnion(const std::vector<TypeId>& types)
|
||||
for (TypeId ty : r)
|
||||
{
|
||||
ty = follow(ty);
|
||||
if (get<NeverTypeVar>(ty))
|
||||
continue;
|
||||
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
|
||||
return {ty};
|
||||
|
||||
@ -2314,14 +2402,14 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
|
||||
return {booleanType, {NotPredicate{std::move(result.predicates)}}};
|
||||
case AstExprUnary::Minus:
|
||||
{
|
||||
const bool operandIsAny = get<AnyTypeVar>(operandType) || get<ErrorTypeVar>(operandType);
|
||||
const bool operandIsAny = get<AnyTypeVar>(operandType) || get<ErrorTypeVar>(operandType) || get<NeverTypeVar>(operandType);
|
||||
|
||||
if (operandIsAny)
|
||||
return {operandType};
|
||||
|
||||
if (typeCouldHaveMetatable(operandType))
|
||||
{
|
||||
if (auto fnt = findMetatableEntry(operandType, "__unm", expr.location))
|
||||
if (auto fnt = findMetatableEntry(operandType, "__unm", expr.location, /* addErrors= */ true))
|
||||
{
|
||||
TypeId actualFunctionType = instantiate(scope, *fnt, expr.location);
|
||||
TypePackId arguments = addTypePack({operandType});
|
||||
@ -2355,14 +2443,14 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
|
||||
|
||||
operandType = stripFromNilAndReport(operandType, expr.location);
|
||||
|
||||
if (get<ErrorTypeVar>(operandType))
|
||||
return {errorRecoveryType(scope)};
|
||||
if (get<ErrorTypeVar>(operandType) || get<NeverTypeVar>(operandType))
|
||||
return {!FFlag::LuauUnknownAndNeverType ? errorRecoveryType(scope) : operandType};
|
||||
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
|
||||
if (FFlag::LuauCheckLenMT && typeCouldHaveMetatable(operandType))
|
||||
{
|
||||
if (auto fnt = findMetatableEntry(operandType, "__len", expr.location))
|
||||
if (auto fnt = findMetatableEntry(operandType, "__len", expr.location, /* addErrors= */ true))
|
||||
{
|
||||
TypeId actualFunctionType = instantiate(scope, *fnt, expr.location);
|
||||
TypePackId arguments = addTypePack({operandType});
|
||||
@ -2433,6 +2521,9 @@ TypeId TypeChecker::unionOfTypes(TypeId a, TypeId b, const Location& location, b
|
||||
return a;
|
||||
|
||||
std::vector<TypeId> types = reduceUnion({a, b});
|
||||
if (FFlag::LuauUnknownAndNeverType && types.empty())
|
||||
return neverType;
|
||||
|
||||
if (types.size() == 1)
|
||||
return types[0];
|
||||
|
||||
@ -2485,7 +2576,7 @@ TypeId TypeChecker::checkRelationalOperation(
|
||||
|
||||
// If we know nothing at all about the lhs type, we can usually say nothing about the result.
|
||||
// The notable exception to this is the equality and inequality operators, which always produce a boolean.
|
||||
const bool lhsIsAny = get<AnyTypeVar>(lhsType) || get<ErrorTypeVar>(lhsType);
|
||||
const bool lhsIsAny = get<AnyTypeVar>(lhsType) || get<ErrorTypeVar>(lhsType) || get<NeverTypeVar>(lhsType);
|
||||
|
||||
// Peephole check for `cond and a or b -> type(a)|type(b)`
|
||||
// TODO: Kill this when singleton types arrive. :(
|
||||
@ -2508,7 +2599,7 @@ TypeId TypeChecker::checkRelationalOperation(
|
||||
if (isNonstrictMode() && (isNil(lhsType) || isNil(rhsType)))
|
||||
return booleanType;
|
||||
|
||||
const bool rhsIsAny = get<AnyTypeVar>(rhsType) || get<ErrorTypeVar>(rhsType);
|
||||
const bool rhsIsAny = get<AnyTypeVar>(rhsType) || get<ErrorTypeVar>(rhsType) || get<NeverTypeVar>(rhsType);
|
||||
if (lhsIsAny || rhsIsAny)
|
||||
return booleanType;
|
||||
|
||||
@ -2596,7 +2687,7 @@ TypeId TypeChecker::checkRelationalOperation(
|
||||
|
||||
if (leftMetatable)
|
||||
{
|
||||
std::optional<TypeId> metamethod = findMetatableEntry(lhsType, metamethodName, expr.location);
|
||||
std::optional<TypeId> metamethod = findMetatableEntry(lhsType, metamethodName, expr.location, /* addErrors= */ true);
|
||||
if (metamethod)
|
||||
{
|
||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(*metamethod))
|
||||
@ -2757,9 +2848,9 @@ TypeId TypeChecker::checkBinaryOperation(
|
||||
};
|
||||
|
||||
std::string op = opToMetaTableEntry(expr.op);
|
||||
if (auto fnt = findMetatableEntry(lhsType, op, expr.location))
|
||||
if (auto fnt = findMetatableEntry(lhsType, op, expr.location, /* addErrors= */ true))
|
||||
return checkMetatableCall(*fnt, lhsType, rhsType);
|
||||
if (auto fnt = findMetatableEntry(rhsType, op, expr.location))
|
||||
if (auto fnt = findMetatableEntry(rhsType, op, expr.location, /* addErrors= */ true))
|
||||
{
|
||||
// Note the intentionally reversed arguments here.
|
||||
return checkMetatableCall(*fnt, rhsType, lhsType);
|
||||
@ -2793,27 +2884,27 @@ TypeId TypeChecker::checkBinaryOperation(
|
||||
}
|
||||
}
|
||||
|
||||
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBinary& expr)
|
||||
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType)
|
||||
{
|
||||
if (expr.op == AstExprBinary::And)
|
||||
{
|
||||
auto [lhsTy, lhsPredicates] = checkExpr(scope, *expr.left);
|
||||
auto [lhsTy, lhsPredicates] = checkExpr(scope, *expr.left, expectedType);
|
||||
|
||||
ScopePtr innerScope = childScope(scope, expr.location);
|
||||
resolve(lhsPredicates, innerScope, true);
|
||||
|
||||
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right);
|
||||
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right, expectedType);
|
||||
|
||||
return {checkBinaryOperation(scope, expr, lhsTy, rhsTy), {AndPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
|
||||
}
|
||||
else if (expr.op == AstExprBinary::Or)
|
||||
{
|
||||
auto [lhsTy, lhsPredicates] = checkExpr(scope, *expr.left);
|
||||
auto [lhsTy, lhsPredicates] = checkExpr(scope, *expr.left, expectedType);
|
||||
|
||||
ScopePtr innerScope = childScope(scope, expr.location);
|
||||
resolve(lhsPredicates, innerScope, false);
|
||||
|
||||
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right);
|
||||
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right, expectedType);
|
||||
|
||||
// Because of C++, I'm not sure if lhsPredicates was not moved out by the time we call checkBinaryOperation.
|
||||
TypeId result = checkBinaryOperation(scope, expr, lhsTy, rhsTy, lhsPredicates);
|
||||
@ -2824,6 +2915,8 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
|
||||
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);
|
||||
|
||||
@ -2842,6 +2935,7 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
|
||||
}
|
||||
else
|
||||
{
|
||||
// Expected types are not useful for other binary operators.
|
||||
WithPredicate<TypeId> lhs = checkExpr(scope, *expr.left);
|
||||
WithPredicate<TypeId> rhs = checkExpr(scope, *expr.right);
|
||||
|
||||
@ -2896,6 +2990,8 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
|
||||
return {trueType.type};
|
||||
|
||||
std::vector<TypeId> types = reduceUnion({trueType.type, falseType.type});
|
||||
if (FFlag::LuauUnknownAndNeverType && types.empty())
|
||||
return {neverType};
|
||||
return {types.size() == 1 ? types[0] : addType(UnionTypeVar{std::move(types)})};
|
||||
}
|
||||
|
||||
@ -2927,7 +3023,10 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExpr& exp
|
||||
TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprLocal& expr)
|
||||
{
|
||||
if (std::optional<TypeId> ty = scope->lookup(expr.local))
|
||||
return *ty;
|
||||
{
|
||||
ty = follow(*ty);
|
||||
return get<NeverTypeVar>(*ty) ? unknownType : *ty;
|
||||
}
|
||||
|
||||
reportError(expr.location, UnknownSymbol{expr.local->name.value, UnknownSymbol::Binding});
|
||||
return errorRecoveryType(scope);
|
||||
@ -2941,7 +3040,10 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprGloba
|
||||
const auto it = moduleScope->bindings.find(expr.name);
|
||||
|
||||
if (it != moduleScope->bindings.end())
|
||||
return it->second.typeId;
|
||||
{
|
||||
TypeId ty = follow(it->second.typeId);
|
||||
return get<NeverTypeVar>(ty) ? unknownType : ty;
|
||||
}
|
||||
|
||||
TypeId result = freshType(scope);
|
||||
Binding& binding = moduleScope->bindings[expr.name];
|
||||
@ -2962,6 +3064,9 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
|
||||
if (get<ErrorTypeVar>(lhs) || get<AnyTypeVar>(lhs))
|
||||
return lhs;
|
||||
|
||||
if (get<NeverTypeVar>(lhs))
|
||||
return unknownType;
|
||||
|
||||
tablify(lhs);
|
||||
|
||||
Name name = expr.index.value;
|
||||
@ -3023,7 +3128,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
|
||||
}
|
||||
else if (get<IntersectionTypeVar>(lhs))
|
||||
{
|
||||
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, lhs, name, expr.location, false))
|
||||
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, lhs, name, expr.location, /* addErrors= */ false))
|
||||
return *ty;
|
||||
|
||||
// If intersection has a table part, report that it cannot be extended just as a sealed table
|
||||
@ -3050,6 +3155,9 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
|
||||
if (get<AnyTypeVar>(exprType) || get<ErrorTypeVar>(exprType))
|
||||
return exprType;
|
||||
|
||||
if (get<NeverTypeVar>(exprType))
|
||||
return unknownType;
|
||||
|
||||
AstExprConstantString* value = expr.index->as<AstExprConstantString>();
|
||||
|
||||
if (value)
|
||||
@ -3156,7 +3264,7 @@ TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName, T
|
||||
|
||||
if (!ttv || ttv->state == TableState::Sealed)
|
||||
{
|
||||
if (auto ty = getIndexTypeFromType(scope, lhsType, indexName->index.value, indexName->indexLocation, false))
|
||||
if (auto ty = getIndexTypeFromType(scope, lhsType, indexName->index.value, indexName->indexLocation, /* addErrors= */ false))
|
||||
return *ty;
|
||||
|
||||
return errorRecoveryType(scope);
|
||||
@ -3228,9 +3336,12 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(const ScopePtr&
|
||||
}
|
||||
}
|
||||
|
||||
// We do not infer type binders, so if a generic function is required we do not propagate
|
||||
if (expectedFunctionType && !(expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty()))
|
||||
expectedFunctionType = nullptr;
|
||||
if (!FFlag::LuauCheckGenericHOFTypes)
|
||||
{
|
||||
// We do not infer type binders, so if a generic function is required we do not propagate
|
||||
if (expectedFunctionType && !(expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty()))
|
||||
expectedFunctionType = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, expr, expr.generics, expr.genericPacks);
|
||||
@ -3240,7 +3351,8 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(const ScopePtr&
|
||||
retPack = resolveTypePack(funScope, *expr.returnAnnotation);
|
||||
else if (FFlag::LuauReturnTypeInferenceInNonstrict ? (!FFlag::LuauLowerBoundsCalculation && isNonstrictMode()) : isNonstrictMode())
|
||||
retPack = anyTypePack;
|
||||
else if (expectedFunctionType)
|
||||
else if (expectedFunctionType &&
|
||||
(!FFlag::LuauCheckGenericHOFTypes || (expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty())))
|
||||
{
|
||||
auto [head, tail] = flatten(expectedFunctionType->retTypes);
|
||||
|
||||
@ -3371,16 +3483,50 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(const ScopePtr&
|
||||
defn.originalNameLocation = originalName.value_or(Location(expr.location.begin, 0));
|
||||
|
||||
std::vector<TypeId> genericTys;
|
||||
genericTys.reserve(generics.size());
|
||||
std::transform(generics.begin(), generics.end(), std::back_inserter(genericTys), [](auto&& el) {
|
||||
return el.ty;
|
||||
});
|
||||
// if we have a generic expected function type and no generics, we should use the expected ones.
|
||||
if (FFlag::LuauCheckGenericHOFTypes)
|
||||
{
|
||||
if (expectedFunctionType && generics.empty())
|
||||
{
|
||||
genericTys = expectedFunctionType->generics;
|
||||
}
|
||||
else
|
||||
{
|
||||
genericTys.reserve(generics.size());
|
||||
for (const GenericTypeDefinition& generic : generics)
|
||||
genericTys.push_back(generic.ty);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
genericTys.reserve(generics.size());
|
||||
std::transform(generics.begin(), generics.end(), std::back_inserter(genericTys), [](auto&& el) {
|
||||
return el.ty;
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<TypePackId> genericTps;
|
||||
genericTps.reserve(genericPacks.size());
|
||||
std::transform(genericPacks.begin(), genericPacks.end(), std::back_inserter(genericTps), [](auto&& el) {
|
||||
return el.tp;
|
||||
});
|
||||
// if we have a generic expected function type and no generic typepacks, we should use the expected ones.
|
||||
if (FFlag::LuauCheckGenericHOFTypes)
|
||||
{
|
||||
if (expectedFunctionType && genericPacks.empty())
|
||||
{
|
||||
genericTps = expectedFunctionType->genericPacks;
|
||||
}
|
||||
else
|
||||
{
|
||||
genericTps.reserve(genericPacks.size());
|
||||
for (const GenericTypePackDefinition& generic : genericPacks)
|
||||
genericTps.push_back(generic.tp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
genericTps.reserve(genericPacks.size());
|
||||
std::transform(genericPacks.begin(), genericPacks.end(), std::back_inserter(genericTps), [](auto&& el) {
|
||||
return el.tp;
|
||||
});
|
||||
}
|
||||
|
||||
TypeId funTy =
|
||||
addType(FunctionTypeVar(funScope->level, std::move(genericTys), std::move(genericTps), argPack, retPack, std::move(defn), bool(expr.self)));
|
||||
@ -3474,9 +3620,22 @@ void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstE
|
||||
}
|
||||
|
||||
WithPredicate<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, const AstExpr& expr)
|
||||
{
|
||||
if (FFlag::LuauUnknownAndNeverType)
|
||||
{
|
||||
WithPredicate<TypePackId> result = checkExprPackHelper(scope, expr);
|
||||
if (containsNever(result.type))
|
||||
return {uninhabitableTypePack};
|
||||
return result;
|
||||
}
|
||||
else
|
||||
return checkExprPackHelper(scope, expr);
|
||||
}
|
||||
|
||||
WithPredicate<TypePackId> TypeChecker::checkExprPackHelper(const ScopePtr& scope, const AstExpr& expr)
|
||||
{
|
||||
if (auto a = expr.as<AstExprCall>())
|
||||
return checkExprPack(scope, *a);
|
||||
return checkExprPackHelper(scope, *a);
|
||||
else if (expr.is<AstExprVarargs>())
|
||||
{
|
||||
if (!scope->varargPack)
|
||||
@ -3739,7 +3898,7 @@ void TypeChecker::checkArgumentList(
|
||||
}
|
||||
}
|
||||
|
||||
WithPredicate<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, const AstExprCall& expr)
|
||||
WithPredicate<TypePackId> TypeChecker::checkExprPackHelper(const ScopePtr& scope, const AstExprCall& expr)
|
||||
{
|
||||
// evaluate type of function
|
||||
// decompose an intersection into its component overloads
|
||||
@ -3763,7 +3922,7 @@ WithPredicate<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, cons
|
||||
selfType = checkExpr(scope, *indexExpr->expr).type;
|
||||
selfType = stripFromNilAndReport(selfType, expr.func->location);
|
||||
|
||||
if (std::optional<TypeId> propTy = getIndexTypeFromType(scope, selfType, indexExpr->index.value, expr.location, true))
|
||||
if (std::optional<TypeId> propTy = getIndexTypeFromType(scope, selfType, indexExpr->index.value, expr.location, /* addErrors= */ true))
|
||||
{
|
||||
functionType = *propTy;
|
||||
actualFunctionType = instantiate(scope, functionType, expr.func->location);
|
||||
@ -3813,11 +3972,25 @@ WithPredicate<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, cons
|
||||
if (get<Unifiable::Error>(argPack))
|
||||
return {errorRecoveryTypePack(scope)};
|
||||
|
||||
TypePack* args = getMutable<TypePack>(argPack);
|
||||
LUAU_ASSERT(args != nullptr);
|
||||
TypePack* args = nullptr;
|
||||
if (FFlag::LuauUnknownAndNeverType)
|
||||
{
|
||||
if (expr.self)
|
||||
{
|
||||
argPack = addTypePack(TypePack{{selfType}, argPack});
|
||||
argListResult.type = argPack;
|
||||
}
|
||||
args = getMutable<TypePack>(argPack);
|
||||
LUAU_ASSERT(args);
|
||||
}
|
||||
else
|
||||
{
|
||||
args = getMutable<TypePack>(argPack);
|
||||
LUAU_ASSERT(args != nullptr);
|
||||
|
||||
if (expr.self)
|
||||
args->head.insert(args->head.begin(), selfType);
|
||||
if (expr.self)
|
||||
args->head.insert(args->head.begin(), selfType);
|
||||
}
|
||||
|
||||
std::vector<Location> argLocations;
|
||||
argLocations.reserve(expr.args.size + 1);
|
||||
@ -3876,7 +4049,10 @@ std::vector<std::optional<TypeId>> TypeChecker::getExpectedTypesForCall(const st
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> result = reduceUnion({*el, ty});
|
||||
el = result.size() == 1 ? result[0] : addType(UnionTypeVar{std::move(result)});
|
||||
if (FFlag::LuauUnknownAndNeverType && result.empty())
|
||||
el = neverType;
|
||||
else
|
||||
el = result.size() == 1 ? result[0] : addType(UnionTypeVar{std::move(result)});
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -3930,6 +4106,9 @@ std::optional<WithPredicate<TypePackId>> TypeChecker::checkCallOverload(const Sc
|
||||
return {{errorRecoveryTypePack(scope)}};
|
||||
}
|
||||
|
||||
if (get<NeverTypeVar>(fn))
|
||||
return {{uninhabitableTypePack}};
|
||||
|
||||
if (auto ftv = get<FreeTypeVar>(fn))
|
||||
{
|
||||
// fn is one of the overloads of actualFunctionType, which
|
||||
@ -3975,7 +4154,7 @@ std::optional<WithPredicate<TypePackId>> TypeChecker::checkCallOverload(const Sc
|
||||
// Might be a callable table
|
||||
if (const MetatableTypeVar* mttv = get<MetatableTypeVar>(fn))
|
||||
{
|
||||
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, mttv->metatable, "__call", expr.func->location, false))
|
||||
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, mttv->metatable, "__call", expr.func->location, /* addErrors= */ false))
|
||||
{
|
||||
// Construct arguments with 'self' added in front
|
||||
TypePackId metaCallArgPack = addTypePack(TypePackVar(TypePack{args->head, args->tail}));
|
||||
@ -4202,6 +4381,7 @@ void TypeChecker::reportOverloadResolutionError(const ScopePtr& scope, const Ast
|
||||
WithPredicate<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, const Location& location, const AstArray<AstExpr*>& exprs,
|
||||
bool substituteFreeForNil, const std::vector<bool>& instantiateGenerics, const std::vector<std::optional<TypeId>>& expectedTypes)
|
||||
{
|
||||
bool uninhabitable = false;
|
||||
TypePackId pack = addTypePack(TypePack{});
|
||||
PredicateVec predicates; // At the moment we will be pushing all predicate sets into this. Do we need some way to split them up?
|
||||
|
||||
@ -4232,7 +4412,13 @@ WithPredicate<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, cons
|
||||
auto [typePack, exprPredicates] = checkExprPack(scope, *expr);
|
||||
insert(exprPredicates);
|
||||
|
||||
if (std::optional<TypeId> firstTy = first(typePack))
|
||||
if (FFlag::LuauUnknownAndNeverType && containsNever(typePack))
|
||||
{
|
||||
// f(), g() where f() returns (never, string) or (string, never) means this whole TypePackId is uninhabitable, so return (never, ...never)
|
||||
uninhabitable = true;
|
||||
continue;
|
||||
}
|
||||
else if (std::optional<TypeId> firstTy = first(typePack))
|
||||
{
|
||||
if (!currentModule->astTypes.find(expr))
|
||||
currentModule->astTypes[expr] = follow(*firstTy);
|
||||
@ -4248,6 +4434,13 @@ WithPredicate<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, cons
|
||||
auto [type, exprPredicates] = checkExpr(scope, *expr, expectedType);
|
||||
insert(exprPredicates);
|
||||
|
||||
if (FFlag::LuauUnknownAndNeverType && get<NeverTypeVar>(type))
|
||||
{
|
||||
// f(), g() where f() returns (never, string) or (string, never) means this whole TypePackId is uninhabitable, so return (never, ...never)
|
||||
uninhabitable = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
TypeId actualType = substituteFreeForNil && expr->is<AstExprConstantNil>() ? freshType(scope) : type;
|
||||
|
||||
if (instantiateGenerics.size() > i && instantiateGenerics[i])
|
||||
@ -4272,6 +4465,8 @@ WithPredicate<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, cons
|
||||
for (TxnLog& log : inverseLogs)
|
||||
log.commit();
|
||||
|
||||
if (FFlag::LuauUnknownAndNeverType && uninhabitable)
|
||||
return {uninhabitableTypePack};
|
||||
return {pack, predicates};
|
||||
}
|
||||
|
||||
@ -4830,7 +5025,7 @@ TypeIdPredicate TypeChecker::mkTruthyPredicate(bool sense)
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<TypeId> TypeChecker::filterMap(TypeId type, TypeIdPredicate predicate)
|
||||
std::optional<TypeId> TypeChecker::filterMapImpl(TypeId type, TypeIdPredicate predicate)
|
||||
{
|
||||
std::vector<TypeId> types = Luau::filterMap(type, predicate);
|
||||
if (!types.empty())
|
||||
@ -4838,7 +5033,21 @@ std::optional<TypeId> TypeChecker::filterMap(TypeId type, TypeIdPredicate predic
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypeId> TypeChecker::pickTypesFromSense(TypeId type, bool sense)
|
||||
std::pair<std::optional<TypeId>, bool> TypeChecker::filterMap(TypeId type, TypeIdPredicate predicate)
|
||||
{
|
||||
if (FFlag::LuauUnknownAndNeverType)
|
||||
{
|
||||
TypeId ty = filterMapImpl(type, predicate).value_or(neverType);
|
||||
return {ty, !bool(get<NeverTypeVar>(ty))};
|
||||
}
|
||||
else
|
||||
{
|
||||
std::optional<TypeId> ty = filterMapImpl(type, predicate);
|
||||
return {ty, bool(ty)};
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<std::optional<TypeId>, bool> TypeChecker::pickTypesFromSense(TypeId type, bool sense)
|
||||
{
|
||||
return filterMap(type, mkTruthyPredicate(sense));
|
||||
}
|
||||
@ -4884,6 +5093,13 @@ TypePackId TypeChecker::freshTypePack(TypeLevel level)
|
||||
}
|
||||
|
||||
TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation)
|
||||
{
|
||||
TypeId ty = resolveTypeWorker(scope, annotation);
|
||||
currentModule->astResolvedTypes[&annotation] = ty;
|
||||
return ty;
|
||||
}
|
||||
|
||||
TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& annotation)
|
||||
{
|
||||
if (const auto& lit = annotation.as<AstTypeReference>())
|
||||
{
|
||||
@ -5200,9 +5416,10 @@ TypePackId TypeChecker::resolveTypePack(const ScopePtr& scope, const AstTypeList
|
||||
|
||||
TypePackId TypeChecker::resolveTypePack(const ScopePtr& scope, const AstTypePack& annotation)
|
||||
{
|
||||
TypePackId result;
|
||||
if (const AstTypePackVariadic* variadic = annotation.as<AstTypePackVariadic>())
|
||||
{
|
||||
return addTypePack(TypePackVar{VariadicTypePack{resolveType(scope, *variadic->variadicType)}});
|
||||
result = addTypePack(TypePackVar{VariadicTypePack{resolveType(scope, *variadic->variadicType)}});
|
||||
}
|
||||
else if (const AstTypePackGeneric* generic = annotation.as<AstTypePackGeneric>())
|
||||
{
|
||||
@ -5216,10 +5433,12 @@ TypePackId TypeChecker::resolveTypePack(const ScopePtr& scope, const AstTypePack
|
||||
else
|
||||
reportError(TypeError{generic->location, UnknownSymbol{genericName, UnknownSymbol::Type}});
|
||||
|
||||
return errorRecoveryTypePack(scope);
|
||||
result = errorRecoveryTypePack(scope);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = *genericTy;
|
||||
}
|
||||
|
||||
return *genericTy;
|
||||
}
|
||||
else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
|
||||
{
|
||||
@ -5229,14 +5448,17 @@ TypePackId TypeChecker::resolveTypePack(const ScopePtr& scope, const AstTypePack
|
||||
types.push_back(resolveType(scope, *type));
|
||||
|
||||
if (auto tailType = explicitTp->typeList.tailType)
|
||||
return addTypePack(types, resolveTypePack(scope, *tailType));
|
||||
|
||||
return addTypePack(types);
|
||||
result = addTypePack(types, resolveTypePack(scope, *tailType));
|
||||
else
|
||||
result = addTypePack(types);
|
||||
}
|
||||
else
|
||||
{
|
||||
ice("Unknown AstTypePack kind");
|
||||
}
|
||||
|
||||
currentModule->astResolvedTypePacks[&annotation] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ApplyTypeFunction::isDirty(TypeId ty)
|
||||
@ -5452,10 +5674,18 @@ void TypeChecker::refineLValue(const LValue& lvalue, RefinementMap& refis, const
|
||||
// If we do not have a key, it means we're not trying to discriminate anything, so it's a simple matter of just filtering for a subset.
|
||||
if (!key)
|
||||
{
|
||||
if (std::optional<TypeId> result = filterMap(*ty, predicate))
|
||||
auto [result, ok] = filterMap(*ty, predicate);
|
||||
if (FFlag::LuauUnknownAndNeverType)
|
||||
{
|
||||
addRefinement(refis, *target, *result);
|
||||
}
|
||||
else
|
||||
addRefinement(refis, *target, errorRecoveryType(scope));
|
||||
{
|
||||
if (ok)
|
||||
addRefinement(refis, *target, *result);
|
||||
else
|
||||
addRefinement(refis, *target, errorRecoveryType(scope));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@ -5471,17 +5701,29 @@ void TypeChecker::refineLValue(const LValue& lvalue, RefinementMap& refis, const
|
||||
{
|
||||
std::optional<TypeId> discriminantTy;
|
||||
if (auto field = Luau::get<Field>(*key)) // need to fully qualify Luau::get because of ADL.
|
||||
discriminantTy = getIndexTypeFromType(scope, option, field->key, Location(), false);
|
||||
discriminantTy = getIndexTypeFromType(scope, option, field->key, Location(), /* addErrors= */ false);
|
||||
else
|
||||
LUAU_ASSERT(!"Unhandled LValue alternative?");
|
||||
|
||||
if (!discriminantTy)
|
||||
return; // Do nothing. An error was already reported, as per usual.
|
||||
|
||||
if (std::optional<TypeId> result = filterMap(*discriminantTy, predicate))
|
||||
auto [result, ok] = filterMap(*discriminantTy, predicate);
|
||||
if (FFlag::LuauUnknownAndNeverType)
|
||||
{
|
||||
viableTargetOptions.insert(option);
|
||||
viableChildOptions.insert(*result);
|
||||
if (!get<NeverTypeVar>(*result))
|
||||
{
|
||||
viableTargetOptions.insert(option);
|
||||
viableChildOptions.insert(*result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ok)
|
||||
{
|
||||
viableTargetOptions.insert(option);
|
||||
viableChildOptions.insert(*result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5560,7 +5802,7 @@ std::optional<TypeId> TypeChecker::resolveLValue(const ScopePtr& scope, const LV
|
||||
continue;
|
||||
else if (auto field = get<Field>(key))
|
||||
{
|
||||
found = getIndexTypeFromType(scope, *found, field->key, Location(), false);
|
||||
found = getIndexTypeFromType(scope, *found, field->key, Location(), /* addErrors= */ false);
|
||||
if (!found)
|
||||
return std::nullopt; // Turns out this type doesn't have the property at all. We're done.
|
||||
}
|
||||
@ -5740,6 +5982,9 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
|
||||
auto mkFilter = [](ConditionFunc f, std::optional<TypeId> other = std::nullopt) -> SenseToTypeIdPredicate {
|
||||
return [f, other](bool sense) -> TypeIdPredicate {
|
||||
return [f, other, sense](TypeId ty) -> std::optional<TypeId> {
|
||||
if (FFlag::LuauUnknownAndNeverType && sense && get<UnknownTypeVar>(ty))
|
||||
return other.value_or(ty);
|
||||
|
||||
if (f(ty) == sense)
|
||||
return ty;
|
||||
|
||||
@ -5847,8 +6092,15 @@ std::vector<TypeId> TypeChecker::unTypePack(const ScopePtr& scope, TypePackId tp
|
||||
for (size_t i = 0; i < expectedLength; ++i)
|
||||
expectedPack->head.push_back(freshType(scope));
|
||||
|
||||
size_t oldErrorsSize = currentModule->errors.size();
|
||||
|
||||
unify(tp, expectedTypePack, location);
|
||||
|
||||
// HACK: tryUnify would undo the changes to the expectedTypePack if the length mismatches, but
|
||||
// we want to tie up free types to be error types, so we do this instead.
|
||||
if (FFlag::LuauUnknownAndNeverType)
|
||||
currentModule->errors.resize(oldErrorsSize);
|
||||
|
||||
for (TypeId& tp : expectedPack->head)
|
||||
tp = follow(tp);
|
||||
|
||||
|
@ -5,8 +5,6 @@
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(LuauNonCopyableTypeVarFields)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -40,19 +38,10 @@ TypePackVar& TypePackVar::operator=(TypePackVariant&& tp)
|
||||
|
||||
TypePackVar& TypePackVar::operator=(const TypePackVar& rhs)
|
||||
{
|
||||
if (FFlag::LuauNonCopyableTypeVarFields)
|
||||
{
|
||||
LUAU_ASSERT(owningArena == rhs.owningArena);
|
||||
LUAU_ASSERT(!rhs.persistent);
|
||||
LUAU_ASSERT(owningArena == rhs.owningArena);
|
||||
LUAU_ASSERT(!rhs.persistent);
|
||||
|
||||
reassign(rhs);
|
||||
}
|
||||
else
|
||||
{
|
||||
ty = rhs.ty;
|
||||
persistent = rhs.persistent;
|
||||
owningArena = rhs.owningArena;
|
||||
}
|
||||
reassign(rhs);
|
||||
|
||||
return *this;
|
||||
}
|
||||
@ -294,6 +283,16 @@ std::optional<TypeId> first(TypePackId tp, bool ignoreHiddenVariadics)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TypePackVar* asMutable(TypePackId tp)
|
||||
{
|
||||
return const_cast<TypePackVar*>(tp);
|
||||
}
|
||||
|
||||
TypePack* asMutable(const TypePack* tp)
|
||||
{
|
||||
return const_cast<TypePack*>(tp);
|
||||
}
|
||||
|
||||
bool isEmpty(TypePackId tp)
|
||||
{
|
||||
tp = follow(tp);
|
||||
@ -360,13 +359,25 @@ bool isVariadic(TypePackId tp, const TxnLog& log)
|
||||
return false;
|
||||
}
|
||||
|
||||
TypePackVar* asMutable(TypePackId tp)
|
||||
bool containsNever(TypePackId tp)
|
||||
{
|
||||
return const_cast<TypePackVar*>(tp);
|
||||
auto it = begin(tp);
|
||||
auto endIt = end(tp);
|
||||
|
||||
while (it != endIt)
|
||||
{
|
||||
if (get<NeverTypeVar>(follow(*it)))
|
||||
return true;
|
||||
++it;
|
||||
}
|
||||
|
||||
if (auto tail = it.tail())
|
||||
{
|
||||
if (auto vtp = get<VariadicTypePack>(*tail); vtp && get<NeverTypeVar>(follow(vtp->ty)))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
TypePack* asMutable(const TypePack* tp)
|
||||
{
|
||||
return const_cast<TypePack*>(tp);
|
||||
}
|
||||
} // namespace Luau
|
||||
|
@ -24,7 +24,7 @@ std::optional<TypeId> findMetatableEntry(ErrorVec& errors, TypeId type, std::str
|
||||
const TableTypeVar* mtt = getTableType(unwrapped);
|
||||
if (!mtt)
|
||||
{
|
||||
errors.push_back(TypeError{location, GenericError{"Metatable was not a table."}});
|
||||
errors.push_back(TypeError{location, GenericError{"Metatable was not a table"}});
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,9 @@ LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
||||
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauNonCopyableTypeVarFields)
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDeduceGmatchReturnTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -31,6 +33,9 @@ namespace Luau
|
||||
std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
||||
|
||||
static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
||||
|
||||
TypeId follow(TypeId t)
|
||||
{
|
||||
return follow(t, [](TypeId t) {
|
||||
@ -173,8 +178,8 @@ bool maybeString(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
if (isPrim(ty, PrimitiveTypeVar::String) || get<AnyTypeVar>(ty))
|
||||
return true;
|
||||
if (isPrim(ty, PrimitiveTypeVar::String) || get<AnyTypeVar>(ty))
|
||||
return true;
|
||||
|
||||
if (auto utv = get<UnionTypeVar>(ty))
|
||||
return std::any_of(begin(utv), end(utv), maybeString);
|
||||
@ -194,7 +199,7 @@ bool isOptional(TypeId ty)
|
||||
|
||||
ty = follow(ty);
|
||||
|
||||
if (get<AnyTypeVar>(ty))
|
||||
if (get<AnyTypeVar>(ty) || (FFlag::LuauUnknownAndNeverType && get<UnknownTypeVar>(ty)))
|
||||
return true;
|
||||
|
||||
auto utv = get<UnionTypeVar>(ty);
|
||||
@ -334,6 +339,28 @@ bool isGeneric(TypeId ty)
|
||||
|
||||
bool maybeGeneric(TypeId ty)
|
||||
{
|
||||
if (FFlag::LuauMaybeGenericIntersectionTypes)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
if (get<FreeTypeVar>(ty))
|
||||
return true;
|
||||
|
||||
if (auto ttv = get<TableTypeVar>(ty))
|
||||
{
|
||||
// TODO: recurse on table types CLI-39914
|
||||
(void)ttv;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auto itv = get<IntersectionTypeVar>(ty))
|
||||
{
|
||||
return std::any_of(begin(itv), end(itv), maybeGeneric);
|
||||
}
|
||||
|
||||
return isGeneric(ty);
|
||||
}
|
||||
|
||||
ty = follow(ty);
|
||||
if (get<FreeTypeVar>(ty))
|
||||
return true;
|
||||
@ -646,20 +673,10 @@ TypeVar& TypeVar::operator=(TypeVariant&& rhs)
|
||||
|
||||
TypeVar& TypeVar::operator=(const TypeVar& rhs)
|
||||
{
|
||||
if (FFlag::LuauNonCopyableTypeVarFields)
|
||||
{
|
||||
LUAU_ASSERT(owningArena == rhs.owningArena);
|
||||
LUAU_ASSERT(!rhs.persistent);
|
||||
LUAU_ASSERT(owningArena == rhs.owningArena);
|
||||
LUAU_ASSERT(!rhs.persistent);
|
||||
|
||||
reassign(rhs);
|
||||
}
|
||||
else
|
||||
{
|
||||
ty = rhs.ty;
|
||||
persistent = rhs.persistent;
|
||||
normal = rhs.normal;
|
||||
owningArena = rhs.owningArena;
|
||||
}
|
||||
reassign(rhs);
|
||||
|
||||
return *this;
|
||||
}
|
||||
@ -676,10 +693,14 @@ static TypeVar threadType_{PrimitiveTypeVar{PrimitiveTypeVar::Thread}, /*persist
|
||||
static TypeVar trueType_{SingletonTypeVar{BooleanSingleton{true}}, /*persistent*/ true};
|
||||
static TypeVar falseType_{SingletonTypeVar{BooleanSingleton{false}}, /*persistent*/ true};
|
||||
static TypeVar anyType_{AnyTypeVar{}, /*persistent*/ true};
|
||||
static TypeVar unknownType_{UnknownTypeVar{}, /*persistent*/ true};
|
||||
static TypeVar neverType_{NeverTypeVar{}, /*persistent*/ true};
|
||||
static TypeVar errorType_{ErrorTypeVar{}, /*persistent*/ true};
|
||||
|
||||
static TypePackVar anyTypePack_{VariadicTypePack{&anyType_}, true};
|
||||
static TypePackVar errorTypePack_{Unifiable::Error{}};
|
||||
static TypePackVar anyTypePack_{VariadicTypePack{&anyType_}, /*persistent*/ true};
|
||||
static TypePackVar errorTypePack_{Unifiable::Error{}, /*persistent*/ true};
|
||||
static TypePackVar neverTypePack_{VariadicTypePack{&neverType_}, /*persistent*/ true};
|
||||
static TypePackVar uninhabitableTypePack_{TypePack{{&neverType_}, &neverTypePack_}, /*persistent*/ true};
|
||||
|
||||
SingletonTypes::SingletonTypes()
|
||||
: nilType(&nilType_)
|
||||
@ -690,7 +711,11 @@ SingletonTypes::SingletonTypes()
|
||||
, trueType(&trueType_)
|
||||
, falseType(&falseType_)
|
||||
, anyType(&anyType_)
|
||||
, unknownType(&unknownType_)
|
||||
, neverType(&neverType_)
|
||||
, anyTypePack(&anyTypePack_)
|
||||
, neverTypePack(&neverTypePack_)
|
||||
, uninhabitableTypePack(&uninhabitableTypePack_)
|
||||
, arena(new TypeArena)
|
||||
{
|
||||
TypeId stringMetatable = makeStringMetatable();
|
||||
@ -738,6 +763,7 @@ TypeId SingletonTypes::makeStringMetatable()
|
||||
const TypeId gsubFunc = makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType});
|
||||
const TypeId gmatchFunc =
|
||||
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionTypeVar{emptyPack, stringVariadicList})});
|
||||
attachMagicFunction(gmatchFunc, magicFunctionGmatch);
|
||||
|
||||
TableTypeVar::Props stringLib = {
|
||||
{"byte", {arena->addType(FunctionTypeVar{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList})}},
|
||||
@ -911,6 +937,8 @@ const TypeLevel* getLevel(TypeId ty)
|
||||
return &ttv->level;
|
||||
else if (auto ftv = get<FunctionTypeVar>(ty))
|
||||
return &ftv->level;
|
||||
else if (auto ctv = get<ConstrainedTypeVar>(ty))
|
||||
return &ctv->level;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
@ -965,94 +993,19 @@ bool isSubclass(const ClassTypeVar* cls, const ClassTypeVar* parent)
|
||||
return false;
|
||||
}
|
||||
|
||||
UnionTypeVarIterator::UnionTypeVarIterator(const UnionTypeVar* utv)
|
||||
const std::vector<TypeId>& getTypes(const UnionTypeVar* utv)
|
||||
{
|
||||
LUAU_ASSERT(utv);
|
||||
|
||||
if (!utv->options.empty())
|
||||
stack.push_front({utv, 0});
|
||||
|
||||
seen.insert(utv);
|
||||
return utv->options;
|
||||
}
|
||||
|
||||
UnionTypeVarIterator& UnionTypeVarIterator::operator++()
|
||||
const std::vector<TypeId>& getTypes(const IntersectionTypeVar* itv)
|
||||
{
|
||||
advance();
|
||||
descend();
|
||||
return *this;
|
||||
return itv->parts;
|
||||
}
|
||||
|
||||
UnionTypeVarIterator UnionTypeVarIterator::operator++(int)
|
||||
const std::vector<TypeId>& getTypes(const ConstrainedTypeVar* ctv)
|
||||
{
|
||||
UnionTypeVarIterator copy = *this;
|
||||
++copy;
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool UnionTypeVarIterator::operator!=(const UnionTypeVarIterator& rhs)
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
bool UnionTypeVarIterator::operator==(const UnionTypeVarIterator& rhs)
|
||||
{
|
||||
if (!stack.empty() && !rhs.stack.empty())
|
||||
return stack.front() == rhs.stack.front();
|
||||
|
||||
return stack.empty() && rhs.stack.empty();
|
||||
}
|
||||
|
||||
const TypeId& UnionTypeVarIterator::operator*()
|
||||
{
|
||||
LUAU_ASSERT(!stack.empty());
|
||||
|
||||
descend();
|
||||
|
||||
auto [utv, currentIndex] = stack.front();
|
||||
LUAU_ASSERT(utv);
|
||||
LUAU_ASSERT(currentIndex < utv->options.size());
|
||||
|
||||
const TypeId& ty = utv->options[currentIndex];
|
||||
LUAU_ASSERT(!get<UnionTypeVar>(follow(ty)));
|
||||
return ty;
|
||||
}
|
||||
|
||||
void UnionTypeVarIterator::advance()
|
||||
{
|
||||
while (!stack.empty())
|
||||
{
|
||||
auto& [utv, currentIndex] = stack.front();
|
||||
++currentIndex;
|
||||
|
||||
if (currentIndex >= utv->options.size())
|
||||
stack.pop_front();
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UnionTypeVarIterator::descend()
|
||||
{
|
||||
while (!stack.empty())
|
||||
{
|
||||
auto [utv, currentIndex] = stack.front();
|
||||
if (auto innerUnion = get<UnionTypeVar>(follow(utv->options[currentIndex])))
|
||||
{
|
||||
// If we're about to descend into a cyclic UnionTypeVar, we should skip over this.
|
||||
// Ideally this should never happen, but alas it does from time to time. :(
|
||||
if (seen.find(innerUnion) != seen.end())
|
||||
advance();
|
||||
else
|
||||
{
|
||||
seen.insert(innerUnion);
|
||||
stack.push_front({innerUnion, 0});
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
return ctv->parts;
|
||||
}
|
||||
|
||||
UnionTypeVarIterator begin(const UnionTypeVar* utv)
|
||||
@ -1065,6 +1018,27 @@ UnionTypeVarIterator end(const UnionTypeVar* utv)
|
||||
return UnionTypeVarIterator{};
|
||||
}
|
||||
|
||||
IntersectionTypeVarIterator begin(const IntersectionTypeVar* itv)
|
||||
{
|
||||
return IntersectionTypeVarIterator{itv};
|
||||
}
|
||||
|
||||
IntersectionTypeVarIterator end(const IntersectionTypeVar* itv)
|
||||
{
|
||||
return IntersectionTypeVarIterator{};
|
||||
}
|
||||
|
||||
ConstrainedTypeVarIterator begin(const ConstrainedTypeVar* ctv)
|
||||
{
|
||||
return ConstrainedTypeVarIterator{ctv};
|
||||
}
|
||||
|
||||
ConstrainedTypeVarIterator end(const ConstrainedTypeVar* ctv)
|
||||
{
|
||||
return ConstrainedTypeVarIterator{};
|
||||
}
|
||||
|
||||
|
||||
static std::vector<TypeId> parseFormatString(TypeChecker& typechecker, const char* data, size_t size)
|
||||
{
|
||||
const char* options = "cdiouxXeEfgGqs";
|
||||
@ -1144,6 +1118,101 @@ std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
|
||||
return WithPredicate<TypePackId>{arena.addTypePack({typechecker.stringType})};
|
||||
}
|
||||
|
||||
static std::vector<TypeId> parsePatternString(TypeChecker& typechecker, const char* data, size_t size)
|
||||
{
|
||||
std::vector<TypeId> result;
|
||||
int depth = 0;
|
||||
bool parsingSet = false;
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
if (data[i] == '%')
|
||||
{
|
||||
++i;
|
||||
if (!parsingSet && i < size && data[i] == 'b')
|
||||
i += 2;
|
||||
}
|
||||
else if (!parsingSet && data[i] == '[')
|
||||
{
|
||||
parsingSet = true;
|
||||
if (i + 1 < size && data[i + 1] == ']')
|
||||
i += 1;
|
||||
}
|
||||
else if (parsingSet && data[i] == ']')
|
||||
{
|
||||
parsingSet = false;
|
||||
}
|
||||
else if (data[i] == '(')
|
||||
{
|
||||
if (parsingSet)
|
||||
continue;
|
||||
|
||||
if (i + 1 < size && data[i + 1] == ')')
|
||||
{
|
||||
i++;
|
||||
result.push_back(typechecker.numberType);
|
||||
continue;
|
||||
}
|
||||
|
||||
++depth;
|
||||
result.push_back(typechecker.stringType);
|
||||
}
|
||||
else if (data[i] == ')')
|
||||
{
|
||||
if (parsingSet)
|
||||
continue;
|
||||
|
||||
--depth;
|
||||
|
||||
if (depth < 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (depth != 0 || parsingSet)
|
||||
return std::vector<TypeId>();
|
||||
|
||||
if (result.empty())
|
||||
result.push_back(typechecker.stringType);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
||||
{
|
||||
if (!FFlag::LuauDeduceGmatchReturnTypes)
|
||||
return std::nullopt;
|
||||
|
||||
auto [paramPack, _predicates] = withPredicate;
|
||||
const auto& [params, tail] = flatten(paramPack);
|
||||
|
||||
if (params.size() != 2)
|
||||
return std::nullopt;
|
||||
|
||||
TypeArena& arena = typechecker.currentModule->internalTypes;
|
||||
|
||||
AstExprConstantString* pattern = nullptr;
|
||||
size_t index = expr.self ? 0 : 1;
|
||||
if (expr.args.size > index)
|
||||
pattern = expr.args.data[index]->as<AstExprConstantString>();
|
||||
|
||||
if (!pattern)
|
||||
return std::nullopt;
|
||||
|
||||
std::vector<TypeId> returnTypes = parsePatternString(typechecker, pattern->value.data, pattern->value.size);
|
||||
|
||||
if (returnTypes.empty())
|
||||
return std::nullopt;
|
||||
|
||||
typechecker.unify(params[0], typechecker.stringType, expr.args.data[0]->location);
|
||||
|
||||
const TypePackId emptyPack = arena.addTypePack({});
|
||||
const TypePackId returnList = arena.addTypePack(returnTypes);
|
||||
const TypeId iteratorType = arena.addType(FunctionTypeVar{emptyPack, returnList});
|
||||
return WithPredicate<TypePackId>{arena.addTypePack({iteratorType})};
|
||||
}
|
||||
|
||||
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate)
|
||||
{
|
||||
type = follow(type);
|
||||
|
@ -19,6 +19,7 @@ LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeInferLowerBoundsIterationLimit, 2000);
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAG(LuauQuantifyConstrained)
|
||||
|
||||
namespace Luau
|
||||
@ -47,33 +48,6 @@ struct PromoteTypeLevels final : TypeVarOnceVisitor
|
||||
}
|
||||
}
|
||||
|
||||
// TODO cycle and operator() need to be clipped when FFlagLuauUseVisitRecursionLimit is clipped
|
||||
template<typename TID>
|
||||
void cycle(TID)
|
||||
{
|
||||
}
|
||||
template<typename TID, typename T>
|
||||
bool operator()(TID ty, const T&)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
bool operator()(TypeId ty, const FreeTypeVar& ftv)
|
||||
{
|
||||
return visit(ty, ftv);
|
||||
}
|
||||
bool operator()(TypeId ty, const FunctionTypeVar& ftv)
|
||||
{
|
||||
return visit(ty, ftv);
|
||||
}
|
||||
bool operator()(TypeId ty, const TableTypeVar& ttv)
|
||||
{
|
||||
return visit(ty, ttv);
|
||||
}
|
||||
bool operator()(TypePackId tp, const FreeTypePack& ftp)
|
||||
{
|
||||
return visit(tp, ftp);
|
||||
}
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
// Type levels of types from other modules are already global, so we don't need to promote anything inside
|
||||
@ -103,6 +77,15 @@ struct PromoteTypeLevels final : TypeVarOnceVisitor
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const ConstrainedTypeVar&) override
|
||||
{
|
||||
if (!FFlag::LuauUnknownAndNeverType)
|
||||
return visit(ty);
|
||||
|
||||
promote(ty, log.getMutable<ConstrainedTypeVar>(ty));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const FunctionTypeVar&) override
|
||||
{
|
||||
// Type levels of types from other modules are already global, so we don't need to promote anything inside
|
||||
@ -445,6 +428,14 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||
}
|
||||
else if (subFree)
|
||||
{
|
||||
if (FFlag::LuauUnknownAndNeverType)
|
||||
{
|
||||
// Normally, if the subtype is free, it should not be bound to any, unknown, or error types.
|
||||
// But for bug compatibility, we'll only apply this rule to unknown. Doing this will silence cascading type errors.
|
||||
if (get<UnknownTypeVar>(superTy))
|
||||
return;
|
||||
}
|
||||
|
||||
TypeLevel subLevel = subFree->level;
|
||||
|
||||
occursCheck(subTy, superTy);
|
||||
@ -468,7 +459,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||
return;
|
||||
}
|
||||
|
||||
if (get<ErrorTypeVar>(superTy) || get<AnyTypeVar>(superTy))
|
||||
if (get<ErrorTypeVar>(superTy) || get<AnyTypeVar>(superTy) || get<UnknownTypeVar>(superTy))
|
||||
return tryUnifyWithAny(subTy, superTy);
|
||||
|
||||
if (get<AnyTypeVar>(subTy))
|
||||
@ -485,6 +476,9 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||
if (get<ErrorTypeVar>(subTy))
|
||||
return tryUnifyWithAny(superTy, subTy);
|
||||
|
||||
if (get<NeverTypeVar>(subTy))
|
||||
return tryUnifyWithAny(superTy, subTy);
|
||||
|
||||
auto& cache = sharedState.cachedUnify;
|
||||
|
||||
// What if the types are immutable and we proved their relation before
|
||||
@ -1862,6 +1856,7 @@ static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHas
|
||||
|
||||
if (state.log.getMutable<FreeTypeVar>(ty))
|
||||
{
|
||||
// TODO: Only bind if the anyType isn't any, unknown, or error (?)
|
||||
state.log.replace(ty, BoundTypeVar{anyType});
|
||||
}
|
||||
else if (auto fun = state.log.getMutable<FunctionTypeVar>(ty))
|
||||
@ -1901,22 +1896,27 @@ static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHas
|
||||
|
||||
void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy)
|
||||
{
|
||||
LUAU_ASSERT(get<AnyTypeVar>(anyTy) || get<ErrorTypeVar>(anyTy));
|
||||
LUAU_ASSERT(get<AnyTypeVar>(anyTy) || get<ErrorTypeVar>(anyTy) || get<UnknownTypeVar>(anyTy) || get<NeverTypeVar>(anyTy));
|
||||
|
||||
// These types are not visited in general loop below
|
||||
if (get<PrimitiveTypeVar>(subTy) || get<AnyTypeVar>(subTy) || get<ClassTypeVar>(subTy))
|
||||
return;
|
||||
|
||||
const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{getSingletonTypes().anyType}});
|
||||
|
||||
const TypePackId anyTP = get<AnyTypeVar>(anyTy) ? anyTypePack : types->addTypePack(TypePackVar{Unifiable::Error{}});
|
||||
TypePackId anyTp;
|
||||
if (FFlag::LuauUnknownAndNeverType)
|
||||
anyTp = types->addTypePack(TypePackVar{VariadicTypePack{anyTy}});
|
||||
else
|
||||
{
|
||||
const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{getSingletonTypes().anyType}});
|
||||
anyTp = get<AnyTypeVar>(anyTy) ? anyTypePack : types->addTypePack(TypePackVar{Unifiable::Error{}});
|
||||
}
|
||||
|
||||
std::vector<TypeId> queue = {subTy};
|
||||
|
||||
sharedState.tempSeenTy.clear();
|
||||
sharedState.tempSeenTp.clear();
|
||||
|
||||
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types, getSingletonTypes().anyType, anyTP);
|
||||
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types, FFlag::LuauUnknownAndNeverType ? anyTy : getSingletonTypes().anyType, anyTp);
|
||||
}
|
||||
|
||||
void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp)
|
||||
|
@ -15,7 +15,6 @@ LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
|
||||
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauParserFunctionKeywordAsTypeHelp, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnTypeTokenConfusion, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixNamedFunctionParse, false)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseWrongNamedType, false)
|
||||
@ -1134,10 +1133,9 @@ AstTypePack* Parser::parseTypeList(TempVector<AstType*>& result, TempVector<std:
|
||||
|
||||
std::optional<AstTypeList> Parser::parseOptionalReturnTypeAnnotation()
|
||||
{
|
||||
if (options.allowTypeAnnotations &&
|
||||
(lexer.current().type == ':' || (FFlag::LuauReturnTypeTokenConfusion && lexer.current().type == Lexeme::SkinnyArrow)))
|
||||
if (options.allowTypeAnnotations && (lexer.current().type == ':' || lexer.current().type == Lexeme::SkinnyArrow))
|
||||
{
|
||||
if (FFlag::LuauReturnTypeTokenConfusion && lexer.current().type == Lexeme::SkinnyArrow)
|
||||
if (lexer.current().type == Lexeme::SkinnyArrow)
|
||||
report(lexer.current().location, "Function return type annotations are written after ':' instead of '->'");
|
||||
|
||||
nextLexeme();
|
||||
@ -1373,12 +1371,10 @@ AstTypeOrPack Parser::parseFunctionTypeAnnotation(bool allowPack)
|
||||
if (FFlag::LuauFixNamedFunctionParse && !names.empty())
|
||||
forceFunctionType = true;
|
||||
|
||||
bool returnTypeIntroducer =
|
||||
FFlag::LuauReturnTypeTokenConfusion ? lexer.current().type == Lexeme::SkinnyArrow || lexer.current().type == ':' : false;
|
||||
bool returnTypeIntroducer = lexer.current().type == Lexeme::SkinnyArrow || lexer.current().type == ':';
|
||||
|
||||
// Not a function at all. Just a parenthesized type. Or maybe a type pack with a single element
|
||||
if (params.size() == 1 && !varargAnnotation && !forceFunctionType &&
|
||||
(FFlag::LuauReturnTypeTokenConfusion ? !returnTypeIntroducer : lexer.current().type != Lexeme::SkinnyArrow))
|
||||
if (params.size() == 1 && !varargAnnotation && !forceFunctionType && !returnTypeIntroducer)
|
||||
{
|
||||
if (DFFlag::LuaReportParseWrongNamedType && !names.empty())
|
||||
lua_telemetry_parsed_named_non_function_type = true;
|
||||
@ -1389,8 +1385,7 @@ AstTypeOrPack Parser::parseFunctionTypeAnnotation(bool allowPack)
|
||||
return {params[0], {}};
|
||||
}
|
||||
|
||||
if ((FFlag::LuauReturnTypeTokenConfusion ? !returnTypeIntroducer : lexer.current().type != Lexeme::SkinnyArrow) && !forceFunctionType &&
|
||||
allowPack)
|
||||
if (!forceFunctionType && !returnTypeIntroducer && allowPack)
|
||||
{
|
||||
if (DFFlag::LuaReportParseWrongNamedType && !names.empty())
|
||||
lua_telemetry_parsed_named_non_function_type = true;
|
||||
@ -1409,7 +1404,7 @@ AstType* Parser::parseFunctionTypeAnnotationTail(const Lexeme& begin, AstArray<A
|
||||
{
|
||||
incrementRecursionCounter("type annotation");
|
||||
|
||||
if (FFlag::LuauReturnTypeTokenConfusion && lexer.current().type == ':')
|
||||
if (lexer.current().type == ':')
|
||||
{
|
||||
report(lexer.current().location, "Return types in function type annotations are written after '->' instead of ':'");
|
||||
lexer.next();
|
||||
|
@ -8,6 +8,10 @@
|
||||
|
||||
#include "FileUtils.h"
|
||||
|
||||
#ifdef CALLGRIND
|
||||
#include <valgrind/callgrind.h>
|
||||
#endif
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauTimeTracing)
|
||||
LUAU_FASTFLAG(LuauTypeMismatchModuleNameResolution)
|
||||
|
||||
@ -112,6 +116,7 @@ static void displayHelp(const char* argv0)
|
||||
printf("Available options:\n");
|
||||
printf(" --formatter=plain: report analysis errors in Luacheck-compatible format\n");
|
||||
printf(" --formatter=gnu: report analysis errors in GNU-compatible format\n");
|
||||
printf(" --mode=strict: default to strict mode when typechecking\n");
|
||||
printf(" --timetrace: record compiler time tracing information into trace.json\n");
|
||||
}
|
||||
|
||||
@ -178,9 +183,9 @@ struct CliConfigResolver : Luau::ConfigResolver
|
||||
mutable std::unordered_map<std::string, Luau::Config> configCache;
|
||||
mutable std::vector<std::pair<std::string, std::string>> configErrors;
|
||||
|
||||
CliConfigResolver()
|
||||
CliConfigResolver(Luau::Mode mode)
|
||||
{
|
||||
defaultConfig.mode = Luau::Mode::Nonstrict;
|
||||
defaultConfig.mode = mode;
|
||||
}
|
||||
|
||||
const Luau::Config& getConfig(const Luau::ModuleName& name) const override
|
||||
@ -229,6 +234,7 @@ int main(int argc, char** argv)
|
||||
}
|
||||
|
||||
ReportFormat format = ReportFormat::Default;
|
||||
Luau::Mode mode = Luau::Mode::Nonstrict;
|
||||
bool annotate = false;
|
||||
|
||||
for (int i = 1; i < argc; ++i)
|
||||
@ -240,6 +246,8 @@ int main(int argc, char** argv)
|
||||
format = ReportFormat::Luacheck;
|
||||
else if (strcmp(argv[i], "--formatter=gnu") == 0)
|
||||
format = ReportFormat::Gnu;
|
||||
else if (strcmp(argv[i], "--mode=strict") == 0)
|
||||
mode = Luau::Mode::Strict;
|
||||
else if (strcmp(argv[i], "--annotate") == 0)
|
||||
annotate = true;
|
||||
else if (strcmp(argv[i], "--timetrace") == 0)
|
||||
@ -258,12 +266,16 @@ int main(int argc, char** argv)
|
||||
frontendOptions.retainFullTypeGraphs = annotate;
|
||||
|
||||
CliFileResolver fileResolver;
|
||||
CliConfigResolver configResolver;
|
||||
CliConfigResolver configResolver(mode);
|
||||
Luau::Frontend frontend(&fileResolver, &configResolver, frontendOptions);
|
||||
|
||||
Luau::registerBuiltinTypes(frontend.typeChecker);
|
||||
Luau::freeze(frontend.typeChecker.globalTypes);
|
||||
|
||||
#ifdef CALLGRIND
|
||||
CALLGRIND_ZERO_STATS;
|
||||
#endif
|
||||
|
||||
std::vector<std::string> files = getSourceFiles(argc, argv);
|
||||
|
||||
int failed = 0;
|
||||
|
37
CLI/Repl.cpp
37
CLI/Repl.cpp
@ -21,6 +21,10 @@
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
#ifdef CALLGRIND
|
||||
#include <valgrind/callgrind.h>
|
||||
#endif
|
||||
|
||||
#include <locale.h>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauTimeTracing)
|
||||
@ -166,6 +170,36 @@ static int lua_collectgarbage(lua_State* L)
|
||||
luaL_error(L, "collectgarbage must be called with 'count' or 'collect'");
|
||||
}
|
||||
|
||||
#ifdef CALLGRIND
|
||||
static int lua_callgrind(lua_State* L)
|
||||
{
|
||||
const char* option = luaL_checkstring(L, 1);
|
||||
|
||||
if (strcmp(option, "running") == 0)
|
||||
{
|
||||
int r = RUNNING_ON_VALGRIND;
|
||||
lua_pushboolean(L, r);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(option, "zero") == 0)
|
||||
{
|
||||
CALLGRIND_ZERO_STATS;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(option, "dump") == 0)
|
||||
{
|
||||
const char* name = luaL_checkstring(L, 2);
|
||||
|
||||
CALLGRIND_DUMP_STATS_AT(name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
luaL_error(L, "callgrind must be called with one of 'running', 'zero', 'dump'");
|
||||
}
|
||||
#endif
|
||||
|
||||
void setupState(lua_State* L)
|
||||
{
|
||||
luaL_openlibs(L);
|
||||
@ -174,6 +208,9 @@ void setupState(lua_State* L)
|
||||
{"loadstring", lua_loadstring},
|
||||
{"require", lua_require},
|
||||
{"collectgarbage", lua_collectgarbage},
|
||||
#ifdef CALLGRIND
|
||||
{"callgrind", lua_callgrind},
|
||||
#endif
|
||||
{NULL, NULL},
|
||||
};
|
||||
|
||||
|
@ -58,6 +58,9 @@ public:
|
||||
void jmp(Label& label);
|
||||
void jmp(OperandX64 op);
|
||||
|
||||
void call(Label& label);
|
||||
void call(OperandX64 op);
|
||||
|
||||
// AVX
|
||||
void vaddpd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
void vaddps(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
|
@ -286,11 +286,34 @@ void AssemblyBuilderX64::jmp(OperandX64 op)
|
||||
if (logText)
|
||||
log("jmp", op);
|
||||
|
||||
placeRex(op);
|
||||
place(0xff);
|
||||
placeModRegMem(op, 4);
|
||||
commit();
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::call(Label& label)
|
||||
{
|
||||
place(0xe8);
|
||||
placeLabel(label);
|
||||
|
||||
if (logText)
|
||||
log("call", label);
|
||||
|
||||
commit();
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::call(OperandX64 op)
|
||||
{
|
||||
if (logText)
|
||||
log("call", op);
|
||||
|
||||
placeRex(op);
|
||||
place(0xff);
|
||||
placeModRegMem(op, 2);
|
||||
commit();
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::vaddpd(OperandX64 dst, OperandX64 src1, OperandX64 src2)
|
||||
{
|
||||
placeAvx("vaddpd", dst, src1, src2, 0x58, false, AVX_0F, AVX_66);
|
||||
|
4
Makefile
4
Makefile
@ -93,6 +93,10 @@ ifeq ($(config),fuzz)
|
||||
LDFLAGS+=-fsanitize=address,fuzzer
|
||||
endif
|
||||
|
||||
ifneq ($(CALLGRIND),)
|
||||
CXXFLAGS+=-DCALLGRIND=$(CALLGRIND)
|
||||
endif
|
||||
|
||||
# target-specific flags
|
||||
$(AST_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include
|
||||
$(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -ICommon/include -IAst/include
|
||||
|
@ -247,12 +247,15 @@ if(TARGET Luau.UnitTest)
|
||||
tests/IostreamOptional.h
|
||||
tests/ScopedFlags.h
|
||||
tests/Fixture.cpp
|
||||
tests/AssemblyBuilderX64.test.cpp
|
||||
tests/AstQuery.test.cpp
|
||||
tests/AstVisitor.test.cpp
|
||||
tests/Autocomplete.test.cpp
|
||||
tests/BuiltinDefinitions.test.cpp
|
||||
tests/Compiler.test.cpp
|
||||
tests/Config.test.cpp
|
||||
tests/ConstraintGraphBuilder.test.cpp
|
||||
tests/ConstraintSolver.test.cpp
|
||||
tests/CostModel.test.cpp
|
||||
tests/Error.test.cpp
|
||||
tests/Frontend.test.cpp
|
||||
@ -262,8 +265,7 @@ if(TARGET Luau.UnitTest)
|
||||
tests/Module.test.cpp
|
||||
tests/NonstrictMode.test.cpp
|
||||
tests/Normalize.test.cpp
|
||||
tests/ConstraintGraphBuilder.test.cpp
|
||||
tests/ConstraintSolver.test.cpp
|
||||
tests/NotNull.test.cpp
|
||||
tests/Parser.test.cpp
|
||||
tests/RequireTracer.test.cpp
|
||||
tests/RuntimeLimits.test.cpp
|
||||
@ -295,11 +297,11 @@ if(TARGET Luau.UnitTest)
|
||||
tests/TypeInfer.tryUnify.test.cpp
|
||||
tests/TypeInfer.typePacks.cpp
|
||||
tests/TypeInfer.unionTypes.test.cpp
|
||||
tests/TypeInfer.unknownnever.test.cpp
|
||||
tests/TypePack.test.cpp
|
||||
tests/TypeVar.test.cpp
|
||||
tests/Variant.test.cpp
|
||||
tests/VisitTypeVar.test.cpp
|
||||
tests/AssemblyBuilderX64.test.cpp
|
||||
tests/main.cpp)
|
||||
endif()
|
||||
|
||||
|
@ -108,9 +108,9 @@ static LuaNode* hashvec(const Table* t, const float* v)
|
||||
memcpy(i, v, sizeof(i));
|
||||
|
||||
// convert -0 to 0 to make sure they hash to the same value
|
||||
i[0] = (i[0] == 0x8000000) ? 0 : i[0];
|
||||
i[1] = (i[1] == 0x8000000) ? 0 : i[1];
|
||||
i[2] = (i[2] == 0x8000000) ? 0 : i[2];
|
||||
i[0] = (i[0] == 0x80000000) ? 0 : i[0];
|
||||
i[1] = (i[1] == 0x80000000) ? 0 : i[1];
|
||||
i[2] = (i[2] == 0x80000000) ? 0 : i[2];
|
||||
|
||||
// scramble bits to make sure that integer coordinates have entropy in lower bits
|
||||
i[0] ^= i[0] >> 17;
|
||||
@ -121,7 +121,7 @@ static LuaNode* hashvec(const Table* t, const float* v)
|
||||
unsigned int h = (i[0] * 73856093) ^ (i[1] * 19349663) ^ (i[2] * 83492791);
|
||||
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
i[3] = (i[3] == 0x8000000) ? 0 : i[3];
|
||||
i[3] = (i[3] == 0x80000000) ? 0 : i[3];
|
||||
i[3] ^= i[3] >> 17;
|
||||
h ^= i[3] * 39916801;
|
||||
#endif
|
||||
|
@ -640,20 +640,16 @@ static void luau_execute(lua_State* L)
|
||||
VM_PATCH_C(pc - 2, L->cachedslot);
|
||||
VM_NEXT();
|
||||
}
|
||||
else
|
||||
{
|
||||
// slow-path, may invoke Lua calls via __index metamethod
|
||||
VM_PROTECT(luaV_gettable(L, rb, kv, ra));
|
||||
VM_NEXT();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// slow-path, may invoke Lua calls via __index metamethod
|
||||
VM_PROTECT(luaV_gettable(L, rb, kv, ra));
|
||||
VM_NEXT();
|
||||
|
||||
// fall through to slow path
|
||||
}
|
||||
|
||||
// fall through to slow path
|
||||
}
|
||||
|
||||
// slow-path, may invoke Lua calls via __index metamethod
|
||||
VM_PROTECT(luaV_gettable(L, rb, kv, ra));
|
||||
VM_NEXT();
|
||||
}
|
||||
|
||||
VM_CASE(LOP_SETTABLEKS)
|
||||
@ -753,19 +749,13 @@ static void luau_execute(lua_State* L)
|
||||
setobj2s(L, ra, &h->array[unsigned(index - 1)]);
|
||||
VM_NEXT();
|
||||
}
|
||||
else
|
||||
{
|
||||
// slow-path: handles out of bounds array lookups and non-integer numeric keys
|
||||
VM_PROTECT(luaV_gettable(L, rb, rc, ra));
|
||||
VM_NEXT();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// slow-path: handles non-array table lookup as well as __index MT calls
|
||||
VM_PROTECT(luaV_gettable(L, rb, rc, ra));
|
||||
VM_NEXT();
|
||||
|
||||
// fall through to slow path
|
||||
}
|
||||
|
||||
// slow-path: handles out of bounds array lookups, non-integer numeric keys, non-array table lookup, __index MT calls
|
||||
VM_PROTECT(luaV_gettable(L, rb, rc, ra));
|
||||
VM_NEXT();
|
||||
}
|
||||
|
||||
VM_CASE(LOP_SETTABLE)
|
||||
@ -790,19 +780,13 @@ static void luau_execute(lua_State* L)
|
||||
luaC_barriert(L, h, ra);
|
||||
VM_NEXT();
|
||||
}
|
||||
else
|
||||
{
|
||||
// slow-path: handles out of bounds array assignments and non-integer numeric keys
|
||||
VM_PROTECT(luaV_settable(L, rb, rc, ra));
|
||||
VM_NEXT();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// slow-path: handles non-array table access as well as __newindex MT calls
|
||||
VM_PROTECT(luaV_settable(L, rb, rc, ra));
|
||||
VM_NEXT();
|
||||
|
||||
// fall through to slow path
|
||||
}
|
||||
|
||||
// slow-path: handles out of bounds array assignments, non-integer numeric keys, non-array table access, __newindex MT calls
|
||||
VM_PROTECT(luaV_settable(L, rb, rc, ra));
|
||||
VM_NEXT();
|
||||
}
|
||||
|
||||
VM_CASE(LOP_GETTABLEN)
|
||||
@ -822,6 +806,8 @@ static void luau_execute(lua_State* L)
|
||||
setobj2s(L, ra, &h->array[c]);
|
||||
VM_NEXT();
|
||||
}
|
||||
|
||||
// fall through to slow path
|
||||
}
|
||||
|
||||
// slow-path: handles out of bounds array lookups
|
||||
@ -849,6 +835,8 @@ static void luau_execute(lua_State* L)
|
||||
luaC_barriert(L, h, ra);
|
||||
VM_NEXT();
|
||||
}
|
||||
|
||||
// fall through to slow path
|
||||
}
|
||||
|
||||
// slow-path: handles out of bounds array lookups
|
||||
@ -2176,8 +2164,10 @@ static void luau_execute(lua_State* L)
|
||||
if (!ttisnumber(ra + 0) || !ttisnumber(ra + 1) || !ttisnumber(ra + 2))
|
||||
{
|
||||
// slow-path: can convert arguments to numbers and trigger Lua errors
|
||||
// Note: this doesn't reallocate stack so we don't need to recompute ra
|
||||
VM_PROTECT(luau_prepareFORN(L, ra + 0, ra + 1, ra + 2));
|
||||
// Note: this doesn't reallocate stack so we don't need to recompute ra/base
|
||||
VM_PROTECT_PC();
|
||||
|
||||
luau_prepareFORN(L, ra + 0, ra + 1, ra + 2);
|
||||
}
|
||||
|
||||
double limit = nvalue(ra + 0);
|
||||
|
@ -40,6 +40,7 @@ argumentParser.add_argument('--results', dest='results',type=str,nargs='*',help=
|
||||
argumentParser.add_argument('--run-test', action='store', default=None, help='Regex test filter')
|
||||
argumentParser.add_argument('--extra-loops', action='store',type=int,default=0, help='Amount of times to loop over one test (one test already performs multiple runs)')
|
||||
argumentParser.add_argument('--filename', action='store',type=str,default='bench', help='File name for graph and results file')
|
||||
argumentParser.add_argument('--callgrind', dest='callgrind',action='store_const',const=1,default=0,help='Use callgrind to run benchmarks')
|
||||
|
||||
if matplotlib != None:
|
||||
argumentParser.add_argument('--absolute', dest='absolute',action='store_const',const=1,default=0,help='Display absolute values instead of relative (enabled by default when benchmarking a single VM)')
|
||||
@ -55,6 +56,9 @@ argumentParser.add_argument('--no-print-influx-debugging', action='store_false',
|
||||
|
||||
argumentParser.add_argument('--no-print-final-summary', action='store_false', dest='print_final_summary', help="Don't print a table summarizing the results after all tests are run")
|
||||
|
||||
# Assume 2.5 IPC on a 4 GHz CPU; this is obviously incorrect but it allows us to display simulated instruction counts using regular time units
|
||||
CALLGRIND_INSN_PER_SEC = 2.5 * 4e9
|
||||
|
||||
def arrayRange(count):
|
||||
result = []
|
||||
|
||||
@ -71,6 +75,21 @@ def arrayRangeOffset(count, offset):
|
||||
|
||||
return result
|
||||
|
||||
def getCallgrindOutput(lines):
|
||||
result = []
|
||||
name = None
|
||||
|
||||
for l in lines:
|
||||
if l.startswith("desc: Trigger: Client Request: "):
|
||||
name = l[31:].strip()
|
||||
elif l.startswith("summary: ") and name != None:
|
||||
insn = int(l[9:])
|
||||
# Note: we only run each bench once under callgrind so we only report a single time per run; callgrind instruction count variance is ~0.01% so it might as well be zero
|
||||
result += "|><|" + name + "|><|" + str(insn / CALLGRIND_INSN_PER_SEC * 1000.0) + "||_||"
|
||||
name = None
|
||||
|
||||
return "".join(result)
|
||||
|
||||
def getVmOutput(cmd):
|
||||
if os.name == "nt":
|
||||
try:
|
||||
@ -79,6 +98,16 @@ def getVmOutput(cmd):
|
||||
exit(1)
|
||||
except:
|
||||
return ""
|
||||
elif arguments.callgrind:
|
||||
try:
|
||||
subprocess.check_call("valgrind --tool=callgrind --callgrind-out-file=callgrind.out --combine-dumps=yes --dump-line=no " + cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=scriptdir)
|
||||
path = os.path.join(scriptdir, "callgrind.out")
|
||||
with open(path, "r") as file:
|
||||
lines = file.readlines()
|
||||
os.unlink(path)
|
||||
return getCallgrindOutput(lines)
|
||||
except:
|
||||
return ""
|
||||
else:
|
||||
with subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=scriptdir) as p:
|
||||
# Try to lock to a single processor
|
||||
@ -375,12 +404,12 @@ def analyzeResult(subdir, main, comparisons):
|
||||
|
||||
continue
|
||||
|
||||
pooledStdDev = math.sqrt((main.unbiasedEst + compare.unbiasedEst) / 2)
|
||||
if main.count > 1 and stats:
|
||||
pooledStdDev = math.sqrt((main.unbiasedEst + compare.unbiasedEst) / 2)
|
||||
|
||||
tStat = abs(main.avg - compare.avg) / (pooledStdDev * math.sqrt(2 / main.count))
|
||||
degreesOfFreedom = 2 * main.count - 2
|
||||
tStat = abs(main.avg - compare.avg) / (pooledStdDev * math.sqrt(2 / main.count))
|
||||
degreesOfFreedom = 2 * main.count - 2
|
||||
|
||||
if stats:
|
||||
# Two-tailed distribution with 95% conf.
|
||||
tCritical = stats.t.ppf(1 - 0.05 / 2, degreesOfFreedom)
|
||||
|
||||
|
@ -5,6 +5,16 @@ bench.runs = 20
|
||||
bench.extraRuns = 4
|
||||
|
||||
function bench.runCode(f, description)
|
||||
-- Under Callgrind, run the test only once and measure just the execution cost
|
||||
if callgrind and callgrind("running") then
|
||||
if collectgarbage then collectgarbage() end
|
||||
|
||||
callgrind("zero")
|
||||
f() -- unfortunately we can't easily separate setup cost from runtime cost in f unless it calls callgrind()
|
||||
callgrind("dump", description)
|
||||
return
|
||||
end
|
||||
|
||||
local timeTable = {}
|
||||
|
||||
for i = 1,bench.runs + bench.extraRuns do
|
||||
|
961
bench/other/LuauPolyfillMap.lua
Normal file
961
bench/other/LuauPolyfillMap.lua
Normal file
@ -0,0 +1,961 @@
|
||||
-- This file is part of the Roblox luau-polyfill repository and is licensed under MIT License; see LICENSE.txt for details
|
||||
-- #region Array
|
||||
-- Array related
|
||||
local Array = {}
|
||||
local Object = {}
|
||||
local Map = {}
|
||||
|
||||
type Array<T> = { [number]: T }
|
||||
type callbackFn<K, V> = (element: V, key: K, map: Map<K, V>) -> ()
|
||||
type callbackFnWithThisArg<K, V> = (thisArg: Object, value: V, key: K, map: Map<K, V>) -> ()
|
||||
type Map<K, V> = {
|
||||
size: number,
|
||||
-- method definitions
|
||||
set: (self: Map<K, V>, K, V) -> Map<K, V>,
|
||||
get: (self: Map<K, V>, K) -> V | nil,
|
||||
clear: (self: Map<K, V>) -> (),
|
||||
delete: (self: Map<K, V>, K) -> boolean,
|
||||
forEach: (self: Map<K, V>, callback: callbackFn<K, V> | callbackFnWithThisArg<K, V>, thisArg: Object?) -> (),
|
||||
has: (self: Map<K, V>, K) -> boolean,
|
||||
keys: (self: Map<K, V>) -> Array<K>,
|
||||
values: (self: Map<K, V>) -> Array<V>,
|
||||
entries: (self: Map<K, V>) -> Array<Tuple<K, V>>,
|
||||
ipairs: (self: Map<K, V>) -> any,
|
||||
[K]: V,
|
||||
_map: { [K]: V },
|
||||
_array: { [number]: K },
|
||||
}
|
||||
type mapFn<T, U> = (element: T, index: number) -> U
|
||||
type mapFnWithThisArg<T, U> = (thisArg: any, element: T, index: number) -> U
|
||||
type Object = { [string]: any }
|
||||
type Table<T, V> = { [T]: V }
|
||||
type Tuple<T, V> = Array<T | V>
|
||||
|
||||
local Set = {}
|
||||
|
||||
-- #region Array
|
||||
function Array.isArray(value: any): boolean
|
||||
if typeof(value) ~= "table" then
|
||||
return false
|
||||
end
|
||||
if next(value) == nil then
|
||||
-- an empty table is an empty array
|
||||
return true
|
||||
end
|
||||
|
||||
local length = #value
|
||||
|
||||
if length == 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
local count = 0
|
||||
local sum = 0
|
||||
for key in pairs(value) do
|
||||
if typeof(key) ~= "number" then
|
||||
return false
|
||||
end
|
||||
if key % 1 ~= 0 or key < 1 then
|
||||
return false
|
||||
end
|
||||
count += 1
|
||||
sum += key
|
||||
end
|
||||
|
||||
return sum == (count * (count + 1) / 2)
|
||||
end
|
||||
|
||||
function Array.from<T, U>(
|
||||
value: string | Array<T> | Object,
|
||||
mapFn: (mapFn<T, U> | mapFnWithThisArg<T, U>)?,
|
||||
thisArg: Object?
|
||||
): Array<U>
|
||||
if value == nil then
|
||||
error("cannot create array from a nil value")
|
||||
end
|
||||
local valueType = typeof(value)
|
||||
|
||||
local array = {}
|
||||
|
||||
if valueType == "table" and Array.isArray(value) then
|
||||
if mapFn then
|
||||
for i = 1, #(value :: Array<T>) do
|
||||
if thisArg ~= nil then
|
||||
array[i] = (mapFn :: mapFnWithThisArg<T, U>)(thisArg, (value :: Array<T>)[i], i)
|
||||
else
|
||||
array[i] = (mapFn :: mapFn<T, U>)((value :: Array<T>)[i], i)
|
||||
end
|
||||
end
|
||||
else
|
||||
for i = 1, #(value :: Array<T>) do
|
||||
array[i] = (value :: Array<any>)[i]
|
||||
end
|
||||
end
|
||||
elseif instanceOf(value, Set) then
|
||||
if mapFn then
|
||||
for i, v in (value :: any):ipairs() do
|
||||
if thisArg ~= nil then
|
||||
array[i] = (mapFn :: mapFnWithThisArg<T, U>)(thisArg, v, i)
|
||||
else
|
||||
array[i] = (mapFn :: mapFn<T, U>)(v, i)
|
||||
end
|
||||
end
|
||||
else
|
||||
for i, v in (value :: any):ipairs() do
|
||||
array[i] = v
|
||||
end
|
||||
end
|
||||
elseif instanceOf(value, Map) then
|
||||
if mapFn then
|
||||
for i, v in (value :: any):ipairs() do
|
||||
if thisArg ~= nil then
|
||||
array[i] = (mapFn :: mapFnWithThisArg<T, U>)(thisArg, v, i)
|
||||
else
|
||||
array[i] = (mapFn :: mapFn<T, U>)(v, i)
|
||||
end
|
||||
end
|
||||
else
|
||||
for i, v in (value :: any):ipairs() do
|
||||
array[i] = v
|
||||
end
|
||||
end
|
||||
elseif valueType == "string" then
|
||||
if mapFn then
|
||||
for i = 1, (value :: string):len() do
|
||||
if thisArg ~= nil then
|
||||
array[i] = (mapFn :: mapFnWithThisArg<T, U>)(thisArg, (value :: any):sub(i, i), i)
|
||||
else
|
||||
array[i] = (mapFn :: mapFn<T, U>)((value :: any):sub(i, i), i)
|
||||
end
|
||||
end
|
||||
else
|
||||
for i = 1, (value :: string):len() do
|
||||
array[i] = (value :: any):sub(i, i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return array
|
||||
end
|
||||
|
||||
type callbackFnArrayMap<T, U> = (element: T, index: number, array: Array<T>) -> U
|
||||
type callbackFnWithThisArgArrayMap<T, U, V> = (thisArg: V, element: T, index: number, array: Array<T>) -> U
|
||||
|
||||
-- Implements Javascript's `Array.prototype.map` as defined below
|
||||
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
|
||||
function Array.map<T, U, V>(
|
||||
t: Array<T>,
|
||||
callback: callbackFnArrayMap<T, U> | callbackFnWithThisArgArrayMap<T, U, V>,
|
||||
thisArg: V?
|
||||
): Array<U>
|
||||
if typeof(t) ~= "table" then
|
||||
error(string.format("Array.map called on %s", typeof(t)))
|
||||
end
|
||||
if typeof(callback) ~= "function" then
|
||||
error("callback is not a function")
|
||||
end
|
||||
|
||||
local len = #t
|
||||
local A = {}
|
||||
local k = 1
|
||||
|
||||
while k <= len do
|
||||
local kValue = t[k]
|
||||
|
||||
if kValue ~= nil then
|
||||
local mappedValue
|
||||
|
||||
if thisArg ~= nil then
|
||||
mappedValue = (callback :: callbackFnWithThisArgArrayMap<T, U, V>)(thisArg, kValue, k, t)
|
||||
else
|
||||
mappedValue = (callback :: callbackFnArrayMap<T, U>)(kValue, k, t)
|
||||
end
|
||||
|
||||
A[k] = mappedValue
|
||||
end
|
||||
k += 1
|
||||
end
|
||||
|
||||
return A
|
||||
end
|
||||
|
||||
type Function = (any, any, number, any) -> any
|
||||
|
||||
-- Implements Javascript's `Array.prototype.reduce` as defined below
|
||||
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
|
||||
function Array.reduce<T>(array: Array<T>, callback: Function, initialValue: any?): any
|
||||
if typeof(array) ~= "table" then
|
||||
error(string.format("Array.reduce called on %s", typeof(array)))
|
||||
end
|
||||
if typeof(callback) ~= "function" then
|
||||
error("callback is not a function")
|
||||
end
|
||||
|
||||
local length = #array
|
||||
|
||||
local value
|
||||
local initial = 1
|
||||
|
||||
if initialValue ~= nil then
|
||||
value = initialValue
|
||||
else
|
||||
initial = 2
|
||||
if length == 0 then
|
||||
error("reduce of empty array with no initial value")
|
||||
end
|
||||
value = array[1]
|
||||
end
|
||||
|
||||
for i = initial, length do
|
||||
value = callback(value, array[i], i, array)
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
type callbackFnArrayForEach<T> = (element: T, index: number, array: Array<T>) -> ()
|
||||
type callbackFnWithThisArgArrayForEach<T, U> = (thisArg: U, element: T, index: number, array: Array<T>) -> ()
|
||||
|
||||
-- Implements Javascript's `Array.prototype.forEach` as defined below
|
||||
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
|
||||
function Array.forEach<T, U>(
|
||||
t: Array<T>,
|
||||
callback: callbackFnArrayForEach<T> | callbackFnWithThisArgArrayForEach<T, U>,
|
||||
thisArg: U?
|
||||
): ()
|
||||
if typeof(t) ~= "table" then
|
||||
error(string.format("Array.forEach called on %s", typeof(t)))
|
||||
end
|
||||
if typeof(callback) ~= "function" then
|
||||
error("callback is not a function")
|
||||
end
|
||||
|
||||
local len = #t
|
||||
local k = 1
|
||||
|
||||
while k <= len do
|
||||
local kValue = t[k]
|
||||
|
||||
if thisArg ~= nil then
|
||||
(callback :: callbackFnWithThisArgArrayForEach<T, U>)(thisArg, kValue, k, t)
|
||||
else
|
||||
(callback :: callbackFnArrayForEach<T>)(kValue, k, t)
|
||||
end
|
||||
|
||||
if #t < len then
|
||||
-- don't iterate on removed items, don't iterate more than original length
|
||||
len = #t
|
||||
end
|
||||
k += 1
|
||||
end
|
||||
end
|
||||
-- #endregion
|
||||
|
||||
-- #region Set
|
||||
Set.__index = Set
|
||||
|
||||
type callbackFnSet<T> = (value: T, key: T, set: Set<T>) -> ()
|
||||
type callbackFnWithThisArgSet<T> = (thisArg: Object, value: T, key: T, set: Set<T>) -> ()
|
||||
|
||||
export type Set<T> = {
|
||||
size: number,
|
||||
-- method definitions
|
||||
add: (self: Set<T>, T) -> Set<T>,
|
||||
clear: (self: Set<T>) -> (),
|
||||
delete: (self: Set<T>, T) -> boolean,
|
||||
forEach: (self: Set<T>, callback: callbackFnSet<T> | callbackFnWithThisArgSet<T>, thisArg: Object?) -> (),
|
||||
has: (self: Set<T>, T) -> boolean,
|
||||
ipairs: (self: Set<T>) -> any,
|
||||
}
|
||||
|
||||
type Iterable = { ipairs: (any) -> any }
|
||||
|
||||
function Set.new<T>(iterable: Array<T> | Set<T> | Iterable | string | nil): Set<T>
|
||||
local array = {}
|
||||
local map = {}
|
||||
if iterable ~= nil then
|
||||
local arrayIterable: Array<any>
|
||||
-- ROBLOX TODO: remove type casting from (iterable :: any).ipairs in next release
|
||||
if typeof(iterable) == "table" then
|
||||
if Array.isArray(iterable) then
|
||||
arrayIterable = Array.from(iterable :: Array<any>)
|
||||
elseif typeof((iterable :: Iterable).ipairs) == "function" then
|
||||
-- handle in loop below
|
||||
elseif _G.__DEV__ then
|
||||
error("cannot create array from an object-like table")
|
||||
end
|
||||
elseif typeof(iterable) == "string" then
|
||||
arrayIterable = Array.from(iterable :: string)
|
||||
else
|
||||
error(("cannot create array from value of type `%s`"):format(typeof(iterable)))
|
||||
end
|
||||
|
||||
if arrayIterable then
|
||||
for _, element in ipairs(arrayIterable) do
|
||||
if not map[element] then
|
||||
map[element] = true
|
||||
table.insert(array, element)
|
||||
end
|
||||
end
|
||||
elseif typeof(iterable) == "table" and typeof((iterable :: Iterable).ipairs) == "function" then
|
||||
for _, element in (iterable :: Iterable):ipairs() do
|
||||
if not map[element] then
|
||||
map[element] = true
|
||||
table.insert(array, element)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return (setmetatable({
|
||||
size = #array,
|
||||
_map = map,
|
||||
_array = array,
|
||||
}, Set) :: any) :: Set<T>
|
||||
end
|
||||
|
||||
function Set:add(value)
|
||||
if not self._map[value] then
|
||||
-- Luau FIXME: analyze should know self is Set<T> which includes size as a number
|
||||
self.size = self.size :: number + 1
|
||||
self._map[value] = true
|
||||
table.insert(self._array, value)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Set:clear()
|
||||
self.size = 0
|
||||
table.clear(self._map)
|
||||
table.clear(self._array)
|
||||
end
|
||||
|
||||
function Set:delete(value): boolean
|
||||
if not self._map[value] then
|
||||
return false
|
||||
end
|
||||
-- Luau FIXME: analyze should know self is Map<K, V> which includes size as a number
|
||||
self.size = self.size :: number - 1
|
||||
self._map[value] = nil
|
||||
local index = table.find(self._array, value)
|
||||
if index then
|
||||
table.remove(self._array, index)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Implements Javascript's `Map.prototype.forEach` as defined below
|
||||
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/forEach
|
||||
function Set:forEach<T>(callback: callbackFnSet<T> | callbackFnWithThisArgSet<T>, thisArg: Object?): ()
|
||||
if typeof(callback) ~= "function" then
|
||||
error("callback is not a function")
|
||||
end
|
||||
|
||||
return Array.forEach(self._array, function(value: T)
|
||||
if thisArg ~= nil then
|
||||
(callback :: callbackFnWithThisArgSet<T>)(thisArg, value, value, self)
|
||||
else
|
||||
(callback :: callbackFnSet<T>)(value, value, self)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Set:has(value): boolean
|
||||
return self._map[value] ~= nil
|
||||
end
|
||||
|
||||
function Set:ipairs()
|
||||
return ipairs(self._array)
|
||||
end
|
||||
|
||||
-- #endregion Set
|
||||
|
||||
-- #region Object
|
||||
function Object.entries(value: string | Object | Array<any>): Array<any>
|
||||
assert(value :: any ~= nil, "cannot get entries from a nil value")
|
||||
local valueType = typeof(value)
|
||||
|
||||
local entries: Array<Tuple<string, any>> = {}
|
||||
if valueType == "table" then
|
||||
for key, keyValue in pairs(value :: Object) do
|
||||
-- Luau FIXME: Luau should see entries as Array<any>, given object is [string]: any, but it sees it as Array<Array<string>> despite all the manual annotation
|
||||
table.insert(entries, { key :: string, keyValue :: any })
|
||||
end
|
||||
elseif valueType == "string" then
|
||||
for i = 1, string.len(value :: string) do
|
||||
entries[i] = { tostring(i), string.sub(value :: string, i, i) }
|
||||
end
|
||||
end
|
||||
|
||||
return entries
|
||||
end
|
||||
|
||||
-- #endregion
|
||||
|
||||
-- #region instanceOf
|
||||
|
||||
-- ROBLOX note: Typed tbl as any to work with strict type analyze
|
||||
-- polyfill for https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof
|
||||
function instanceOf(tbl: any, class)
|
||||
assert(typeof(class) == "table", "Received a non-table as the second argument for instanceof")
|
||||
|
||||
if typeof(tbl) ~= "table" then
|
||||
return false
|
||||
end
|
||||
|
||||
local ok, hasNew = pcall(function()
|
||||
return class.new ~= nil and tbl.new == class.new
|
||||
end)
|
||||
if ok and hasNew then
|
||||
return true
|
||||
end
|
||||
|
||||
local seen = { tbl = true }
|
||||
|
||||
while tbl and typeof(tbl) == "table" do
|
||||
tbl = getmetatable(tbl)
|
||||
if typeof(tbl) == "table" then
|
||||
tbl = tbl.__index
|
||||
|
||||
if tbl == class then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- if we still have a valid table then check against seen
|
||||
if typeof(tbl) == "table" then
|
||||
if seen[tbl] then
|
||||
return false
|
||||
end
|
||||
seen[tbl] = true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
-- #endregion
|
||||
|
||||
function Map.new<K, V>(iterable: Array<Array<any>>?): Map<K, V>
|
||||
local array = {}
|
||||
local map = {}
|
||||
if iterable ~= nil then
|
||||
local arrayFromIterable
|
||||
local iterableType = typeof(iterable)
|
||||
if iterableType == "table" then
|
||||
if #iterable > 0 and typeof(iterable[1]) ~= "table" then
|
||||
error("cannot create Map from {K, V} form, it must be { {K, V}... }")
|
||||
end
|
||||
|
||||
arrayFromIterable = Array.from(iterable)
|
||||
else
|
||||
error(("cannot create array from value of type `%s`"):format(iterableType))
|
||||
end
|
||||
|
||||
for _, entry in ipairs(arrayFromIterable) do
|
||||
local key = entry[1]
|
||||
if _G.__DEV__ then
|
||||
if key == nil then
|
||||
error("cannot create Map from a table that isn't an array.")
|
||||
end
|
||||
end
|
||||
local val = entry[2]
|
||||
-- only add to array if new
|
||||
if map[key] == nil then
|
||||
table.insert(array, key)
|
||||
end
|
||||
-- always assign
|
||||
map[key] = val
|
||||
end
|
||||
end
|
||||
|
||||
return (setmetatable({
|
||||
size = #array,
|
||||
_map = map,
|
||||
_array = array,
|
||||
}, Map) :: any) :: Map<K, V>
|
||||
end
|
||||
|
||||
function Map:set<K, V>(key: K, value: V): Map<K, V>
|
||||
-- preserve initial insertion order
|
||||
if self._map[key] == nil then
|
||||
-- Luau FIXME: analyze should know self is Map<K, V> which includes size as a number
|
||||
self.size = self.size :: number + 1
|
||||
table.insert(self._array, key)
|
||||
end
|
||||
-- always update value
|
||||
self._map[key] = value
|
||||
return self
|
||||
end
|
||||
|
||||
function Map:get(key)
|
||||
return self._map[key]
|
||||
end
|
||||
|
||||
function Map:clear()
|
||||
local table_: any = table
|
||||
self.size = 0
|
||||
table_.clear(self._map)
|
||||
table_.clear(self._array)
|
||||
end
|
||||
|
||||
function Map:delete(key): boolean
|
||||
if self._map[key] == nil then
|
||||
return false
|
||||
end
|
||||
-- Luau FIXME: analyze should know self is Map<K, V> which includes size as a number
|
||||
self.size = self.size :: number - 1
|
||||
self._map[key] = nil
|
||||
local index = table.find(self._array, key)
|
||||
if index then
|
||||
table.remove(self._array, index)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Implements Javascript's `Map.prototype.forEach` as defined below
|
||||
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/forEach
|
||||
function Map:forEach<K, V>(callback: callbackFn<K, V> | callbackFnWithThisArg<K, V>, thisArg: Object?): ()
|
||||
if typeof(callback) ~= "function" then
|
||||
error("callback is not a function")
|
||||
end
|
||||
|
||||
return Array.forEach(self._array, function(key: K)
|
||||
local value: V = self._map[key] :: V
|
||||
|
||||
if thisArg ~= nil then
|
||||
(callback :: callbackFnWithThisArg<K, V>)(thisArg, value, key, self)
|
||||
else
|
||||
(callback :: callbackFn<K, V>)(value, key, self)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Map:has(key): boolean
|
||||
return self._map[key] ~= nil
|
||||
end
|
||||
|
||||
function Map:keys()
|
||||
return self._array
|
||||
end
|
||||
|
||||
function Map:values()
|
||||
return Array.map(self._array, function(key)
|
||||
return self._map[key]
|
||||
end)
|
||||
end
|
||||
|
||||
function Map:entries()
|
||||
return Array.map(self._array, function(key)
|
||||
return { key, self._map[key] }
|
||||
end)
|
||||
end
|
||||
|
||||
function Map:ipairs()
|
||||
return ipairs(self:entries())
|
||||
end
|
||||
|
||||
function Map.__index(self, key)
|
||||
local mapProp = rawget(Map, key)
|
||||
if mapProp ~= nil then
|
||||
return mapProp
|
||||
end
|
||||
|
||||
return Map.get(self, key)
|
||||
end
|
||||
|
||||
function Map.__newindex(table_, key, value)
|
||||
table_:set(key, value)
|
||||
end
|
||||
|
||||
local function coerceToMap(mapLike: Map<any, any> | Table<any, any>): Map<any, any>
|
||||
return instanceOf(mapLike, Map) and mapLike :: Map<any, any> -- ROBLOX: order is preservered
|
||||
or Map.new(Object.entries(mapLike)) -- ROBLOX: order is not preserved
|
||||
end
|
||||
|
||||
-- local function coerceToTable(mapLike: Map<any, any> | Table<any, any>): Table<any, any>
|
||||
-- if not instanceOf(mapLike, Map) then
|
||||
-- return mapLike
|
||||
-- end
|
||||
|
||||
-- -- create table from map
|
||||
-- return Array.reduce(mapLike:entries(), function(tbl, entry)
|
||||
-- tbl[entry[1]] = entry[2]
|
||||
-- return tbl
|
||||
-- end, {})
|
||||
-- end
|
||||
|
||||
-- #region Tests to verify it works as expected
|
||||
local function it(description: string, fn: () -> ())
|
||||
local ok, result = pcall(fn)
|
||||
|
||||
if not ok then
|
||||
error("Failed test: " .. description .. "\n" .. result)
|
||||
end
|
||||
end
|
||||
|
||||
local AN_ITEM = "bar"
|
||||
local ANOTHER_ITEM = "baz"
|
||||
|
||||
-- #region [Describe] "Map"
|
||||
-- #region [Child Describe] "constructors"
|
||||
it("creates an empty array", function()
|
||||
local foo = Map.new()
|
||||
assert(foo.size == 0)
|
||||
end)
|
||||
|
||||
it("creates a Map from an array", function()
|
||||
local foo = Map.new({
|
||||
{ AN_ITEM, "foo" },
|
||||
{ ANOTHER_ITEM, "val" },
|
||||
})
|
||||
assert(foo.size == 2)
|
||||
assert(foo:has(AN_ITEM) == true)
|
||||
assert(foo:has(ANOTHER_ITEM) == true)
|
||||
end)
|
||||
|
||||
it("creates a Map from an array with duplicate keys", function()
|
||||
local foo = Map.new({
|
||||
{ AN_ITEM, "foo1" },
|
||||
{ AN_ITEM, "foo2" },
|
||||
})
|
||||
assert(foo.size == 1)
|
||||
assert(foo:get(AN_ITEM) == "foo2")
|
||||
|
||||
assert(#foo:keys() == 1 and foo:keys()[1] == AN_ITEM)
|
||||
assert(#foo:values() == 1 and foo:values()[1] == "foo2")
|
||||
assert(#foo:entries() == 1)
|
||||
assert(#foo:entries()[1] == 2)
|
||||
|
||||
assert(foo:entries()[1][1] == AN_ITEM)
|
||||
assert(foo:entries()[1][2] == "foo2")
|
||||
end)
|
||||
|
||||
it("preserves the order of keys first assignment", function()
|
||||
local foo = Map.new({
|
||||
{ AN_ITEM, "foo1" },
|
||||
{ ANOTHER_ITEM, "bar" },
|
||||
{ AN_ITEM, "foo2" },
|
||||
})
|
||||
assert(foo.size == 2)
|
||||
assert(foo:get(AN_ITEM) == "foo2")
|
||||
assert(foo:get(ANOTHER_ITEM) == "bar")
|
||||
|
||||
assert(foo:keys()[1] == AN_ITEM)
|
||||
assert(foo:keys()[2] == ANOTHER_ITEM)
|
||||
assert(foo:values()[1] == "foo2")
|
||||
assert(foo:values()[2] == "bar")
|
||||
assert(foo:entries()[1][1] == AN_ITEM)
|
||||
assert(foo:entries()[1][2] == "foo2")
|
||||
assert(foo:entries()[2][1] == ANOTHER_ITEM)
|
||||
assert(foo:entries()[2][2] == "bar")
|
||||
end)
|
||||
-- #endregion
|
||||
|
||||
-- #region [Child Describe] "type"
|
||||
it("instanceOf return true for an actual Map object", function()
|
||||
local foo = Map.new()
|
||||
assert(instanceOf(foo, Map) == true)
|
||||
end)
|
||||
|
||||
it("instanceOf return false for an regular plain object", function()
|
||||
local foo = {}
|
||||
assert(instanceOf(foo, Map) == false)
|
||||
end)
|
||||
-- #endregion
|
||||
|
||||
-- #region [Child Describe] "set"
|
||||
it("returns the Map object", function()
|
||||
local foo = Map.new()
|
||||
assert(foo:set(1, "baz") == foo)
|
||||
end)
|
||||
|
||||
it("increments the size if the element is added for the first time", function()
|
||||
local foo = Map.new()
|
||||
foo:set(AN_ITEM, "foo")
|
||||
assert(foo.size == 1)
|
||||
end)
|
||||
|
||||
it("does not increment the size the second time an element is added", function()
|
||||
local foo = Map.new()
|
||||
foo:set(AN_ITEM, "foo")
|
||||
foo:set(AN_ITEM, "val")
|
||||
assert(foo.size == 1)
|
||||
end)
|
||||
|
||||
it("sets values correctly to true/false", function()
|
||||
-- Luau FIXME: Luau insists that arrays can't be mixed type
|
||||
local foo = Map.new({ { AN_ITEM, false :: any } })
|
||||
foo:set(AN_ITEM, false)
|
||||
assert(foo.size == 1)
|
||||
assert(foo:get(AN_ITEM) == false)
|
||||
|
||||
foo:set(AN_ITEM, true)
|
||||
assert(foo.size == 1)
|
||||
assert(foo:get(AN_ITEM) == true)
|
||||
|
||||
foo:set(AN_ITEM, false)
|
||||
assert(foo.size == 1)
|
||||
assert(foo:get(AN_ITEM) == false)
|
||||
end)
|
||||
|
||||
-- #endregion
|
||||
|
||||
-- #region [Child Describe] "get"
|
||||
it("returns value of item from provided key", function()
|
||||
local foo = Map.new()
|
||||
foo:set(AN_ITEM, "foo")
|
||||
assert(foo:get(AN_ITEM) == "foo")
|
||||
end)
|
||||
|
||||
it("returns nil if the item is not in the Map", function()
|
||||
local foo = Map.new()
|
||||
assert(foo:get(AN_ITEM) == nil)
|
||||
end)
|
||||
-- #endregion
|
||||
|
||||
-- #region [Child Describe] "clear"
|
||||
it("sets the size to zero", function()
|
||||
local foo = Map.new()
|
||||
foo:set(AN_ITEM, "foo")
|
||||
foo:clear()
|
||||
assert(foo.size == 0)
|
||||
end)
|
||||
|
||||
it("removes the items from the Map", function()
|
||||
local foo = Map.new()
|
||||
foo:set(AN_ITEM, "foo")
|
||||
foo:clear()
|
||||
assert(foo:has(AN_ITEM) == false)
|
||||
end)
|
||||
-- #endregion
|
||||
|
||||
-- #region [Child Describe] "delete"
|
||||
it("removes the items from the Map", function()
|
||||
local foo = Map.new()
|
||||
foo:set(AN_ITEM, "foo")
|
||||
foo:delete(AN_ITEM)
|
||||
assert(foo:has(AN_ITEM) == false)
|
||||
end)
|
||||
|
||||
it("returns true if the item was in the Map", function()
|
||||
local foo = Map.new()
|
||||
foo:set(AN_ITEM, "foo")
|
||||
assert(foo:delete(AN_ITEM) == true)
|
||||
end)
|
||||
|
||||
it("returns false if the item was not in the Map", function()
|
||||
local foo = Map.new()
|
||||
assert(foo:delete(AN_ITEM) == false)
|
||||
end)
|
||||
|
||||
it("decrements the size if the item was in the Map", function()
|
||||
local foo = Map.new()
|
||||
foo:set(AN_ITEM, "foo")
|
||||
foo:delete(AN_ITEM)
|
||||
assert(foo.size == 0)
|
||||
end)
|
||||
|
||||
it("does not decrement the size if the item was not in the Map", function()
|
||||
local foo = Map.new()
|
||||
foo:set(AN_ITEM, "foo")
|
||||
foo:delete(ANOTHER_ITEM)
|
||||
assert(foo.size == 1)
|
||||
end)
|
||||
|
||||
it("deletes value set to false", function()
|
||||
-- Luau FIXME: Luau insists arrays can't be mixed type
|
||||
local foo = Map.new({ { AN_ITEM, false :: any } })
|
||||
|
||||
foo:delete(AN_ITEM)
|
||||
|
||||
assert(foo.size == 0)
|
||||
assert(foo:get(AN_ITEM) == nil)
|
||||
end)
|
||||
-- #endregion
|
||||
|
||||
-- #region [Child Describe] "has"
|
||||
it("returns true if the item is in the Map", function()
|
||||
local foo = Map.new()
|
||||
foo:set(AN_ITEM, "foo")
|
||||
assert(foo:has(AN_ITEM) == true)
|
||||
end)
|
||||
|
||||
it("returns false if the item is not in the Map", function()
|
||||
local foo = Map.new()
|
||||
assert(foo:has(AN_ITEM) == false)
|
||||
end)
|
||||
|
||||
it("returns correctly with value set to false", function()
|
||||
-- Luau FIXME: Luau insists arrays can't be mixed type
|
||||
local foo = Map.new({ { AN_ITEM, false :: any } })
|
||||
|
||||
assert(foo:has(AN_ITEM) == true)
|
||||
end)
|
||||
-- #endregion
|
||||
|
||||
-- #region [Child Describe] "keys / values / entries"
|
||||
it("returns array of elements", function()
|
||||
local myMap = Map.new()
|
||||
myMap:set(AN_ITEM, "foo")
|
||||
myMap:set(ANOTHER_ITEM, "val")
|
||||
|
||||
assert(myMap:keys()[1] == AN_ITEM)
|
||||
assert(myMap:keys()[2] == ANOTHER_ITEM)
|
||||
|
||||
assert(myMap:values()[1] == "foo")
|
||||
assert(myMap:values()[2] == "val")
|
||||
|
||||
assert(myMap:entries()[1][1] == AN_ITEM)
|
||||
assert(myMap:entries()[1][2] == "foo")
|
||||
assert(myMap:entries()[2][1] == ANOTHER_ITEM)
|
||||
assert(myMap:entries()[2][2] == "val")
|
||||
end)
|
||||
-- #endregion
|
||||
|
||||
-- #region [Child Describe] "__index"
|
||||
it("can access fields directly without using get", function()
|
||||
local typeName = "size"
|
||||
|
||||
local foo = Map.new({
|
||||
{ AN_ITEM, "foo" },
|
||||
{ ANOTHER_ITEM, "val" },
|
||||
{ typeName, "buzz" },
|
||||
})
|
||||
|
||||
assert(foo.size == 3)
|
||||
assert(foo[AN_ITEM] == "foo")
|
||||
assert(foo[ANOTHER_ITEM] == "val")
|
||||
assert(foo:get(typeName) == "buzz")
|
||||
end)
|
||||
-- #endregion
|
||||
|
||||
-- #region [Child Describe] "__newindex"
|
||||
it("can set fields directly without using set", function()
|
||||
local foo = Map.new()
|
||||
|
||||
assert(foo.size == 0)
|
||||
|
||||
foo[AN_ITEM] = "foo"
|
||||
foo[ANOTHER_ITEM] = "val"
|
||||
foo.fizz = "buzz"
|
||||
|
||||
assert(foo.size == 3)
|
||||
assert(foo:get(AN_ITEM) == "foo")
|
||||
assert(foo:get(ANOTHER_ITEM) == "val")
|
||||
assert(foo:get("fizz") == "buzz")
|
||||
end)
|
||||
-- #endregion
|
||||
|
||||
-- #region [Child Describe] "ipairs"
|
||||
local function makeArray(...)
|
||||
local array = {}
|
||||
for _, item in ... do
|
||||
table.insert(array, item)
|
||||
end
|
||||
return array
|
||||
end
|
||||
|
||||
it("iterates on the elements by their insertion order", function()
|
||||
local foo = Map.new()
|
||||
foo:set(AN_ITEM, "foo")
|
||||
foo:set(ANOTHER_ITEM, "val")
|
||||
assert(makeArray(foo:ipairs())[1][1] == AN_ITEM)
|
||||
assert(makeArray(foo:ipairs())[1][2] == "foo")
|
||||
assert(makeArray(foo:ipairs())[2][1] == ANOTHER_ITEM)
|
||||
assert(makeArray(foo:ipairs())[2][2] == "val")
|
||||
end)
|
||||
|
||||
it("does not iterate on removed elements", function()
|
||||
local foo = Map.new()
|
||||
foo:set(AN_ITEM, "foo")
|
||||
foo:set(ANOTHER_ITEM, "val")
|
||||
foo:delete(AN_ITEM)
|
||||
assert(makeArray(foo:ipairs())[1][1] == ANOTHER_ITEM)
|
||||
assert(makeArray(foo:ipairs())[1][2] == "val")
|
||||
end)
|
||||
|
||||
it("iterates on elements if the added back to the Map", function()
|
||||
local foo = Map.new()
|
||||
foo:set(AN_ITEM, "foo")
|
||||
foo:set(ANOTHER_ITEM, "val")
|
||||
foo:delete(AN_ITEM)
|
||||
foo:set(AN_ITEM, "food")
|
||||
assert(makeArray(foo:ipairs())[1][1] == ANOTHER_ITEM)
|
||||
assert(makeArray(foo:ipairs())[1][2] == "val")
|
||||
assert(makeArray(foo:ipairs())[2][1] == AN_ITEM)
|
||||
assert(makeArray(foo:ipairs())[2][2] == "food")
|
||||
end)
|
||||
-- #endregion
|
||||
|
||||
-- #region [Child Describe] "Integration Tests"
|
||||
-- it("MDN Examples", function()
|
||||
-- local myMap = Map.new() :: Map<string | Object | Function, string>
|
||||
|
||||
-- local keyString = "a string"
|
||||
-- local keyObj = {}
|
||||
-- local keyFunc = function() end
|
||||
|
||||
-- -- setting the values
|
||||
-- myMap:set(keyString, "value associated with 'a string'")
|
||||
-- myMap:set(keyObj, "value associated with keyObj")
|
||||
-- myMap:set(keyFunc, "value associated with keyFunc")
|
||||
|
||||
-- assert(myMap.size == 3)
|
||||
|
||||
-- -- getting the values
|
||||
-- assert(myMap:get(keyString) == "value associated with 'a string'")
|
||||
-- assert(myMap:get(keyObj) == "value associated with keyObj")
|
||||
-- assert(myMap:get(keyFunc) == "value associated with keyFunc")
|
||||
|
||||
-- assert(myMap:get("a string") == "value associated with 'a string'")
|
||||
|
||||
-- assert(myMap:get({}) == nil) -- nil, because keyObj !== {}
|
||||
-- assert(myMap:get(function() -- nil because keyFunc !== function () {}
|
||||
-- end) == nil)
|
||||
-- end)
|
||||
|
||||
it("handles non-traditional keys", function()
|
||||
local myMap = Map.new() :: Map<boolean | number | string, string>
|
||||
|
||||
local falseKey = false
|
||||
local trueKey = true
|
||||
local negativeKey = -1
|
||||
local emptyKey = ""
|
||||
|
||||
myMap:set(falseKey, "apple")
|
||||
myMap:set(trueKey, "bear")
|
||||
myMap:set(negativeKey, "corgi")
|
||||
myMap:set(emptyKey, "doge")
|
||||
|
||||
assert(myMap.size == 4)
|
||||
|
||||
assert(myMap:get(falseKey) == "apple")
|
||||
assert(myMap:get(trueKey) == "bear")
|
||||
assert(myMap:get(negativeKey) == "corgi")
|
||||
assert(myMap:get(emptyKey) == "doge")
|
||||
|
||||
myMap:delete(falseKey)
|
||||
myMap:delete(trueKey)
|
||||
myMap:delete(negativeKey)
|
||||
myMap:delete(emptyKey)
|
||||
|
||||
assert(myMap.size == 0)
|
||||
end)
|
||||
-- #endregion
|
||||
|
||||
-- #endregion [Describe] "Map"
|
||||
|
||||
-- #region [Describe] "coerceToMap"
|
||||
it("returns the same object if instance of Map", function()
|
||||
local map = Map.new()
|
||||
assert(coerceToMap(map) == map)
|
||||
|
||||
map = Map.new({})
|
||||
assert(coerceToMap(map) == map)
|
||||
|
||||
map = Map.new({ { AN_ITEM, "foo" } })
|
||||
assert(coerceToMap(map) == map)
|
||||
end)
|
||||
-- #endregion [Describe] "coerceToMap"
|
||||
|
||||
-- #endregion Tests to verify it works as expected
|
2089
bench/other/regex.lua
Normal file
2089
bench/other/regex.lua
Normal file
File diff suppressed because it is too large
Load Diff
@ -213,6 +213,16 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfLea")
|
||||
SINGLE_COMPARE(lea(rax, qword[r13 + r12 * 4 + 4]), 0x4b, 0x8d, 0x44, 0xa5, 0x04);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfAbsoluteJumps")
|
||||
{
|
||||
SINGLE_COMPARE(jmp(rax), 0x48, 0xff, 0xe0);
|
||||
SINGLE_COMPARE(jmp(r14), 0x49, 0xff, 0xe6);
|
||||
SINGLE_COMPARE(jmp(qword[r14 + rdx * 4]), 0x49, 0xff, 0x24, 0x96);
|
||||
SINGLE_COMPARE(call(rax), 0x48, 0xff, 0xd0);
|
||||
SINGLE_COMPARE(call(r14), 0x49, 0xff, 0xd6);
|
||||
SINGLE_COMPARE(call(qword[r14 + rdx * 4]), 0x49, 0xff, 0x14, 0x96);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "ControlFlow")
|
||||
{
|
||||
// Jump back
|
||||
@ -260,6 +270,23 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "ControlFlow")
|
||||
{0xe9, 0x04, 0x00, 0x00, 0x00, 0x48, 0x83, 0xe7, 0x3e});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "LabelCall")
|
||||
{
|
||||
check(
|
||||
[](AssemblyBuilderX64& build) {
|
||||
Label fnB;
|
||||
|
||||
build.and_(rcx, 0x3e);
|
||||
build.call(fnB);
|
||||
build.ret();
|
||||
|
||||
build.setLabel(fnB);
|
||||
build.lea(rax, qword[rcx + 0x1f]);
|
||||
build.ret();
|
||||
},
|
||||
{0x48, 0x83, 0xe1, 0x3e, 0xe8, 0x01, 0x00, 0x00, 0x00, 0xc3, 0x48, 0x8d, 0x41, 0x1f, 0xc3});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXBinaryInstructionForms")
|
||||
{
|
||||
SINGLE_COMPARE(vaddpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xa9, 0x58, 0xc6);
|
||||
|
@ -105,4 +105,37 @@ if true then
|
||||
REQUIRE(parentStat->is<AstStatIf>());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "ac_ast_ancestry_at_number_const")
|
||||
{
|
||||
check(R"(
|
||||
print(3.)
|
||||
)");
|
||||
|
||||
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(*getMainSourceModule(), Position(1, 8));
|
||||
REQUIRE_GE(ancestry.size(), 2);
|
||||
REQUIRE(ancestry.back()->is<AstExprConstantNumber>());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "ac_ast_ancestry_in_workspace_dot")
|
||||
{
|
||||
check(R"(
|
||||
print(workspace.)
|
||||
)");
|
||||
|
||||
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(*getMainSourceModule(), Position(1, 16));
|
||||
REQUIRE_GE(ancestry.size(), 2);
|
||||
REQUIRE(ancestry.back()->is<AstExprIndexName>());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "ac_ast_ancestry_in_workspace_colon")
|
||||
{
|
||||
check(R"(
|
||||
print(workspace:)
|
||||
)");
|
||||
|
||||
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(*getMainSourceModule(), Position(1, 16));
|
||||
REQUIRE_GE(ancestry.size(), 2);
|
||||
REQUIRE(ancestry.back()->is<AstExprIndexName>());
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -128,6 +128,7 @@ struct Fixture
|
||||
std::optional<TypeId> lookupImportedType(const std::string& moduleAlias, const std::string& name);
|
||||
|
||||
ScopedFastFlag sff_DebugLuauFreezeArena;
|
||||
ScopedFastFlag sff_UnknownNever{"LuauUnknownAndNeverType", true};
|
||||
|
||||
TestFileResolver fileResolver;
|
||||
TestConfigResolver configResolver;
|
||||
|
@ -791,6 +791,8 @@ TEST_CASE_FIXTURE(FrontendFixture, "discard_type_graphs")
|
||||
CHECK_EQ(0, module->internalTypes.typeVars.size());
|
||||
CHECK_EQ(0, module->internalTypes.typePacks.size());
|
||||
CHECK_EQ(0, module->astTypes.size());
|
||||
CHECK_EQ(0, module->astResolvedTypes.size());
|
||||
CHECK_EQ(0, module->astResolvedTypePacks.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded")
|
||||
|
@ -301,8 +301,6 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "any_persistance_does_not_leak")
|
||||
{
|
||||
ScopedFastFlag luauNonCopyableTypeVarFields{"LuauNonCopyableTypeVarFields", true};
|
||||
|
||||
fileResolver.source["Module/A"] = R"(
|
||||
export type A = B
|
||||
type B = A
|
||||
|
@ -1055,8 +1055,6 @@ export type t1 = { a: typeof(string.byte) }
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_combine_on_bound_self")
|
||||
{
|
||||
ScopedFastFlag luauNormalizeCombineEqFix{"LuauNormalizeCombineEqFix", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
export type t0 = (((any)&({_:l0.t0,n0:t0,_G:any,}))&({_:any,}))&(((any)&({_:l0.t0,n0:t0,_G:any,}))&({_:any,}))
|
||||
)");
|
||||
@ -1064,6 +1062,46 @@ export type t0 = (((any)&({_:l0.t0,n0:t0,_G:any,}))&({_:any,}))&(((any)&({_:l0.t
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "normalize_unions_containing_never")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauLowerBoundsCalculation", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Foo = string | never
|
||||
local foo: Foo
|
||||
)");
|
||||
|
||||
CHECK_EQ("string", toString(requireType("foo")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "normalize_unions_containing_unknown")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauLowerBoundsCalculation", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Foo = string | unknown
|
||||
local foo: Foo
|
||||
)");
|
||||
|
||||
CHECK_EQ("unknown", toString(requireType("foo")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "any_wins_the_battle_over_unknown_in_unions")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauLowerBoundsCalculation", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Foo = unknown | any
|
||||
local foo: Foo
|
||||
|
||||
type Bar = any | unknown
|
||||
local bar: Bar
|
||||
)");
|
||||
|
||||
CHECK_EQ("any", toString(requireType("foo")));
|
||||
CHECK_EQ("any", toString(requireType("bar")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "normalization_does_not_convert_ever")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
|
@ -2648,7 +2648,6 @@ type Z<T> = { a: string | T..., b: number }
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "recover_function_return_type_annotations")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauReturnTypeTokenConfusion", true};
|
||||
ParseResult result = tryParse(R"(
|
||||
type Custom<A, B, C> = { x: A, y: B, z: C }
|
||||
type Packed<A...> = { x: (A...) -> () }
|
||||
|
@ -96,6 +96,37 @@ TEST_CASE_FIXTURE(Fixture, "table_respects_use_line_break")
|
||||
//clang-format on
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "metatable")
|
||||
{
|
||||
TypeVar table{TypeVariant(TableTypeVar())};
|
||||
TypeVar metatable{TypeVariant(TableTypeVar())};
|
||||
TypeVar mtv{TypeVariant(MetatableTypeVar{&table, &metatable})};
|
||||
CHECK_EQ("{ @metatable { }, { } }", toString(&mtv));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "named_metatable")
|
||||
{
|
||||
TypeVar table{TypeVariant(TableTypeVar())};
|
||||
TypeVar metatable{TypeVariant(TableTypeVar())};
|
||||
TypeVar mtv{TypeVariant(MetatableTypeVar{&table, &metatable, "NamedMetatable"})};
|
||||
CHECK_EQ("NamedMetatable", toString(&mtv));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "named_metatable_toStringNamedFunction")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function createTbl(): NamedMetatable
|
||||
return setmetatable({}, {})
|
||||
end
|
||||
type NamedMetatable = typeof(createTbl())
|
||||
)");
|
||||
|
||||
TypeId ty = requireType("createTbl");
|
||||
const FunctionTypeVar* ftv = get<FunctionTypeVar>(follow(ty));
|
||||
REQUIRE(ftv);
|
||||
CHECK_EQ("createTbl(): NamedMetatable", toStringNamedFunction("createTbl", *ftv));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "exhaustive_toString_of_cyclic_table")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
@ -468,7 +499,7 @@ local function target(callback: nil) return callback(4, "hello") end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ("(nil) -> (*unknown*)", toString(requireType("target")));
|
||||
CHECK_EQ("(nil) -> (<error-type>)", toString(requireType("target")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "toStringGenericPack")
|
||||
|
@ -583,7 +583,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_error_expr")
|
||||
auto names = AstNameTable{allocator};
|
||||
ParseResult parseResult = Parser::parse(code.data(), code.size(), names, allocator, {});
|
||||
|
||||
CHECK_EQ("local a = (error-expr: f.%error-id%)-(error-expr)", transpileWithTypes(*parseResult.root));
|
||||
CHECK_EQ("local a = (error-expr: f:%error-id%)-(error-expr)", transpileWithTypes(*parseResult.root));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_error_stat")
|
||||
|
@ -94,7 +94,7 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_error")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ("*unknown*", toString(requireType("a")));
|
||||
CHECK_EQ("<error-type>", toString(requireType("a")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_error2")
|
||||
@ -110,7 +110,7 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_error2")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ("*unknown*", toString(requireType("a")));
|
||||
CHECK_EQ("<error-type>", toString(requireType("a")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "length_of_error_type_does_not_produce_an_error")
|
||||
@ -225,7 +225,7 @@ TEST_CASE_FIXTURE(Fixture, "calling_error_type_yields_error")
|
||||
|
||||
CHECK_EQ("unknown", err->name);
|
||||
|
||||
CHECK_EQ("*unknown*", toString(requireType("a")));
|
||||
CHECK_EQ("<error-type>", toString(requireType("a")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "chain_calling_error_type_yields_error")
|
||||
@ -234,7 +234,7 @@ TEST_CASE_FIXTURE(Fixture, "chain_calling_error_type_yields_error")
|
||||
local a = Utility.Create "Foo" {}
|
||||
)");
|
||||
|
||||
CHECK_EQ("*unknown*", toString(requireType("a")));
|
||||
CHECK_EQ("<error-type>", toString(requireType("a")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "replace_every_free_type_when_unifying_a_complex_function_with_any")
|
||||
|
@ -925,7 +925,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_returns_false_and_string_iff_it_knows
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ("(nil) -> nil", toString(requireType("f")));
|
||||
CHECK_EQ("(nil) -> (never, ...never)", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_is_generic")
|
||||
@ -952,7 +952,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_is_generic")
|
||||
CHECK_EQ("number", toString(requireType("a")));
|
||||
CHECK_EQ("string", toString(requireType("b")));
|
||||
CHECK_EQ("boolean", toString(requireType("c")));
|
||||
CHECK_EQ("*unknown*", toString(requireType("d")));
|
||||
CHECK_EQ("<error-type>", toString(requireType("d")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "set_metatable_needs_arguments")
|
||||
@ -965,8 +965,8 @@ a:b()
|
||||
a:b({})
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK_EQ(result.errors[0], (TypeError{Location{{2, 0}, {2, 5}}, CountMismatch{2, 0}}));
|
||||
CHECK_EQ(result.errors[1], (TypeError{Location{{3, 0}, {3, 5}}, CountMismatch{2, 1}}));
|
||||
CHECK_EQ(toString(result.errors[0]), "Argument count mismatch. Function expects 2 arguments, but none are specified");
|
||||
CHECK_EQ(toString(result.errors[1]), "Argument count mismatch. Function expects 2 arguments, but only 1 is specified");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "typeof_unresolved_function")
|
||||
@ -1008,4 +1008,139 @@ end
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types")
|
||||
{
|
||||
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
|
||||
CheckResult result = check(R"END(
|
||||
local a, b, c = string.gmatch("This is a string", "(.()(%a+))")()
|
||||
)END");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(toString(requireType("a")), "string");
|
||||
CHECK_EQ(toString(requireType("b")), "number");
|
||||
CHECK_EQ(toString(requireType("c")), "string");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types2")
|
||||
{
|
||||
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
|
||||
CheckResult result = check(R"END(
|
||||
local a, b, c = ("This is a string"):gmatch("(.()(%a+))")()
|
||||
)END");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(toString(requireType("a")), "string");
|
||||
CHECK_EQ(toString(requireType("b")), "number");
|
||||
CHECK_EQ(toString(requireType("c")), "string");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture")
|
||||
{
|
||||
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
|
||||
CheckResult result = check(R"END(
|
||||
local a, b, c, d = string.gmatch("T(his)() is a string", ".")()
|
||||
)END");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
|
||||
REQUIRE(acm);
|
||||
CHECK_EQ(acm->context, CountMismatch::Result);
|
||||
CHECK_EQ(acm->expected, 1);
|
||||
CHECK_EQ(acm->actual, 4);
|
||||
|
||||
CHECK_EQ(toString(requireType("a")), "string");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens")
|
||||
{
|
||||
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
|
||||
CheckResult result = check(R"END(
|
||||
local a, b, c, d = string.gmatch("T(his) is a string", "((.)%b()())")()
|
||||
)END");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
|
||||
REQUIRE(acm);
|
||||
CHECK_EQ(acm->context, CountMismatch::Result);
|
||||
CHECK_EQ(acm->expected, 3);
|
||||
CHECK_EQ(acm->actual, 4);
|
||||
|
||||
CHECK_EQ(toString(requireType("a")), "string");
|
||||
CHECK_EQ(toString(requireType("b")), "string");
|
||||
CHECK_EQ(toString(requireType("c")), "number");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_parens_in_sets_are_ignored")
|
||||
{
|
||||
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
|
||||
CheckResult result = check(R"END(
|
||||
local a, b, c = string.gmatch("T(his)() is a string", "(T[()])()")()
|
||||
)END");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
|
||||
REQUIRE(acm);
|
||||
CHECK_EQ(acm->context, CountMismatch::Result);
|
||||
CHECK_EQ(acm->expected, 2);
|
||||
CHECK_EQ(acm->actual, 3);
|
||||
|
||||
CHECK_EQ(toString(requireType("a")), "string");
|
||||
CHECK_EQ(toString(requireType("b")), "number");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_set_containing_lbracket")
|
||||
{
|
||||
ScopedFastFlag sffs{"LuauDeduceGmatchReturnTypes", true};
|
||||
CheckResult result = check(R"END(
|
||||
local a, b = string.gmatch("[[[", "()([[])")()
|
||||
)END");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(toString(requireType("a")), "number");
|
||||
CHECK_EQ(toString(requireType("b")), "string");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_leading_end_bracket_is_part_of_set")
|
||||
{
|
||||
CheckResult result = check(R"END(
|
||||
-- An immediate right-bracket following a left-bracket is included within the set;
|
||||
-- thus, '[]]'' is the set containing ']', and '[]' is an invalid set missing an enclosing
|
||||
-- right-bracket. We detect an invalid set in this case and fall back to to default gmatch
|
||||
-- typing.
|
||||
local foo = string.gmatch("T[hi%]s]]]() is a string", "([]s)")
|
||||
)END");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(toString(requireType("foo")), "() -> (...string)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_invalid_pattern_fallback_to_builtin")
|
||||
{
|
||||
CheckResult result = check(R"END(
|
||||
local foo = string.gmatch("T(his)() is a string", ")")
|
||||
)END");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(toString(requireType("foo")), "() -> (...string)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_invalid_pattern_fallback_to_builtin2")
|
||||
{
|
||||
CheckResult result = check(R"END(
|
||||
local foo = string.gmatch("T(his)() is a string", "[")
|
||||
)END");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(toString(requireType("foo")), "() -> (...string)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -916,13 +916,13 @@ TEST_CASE_FIXTURE(Fixture, "function_cast_error_uses_correct_language")
|
||||
REQUIRE(tm1);
|
||||
|
||||
CHECK_EQ("(string) -> number", toString(tm1->wantedType));
|
||||
CHECK_EQ("(string, *unknown*) -> number", toString(tm1->givenType));
|
||||
CHECK_EQ("(string, <error-type>) -> number", toString(tm1->givenType));
|
||||
|
||||
auto tm2 = get<TypeMismatch>(result.errors[1]);
|
||||
REQUIRE(tm2);
|
||||
|
||||
CHECK_EQ("(number, number) -> (number, number)", toString(tm2->wantedType));
|
||||
CHECK_EQ("(string, *unknown*) -> number", toString(tm2->givenType));
|
||||
CHECK_EQ("(string, <error-type>) -> number", toString(tm2->givenType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "no_lossy_function_type")
|
||||
@ -1535,7 +1535,7 @@ function t:b() return 2 end -- not OK
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(R"(Type '(*unknown*) -> 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]));
|
||||
@ -1692,4 +1692,52 @@ TEST_CASE_FIXTURE(Fixture, "call_o_with_another_argument_after_foo_was_quantifie
|
||||
// TODO: check the normalized type of f
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "free_is_not_bound_to_unknown")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function foo(f: (unknown) -> (), x)
|
||||
f(x)
|
||||
end
|
||||
)");
|
||||
|
||||
CHECK_EQ("<a>((unknown) -> (), a) -> ()", toString(requireType("foo")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_call_site")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local t = {}
|
||||
|
||||
function t.f(x)
|
||||
return x
|
||||
end
|
||||
|
||||
t.__index = t
|
||||
|
||||
function g(s)
|
||||
local q = s.p and s.p.q or nil
|
||||
return q and t.f(q) or nil
|
||||
end
|
||||
|
||||
local f = t.f
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("<a>(a) -> a", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "dont_mutate_the_underlying_head_of_typepack_when_calling_with_self")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local t = {}
|
||||
function t:m(x) end
|
||||
function f(): never return 5 :: never end
|
||||
t:m(f())
|
||||
t:m(f())
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -9,6 +9,8 @@
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauCheckGenericHOFTypes)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
TEST_SUITE_BEGIN("GenericsTests");
|
||||
@ -1001,7 +1003,7 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying")
|
||||
|
||||
std::optional<TypeFun> t0 = getMainModule()->getModuleScope()->lookupType("t0");
|
||||
REQUIRE(t0);
|
||||
CHECK_EQ("*unknown*", toString(t0->type));
|
||||
CHECK_EQ("<error-type>", toString(t0->type));
|
||||
|
||||
auto it = std::find_if(result.errors.begin(), result.errors.end(), [](TypeError& err) {
|
||||
return get<OccursCheckFailed>(err);
|
||||
@ -1095,10 +1097,18 @@ local b = sumrec(sum) -- ok
|
||||
local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not inferred
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ("Type '(a, b, (a, b) -> (c...)) -> (c...)' could not be converted into '<a>(a, a, (a, a) -> a) -> a'; different number of generic type "
|
||||
"parameters",
|
||||
toString(result.errors[0]));
|
||||
if (FFlag::LuauCheckGenericHOFTypes)
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ(
|
||||
"Type '(a, b, (a, b) -> (c...)) -> (c...)' could not be converted into '<a>(a, a, (a, a) -> a) -> a'; different number of generic type "
|
||||
"parameters",
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "substitution_with_bound_table")
|
||||
@ -1185,4 +1195,23 @@ TEST_CASE_FIXTURE(Fixture, "quantify_functions_even_if_they_have_an_explicit_gen
|
||||
CHECK("<X, a...>((X) -> (a...), X) -> (a...)" == toString(requireType("foo")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "do_not_always_instantiate_generic_intersection_types")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauMaybeGenericIntersectionTypes", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
type Array<T> = { [number]: T }
|
||||
|
||||
type Array_Statics = {
|
||||
new: <T>() -> Array<T>,
|
||||
}
|
||||
|
||||
local _Arr : Array<any> & Array_Statics = {} :: Array_Statics
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -142,7 +142,7 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_error")
|
||||
CHECK_EQ(2, result.errors.size());
|
||||
|
||||
TypeId p = requireType("p");
|
||||
CHECK_EQ("*unknown*", toString(p));
|
||||
CHECK_EQ("<error-type>", toString(p));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_non_function")
|
||||
|
@ -143,7 +143,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "require_module_that_does_not_export")
|
||||
|
||||
auto hootyType = requireType(bModule, "Hooty");
|
||||
|
||||
CHECK_EQ("*unknown*", toString(hootyType));
|
||||
CHECK_EQ("<error-type>", toString(hootyType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "warn_if_you_try_to_require_a_non_modulescript")
|
||||
@ -244,7 +244,7 @@ local ModuleA = require(game.A)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
std::optional<TypeId> oty = requireType("ModuleA");
|
||||
CHECK_EQ("*unknown*", toString(*oty));
|
||||
CHECK_EQ("<error-type>", toString(*oty));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_modify_imported_types")
|
||||
@ -302,6 +302,30 @@ type Rename = typeof(x.x)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_modify_imported_types_4")
|
||||
{
|
||||
fileResolver.source["game/A"] = R"(
|
||||
export type Array<T> = {T}
|
||||
local arrayops = {}
|
||||
function arrayops.foo(x: Array<any>) end
|
||||
return arrayops
|
||||
)";
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local arrayops = require(game.A)
|
||||
|
||||
local tbl = {}
|
||||
tbl.a = 2
|
||||
function tbl:foo(b: number, c: number)
|
||||
-- introduce BoundTypeVar to imported type
|
||||
arrayops.foo(self._regions)
|
||||
end
|
||||
type Table = typeof(tbl)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "module_type_conflict")
|
||||
{
|
||||
fileResolver.source["game/A"] = R"(
|
||||
@ -363,4 +387,21 @@ caused by:
|
||||
Property 'x' is not compatible. Type 'number' could not be converted into 'string')");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "constrained_anyification_clone_immutable_types")
|
||||
{
|
||||
ScopedFastFlag luauAnyificationMustClone{"LuauAnyificationMustClone", true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
return function(...) end
|
||||
)";
|
||||
|
||||
fileResolver.source["game/B"] = R"(
|
||||
local l0 = require(game.A)
|
||||
return l0
|
||||
)";
|
||||
|
||||
CheckResult result = frontend.check("game/B");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -871,4 +871,26 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "equality_operations_succeed_if_any_union_bra
|
||||
CHECK(toString(result2.errors[0]) == "Types Foo and Bar cannot be compared with == because they do not have the same metatable");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "expected_types_through_binary_and")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauBinaryNeedsExpectedTypesToo", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x: "a" | "b" | boolean = math.random() > 0.5 and "a"
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "expected_types_through_binary_or")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauBinaryNeedsExpectedTypesToo", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x: "a" | "b" | boolean = math.random() > 0.5 or "b"
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -47,7 +47,7 @@ TEST_CASE_FIXTURE(Fixture, "string_index")
|
||||
REQUIRE(nat);
|
||||
CHECK_EQ("string", toString(nat->ty));
|
||||
|
||||
CHECK_EQ("*unknown*", toString(requireType("t")));
|
||||
CHECK_EQ("<error-type>", toString(requireType("t")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "string_method")
|
||||
|
@ -225,7 +225,7 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_x_not_equal_to_nil")
|
||||
CHECK_EQ("{| x: nil, y: nil |} | {| x: string, y: number |}", toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "bail_early_if_unification_is_too_complicated" * doctest::timeout(0.5))
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "bail_early_if_unification_is_too_complicated" * doctest::timeout(0.5))
|
||||
{
|
||||
ScopedFastInt sffi{"LuauTarjanChildLimit", 1};
|
||||
ScopedFastInt sffi2{"LuauTypeInferIterationLimit", 1};
|
||||
@ -499,6 +499,17 @@ TEST_CASE_FIXTURE(Fixture, "constrained_is_level_dependent")
|
||||
CHECK_EQ("<a...>(t1) -> {| [t1]: boolean |} where t1 = t2 ; t2 = {+ m1: (t1) -> (a...), m2: (t2) -> (b...) +}", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "free_is_not_bound_to_any")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function foo(f: (any) -> (), x)
|
||||
f(x)
|
||||
end
|
||||
)");
|
||||
|
||||
CHECK_EQ("((any) -> (), any) -> ()", toString(requireType("foo")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "greedy_inference_with_shared_self_triggers_function_with_no_returns")
|
||||
{
|
||||
ScopedFastFlag sff{"DebugLuauSharedSelf", true};
|
||||
@ -518,7 +529,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "greedy_inference_with_shared_self_triggers_f
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ("Not all codepaths in this function return '{ @metatable T, {| |} }, a...'.", toString(result.errors[0]));
|
||||
CHECK_EQ("Not all codepaths in this function return 'self, a...'.", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -272,8 +272,8 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_only_look_up_types_from_global_scope")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("*unknown*", toString(requireTypeAtPosition({8, 44})));
|
||||
CHECK_EQ("*unknown*", toString(requireTypeAtPosition({9, 38})));
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({8, 44})));
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({9, 38})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "call_a_more_specific_function_using_typeguard")
|
||||
@ -526,7 +526,7 @@ TEST_CASE_FIXTURE(Fixture, "type_narrow_to_vector")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("*unknown*", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("<error-type>", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "nonoptional_type_can_narrow_to_nil_if_sense_is_true")
|
||||
@ -651,7 +651,7 @@ TEST_CASE_FIXTURE(Fixture, "type_guard_can_filter_for_overloaded_function")
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_warns_on_no_overlapping_types_only_when_sense_is_true")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_narrowed_into_nothingness")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(t: {x: number})
|
||||
@ -666,7 +666,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_warns_on_no_overlapping_types_onl
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("*unknown*", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "not_a_or_not_b")
|
||||
@ -1074,7 +1074,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_free_table_to_vector")
|
||||
|
||||
CHECK_EQ("Vector3", toString(requireTypeAtPosition({5, 28}))); // type(vec) == "vector"
|
||||
|
||||
CHECK_EQ("*unknown*", toString(requireTypeAtPosition({7, 28}))); // typeof(vec) == "Instance"
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({7, 28}))); // typeof(vec) == "Instance"
|
||||
|
||||
CHECK_EQ("{+ X: a, Y: b, Z: c +}", toString(requireTypeAtPosition({9, 28}))); // type(vec) ~= "vector" and typeof(vec) ~= "Instance"
|
||||
}
|
||||
@ -1206,6 +1206,24 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_doesnt_leak_to_elseif")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknowns")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: unknown)
|
||||
if type(x) == "string" then
|
||||
local foo = x
|
||||
else
|
||||
local bar = x
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("unknown", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "falsiness_of_TruthyPredicate_narrows_into_nil")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauFalsyPredicateReturnsNilInstead", true};
|
||||
@ -1227,4 +1245,19 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "falsiness_of_TruthyPredicate_narrows_into_ni
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({6, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "what_nonsensical_condition")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x)
|
||||
if type(x) == "string" and type(x) == "number" then
|
||||
local foo = x
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -3070,4 +3070,18 @@ TEST_CASE_FIXTURE(Fixture, "quantify_even_that_table_was_never_exported_at_all")
|
||||
CHECK_EQ("{| m: <a, b>({+ x: a, y: b +}) -> a, n: <a, b>({+ x: a, y: b +}) -> b |}", toString(requireType("T"), opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "leaking_bad_metatable_errors")
|
||||
{
|
||||
ScopedFastFlag luauIndexSilenceErrors{"LuauIndexSilenceErrors", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a = setmetatable({}, 1)
|
||||
local b = a.x
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK_EQ("Metatable was not a table", toString(result.errors[0]));
|
||||
CHECK_EQ("Type 'a' does not have key 'x'", toString(result.errors[1]));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -238,10 +238,10 @@ TEST_CASE_FIXTURE(Fixture, "type_errors_infer_types")
|
||||
// TODO: Should we assert anything about these tests when DCR is being used?
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("*unknown*", toString(requireType("c")));
|
||||
CHECK_EQ("*unknown*", toString(requireType("d")));
|
||||
CHECK_EQ("*unknown*", toString(requireType("e")));
|
||||
CHECK_EQ("*unknown*", toString(requireType("f")));
|
||||
CHECK_EQ("<error-type>", toString(requireType("c")));
|
||||
CHECK_EQ("<error-type>", toString(requireType("d")));
|
||||
CHECK_EQ("<error-type>", toString(requireType("e")));
|
||||
CHECK_EQ("<error-type>", toString(requireType("f")));
|
||||
}
|
||||
}
|
||||
|
||||
@ -622,7 +622,7 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_isoptional")
|
||||
|
||||
std::optional<TypeFun> t0 = getMainModule()->getModuleScope()->lookupType("t0");
|
||||
REQUIRE(t0);
|
||||
CHECK_EQ("*unknown*", toString(t0->type));
|
||||
CHECK_EQ("<error-type>", toString(t0->type));
|
||||
|
||||
auto it = std::find_if(result.errors.begin(), result.errors.end(), [](TypeError& err) {
|
||||
return get<OccursCheckFailed>(err);
|
||||
@ -1003,4 +1003,27 @@ TEST_CASE_FIXTURE(Fixture, "do_not_bind_a_free_table_to_a_union_containing_that_
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "types stored in astResolvedTypes")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type alias = typeof("hello")
|
||||
local function foo(param: alias)
|
||||
end
|
||||
)");
|
||||
|
||||
auto node = findNodeAtPosition(*getMainSourceModule(), {2, 16});
|
||||
auto ty = lookupType("alias");
|
||||
REQUIRE(node);
|
||||
REQUIRE(node->is<AstExprFunction>());
|
||||
REQUIRE(ty);
|
||||
|
||||
auto func = node->as<AstExprFunction>();
|
||||
REQUIRE(func->args.size == 1);
|
||||
|
||||
auto arg = *func->args.begin();
|
||||
auto annotation = arg->annotation;
|
||||
|
||||
CHECK_EQ(*getMainModule()->astResolvedTypes.find(annotation), *ty);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -121,7 +121,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "members_of_failed_typepack_unification_are_u
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ("a", toString(requireType("a")));
|
||||
CHECK_EQ("*unknown*", toString(requireType("b")));
|
||||
CHECK_EQ("<error-type>", toString(requireType("b")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "result_of_failed_typepack_unification_is_constrained")
|
||||
@ -136,7 +136,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "result_of_failed_typepack_unification_is_con
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ("a", toString(requireType("a")));
|
||||
CHECK_EQ("*unknown*", toString(requireType("b")));
|
||||
CHECK_EQ("<error-type>", toString(requireType("b")));
|
||||
CHECK_EQ("number", toString(requireType("c")));
|
||||
}
|
||||
|
||||
|
@ -199,7 +199,7 @@ TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_missing_property")
|
||||
CHECK_EQ(mup->missing[0], *bTy);
|
||||
CHECK_EQ(mup->key, "x");
|
||||
|
||||
CHECK_EQ("*unknown*", toString(requireType("r")));
|
||||
CHECK_EQ("<error-type>", toString(requireType("r")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_one_property_of_type_any")
|
||||
|
280
tests/TypeInfer.unknownnever.test.cpp
Normal file
280
tests/TypeInfer.unknownnever.test.cpp
Normal file
@ -0,0 +1,280 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "Fixture.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferUnknownNever");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "string_subtype_and_unknown_supertype")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string)
|
||||
local foo: unknown = x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unknown_subtype_and_string_supertype")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: unknown)
|
||||
local foo: string = x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unknown_is_reflexive")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: unknown)
|
||||
local foo: unknown = x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "string_subtype_and_never_supertype")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string)
|
||||
local foo: never = x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "never_subtype_and_string_supertype")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: never)
|
||||
local foo: string = x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "never_is_reflexive")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: never)
|
||||
local foo: never = x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unknown_is_optional_because_it_too_encompasses_nil")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local t: {x: unknown} = {}
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_with_prop_of_type_never_is_uninhabitable")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local t: {x: never} = {}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_with_prop_of_type_never_is_also_reflexive")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local t: {x: never} = {x = 5 :: never}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "array_like_table_of_never_is_inhabitable")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local t: {never} = {}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f() return "foo", 5 :: never end
|
||||
|
||||
local x, y, z = f()
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("never", toString(requireType("x")));
|
||||
CHECK_EQ("never", toString(requireType("y")));
|
||||
CHECK_EQ("never", toString(requireType("z")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable2")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(): (string, never) return "", 5 :: never end
|
||||
local function g(): (never, string) return 5 :: never, "" end
|
||||
|
||||
local x1, x2 = f()
|
||||
local y1, y2 = g()
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("never", toString(requireType("x1")));
|
||||
CHECK_EQ("never", toString(requireType("x2")));
|
||||
CHECK_EQ("never", toString(requireType("y1")));
|
||||
CHECK_EQ("never", toString(requireType("y2")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_never")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x: never = 5 :: never
|
||||
local z = x.y
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("never", toString(requireType("z")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "call_never")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local f: never = 5 :: never
|
||||
local x, y, z = f()
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("never", toString(requireType("x")));
|
||||
CHECK_EQ("never", toString(requireType("y")));
|
||||
CHECK_EQ("never", toString(requireType("z")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "assign_to_local_which_is_never")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local t: never
|
||||
t = 3
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "assign_to_global_which_is_never")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
--!nonstrict
|
||||
t = 5 :: never
|
||||
t = ""
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "assign_to_prop_which_is_never")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local t: never
|
||||
t.x = 5
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "assign_to_subscript_which_is_never")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local t: never
|
||||
t[5] = 7
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "assign_to_subscript_which_is_never")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
for i, v in (5 :: never) do
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "pick_never_from_variadic_type_pack")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(...: never)
|
||||
local x, y = (...)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_union_of_tables_for_properties_that_is_never")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type Disjoint = {foo: never, bar: unknown, tag: "ok"} | {foo: never, baz: unknown, tag: "err"}
|
||||
local disjoint: Disjoint = {foo = 5 :: never, bar = true, tag = "ok"}
|
||||
local foo = disjoint.foo
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("never", toString(requireType("foo")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_union_of_tables_for_properties_that_is_sorta_never")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type Disjoint = {foo: string, bar: unknown, tag: "ok"} | {foo: never, baz: unknown, tag: "err"}
|
||||
local disjoint: Disjoint = {foo = 5 :: never, bar = true, tag = "ok"}
|
||||
local foo = disjoint.foo
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("string", toString(requireType("foo")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unary_minus_of_never")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x = -(5 :: never)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("never", toString(requireType("x")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "length_of_never")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x = #({} :: never)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("never", toString(requireType("x")));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
@ -199,8 +199,6 @@ TEST_CASE_FIXTURE(TypePackFixture, "std_distance")
|
||||
|
||||
TEST_CASE("content_reassignment")
|
||||
{
|
||||
ScopedFastFlag luauNonCopyableTypeVarFields{"LuauNonCopyableTypeVarFields", true};
|
||||
|
||||
TypePackVar myError{Unifiable::Error{}, /*presistent*/ true};
|
||||
|
||||
TypeArena arena;
|
||||
|
@ -418,8 +418,6 @@ TEST_CASE("proof_that_isBoolean_uses_all_of")
|
||||
|
||||
TEST_CASE("content_reassignment")
|
||||
{
|
||||
ScopedFastFlag luauNonCopyableTypeVarFields{"LuauNonCopyableTypeVarFields", true};
|
||||
|
||||
TypeVar myAny{AnyTypeVar{}, /*presistent*/ true};
|
||||
myAny.normal = true;
|
||||
myAny.documentationSymbol = "@global/any";
|
||||
|
@ -101,4 +101,20 @@ if vector_size == 4 then
|
||||
assert(vector(1, 2, 3, 4).W == 4)
|
||||
end
|
||||
|
||||
-- negative zero should hash the same as zero
|
||||
-- note: our earlier test only really checks the low hash bit, so in absence of perfect avalanche it's insufficient
|
||||
do
|
||||
local larget = {}
|
||||
for i = 1, 2^14 do
|
||||
larget[vector(0, 0, i)] = true
|
||||
end
|
||||
|
||||
larget[vector(0, 0, 0)] = 42
|
||||
|
||||
assert(larget[vector(0, 0, 0)] == 42)
|
||||
assert(larget[vector(0, 0, -0)] == 42)
|
||||
assert(larget[vector(0, -0, 0)] == 42)
|
||||
assert(larget[vector(-0, 0, 0)] == 42)
|
||||
end
|
||||
|
||||
return 'OK'
|
||||
|
@ -137,16 +137,28 @@
|
||||
<DisplayString>{data,s}</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="::CallInfo">
|
||||
<Intrinsic Name="cl" Category="Property" Expression="func->value.gc->cl"/>
|
||||
<Intrinsic Name="isC" Category="Property" Expression="cl().isC"/>
|
||||
<Intrinsic Name="proto" Category="Property" Expression="cl().l.p"/>
|
||||
<Intrinsic Name="pcRel" Category="Property" Expression="savedpc ? savedpc - proto()->code - 1 : 0"/>
|
||||
<Intrinsic Name="line" Category="Property" Expression="proto()->abslineinfo[pcRel() >> proto()->linegaplog2] + proto()->lineinfo[pcRel()]"/>
|
||||
|
||||
<!-- Special frames -->
|
||||
<DisplayString Condition="!func">empty</DisplayString>
|
||||
<DisplayString Condition="func->tt != lua_Type::LUA_TFUNCTION">none</DisplayString>
|
||||
|
||||
<!-- Lua functions-->
|
||||
<DisplayString Condition="!isC() && proto()->debugname">{proto()->source->data,sb}:{line()} function {proto()->debugname->data,sb}()</DisplayString>
|
||||
<DisplayString Condition="!isC()">{proto()->source->data,sb}:{line()} function()</DisplayString>
|
||||
|
||||
<!-- C functions-->
|
||||
<DisplayString Condition="isC() && cl().c.debugname">=[C] function {cl().c.debugname,sb}() {cl().c.f,na}</DisplayString>
|
||||
<DisplayString Condition="isC()">=[C] {cl().c.f,na}</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name ="::lua_State">
|
||||
<DisplayString Condition="ci->func->value.gc->cl.isC">
|
||||
{ci->func->value.gc->cl.c.f,na}
|
||||
</DisplayString>
|
||||
<DisplayString Condition="!ci->func->value.gc->cl.isC && ci->func->value.gc->cl.l.p->debugname" Optional="true">
|
||||
{ci->func->value.gc->cl.l.p->source->data,sb}:{ci->func->value.gc->cl.l.p->linedefined,d} {ci->func->value.gc->cl.l.p->debugname->data,sb}
|
||||
</DisplayString>
|
||||
<DisplayString Condition="!ci->func->value.gc->cl.isC" Optional="true">
|
||||
{ci->func->value.gc->cl.l.p->source->data,sb}:{ci->func->value.gc->cl.l.p->linedefined,d}
|
||||
</DisplayString>
|
||||
<DisplayString Condition="ci">{ci,na}</DisplayString>
|
||||
<DisplayString>thread</DisplayString>
|
||||
<Expand>
|
||||
<Synthetic Name="[call stack]">
|
||||
@ -156,7 +168,7 @@
|
||||
<Size>ci-base_ci</Size>
|
||||
<!-- the +1 is omitted here to avoid some issues with a blank call -->
|
||||
<ValueNode>
|
||||
base_ci[ci-base_ci - $i].func->value.gc->cl,view(short)
|
||||
base_ci[ci-base_ci - $i]
|
||||
</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
|
Loading…
Reference in New Issue
Block a user