mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-12 13:00:38 +00:00
Sync to upstream/release/535 (#584)
This commit is contained in:
parent
dbcd5fb28e
commit
506d971421
54 changed files with 1867 additions and 593 deletions
|
@ -63,6 +63,7 @@ private:
|
||||||
AstLocal* local = nullptr;
|
AstLocal* local = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(const SourceModule& source, Position pos);
|
||||||
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos);
|
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos);
|
||||||
AstNode* findNodeAtPosition(const SourceModule& source, Position pos);
|
AstNode* findNodeAtPosition(const SourceModule& source, Position pos);
|
||||||
AstExpr* findExprAtPosition(const SourceModule& source, Position pos);
|
AstExpr* findExprAtPosition(const SourceModule& source, Position pos);
|
||||||
|
|
|
@ -153,7 +153,7 @@ struct TypeChecker
|
||||||
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
|
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
|
||||||
TypeId checkBinaryOperation(
|
TypeId checkBinaryOperation(
|
||||||
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
|
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 AstExprTypeAssertion& expr);
|
||||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprError& 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);
|
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);
|
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 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::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,
|
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,
|
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);
|
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);
|
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> findMetatableEntry(TypeId type, std::string entry, const Location& location, bool addErrors);
|
||||||
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location);
|
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> 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.
|
// Reduces the union to its simplest possible shape.
|
||||||
// (A | B) | B | C yields A | B | C
|
// (A | B) | B | C yields A | B | C
|
||||||
|
@ -316,11 +321,12 @@ private:
|
||||||
|
|
||||||
TypeIdPredicate mkTruthyPredicate(bool sense);
|
TypeIdPredicate mkTruthyPredicate(bool sense);
|
||||||
|
|
||||||
// Returns nullopt if the predicate filters down the TypeId to 0 options.
|
// TODO: Return TypeId only.
|
||||||
std::optional<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);
|
std::optional<TypeId> filterMapImpl(TypeId type, TypeIdPredicate predicate);
|
||||||
|
std::pair<std::optional<TypeId>, bool> filterMap(TypeId type, TypeIdPredicate predicate);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::optional<TypeId> pickTypesFromSense(TypeId type, bool sense);
|
std::pair<std::optional<TypeId>, bool> pickTypesFromSense(TypeId type, bool sense);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TypeId unionOfTypes(TypeId a, TypeId b, const Location& location, bool unifyFreeTypes = true);
|
TypeId unionOfTypes(TypeId a, TypeId b, const Location& location, bool unifyFreeTypes = true);
|
||||||
|
@ -413,8 +419,12 @@ public:
|
||||||
const TypeId booleanType;
|
const TypeId booleanType;
|
||||||
const TypeId threadType;
|
const TypeId threadType;
|
||||||
const TypeId anyType;
|
const TypeId anyType;
|
||||||
|
const TypeId unknownType;
|
||||||
|
const TypeId neverType;
|
||||||
|
|
||||||
const TypePackId anyTypePack;
|
const TypePackId anyTypePack;
|
||||||
|
const TypePackId neverTypePack;
|
||||||
|
const TypePackId uninhabitableTypePack;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int checkRecursionCount = 0;
|
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);
|
||||||
bool isVariadic(TypePackId tp, const TxnLog& log);
|
bool isVariadic(TypePackId tp, const TxnLog& log);
|
||||||
|
|
||||||
|
bool containsNever(TypePackId tp);
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -460,10 +460,18 @@ struct LazyTypeVar
|
||||||
std::function<TypeId()> thunk;
|
std::function<TypeId()> thunk;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct UnknownTypeVar
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NeverTypeVar
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
using ErrorTypeVar = Unifiable::Error;
|
using ErrorTypeVar = Unifiable::Error;
|
||||||
|
|
||||||
using TypeVariant = Unifiable::Variant<TypeId, PrimitiveTypeVar, ConstrainedTypeVar, BlockedTypeVar, SingletonTypeVar, FunctionTypeVar, TableTypeVar,
|
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
|
struct TypeVar final
|
||||||
{
|
{
|
||||||
|
@ -575,8 +583,12 @@ struct SingletonTypes
|
||||||
const TypeId trueType;
|
const TypeId trueType;
|
||||||
const TypeId falseType;
|
const TypeId falseType;
|
||||||
const TypeId anyType;
|
const TypeId anyType;
|
||||||
|
const TypeId unknownType;
|
||||||
|
const TypeId neverType;
|
||||||
|
|
||||||
const TypePackId anyTypePack;
|
const TypePackId anyTypePack;
|
||||||
|
const TypePackId neverTypePack;
|
||||||
|
const TypePackId uninhabitableTypePack;
|
||||||
|
|
||||||
SingletonTypes();
|
SingletonTypes();
|
||||||
~SingletonTypes();
|
~SingletonTypes();
|
||||||
|
@ -632,12 +644,30 @@ T* getMutable(TypeId tv)
|
||||||
return get_if<T>(&asMutable(tv)->ty);
|
return get_if<T>(&asMutable(tv)->ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Traverses the UnionTypeVar yielding each TypeId.
|
const std::vector<TypeId>& getTypes(const UnionTypeVar* utv);
|
||||||
* If the iterator encounters a nested UnionTypeVar, it will instead yield each TypeId within.
|
const std::vector<TypeId>& getTypes(const IntersectionTypeVar* itv);
|
||||||
*
|
const std::vector<TypeId>& getTypes(const ConstrainedTypeVar* ctv);
|
||||||
* Beware: the iterator does not currently filter for unique TypeIds. This may change in the future.
|
|
||||||
|
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 value_type = Luau::TypeId;
|
||||||
using pointer = value_type*;
|
using pointer = value_type*;
|
||||||
|
@ -645,33 +675,116 @@ struct UnionTypeVarIterator
|
||||||
using difference_type = size_t;
|
using difference_type = size_t;
|
||||||
using iterator_category = std::input_iterator_tag;
|
using iterator_category = std::input_iterator_tag;
|
||||||
|
|
||||||
explicit UnionTypeVarIterator(const UnionTypeVar* utv);
|
explicit TypeIterator(const T* t)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(t);
|
||||||
|
|
||||||
UnionTypeVarIterator& operator++();
|
const std::vector<TypeId>& types = getTypes(t);
|
||||||
UnionTypeVarIterator operator++(int);
|
if (!types.empty())
|
||||||
bool operator!=(const UnionTypeVarIterator& rhs);
|
stack.push_front({t, 0});
|
||||||
bool operator==(const UnionTypeVarIterator& rhs);
|
|
||||||
|
|
||||||
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:
|
private:
|
||||||
UnionTypeVarIterator() = default;
|
TypeIterator() = default;
|
||||||
|
|
||||||
// (UnionTypeVar* utv, size_t currentIndex)
|
// (T* t, size_t currentIndex)
|
||||||
using SavedIterInfo = std::pair<const UnionTypeVar*, size_t>;
|
using SavedIterInfo = std::pair<const T*, size_t>;
|
||||||
|
|
||||||
std::deque<SavedIterInfo> stack;
|
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 advance()
|
||||||
void descend();
|
{
|
||||||
|
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)>;
|
using TypeIdPredicate = std::function<std::optional<TypeId>(TypeId)>;
|
||||||
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);
|
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,14 @@ struct GenericTypeVarVisitor
|
||||||
{
|
{
|
||||||
return visit(ty);
|
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)
|
virtual bool visit(TypeId ty, const UnionTypeVar& utv)
|
||||||
{
|
{
|
||||||
return visit(ty);
|
return visit(ty);
|
||||||
|
|
|
@ -17,6 +17,104 @@ namespace Luau
|
||||||
namespace
|
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
|
struct FindNode : public AstVisitor
|
||||||
{
|
{
|
||||||
const Position pos;
|
const Position pos;
|
||||||
|
@ -102,6 +200,13 @@ struct FindFullAncestry final : public AstVisitor
|
||||||
|
|
||||||
} // namespace
|
} // 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)
|
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos)
|
||||||
{
|
{
|
||||||
const Position end = source.root->location.end;
|
const Position end = source.root->location.end;
|
||||||
|
@ -110,7 +215,7 @@ std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Posi
|
||||||
|
|
||||||
FindFullAncestry finder(pos, end);
|
FindFullAncestry finder(pos, end);
|
||||||
source.root->visit(&finder);
|
source.root->visit(&finder);
|
||||||
return std::move(finder.nodes);
|
return finder.nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
AstNode* findNodeAtPosition(const SourceModule& source, Position pos)
|
AstNode* findNodeAtPosition(const SourceModule& source, Position pos)
|
||||||
|
|
|
@ -21,102 +21,6 @@ static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
||||||
namespace Luau
|
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)
|
static bool alreadyHasParens(const std::vector<AstNode*>& nodes)
|
||||||
{
|
{
|
||||||
|
@ -905,7 +809,7 @@ AutocompleteEntryMap autocompleteTypeNames(const Module& module, Position positi
|
||||||
}
|
}
|
||||||
|
|
||||||
AstNode* parent = nullptr;
|
AstNode* parent = nullptr;
|
||||||
AstType* topType = nullptr;
|
AstType* topType = nullptr; // TODO: rename?
|
||||||
|
|
||||||
for (auto it = ancestry.rbegin(), e = ancestry.rend(); it != e; ++it)
|
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))
|
if (isWithinComment(sourceModule, position))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
NodeFinder finder{position, sourceModule.root};
|
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(sourceModule, position);
|
||||||
sourceModule.root->visit(&finder);
|
LUAU_ASSERT(!ancestry.empty());
|
||||||
LUAU_ASSERT(!finder.ancestry.empty());
|
AstNode* node = ancestry.back();
|
||||||
AstNode* node = finder.ancestry.back();
|
|
||||||
|
|
||||||
AstExprConstantNil dummy{Location{}};
|
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 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)
|
if (auto exprFunction = parent->as<AstExprFunction>(); exprFunction && !exprFunction->argLocation && node == exprFunction->body)
|
||||||
{
|
{
|
||||||
finder.ancestry.pop_back();
|
ancestry.pop_back();
|
||||||
|
|
||||||
node = finder.ancestry.back();
|
node = ancestry.back();
|
||||||
parent = finder.ancestry.size() >= 2 ? finder.ancestry.rbegin()[1] : &dummy;
|
parent = ancestry.size() >= 2 ? ancestry.rbegin()[1] : &dummy;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto indexName = node->as<AstExprIndexName>())
|
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;
|
PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point;
|
||||||
|
|
||||||
if (!FFlag::LuauSelfCallAutocompleteFix2 && isString(ty))
|
if (!FFlag::LuauSelfCallAutocompleteFix2 && isString(ty))
|
||||||
return {autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, finder.ancestry),
|
return {autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry),
|
||||||
finder.ancestry};
|
ancestry};
|
||||||
else
|
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>())
|
else if (auto typeReference = node->as<AstTypeReference>())
|
||||||
{
|
{
|
||||||
if (typeReference->prefix)
|
if (typeReference->prefix)
|
||||||
return {autocompleteModuleTypes(*module, position, typeReference->prefix->value), finder.ancestry};
|
return {autocompleteModuleTypes(*module, position, typeReference->prefix->value), ancestry};
|
||||||
else
|
else
|
||||||
return {autocompleteTypeNames(*module, position, finder.ancestry), finder.ancestry};
|
return {autocompleteTypeNames(*module, position, ancestry), ancestry};
|
||||||
}
|
}
|
||||||
else if (node->is<AstTypeError>())
|
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>())
|
else if (AstStatLocal* statLocal = node->as<AstStatLocal>())
|
||||||
{
|
{
|
||||||
if (statLocal->vars.size == 1 && (!statLocal->equalsSignLocation || position < statLocal->equalsSignLocation->begin))
|
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)
|
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
|
else
|
||||||
return {};
|
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->hasDo || position < statFor->doLocation.begin)
|
||||||
{
|
{
|
||||||
if (!statFor->from->is<AstExprError>() && !statFor->to->is<AstExprError>() && (!statFor->step || !statFor->step->is<AstExprError>()))
|
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) ||
|
if (statFor->from->location.containsClosed(position) || statFor->to->location.containsClosed(position) ||
|
||||||
(statFor->step && statFor->step->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 {};
|
||||||
}
|
}
|
||||||
|
|
||||||
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)))
|
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 {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {{{"in", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
|
return {{{"in", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!statForIn->hasDo || position <= statForIn->doLocation.begin)
|
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];
|
AstExpr* lastExpr = statForIn->values.data[statForIn->values.size - 1];
|
||||||
|
|
||||||
if (lastExpr->location.containsClosed(position))
|
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)
|
if (position > lastExpr->location.end)
|
||||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
|
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
|
||||||
|
|
||||||
return {}; // Not sure what this means
|
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.
|
// 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"
|
// ex "for f in f do"
|
||||||
if (!statForIn->hasDo)
|
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)
|
else if (AstStatWhile* statWhile = parent->as<AstStatWhile>(); node->is<AstStatBlock>() && statWhile)
|
||||||
{
|
{
|
||||||
if (!statWhile->hasDo && !statWhile->condition->is<AstStatError>() && position > statWhile->condition->location.end)
|
if (!statWhile->hasDo && !statWhile->condition->is<AstStatError>() && position > statWhile->condition->location.end)
|
||||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
|
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
|
||||||
|
|
||||||
if (!statWhile->hasDo || position < statWhile->doLocation.begin)
|
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)
|
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)
|
else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(ancestry); statWhile && !statWhile->hasDo)
|
||||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
|
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
|
||||||
|
|
||||||
else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->elseLocation.has_value())
|
else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->elseLocation.has_value())
|
||||||
{
|
{
|
||||||
return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}},
|
return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}},
|
||||||
finder.ancestry};
|
ancestry};
|
||||||
}
|
}
|
||||||
else if (AstStatIf* statIf = parent->as<AstStatIf>(); statIf && node->is<AstStatBlock>())
|
else if (AstStatIf* statIf = parent->as<AstStatIf>(); statIf && node->is<AstStatBlock>())
|
||||||
{
|
{
|
||||||
if (statIf->condition->is<AstExprError>())
|
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))
|
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)))
|
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>())
|
else if (AstStatRepeat* statRepeat = node->as<AstStatRepeat>(); statRepeat && statRepeat->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 (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(finder.ancestry); statRepeat)
|
else if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat)
|
||||||
return {autocompleteStatement(sourceModule, *module, finder.ancestry, position), finder.ancestry};
|
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry};
|
||||||
else if (AstExprTable* exprTable = parent->as<AstExprTable>(); exprTable && (node->is<AstExprGlobal>() || node->is<AstExprConstantString>()))
|
else if (AstExprTable* exprTable = parent->as<AstExprTable>(); exprTable && (node->is<AstExprGlobal>() || node->is<AstExprConstantString>()))
|
||||||
{
|
{
|
||||||
for (const auto& [kind, key, value] : exprTable->items)
|
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))
|
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
|
// Remove keys that are already completed
|
||||||
for (const auto& item : exprTable->items)
|
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 we know for sure that a key is being written, do not offer general expression suggestions
|
||||||
if (!key)
|
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;
|
break;
|
||||||
|
@ -1654,11 +1557,11 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (isIdentifier(node) && (parent->is<AstStatExpr>() || parent->is<AstStatError>()))
|
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>())
|
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()))
|
if (auto it = module->astExpectedTypes.find(node->asExpr()))
|
||||||
autocompleteStringSingleton(*it, false, result);
|
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))
|
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)
|
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>())
|
if (node->is<AstExprConstantNumber>())
|
||||||
|
@ -1693,9 +1596,9 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node->asExpr())
|
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())
|
else if (node->asStat())
|
||||||
return {autocompleteStatement(sourceModule, *module, finder.ancestry, position), finder.ancestry};
|
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry};
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false)
|
LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false)
|
||||||
|
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||||
|
|
||||||
/** FIXME: Many of these type definitions are not quite completely accurate.
|
/** 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");
|
addGlobalBinding(typeChecker, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau");
|
||||||
|
|
||||||
// setmetatable<MT>({ @metatable MT }, MT) -> { @metatable MT }
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
// setmetatable<T: {}, MT>(T, MT) -> { @metatable MT, T }
|
||||||
addGlobalBinding(typeChecker, "setmetatable",
|
addGlobalBinding(typeChecker, "setmetatable",
|
||||||
arena.addType(
|
arena.addType(
|
||||||
FunctionTypeVar{
|
FunctionTypeVar{
|
||||||
{genericMT},
|
{genericMT},
|
||||||
{},
|
{},
|
||||||
arena.addTypePack(TypePack{{tableMetaMT, genericMT}}),
|
arena.addTypePack(TypePack{{FFlag::LuauUnknownAndNeverType ? tabTy : tableMetaMT, genericMT}}),
|
||||||
arena.addTypePack(TypePack{{tableMetaMT}})
|
arena.addTypePack(TypePack{{tableMetaMT}})
|
||||||
}
|
}
|
||||||
), "@luau"
|
), "@luau"
|
||||||
|
@ -309,6 +310,12 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
|
||||||
{
|
{
|
||||||
auto [paramPack, _predicates] = withPredicate;
|
auto [paramPack, _predicates] = withPredicate;
|
||||||
|
|
||||||
|
if (FFlag::LuauUnknownAndNeverType)
|
||||||
|
{
|
||||||
|
if (size(paramPack) < 2 && finite(paramPack))
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
TypeArena& arena = typechecker.currentModule->internalTypes;
|
TypeArena& arena = typechecker.currentModule->internalTypes;
|
||||||
|
|
||||||
std::vector<TypeId> expectedArgs = typechecker.unTypePack(scope, paramPack, 2, expr.location);
|
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 target = follow(expectedArgs[0]);
|
||||||
TypeId mt = follow(expectedArgs[1]);
|
TypeId mt = follow(expectedArgs[1]);
|
||||||
|
|
||||||
|
if (FFlag::LuauUnknownAndNeverType)
|
||||||
|
{
|
||||||
|
typechecker.tablify(target);
|
||||||
|
typechecker.tablify(mt);
|
||||||
|
}
|
||||||
|
|
||||||
if (const auto& tab = get<TableTypeVar>(target))
|
if (const auto& tab = get<TableTypeVar>(target))
|
||||||
{
|
{
|
||||||
if (target->persistent)
|
if (target->persistent)
|
||||||
|
@ -324,7 +337,8 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
typechecker.tablify(mt);
|
if (!FFlag::LuauUnknownAndNeverType)
|
||||||
|
typechecker.tablify(mt);
|
||||||
|
|
||||||
const TableTypeVar* mtTtv = get<TableTypeVar>(mt);
|
const TableTypeVar* mtTtv = get<TableTypeVar>(mt);
|
||||||
MetatableTypeVar mtv{target, mt};
|
MetatableTypeVar mtv{target, mt};
|
||||||
|
@ -343,7 +357,10 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
|
||||||
|
|
||||||
if (FFlag::LuauSetMetaTableArgsCheck && expr.args.size < 1)
|
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)
|
if (!FFlag::LuauSetMetaTableArgsCheck || !expr.self)
|
||||||
|
@ -390,11 +407,21 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionAssert(
|
||||||
|
|
||||||
if (head.size() > 0)
|
if (head.size() > 0)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> newhead = typechecker.pickTypesFromSense(head[0], true);
|
auto [ty, ok] = typechecker.pickTypesFromSense(head[0], true);
|
||||||
if (!newhead)
|
if (FFlag::LuauUnknownAndNeverType)
|
||||||
head = {typechecker.nilType};
|
{
|
||||||
|
if (get<NeverTypeVar>(*ty))
|
||||||
|
head = {*ty};
|
||||||
|
else
|
||||||
|
head[0] = *ty;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
head[0] = *newhead;
|
{
|
||||||
|
if (!ty)
|
||||||
|
head = {typechecker.nilType};
|
||||||
|
else
|
||||||
|
head[0] = *ty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return WithPredicate<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
|
return WithPredicate<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
|
||||||
|
|
|
@ -59,6 +59,8 @@ struct TypeCloner
|
||||||
void operator()(const UnionTypeVar& t);
|
void operator()(const UnionTypeVar& t);
|
||||||
void operator()(const IntersectionTypeVar& t);
|
void operator()(const IntersectionTypeVar& t);
|
||||||
void operator()(const LazyTypeVar& t);
|
void operator()(const LazyTypeVar& t);
|
||||||
|
void operator()(const UnknownTypeVar& t);
|
||||||
|
void operator()(const NeverTypeVar& t);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TypePackCloner
|
struct TypePackCloner
|
||||||
|
@ -310,6 +312,16 @@ void TypeCloner::operator()(const LazyTypeVar& t)
|
||||||
defaultClone(t);
|
defaultClone(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TypeCloner::operator()(const UnknownTypeVar& t)
|
||||||
|
{
|
||||||
|
defaultClone(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TypeCloner::operator()(const NeverTypeVar& t)
|
||||||
|
{
|
||||||
|
defaultClone(t);
|
||||||
|
}
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
|
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
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#include "Luau/BuiltinDefinitions.h"
|
#include "Luau/BuiltinDefinitions.h"
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||||
LUAU_FASTFLAG(LuauCheckLenMT)
|
LUAU_FASTFLAG(LuauCheckLenMT)
|
||||||
|
|
||||||
namespace Luau
|
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
|
-- `assert` has a magic function attached that will give more detailed type information
|
||||||
declare function assert<T>(value: T, errorMessage: string?): T
|
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 tostring<T>(value: T): string
|
||||||
declare function tonumber<T>(value: T, radix: number?): number?
|
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 getBuiltinDefinitionSource()
|
||||||
{
|
{
|
||||||
|
|
||||||
std::string result = kBuiltinDefinitionLuaSrc;
|
std::string result = kBuiltinDefinitionLuaSrc;
|
||||||
|
|
||||||
// TODO: move this into kBuiltinDefinitionLuaSrc
|
// TODO: move this into kBuiltinDefinitionLuaSrc
|
||||||
if (FFlag::LuauCheckLenMT)
|
if (FFlag::LuauCheckLenMT)
|
||||||
result += "declare function rawlen<K, V>(obj: {[K]: V} | string): number\n";
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCopyBeforeNormalizing, false)
|
||||||
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false);
|
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeFlagIsConservative, false);
|
LUAU_FASTFLAGVARIABLE(LuauNormalizeFlagIsConservative, false);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineEqFix, false);
|
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||||
LUAU_FASTFLAG(LuauQuantifyConstrained)
|
LUAU_FASTFLAG(LuauQuantifyConstrained)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
|
@ -182,7 +182,6 @@ struct Normalize final : TypeVarVisitor
|
||||||
{
|
{
|
||||||
if (!ty->normal)
|
if (!ty->normal)
|
||||||
asMutable(ty)->normal = true;
|
asMutable(ty)->normal = true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,6 +192,20 @@ struct Normalize final : TypeVarVisitor
|
||||||
return false;
|
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
|
bool visit(TypeId ty, const ConstrainedTypeVar& ctvRef) override
|
||||||
{
|
{
|
||||||
CHECK_ITERATION_LIMIT(false);
|
CHECK_ITERATION_LIMIT(false);
|
||||||
|
@ -416,7 +429,13 @@ struct Normalize final : TypeVarVisitor
|
||||||
std::vector<TypeId> result;
|
std::vector<TypeId> result;
|
||||||
|
|
||||||
for (TypeId part : options)
|
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);
|
combineIntoUnion(result, part);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -427,7 +446,17 @@ struct Normalize final : TypeVarVisitor
|
||||||
if (auto utv = get<UnionTypeVar>(ty))
|
if (auto utv = get<UnionTypeVar>(ty))
|
||||||
{
|
{
|
||||||
for (TypeId t : utv)
|
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);
|
combineIntoUnion(result, t);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -571,8 +600,7 @@ struct Normalize final : TypeVarVisitor
|
||||||
*/
|
*/
|
||||||
TypeId combine(Replacer& replacer, TypeId a, TypeId b)
|
TypeId combine(Replacer& replacer, TypeId a, TypeId b)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauNormalizeCombineEqFix)
|
b = follow(b);
|
||||||
b = follow(b);
|
|
||||||
|
|
||||||
if (FFlag::LuauNormalizeCombineTableFix && a == b)
|
if (FFlag::LuauNormalizeCombineTableFix && a == b)
|
||||||
return a;
|
return a;
|
||||||
|
@ -592,7 +620,7 @@ struct Normalize final : TypeVarVisitor
|
||||||
}
|
}
|
||||||
else if (auto ttv = getMutable<TableTypeVar>(a))
|
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}});
|
return arena.addType(IntersectionTypeVar{{a, b}});
|
||||||
combineIntoTable(replacer, ttv, b);
|
combineIntoTable(replacer, ttv, b);
|
||||||
return a;
|
return a;
|
||||||
|
|
|
@ -8,8 +8,10 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauAnyificationMustClone, false)
|
||||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||||
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
||||||
|
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -154,7 +156,7 @@ TarjanResult Tarjan::loop()
|
||||||
if (currEdge == -1)
|
if (currEdge == -1)
|
||||||
{
|
{
|
||||||
++childCount;
|
++childCount;
|
||||||
if (childLimit > 0 && childLimit < childCount)
|
if (childLimit > 0 && (FFlag::LuauUnknownAndNeverType ? childLimit <= childCount : childLimit < childCount))
|
||||||
return TarjanResult::TooManyChildren;
|
return TarjanResult::TooManyChildren;
|
||||||
|
|
||||||
stack.push_back(index);
|
stack.push_back(index);
|
||||||
|
@ -439,6 +441,9 @@ void Substitution::replaceChildren(TypeId ty)
|
||||||
if (ignoreChildren(ty))
|
if (ignoreChildren(ty))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (FFlag::LuauAnyificationMustClone && ty->owningArena != arena)
|
||||||
|
return;
|
||||||
|
|
||||||
if (FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty))
|
if (FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty))
|
||||||
{
|
{
|
||||||
ftv->argTypes = replace(ftv->argTypes);
|
ftv->argTypes = replace(ftv->argTypes);
|
||||||
|
@ -490,6 +495,9 @@ void Substitution::replaceChildren(TypePackId tp)
|
||||||
if (ignoreChildren(tp))
|
if (ignoreChildren(tp))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (FFlag::LuauAnyificationMustClone && tp->owningArena != arena)
|
||||||
|
return;
|
||||||
|
|
||||||
if (TypePack* tpp = getMutable<TypePack>(tp))
|
if (TypePack* tpp = getMutable<TypePack>(tp))
|
||||||
{
|
{
|
||||||
for (TypeId& tv : tpp->head)
|
for (TypeId& tv : tpp->head)
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||||
|
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prefix generic typenames with gen-
|
* Prefix generic typenames with gen-
|
||||||
|
@ -841,7 +842,7 @@ struct TypeVarStringifier
|
||||||
void operator()(TypeId, const ErrorTypeVar& tv)
|
void operator()(TypeId, const ErrorTypeVar& tv)
|
||||||
{
|
{
|
||||||
state.result.error = true;
|
state.result.error = true;
|
||||||
state.emit("*unknown*");
|
state.emit(FFlag::LuauUnknownAndNeverType ? "<error-type>" : "*unknown*");
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(TypeId, const LazyTypeVar& ltv)
|
void operator()(TypeId, const LazyTypeVar& ltv)
|
||||||
|
@ -850,7 +851,17 @@ struct TypeVarStringifier
|
||||||
state.emit("lazy?");
|
state.emit("lazy?");
|
||||||
}
|
}
|
||||||
|
|
||||||
}; // namespace
|
void operator()(TypeId, const UnknownTypeVar& ttv)
|
||||||
|
{
|
||||||
|
state.emit("unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(TypeId, const NeverTypeVar& ttv)
|
||||||
|
{
|
||||||
|
state.emit("never");
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
struct TypePackStringifier
|
struct TypePackStringifier
|
||||||
{
|
{
|
||||||
|
@ -955,7 +966,7 @@ struct TypePackStringifier
|
||||||
void operator()(TypePackId, const Unifiable::Error& error)
|
void operator()(TypePackId, const Unifiable::Error& error)
|
||||||
{
|
{
|
||||||
state.result.error = true;
|
state.result.error = true;
|
||||||
state.emit("*unknown*");
|
state.emit(FFlag::LuauUnknownAndNeverType ? "<error-type>" : "*unknown*");
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(TypePackId, const VariadicTypePack& pack)
|
void operator()(TypePackId, const VariadicTypePack& pack)
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauNonCopyableTypeVarFields)
|
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -81,34 +81,10 @@ void TxnLog::concat(TxnLog rhs)
|
||||||
void TxnLog::commit()
|
void TxnLog::commit()
|
||||||
{
|
{
|
||||||
for (auto& [ty, rep] : typeVarChanges)
|
for (auto& [ty, rep] : typeVarChanges)
|
||||||
{
|
asMutable(ty)->reassign(rep.get()->pending);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& [tp, rep] : typePackChanges)
|
for (auto& [tp, rep] : typePackChanges)
|
||||||
{
|
asMutable(tp)->reassign(rep.get()->pending);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
@ -196,9 +172,7 @@ PendingType* TxnLog::queue(TypeId ty)
|
||||||
if (!pending)
|
if (!pending)
|
||||||
{
|
{
|
||||||
pending = std::make_unique<PendingType>(*ty);
|
pending = std::make_unique<PendingType>(*ty);
|
||||||
|
pending->pending.owningArena = nullptr;
|
||||||
if (FFlag::LuauNonCopyableTypeVarFields)
|
|
||||||
pending->pending.owningArena = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pending.get();
|
return pending.get();
|
||||||
|
@ -214,9 +188,7 @@ PendingTypePack* TxnLog::queue(TypePackId tp)
|
||||||
if (!pending)
|
if (!pending)
|
||||||
{
|
{
|
||||||
pending = std::make_unique<PendingTypePack>(*tp);
|
pending = std::make_unique<PendingTypePack>(*tp);
|
||||||
|
pending->pending.owningArena = nullptr;
|
||||||
if (FFlag::LuauNonCopyableTypeVarFields)
|
|
||||||
pending->pending.owningArena = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pending.get();
|
return pending.get();
|
||||||
|
@ -255,24 +227,14 @@ PendingTypePack* TxnLog::pending(TypePackId tp) const
|
||||||
PendingType* TxnLog::replace(TypeId ty, TypeVar replacement)
|
PendingType* TxnLog::replace(TypeId ty, TypeVar replacement)
|
||||||
{
|
{
|
||||||
PendingType* newTy = queue(ty);
|
PendingType* newTy = queue(ty);
|
||||||
|
newTy->pending.reassign(replacement);
|
||||||
if (FFlag::LuauNonCopyableTypeVarFields)
|
|
||||||
newTy->pending.reassign(replacement);
|
|
||||||
else
|
|
||||||
newTy->pending = replacement;
|
|
||||||
|
|
||||||
return newTy;
|
return newTy;
|
||||||
}
|
}
|
||||||
|
|
||||||
PendingTypePack* TxnLog::replace(TypePackId tp, TypePackVar replacement)
|
PendingTypePack* TxnLog::replace(TypePackId tp, TypePackVar replacement)
|
||||||
{
|
{
|
||||||
PendingTypePack* newTp = queue(tp);
|
PendingTypePack* newTp = queue(tp);
|
||||||
|
newTp->pending.reassign(replacement);
|
||||||
if (FFlag::LuauNonCopyableTypeVarFields)
|
|
||||||
newTp->pending.reassign(replacement);
|
|
||||||
else
|
|
||||||
newTp->pending = replacement;
|
|
||||||
|
|
||||||
return newTp;
|
return newTp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,7 +251,7 @@ PendingType* TxnLog::bindTable(TypeId ty, std::optional<TypeId> newBoundTo)
|
||||||
|
|
||||||
PendingType* TxnLog::changeLevel(TypeId ty, TypeLevel newLevel)
|
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);
|
PendingType* newTy = queue(ty);
|
||||||
if (FreeTypeVar* ftv = Luau::getMutable<FreeTypeVar>(newTy))
|
if (FreeTypeVar* ftv = Luau::getMutable<FreeTypeVar>(newTy))
|
||||||
|
@ -305,6 +267,11 @@ PendingType* TxnLog::changeLevel(TypeId ty, TypeLevel newLevel)
|
||||||
{
|
{
|
||||||
ftv->level = newLevel;
|
ftv->level = newLevel;
|
||||||
}
|
}
|
||||||
|
else if (ConstrainedTypeVar* ctv = Luau::getMutable<ConstrainedTypeVar>(newTy))
|
||||||
|
{
|
||||||
|
if (FFlag::LuauUnknownAndNeverType)
|
||||||
|
ctv->level = newLevel;
|
||||||
|
}
|
||||||
|
|
||||||
return newTy;
|
return newTy;
|
||||||
}
|
}
|
||||||
|
|
|
@ -335,6 +335,14 @@ public:
|
||||||
{
|
{
|
||||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("<Lazy?>"));
|
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:
|
private:
|
||||||
Allocator* allocator;
|
Allocator* allocator;
|
||||||
|
|
|
@ -31,6 +31,7 @@ LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300)
|
||||||
LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
|
LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
|
||||||
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||||
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauIndexSilenceErrors, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
|
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix2, false)
|
LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix2, false)
|
||||||
|
@ -41,10 +42,12 @@ LUAU_FASTFLAGVARIABLE(LuauReturnTypeInferenceInNonstrict, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false);
|
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAlwaysQuantify, false);
|
LUAU_FASTFLAGVARIABLE(LuauAlwaysQuantify, false);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauReportErrorsOnIndexerKeyMismatch, false)
|
LUAU_FASTFLAGVARIABLE(LuauReportErrorsOnIndexerKeyMismatch, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauUnknownAndNeverType, false)
|
||||||
LUAU_FASTFLAG(LuauQuantifyConstrained)
|
LUAU_FASTFLAG(LuauQuantifyConstrained)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFalsyPredicateReturnsNilInstead, false)
|
LUAU_FASTFLAGVARIABLE(LuauFalsyPredicateReturnsNilInstead, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNonCopyableTypeVarFields, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCheckLenMT, false)
|
LUAU_FASTFLAGVARIABLE(LuauCheckLenMT, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauCheckGenericHOFTypes, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -258,7 +261,11 @@ TypeChecker::TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHan
|
||||||
, booleanType(getSingletonTypes().booleanType)
|
, booleanType(getSingletonTypes().booleanType)
|
||||||
, threadType(getSingletonTypes().threadType)
|
, threadType(getSingletonTypes().threadType)
|
||||||
, anyType(getSingletonTypes().anyType)
|
, anyType(getSingletonTypes().anyType)
|
||||||
|
, unknownType(getSingletonTypes().unknownType)
|
||||||
|
, neverType(getSingletonTypes().neverType)
|
||||||
, anyTypePack(getSingletonTypes().anyTypePack)
|
, anyTypePack(getSingletonTypes().anyTypePack)
|
||||||
|
, neverTypePack(getSingletonTypes().neverTypePack)
|
||||||
|
, uninhabitableTypePack(getSingletonTypes().uninhabitableTypePack)
|
||||||
, duplicateTypeAliases{{false, {}}}
|
, duplicateTypeAliases{{false, {}}}
|
||||||
{
|
{
|
||||||
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
|
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["string"] = TypeFun{{}, stringType};
|
||||||
globalScope->exportedTypeBindings["boolean"] = TypeFun{{}, booleanType};
|
globalScope->exportedTypeBindings["boolean"] = TypeFun{{}, booleanType};
|
||||||
globalScope->exportedTypeBindings["thread"] = TypeFun{{}, threadType};
|
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)
|
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)
|
void TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& block)
|
||||||
{
|
{
|
||||||
int subLevel = 0;
|
int subLevel = 0;
|
||||||
|
@ -559,7 +624,7 @@ void TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, const A
|
||||||
tablify(baseTy);
|
tablify(baseTy);
|
||||||
|
|
||||||
if (!fun->func->self)
|
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))
|
else if (auto ttv = getMutableTableType(baseTy))
|
||||||
{
|
{
|
||||||
if (!baseTy->persistent && ttv->state != TableState::Sealed && !ttv->selfTy)
|
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>())
|
if (auto name = fun->name->as<AstExprIndexName>())
|
||||||
{
|
{
|
||||||
TypeId exprTy = checkExpr(scope, *name->expr).type;
|
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;
|
TypeId type = bindings[name].type;
|
||||||
if (get<FreeTypeVar>(follow(type)))
|
if (get<FreeTypeVar>(follow(type)))
|
||||||
{
|
{
|
||||||
if (FFlag::LuauNonCopyableTypeVarFields)
|
TypeVar* mty = asMutable(follow(type));
|
||||||
{
|
mty->reassign(*errorRecoveryType(anyType));
|
||||||
TypeVar* mty = asMutable(follow(type));
|
|
||||||
mty->reassign(*errorRecoveryType(anyType));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
*asMutable(type) = *errorRecoveryType(anyType);
|
|
||||||
}
|
|
||||||
|
|
||||||
reportError(TypeError{typealias->location, OccursCheckFailed{}});
|
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);
|
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
|
// 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
|
// 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)
|
for (TypeId var : varTypes)
|
||||||
unify(varTy, var, forin.location);
|
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});
|
reportError(firstValue->location, CannotCallNonFunction{iterTy});
|
||||||
|
|
||||||
return check(loopScope, *forin.body);
|
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;
|
TypeId exprTy = checkExpr(scope, *name->expr).type;
|
||||||
TableTypeVar* ttv = getMutableTableType(exprTy);
|
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))
|
if (ttv || isTableIntersection(exprTy))
|
||||||
reportError(TypeError{function.location, CannotExtendTable{exprTy, CannotExtendTable::Property, name->index.value}});
|
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);
|
checkFunctionBody(funScope, ty, *function.func);
|
||||||
|
|
||||||
|
if (FFlag::LuauUnknownAndNeverType)
|
||||||
|
{
|
||||||
|
InplaceDemoter demoter{funScope->level, ¤tModule->internalTypes};
|
||||||
|
demoter.traverse(ty);
|
||||||
|
}
|
||||||
|
|
||||||
if (ttv && ttv->state != TableState::Sealed)
|
if (ttv && ttv->state != TableState::Sealed)
|
||||||
ttv->props[name->index.value] = {follow(quantify(funScope, ty, name->indexLocation)), /* deprecated */ false, {}, name->indexLocation};
|
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>())
|
else if (auto a = expr.as<AstExprUnary>())
|
||||||
result = checkExpr(scope, *a);
|
result = checkExpr(scope, *a);
|
||||||
else if (auto a = expr.as<AstExprBinary>())
|
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>())
|
else if (auto a = expr.as<AstExprTypeAssertion>())
|
||||||
result = checkExpr(scope, *a);
|
result = checkExpr(scope, *a);
|
||||||
else if (auto a = expr.as<AstExprError>())
|
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);
|
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 {*ty};
|
||||||
|
|
||||||
return {errorRecoveryType(scope)};
|
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;
|
ErrorVec errors;
|
||||||
auto result = Luau::findTablePropertyRespectingMeta(errors, lhsType, name, location);
|
auto result = Luau::findTablePropertyRespectingMeta(errors, lhsType, name, location);
|
||||||
reportErrors(errors);
|
if (!FFlag::LuauIndexSilenceErrors || addErrors)
|
||||||
|
reportErrors(errors);
|
||||||
return result;
|
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;
|
ErrorVec errors;
|
||||||
auto result = Luau::findMetatableEntry(errors, type, entry, location);
|
auto result = Luau::findMetatableEntry(errors, type, entry, location);
|
||||||
reportErrors(errors);
|
if (!FFlag::LuauIndexSilenceErrors || addErrors)
|
||||||
|
reportErrors(errors);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
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);
|
type = follow(type);
|
||||||
|
|
||||||
if (get<ErrorTypeVar>(type) || get<AnyTypeVar>(type))
|
if (get<ErrorTypeVar>(type) || get<AnyTypeVar>(type) || get<NeverTypeVar>(type))
|
||||||
return type;
|
return type;
|
||||||
|
|
||||||
tablify(type);
|
tablify(type);
|
||||||
|
|
||||||
if (isString(type))
|
if (isString(type))
|
||||||
{
|
{
|
||||||
std::optional<TypeId> mtIndex = findMetatableEntry(stringType, "__index", location);
|
std::optional<TypeId> mtIndex = findMetatableEntry(stringType, "__index", location, addErrors);
|
||||||
LUAU_ASSERT(mtIndex);
|
LUAU_ASSERT(mtIndex);
|
||||||
type = *mtIndex;
|
type = *mtIndex;
|
||||||
}
|
}
|
||||||
|
@ -1919,7 +1998,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto found = findTablePropertyRespectingMeta(type, name, location))
|
if (auto found = findTablePropertyRespectingMeta(type, name, location, addErrors))
|
||||||
return *found;
|
return *found;
|
||||||
}
|
}
|
||||||
else if (const ClassTypeVar* cls = get<ClassTypeVar>(type))
|
else if (const ClassTypeVar* cls = get<ClassTypeVar>(type))
|
||||||
|
@ -1941,7 +2020,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
||||||
if (get<AnyTypeVar>(follow(t)))
|
if (get<AnyTypeVar>(follow(t)))
|
||||||
return 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);
|
goodOptions.push_back(*ty);
|
||||||
else
|
else
|
||||||
badOptions.push_back(t);
|
badOptions.push_back(t);
|
||||||
|
@ -1972,6 +2051,8 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::vector<TypeId> result = reduceUnion(goodOptions);
|
std::vector<TypeId> result = reduceUnion(goodOptions);
|
||||||
|
if (FFlag::LuauUnknownAndNeverType && result.empty())
|
||||||
|
return neverType;
|
||||||
|
|
||||||
if (result.size() == 1)
|
if (result.size() == 1)
|
||||||
return result[0];
|
return result[0];
|
||||||
|
@ -1987,7 +2068,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
|
||||||
{
|
{
|
||||||
RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit);
|
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);
|
parts.push_back(*ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2017,6 +2098,9 @@ std::vector<TypeId> TypeChecker::reduceUnion(const std::vector<TypeId>& types)
|
||||||
for (TypeId t : types)
|
for (TypeId t : types)
|
||||||
{
|
{
|
||||||
t = follow(t);
|
t = follow(t);
|
||||||
|
if (get<NeverTypeVar>(t))
|
||||||
|
continue;
|
||||||
|
|
||||||
if (get<ErrorTypeVar>(t) || get<AnyTypeVar>(t))
|
if (get<ErrorTypeVar>(t) || get<AnyTypeVar>(t))
|
||||||
return {t};
|
return {t};
|
||||||
|
|
||||||
|
@ -2028,6 +2112,8 @@ std::vector<TypeId> TypeChecker::reduceUnion(const std::vector<TypeId>& types)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauNormalizeFlagIsConservative)
|
if (FFlag::LuauNormalizeFlagIsConservative)
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
|
if (get<NeverTypeVar>(ty))
|
||||||
|
continue;
|
||||||
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
|
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
|
||||||
return {ty};
|
return {ty};
|
||||||
|
|
||||||
|
@ -2041,6 +2127,8 @@ std::vector<TypeId> TypeChecker::reduceUnion(const std::vector<TypeId>& types)
|
||||||
for (TypeId ty : r)
|
for (TypeId ty : r)
|
||||||
{
|
{
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
|
if (get<NeverTypeVar>(ty))
|
||||||
|
continue;
|
||||||
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
|
if (get<ErrorTypeVar>(ty) || get<AnyTypeVar>(ty))
|
||||||
return {ty};
|
return {ty};
|
||||||
|
|
||||||
|
@ -2314,14 +2402,14 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
|
||||||
return {booleanType, {NotPredicate{std::move(result.predicates)}}};
|
return {booleanType, {NotPredicate{std::move(result.predicates)}}};
|
||||||
case AstExprUnary::Minus:
|
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)
|
if (operandIsAny)
|
||||||
return {operandType};
|
return {operandType};
|
||||||
|
|
||||||
if (typeCouldHaveMetatable(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);
|
TypeId actualFunctionType = instantiate(scope, *fnt, expr.location);
|
||||||
TypePackId arguments = addTypePack({operandType});
|
TypePackId arguments = addTypePack({operandType});
|
||||||
|
@ -2355,14 +2443,14 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
|
||||||
|
|
||||||
operandType = stripFromNilAndReport(operandType, expr.location);
|
operandType = stripFromNilAndReport(operandType, expr.location);
|
||||||
|
|
||||||
if (get<ErrorTypeVar>(operandType))
|
if (get<ErrorTypeVar>(operandType) || get<NeverTypeVar>(operandType))
|
||||||
return {errorRecoveryType(scope)};
|
return {!FFlag::LuauUnknownAndNeverType ? errorRecoveryType(scope) : operandType};
|
||||||
|
|
||||||
DenseHashSet<TypeId> seen{nullptr};
|
DenseHashSet<TypeId> seen{nullptr};
|
||||||
|
|
||||||
if (FFlag::LuauCheckLenMT && typeCouldHaveMetatable(operandType))
|
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);
|
TypeId actualFunctionType = instantiate(scope, *fnt, expr.location);
|
||||||
TypePackId arguments = addTypePack({operandType});
|
TypePackId arguments = addTypePack({operandType});
|
||||||
|
@ -2433,6 +2521,9 @@ TypeId TypeChecker::unionOfTypes(TypeId a, TypeId b, const Location& location, b
|
||||||
return a;
|
return a;
|
||||||
|
|
||||||
std::vector<TypeId> types = reduceUnion({a, b});
|
std::vector<TypeId> types = reduceUnion({a, b});
|
||||||
|
if (FFlag::LuauUnknownAndNeverType && types.empty())
|
||||||
|
return neverType;
|
||||||
|
|
||||||
if (types.size() == 1)
|
if (types.size() == 1)
|
||||||
return types[0];
|
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.
|
// 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.
|
// 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)`
|
// Peephole check for `cond and a or b -> type(a)|type(b)`
|
||||||
// TODO: Kill this when singleton types arrive. :(
|
// TODO: Kill this when singleton types arrive. :(
|
||||||
|
@ -2508,7 +2599,7 @@ TypeId TypeChecker::checkRelationalOperation(
|
||||||
if (isNonstrictMode() && (isNil(lhsType) || isNil(rhsType)))
|
if (isNonstrictMode() && (isNil(lhsType) || isNil(rhsType)))
|
||||||
return booleanType;
|
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)
|
if (lhsIsAny || rhsIsAny)
|
||||||
return booleanType;
|
return booleanType;
|
||||||
|
|
||||||
|
@ -2596,7 +2687,7 @@ TypeId TypeChecker::checkRelationalOperation(
|
||||||
|
|
||||||
if (leftMetatable)
|
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 (metamethod)
|
||||||
{
|
{
|
||||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(*metamethod))
|
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(*metamethod))
|
||||||
|
@ -2757,9 +2848,9 @@ TypeId TypeChecker::checkBinaryOperation(
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string op = opToMetaTableEntry(expr.op);
|
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);
|
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.
|
// Note the intentionally reversed arguments here.
|
||||||
return checkMetatableCall(*fnt, rhsType, lhsType);
|
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)
|
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);
|
ScopePtr innerScope = childScope(scope, expr.location);
|
||||||
resolve(lhsPredicates, innerScope, true);
|
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)}}};
|
return {checkBinaryOperation(scope, expr, lhsTy, rhsTy), {AndPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
|
||||||
}
|
}
|
||||||
else if (expr.op == AstExprBinary::Or)
|
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);
|
ScopePtr innerScope = childScope(scope, expr.location);
|
||||||
resolve(lhsPredicates, innerScope, false);
|
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.
|
// 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);
|
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))
|
if (auto predicate = tryGetTypeGuardPredicate(expr))
|
||||||
return {booleanType, {std::move(*predicate)}};
|
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> lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/true);
|
||||||
WithPredicate<TypeId> rhs = checkExpr(scope, *expr.right, 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
|
else
|
||||||
{
|
{
|
||||||
|
// Expected types are not useful for other binary operators.
|
||||||
WithPredicate<TypeId> lhs = checkExpr(scope, *expr.left);
|
WithPredicate<TypeId> lhs = checkExpr(scope, *expr.left);
|
||||||
WithPredicate<TypeId> rhs = checkExpr(scope, *expr.right);
|
WithPredicate<TypeId> rhs = checkExpr(scope, *expr.right);
|
||||||
|
|
||||||
|
@ -2896,6 +2990,8 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
|
||||||
return {trueType.type};
|
return {trueType.type};
|
||||||
|
|
||||||
std::vector<TypeId> types = reduceUnion({trueType.type, falseType.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)})};
|
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)
|
TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprLocal& expr)
|
||||||
{
|
{
|
||||||
if (std::optional<TypeId> ty = scope->lookup(expr.local))
|
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});
|
reportError(expr.location, UnknownSymbol{expr.local->name.value, UnknownSymbol::Binding});
|
||||||
return errorRecoveryType(scope);
|
return errorRecoveryType(scope);
|
||||||
|
@ -2941,7 +3040,10 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprGloba
|
||||||
const auto it = moduleScope->bindings.find(expr.name);
|
const auto it = moduleScope->bindings.find(expr.name);
|
||||||
|
|
||||||
if (it != moduleScope->bindings.end())
|
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);
|
TypeId result = freshType(scope);
|
||||||
Binding& binding = moduleScope->bindings[expr.name];
|
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))
|
if (get<ErrorTypeVar>(lhs) || get<AnyTypeVar>(lhs))
|
||||||
return lhs;
|
return lhs;
|
||||||
|
|
||||||
|
if (get<NeverTypeVar>(lhs))
|
||||||
|
return unknownType;
|
||||||
|
|
||||||
tablify(lhs);
|
tablify(lhs);
|
||||||
|
|
||||||
Name name = expr.index.value;
|
Name name = expr.index.value;
|
||||||
|
@ -3023,7 +3128,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
|
||||||
}
|
}
|
||||||
else if (get<IntersectionTypeVar>(lhs))
|
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;
|
return *ty;
|
||||||
|
|
||||||
// If intersection has a table part, report that it cannot be extended just as a sealed table
|
// 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))
|
if (get<AnyTypeVar>(exprType) || get<ErrorTypeVar>(exprType))
|
||||||
return exprType;
|
return exprType;
|
||||||
|
|
||||||
|
if (get<NeverTypeVar>(exprType))
|
||||||
|
return unknownType;
|
||||||
|
|
||||||
AstExprConstantString* value = expr.index->as<AstExprConstantString>();
|
AstExprConstantString* value = expr.index->as<AstExprConstantString>();
|
||||||
|
|
||||||
if (value)
|
if (value)
|
||||||
|
@ -3156,7 +3264,7 @@ TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName, T
|
||||||
|
|
||||||
if (!ttv || ttv->state == TableState::Sealed)
|
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 *ty;
|
||||||
|
|
||||||
return errorRecoveryType(scope);
|
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 (!FFlag::LuauCheckGenericHOFTypes)
|
||||||
if (expectedFunctionType && !(expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty()))
|
{
|
||||||
expectedFunctionType = nullptr;
|
// 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);
|
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);
|
retPack = resolveTypePack(funScope, *expr.returnAnnotation);
|
||||||
else if (FFlag::LuauReturnTypeInferenceInNonstrict ? (!FFlag::LuauLowerBoundsCalculation && isNonstrictMode()) : isNonstrictMode())
|
else if (FFlag::LuauReturnTypeInferenceInNonstrict ? (!FFlag::LuauLowerBoundsCalculation && isNonstrictMode()) : isNonstrictMode())
|
||||||
retPack = anyTypePack;
|
retPack = anyTypePack;
|
||||||
else if (expectedFunctionType)
|
else if (expectedFunctionType &&
|
||||||
|
(!FFlag::LuauCheckGenericHOFTypes || (expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty())))
|
||||||
{
|
{
|
||||||
auto [head, tail] = flatten(expectedFunctionType->retTypes);
|
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));
|
defn.originalNameLocation = originalName.value_or(Location(expr.location.begin, 0));
|
||||||
|
|
||||||
std::vector<TypeId> genericTys;
|
std::vector<TypeId> genericTys;
|
||||||
genericTys.reserve(generics.size());
|
// if we have a generic expected function type and no generics, we should use the expected ones.
|
||||||
std::transform(generics.begin(), generics.end(), std::back_inserter(genericTys), [](auto&& el) {
|
if (FFlag::LuauCheckGenericHOFTypes)
|
||||||
return el.ty;
|
{
|
||||||
});
|
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;
|
std::vector<TypePackId> genericTps;
|
||||||
genericTps.reserve(genericPacks.size());
|
// if we have a generic expected function type and no generic typepacks, we should use the expected ones.
|
||||||
std::transform(genericPacks.begin(), genericPacks.end(), std::back_inserter(genericTps), [](auto&& el) {
|
if (FFlag::LuauCheckGenericHOFTypes)
|
||||||
return el.tp;
|
{
|
||||||
});
|
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 =
|
TypeId funTy =
|
||||||
addType(FunctionTypeVar(funScope->level, std::move(genericTys), std::move(genericTps), argPack, retPack, std::move(defn), bool(expr.self)));
|
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)
|
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>())
|
if (auto a = expr.as<AstExprCall>())
|
||||||
return checkExprPack(scope, *a);
|
return checkExprPackHelper(scope, *a);
|
||||||
else if (expr.is<AstExprVarargs>())
|
else if (expr.is<AstExprVarargs>())
|
||||||
{
|
{
|
||||||
if (!scope->varargPack)
|
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
|
// evaluate type of function
|
||||||
// decompose an intersection into its component overloads
|
// 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 = checkExpr(scope, *indexExpr->expr).type;
|
||||||
selfType = stripFromNilAndReport(selfType, expr.func->location);
|
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;
|
functionType = *propTy;
|
||||||
actualFunctionType = instantiate(scope, functionType, expr.func->location);
|
actualFunctionType = instantiate(scope, functionType, expr.func->location);
|
||||||
|
@ -3813,11 +3972,25 @@ WithPredicate<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, cons
|
||||||
if (get<Unifiable::Error>(argPack))
|
if (get<Unifiable::Error>(argPack))
|
||||||
return {errorRecoveryTypePack(scope)};
|
return {errorRecoveryTypePack(scope)};
|
||||||
|
|
||||||
TypePack* args = getMutable<TypePack>(argPack);
|
TypePack* args = nullptr;
|
||||||
LUAU_ASSERT(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)
|
if (expr.self)
|
||||||
args->head.insert(args->head.begin(), selfType);
|
args->head.insert(args->head.begin(), selfType);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<Location> argLocations;
|
std::vector<Location> argLocations;
|
||||||
argLocations.reserve(expr.args.size + 1);
|
argLocations.reserve(expr.args.size + 1);
|
||||||
|
@ -3876,7 +4049,10 @@ std::vector<std::optional<TypeId>> TypeChecker::getExpectedTypesForCall(const st
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::vector<TypeId> result = reduceUnion({*el, ty});
|
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)}};
|
return {{errorRecoveryTypePack(scope)}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (get<NeverTypeVar>(fn))
|
||||||
|
return {{uninhabitableTypePack}};
|
||||||
|
|
||||||
if (auto ftv = get<FreeTypeVar>(fn))
|
if (auto ftv = get<FreeTypeVar>(fn))
|
||||||
{
|
{
|
||||||
// fn is one of the overloads of actualFunctionType, which
|
// 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
|
// Might be a callable table
|
||||||
if (const MetatableTypeVar* mttv = get<MetatableTypeVar>(fn))
|
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
|
// Construct arguments with 'self' added in front
|
||||||
TypePackId metaCallArgPack = addTypePack(TypePackVar(TypePack{args->head, args->tail}));
|
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,
|
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 substituteFreeForNil, const std::vector<bool>& instantiateGenerics, const std::vector<std::optional<TypeId>>& expectedTypes)
|
||||||
{
|
{
|
||||||
|
bool uninhabitable = false;
|
||||||
TypePackId pack = addTypePack(TypePack{});
|
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?
|
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);
|
auto [typePack, exprPredicates] = checkExprPack(scope, *expr);
|
||||||
insert(exprPredicates);
|
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))
|
if (!currentModule->astTypes.find(expr))
|
||||||
currentModule->astTypes[expr] = follow(*firstTy);
|
currentModule->astTypes[expr] = follow(*firstTy);
|
||||||
|
@ -4248,6 +4434,13 @@ WithPredicate<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, cons
|
||||||
auto [type, exprPredicates] = checkExpr(scope, *expr, expectedType);
|
auto [type, exprPredicates] = checkExpr(scope, *expr, expectedType);
|
||||||
insert(exprPredicates);
|
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;
|
TypeId actualType = substituteFreeForNil && expr->is<AstExprConstantNil>() ? freshType(scope) : type;
|
||||||
|
|
||||||
if (instantiateGenerics.size() > i && instantiateGenerics[i])
|
if (instantiateGenerics.size() > i && instantiateGenerics[i])
|
||||||
|
@ -4272,6 +4465,8 @@ WithPredicate<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, cons
|
||||||
for (TxnLog& log : inverseLogs)
|
for (TxnLog& log : inverseLogs)
|
||||||
log.commit();
|
log.commit();
|
||||||
|
|
||||||
|
if (FFlag::LuauUnknownAndNeverType && uninhabitable)
|
||||||
|
return {uninhabitableTypePack};
|
||||||
return {pack, predicates};
|
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);
|
std::vector<TypeId> types = Luau::filterMap(type, predicate);
|
||||||
if (!types.empty())
|
if (!types.empty())
|
||||||
|
@ -4838,7 +5033,21 @@ std::optional<TypeId> TypeChecker::filterMap(TypeId type, TypeIdPredicate predic
|
||||||
return std::nullopt;
|
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));
|
return filterMap(type, mkTruthyPredicate(sense));
|
||||||
}
|
}
|
||||||
|
@ -5465,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 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 (!key)
|
||||||
{
|
{
|
||||||
if (std::optional<TypeId> result = filterMap(*ty, predicate))
|
auto [result, ok] = filterMap(*ty, predicate);
|
||||||
|
if (FFlag::LuauUnknownAndNeverType)
|
||||||
|
{
|
||||||
addRefinement(refis, *target, *result);
|
addRefinement(refis, *target, *result);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
addRefinement(refis, *target, errorRecoveryType(scope));
|
{
|
||||||
|
if (ok)
|
||||||
|
addRefinement(refis, *target, *result);
|
||||||
|
else
|
||||||
|
addRefinement(refis, *target, errorRecoveryType(scope));
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -5484,17 +5701,29 @@ void TypeChecker::refineLValue(const LValue& lvalue, RefinementMap& refis, const
|
||||||
{
|
{
|
||||||
std::optional<TypeId> discriminantTy;
|
std::optional<TypeId> discriminantTy;
|
||||||
if (auto field = Luau::get<Field>(*key)) // need to fully qualify Luau::get because of ADL.
|
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
|
else
|
||||||
LUAU_ASSERT(!"Unhandled LValue alternative?");
|
LUAU_ASSERT(!"Unhandled LValue alternative?");
|
||||||
|
|
||||||
if (!discriminantTy)
|
if (!discriminantTy)
|
||||||
return; // Do nothing. An error was already reported, as per usual.
|
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);
|
if (!get<NeverTypeVar>(*result))
|
||||||
viableChildOptions.insert(*result);
|
{
|
||||||
|
viableTargetOptions.insert(option);
|
||||||
|
viableChildOptions.insert(*result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
viableTargetOptions.insert(option);
|
||||||
|
viableChildOptions.insert(*result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5573,7 +5802,7 @@ std::optional<TypeId> TypeChecker::resolveLValue(const ScopePtr& scope, const LV
|
||||||
continue;
|
continue;
|
||||||
else if (auto field = get<Field>(key))
|
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)
|
if (!found)
|
||||||
return std::nullopt; // Turns out this type doesn't have the property at all. We're done.
|
return std::nullopt; // Turns out this type doesn't have the property at all. We're done.
|
||||||
}
|
}
|
||||||
|
@ -5753,6 +5982,9 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
|
||||||
auto mkFilter = [](ConditionFunc f, std::optional<TypeId> other = std::nullopt) -> SenseToTypeIdPredicate {
|
auto mkFilter = [](ConditionFunc f, std::optional<TypeId> other = std::nullopt) -> SenseToTypeIdPredicate {
|
||||||
return [f, other](bool sense) -> TypeIdPredicate {
|
return [f, other](bool sense) -> TypeIdPredicate {
|
||||||
return [f, other, sense](TypeId ty) -> std::optional<TypeId> {
|
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)
|
if (f(ty) == sense)
|
||||||
return ty;
|
return ty;
|
||||||
|
|
||||||
|
@ -5860,8 +6092,15 @@ std::vector<TypeId> TypeChecker::unTypePack(const ScopePtr& scope, TypePackId tp
|
||||||
for (size_t i = 0; i < expectedLength; ++i)
|
for (size_t i = 0; i < expectedLength; ++i)
|
||||||
expectedPack->head.push_back(freshType(scope));
|
expectedPack->head.push_back(freshType(scope));
|
||||||
|
|
||||||
|
size_t oldErrorsSize = currentModule->errors.size();
|
||||||
|
|
||||||
unify(tp, expectedTypePack, location);
|
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)
|
for (TypeId& tp : expectedPack->head)
|
||||||
tp = follow(tp);
|
tp = follow(tp);
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauNonCopyableTypeVarFields)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -40,19 +38,10 @@ TypePackVar& TypePackVar::operator=(TypePackVariant&& tp)
|
||||||
|
|
||||||
TypePackVar& TypePackVar::operator=(const TypePackVar& rhs)
|
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);
|
reassign(rhs);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ty = rhs.ty;
|
|
||||||
persistent = rhs.persistent;
|
|
||||||
owningArena = rhs.owningArena;
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
@ -294,6 +283,16 @@ std::optional<TypeId> first(TypePackId tp, bool ignoreHiddenVariadics)
|
||||||
return std::nullopt;
|
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)
|
bool isEmpty(TypePackId tp)
|
||||||
{
|
{
|
||||||
tp = follow(tp);
|
tp = follow(tp);
|
||||||
|
@ -360,13 +359,25 @@ bool isVariadic(TypePackId tp, const TxnLog& log)
|
||||||
return false;
|
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
|
} // namespace Luau
|
||||||
|
|
|
@ -24,7 +24,7 @@ std::optional<TypeId> findMetatableEntry(ErrorVec& errors, TypeId type, std::str
|
||||||
const TableTypeVar* mtt = getTableType(unwrapped);
|
const TableTypeVar* mtt = getTableType(unwrapped);
|
||||||
if (!mtt)
|
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;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,9 @@ LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||||
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
||||||
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
LUAU_FASTFLAG(LuauNonCopyableTypeVarFields)
|
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauDeduceGmatchReturnTypes, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -31,6 +33,9 @@ namespace Luau
|
||||||
std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
|
std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
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)
|
TypeId follow(TypeId t)
|
||||||
{
|
{
|
||||||
return follow(t, [](TypeId t) {
|
return follow(t, [](TypeId t) {
|
||||||
|
@ -173,8 +178,8 @@ bool maybeString(TypeId ty)
|
||||||
{
|
{
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
|
|
||||||
if (isPrim(ty, PrimitiveTypeVar::String) || get<AnyTypeVar>(ty))
|
if (isPrim(ty, PrimitiveTypeVar::String) || get<AnyTypeVar>(ty))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (auto utv = get<UnionTypeVar>(ty))
|
if (auto utv = get<UnionTypeVar>(ty))
|
||||||
return std::any_of(begin(utv), end(utv), maybeString);
|
return std::any_of(begin(utv), end(utv), maybeString);
|
||||||
|
@ -194,7 +199,7 @@ bool isOptional(TypeId ty)
|
||||||
|
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
|
|
||||||
if (get<AnyTypeVar>(ty))
|
if (get<AnyTypeVar>(ty) || (FFlag::LuauUnknownAndNeverType && get<UnknownTypeVar>(ty)))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
auto utv = get<UnionTypeVar>(ty);
|
auto utv = get<UnionTypeVar>(ty);
|
||||||
|
@ -334,6 +339,28 @@ bool isGeneric(TypeId ty)
|
||||||
|
|
||||||
bool maybeGeneric(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);
|
ty = follow(ty);
|
||||||
if (get<FreeTypeVar>(ty))
|
if (get<FreeTypeVar>(ty))
|
||||||
return true;
|
return true;
|
||||||
|
@ -646,20 +673,10 @@ TypeVar& TypeVar::operator=(TypeVariant&& rhs)
|
||||||
|
|
||||||
TypeVar& TypeVar::operator=(const TypeVar& 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);
|
reassign(rhs);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ty = rhs.ty;
|
|
||||||
persistent = rhs.persistent;
|
|
||||||
normal = rhs.normal;
|
|
||||||
owningArena = rhs.owningArena;
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
@ -676,10 +693,14 @@ static TypeVar threadType_{PrimitiveTypeVar{PrimitiveTypeVar::Thread}, /*persist
|
||||||
static TypeVar trueType_{SingletonTypeVar{BooleanSingleton{true}}, /*persistent*/ true};
|
static TypeVar trueType_{SingletonTypeVar{BooleanSingleton{true}}, /*persistent*/ true};
|
||||||
static TypeVar falseType_{SingletonTypeVar{BooleanSingleton{false}}, /*persistent*/ true};
|
static TypeVar falseType_{SingletonTypeVar{BooleanSingleton{false}}, /*persistent*/ true};
|
||||||
static TypeVar anyType_{AnyTypeVar{}, /*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 TypeVar errorType_{ErrorTypeVar{}, /*persistent*/ true};
|
||||||
|
|
||||||
static TypePackVar anyTypePack_{VariadicTypePack{&anyType_}, true};
|
static TypePackVar anyTypePack_{VariadicTypePack{&anyType_}, /*persistent*/ true};
|
||||||
static TypePackVar errorTypePack_{Unifiable::Error{}};
|
static TypePackVar errorTypePack_{Unifiable::Error{}, /*persistent*/ true};
|
||||||
|
static TypePackVar neverTypePack_{VariadicTypePack{&neverType_}, /*persistent*/ true};
|
||||||
|
static TypePackVar uninhabitableTypePack_{TypePack{{&neverType_}, &neverTypePack_}, /*persistent*/ true};
|
||||||
|
|
||||||
SingletonTypes::SingletonTypes()
|
SingletonTypes::SingletonTypes()
|
||||||
: nilType(&nilType_)
|
: nilType(&nilType_)
|
||||||
|
@ -690,7 +711,11 @@ SingletonTypes::SingletonTypes()
|
||||||
, trueType(&trueType_)
|
, trueType(&trueType_)
|
||||||
, falseType(&falseType_)
|
, falseType(&falseType_)
|
||||||
, anyType(&anyType_)
|
, anyType(&anyType_)
|
||||||
|
, unknownType(&unknownType_)
|
||||||
|
, neverType(&neverType_)
|
||||||
, anyTypePack(&anyTypePack_)
|
, anyTypePack(&anyTypePack_)
|
||||||
|
, neverTypePack(&neverTypePack_)
|
||||||
|
, uninhabitableTypePack(&uninhabitableTypePack_)
|
||||||
, arena(new TypeArena)
|
, arena(new TypeArena)
|
||||||
{
|
{
|
||||||
TypeId stringMetatable = makeStringMetatable();
|
TypeId stringMetatable = makeStringMetatable();
|
||||||
|
@ -738,6 +763,7 @@ TypeId SingletonTypes::makeStringMetatable()
|
||||||
const TypeId gsubFunc = makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType});
|
const TypeId gsubFunc = makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType});
|
||||||
const TypeId gmatchFunc =
|
const TypeId gmatchFunc =
|
||||||
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionTypeVar{emptyPack, stringVariadicList})});
|
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionTypeVar{emptyPack, stringVariadicList})});
|
||||||
|
attachMagicFunction(gmatchFunc, magicFunctionGmatch);
|
||||||
|
|
||||||
TableTypeVar::Props stringLib = {
|
TableTypeVar::Props stringLib = {
|
||||||
{"byte", {arena->addType(FunctionTypeVar{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList})}},
|
{"byte", {arena->addType(FunctionTypeVar{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList})}},
|
||||||
|
@ -911,6 +937,8 @@ const TypeLevel* getLevel(TypeId ty)
|
||||||
return &ttv->level;
|
return &ttv->level;
|
||||||
else if (auto ftv = get<FunctionTypeVar>(ty))
|
else if (auto ftv = get<FunctionTypeVar>(ty))
|
||||||
return &ftv->level;
|
return &ftv->level;
|
||||||
|
else if (auto ctv = get<ConstrainedTypeVar>(ty))
|
||||||
|
return &ctv->level;
|
||||||
else
|
else
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -965,94 +993,19 @@ bool isSubclass(const ClassTypeVar* cls, const ClassTypeVar* parent)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
UnionTypeVarIterator::UnionTypeVarIterator(const UnionTypeVar* utv)
|
const std::vector<TypeId>& getTypes(const UnionTypeVar* utv)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(utv);
|
return utv->options;
|
||||||
|
|
||||||
if (!utv->options.empty())
|
|
||||||
stack.push_front({utv, 0});
|
|
||||||
|
|
||||||
seen.insert(utv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UnionTypeVarIterator& UnionTypeVarIterator::operator++()
|
const std::vector<TypeId>& getTypes(const IntersectionTypeVar* itv)
|
||||||
{
|
{
|
||||||
advance();
|
return itv->parts;
|
||||||
descend();
|
|
||||||
return *this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UnionTypeVarIterator UnionTypeVarIterator::operator++(int)
|
const std::vector<TypeId>& getTypes(const ConstrainedTypeVar* ctv)
|
||||||
{
|
{
|
||||||
UnionTypeVarIterator copy = *this;
|
return ctv->parts;
|
||||||
++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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UnionTypeVarIterator begin(const UnionTypeVar* utv)
|
UnionTypeVarIterator begin(const UnionTypeVar* utv)
|
||||||
|
@ -1065,6 +1018,27 @@ UnionTypeVarIterator end(const UnionTypeVar* utv)
|
||||||
return UnionTypeVarIterator{};
|
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)
|
static std::vector<TypeId> parseFormatString(TypeChecker& typechecker, const char* data, size_t size)
|
||||||
{
|
{
|
||||||
const char* options = "cdiouxXeEfgGqs";
|
const char* options = "cdiouxXeEfgGqs";
|
||||||
|
@ -1144,6 +1118,101 @@ std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
|
||||||
return WithPredicate<TypePackId>{arena.addTypePack({typechecker.stringType})};
|
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)
|
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate)
|
||||||
{
|
{
|
||||||
type = follow(type);
|
type = follow(type);
|
||||||
|
|
|
@ -19,6 +19,7 @@ LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
||||||
LUAU_FASTINTVARIABLE(LuauTypeInferLowerBoundsIterationLimit, 2000);
|
LUAU_FASTINTVARIABLE(LuauTypeInferLowerBoundsIterationLimit, 2000);
|
||||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||||
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
||||||
|
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||||
LUAU_FASTFLAG(LuauQuantifyConstrained)
|
LUAU_FASTFLAG(LuauQuantifyConstrained)
|
||||||
|
|
||||||
namespace Luau
|
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
|
bool visit(TypeId ty) override
|
||||||
{
|
{
|
||||||
// Type levels of types from other modules are already global, so we don't need to promote anything inside
|
// 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;
|
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
|
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
|
// 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)
|
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;
|
TypeLevel subLevel = subFree->level;
|
||||||
|
|
||||||
occursCheck(subTy, superTy);
|
occursCheck(subTy, superTy);
|
||||||
|
@ -468,7 +459,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get<ErrorTypeVar>(superTy) || get<AnyTypeVar>(superTy))
|
if (get<ErrorTypeVar>(superTy) || get<AnyTypeVar>(superTy) || get<UnknownTypeVar>(superTy))
|
||||||
return tryUnifyWithAny(subTy, superTy);
|
return tryUnifyWithAny(subTy, superTy);
|
||||||
|
|
||||||
if (get<AnyTypeVar>(subTy))
|
if (get<AnyTypeVar>(subTy))
|
||||||
|
@ -485,6 +476,9 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||||
if (get<ErrorTypeVar>(subTy))
|
if (get<ErrorTypeVar>(subTy))
|
||||||
return tryUnifyWithAny(superTy, subTy);
|
return tryUnifyWithAny(superTy, subTy);
|
||||||
|
|
||||||
|
if (get<NeverTypeVar>(subTy))
|
||||||
|
return tryUnifyWithAny(superTy, subTy);
|
||||||
|
|
||||||
auto& cache = sharedState.cachedUnify;
|
auto& cache = sharedState.cachedUnify;
|
||||||
|
|
||||||
// What if the types are immutable and we proved their relation before
|
// 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))
|
if (state.log.getMutable<FreeTypeVar>(ty))
|
||||||
{
|
{
|
||||||
|
// TODO: Only bind if the anyType isn't any, unknown, or error (?)
|
||||||
state.log.replace(ty, BoundTypeVar{anyType});
|
state.log.replace(ty, BoundTypeVar{anyType});
|
||||||
}
|
}
|
||||||
else if (auto fun = state.log.getMutable<FunctionTypeVar>(ty))
|
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)
|
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
|
// These types are not visited in general loop below
|
||||||
if (get<PrimitiveTypeVar>(subTy) || get<AnyTypeVar>(subTy) || get<ClassTypeVar>(subTy))
|
if (get<PrimitiveTypeVar>(subTy) || get<AnyTypeVar>(subTy) || get<ClassTypeVar>(subTy))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{getSingletonTypes().anyType}});
|
TypePackId anyTp;
|
||||||
|
if (FFlag::LuauUnknownAndNeverType)
|
||||||
const TypePackId anyTP = get<AnyTypeVar>(anyTy) ? anyTypePack : types->addTypePack(TypePackVar{Unifiable::Error{}});
|
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};
|
std::vector<TypeId> queue = {subTy};
|
||||||
|
|
||||||
sharedState.tempSeenTy.clear();
|
sharedState.tempSeenTy.clear();
|
||||||
sharedState.tempSeenTp.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)
|
void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp)
|
||||||
|
|
|
@ -15,7 +15,6 @@ LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
|
||||||
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauParserFunctionKeywordAsTypeHelp, false)
|
LUAU_FASTFLAGVARIABLE(LuauParserFunctionKeywordAsTypeHelp, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauReturnTypeTokenConfusion, false)
|
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFixNamedFunctionParse, false)
|
LUAU_FASTFLAGVARIABLE(LuauFixNamedFunctionParse, false)
|
||||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseWrongNamedType, false)
|
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseWrongNamedType, false)
|
||||||
|
@ -1134,10 +1133,9 @@ AstTypePack* Parser::parseTypeList(TempVector<AstType*>& result, TempVector<std:
|
||||||
|
|
||||||
std::optional<AstTypeList> Parser::parseOptionalReturnTypeAnnotation()
|
std::optional<AstTypeList> Parser::parseOptionalReturnTypeAnnotation()
|
||||||
{
|
{
|
||||||
if (options.allowTypeAnnotations &&
|
if (options.allowTypeAnnotations && (lexer.current().type == ':' || lexer.current().type == Lexeme::SkinnyArrow))
|
||||||
(lexer.current().type == ':' || (FFlag::LuauReturnTypeTokenConfusion && 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 '->'");
|
report(lexer.current().location, "Function return type annotations are written after ':' instead of '->'");
|
||||||
|
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
|
@ -1373,12 +1371,10 @@ AstTypeOrPack Parser::parseFunctionTypeAnnotation(bool allowPack)
|
||||||
if (FFlag::LuauFixNamedFunctionParse && !names.empty())
|
if (FFlag::LuauFixNamedFunctionParse && !names.empty())
|
||||||
forceFunctionType = true;
|
forceFunctionType = true;
|
||||||
|
|
||||||
bool returnTypeIntroducer =
|
bool returnTypeIntroducer = lexer.current().type == Lexeme::SkinnyArrow || lexer.current().type == ':';
|
||||||
FFlag::LuauReturnTypeTokenConfusion ? lexer.current().type == Lexeme::SkinnyArrow || lexer.current().type == ':' : false;
|
|
||||||
|
|
||||||
// Not a function at all. Just a parenthesized type. Or maybe a type pack with a single element
|
// Not a function at all. Just a parenthesized type. Or maybe a type pack with a single element
|
||||||
if (params.size() == 1 && !varargAnnotation && !forceFunctionType &&
|
if (params.size() == 1 && !varargAnnotation && !forceFunctionType && !returnTypeIntroducer)
|
||||||
(FFlag::LuauReturnTypeTokenConfusion ? !returnTypeIntroducer : lexer.current().type != Lexeme::SkinnyArrow))
|
|
||||||
{
|
{
|
||||||
if (DFFlag::LuaReportParseWrongNamedType && !names.empty())
|
if (DFFlag::LuaReportParseWrongNamedType && !names.empty())
|
||||||
lua_telemetry_parsed_named_non_function_type = true;
|
lua_telemetry_parsed_named_non_function_type = true;
|
||||||
|
@ -1389,8 +1385,7 @@ AstTypeOrPack Parser::parseFunctionTypeAnnotation(bool allowPack)
|
||||||
return {params[0], {}};
|
return {params[0], {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((FFlag::LuauReturnTypeTokenConfusion ? !returnTypeIntroducer : lexer.current().type != Lexeme::SkinnyArrow) && !forceFunctionType &&
|
if (!forceFunctionType && !returnTypeIntroducer && allowPack)
|
||||||
allowPack)
|
|
||||||
{
|
{
|
||||||
if (DFFlag::LuaReportParseWrongNamedType && !names.empty())
|
if (DFFlag::LuaReportParseWrongNamedType && !names.empty())
|
||||||
lua_telemetry_parsed_named_non_function_type = true;
|
lua_telemetry_parsed_named_non_function_type = true;
|
||||||
|
@ -1409,7 +1404,7 @@ AstType* Parser::parseFunctionTypeAnnotationTail(const Lexeme& begin, AstArray<A
|
||||||
{
|
{
|
||||||
incrementRecursionCounter("type annotation");
|
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 ':'");
|
report(lexer.current().location, "Return types in function type annotations are written after '->' instead of ':'");
|
||||||
lexer.next();
|
lexer.next();
|
||||||
|
|
|
@ -58,6 +58,9 @@ public:
|
||||||
void jmp(Label& label);
|
void jmp(Label& label);
|
||||||
void jmp(OperandX64 op);
|
void jmp(OperandX64 op);
|
||||||
|
|
||||||
|
void call(Label& label);
|
||||||
|
void call(OperandX64 op);
|
||||||
|
|
||||||
// AVX
|
// AVX
|
||||||
void vaddpd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
void vaddpd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||||
void vaddps(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)
|
if (logText)
|
||||||
log("jmp", op);
|
log("jmp", op);
|
||||||
|
|
||||||
|
placeRex(op);
|
||||||
place(0xff);
|
place(0xff);
|
||||||
placeModRegMem(op, 4);
|
placeModRegMem(op, 4);
|
||||||
commit();
|
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)
|
void AssemblyBuilderX64::vaddpd(OperandX64 dst, OperandX64 src1, OperandX64 src2)
|
||||||
{
|
{
|
||||||
placeAvx("vaddpd", dst, src1, src2, 0x58, false, AVX_0F, AVX_66);
|
placeAvx("vaddpd", dst, src1, src2, 0x58, false, AVX_0F, AVX_66);
|
||||||
|
|
|
@ -247,12 +247,15 @@ if(TARGET Luau.UnitTest)
|
||||||
tests/IostreamOptional.h
|
tests/IostreamOptional.h
|
||||||
tests/ScopedFlags.h
|
tests/ScopedFlags.h
|
||||||
tests/Fixture.cpp
|
tests/Fixture.cpp
|
||||||
|
tests/AssemblyBuilderX64.test.cpp
|
||||||
tests/AstQuery.test.cpp
|
tests/AstQuery.test.cpp
|
||||||
tests/AstVisitor.test.cpp
|
tests/AstVisitor.test.cpp
|
||||||
tests/Autocomplete.test.cpp
|
tests/Autocomplete.test.cpp
|
||||||
tests/BuiltinDefinitions.test.cpp
|
tests/BuiltinDefinitions.test.cpp
|
||||||
tests/Compiler.test.cpp
|
tests/Compiler.test.cpp
|
||||||
tests/Config.test.cpp
|
tests/Config.test.cpp
|
||||||
|
tests/ConstraintGraphBuilder.test.cpp
|
||||||
|
tests/ConstraintSolver.test.cpp
|
||||||
tests/CostModel.test.cpp
|
tests/CostModel.test.cpp
|
||||||
tests/Error.test.cpp
|
tests/Error.test.cpp
|
||||||
tests/Frontend.test.cpp
|
tests/Frontend.test.cpp
|
||||||
|
@ -262,8 +265,7 @@ if(TARGET Luau.UnitTest)
|
||||||
tests/Module.test.cpp
|
tests/Module.test.cpp
|
||||||
tests/NonstrictMode.test.cpp
|
tests/NonstrictMode.test.cpp
|
||||||
tests/Normalize.test.cpp
|
tests/Normalize.test.cpp
|
||||||
tests/ConstraintGraphBuilder.test.cpp
|
tests/NotNull.test.cpp
|
||||||
tests/ConstraintSolver.test.cpp
|
|
||||||
tests/Parser.test.cpp
|
tests/Parser.test.cpp
|
||||||
tests/RequireTracer.test.cpp
|
tests/RequireTracer.test.cpp
|
||||||
tests/RuntimeLimits.test.cpp
|
tests/RuntimeLimits.test.cpp
|
||||||
|
@ -295,11 +297,11 @@ if(TARGET Luau.UnitTest)
|
||||||
tests/TypeInfer.tryUnify.test.cpp
|
tests/TypeInfer.tryUnify.test.cpp
|
||||||
tests/TypeInfer.typePacks.cpp
|
tests/TypeInfer.typePacks.cpp
|
||||||
tests/TypeInfer.unionTypes.test.cpp
|
tests/TypeInfer.unionTypes.test.cpp
|
||||||
|
tests/TypeInfer.unknownnever.test.cpp
|
||||||
tests/TypePack.test.cpp
|
tests/TypePack.test.cpp
|
||||||
tests/TypeVar.test.cpp
|
tests/TypeVar.test.cpp
|
||||||
tests/Variant.test.cpp
|
tests/Variant.test.cpp
|
||||||
tests/VisitTypeVar.test.cpp
|
tests/VisitTypeVar.test.cpp
|
||||||
tests/AssemblyBuilderX64.test.cpp
|
|
||||||
tests/main.cpp)
|
tests/main.cpp)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
@ -108,9 +108,9 @@ static LuaNode* hashvec(const Table* t, const float* v)
|
||||||
memcpy(i, v, sizeof(i));
|
memcpy(i, v, sizeof(i));
|
||||||
|
|
||||||
// convert -0 to 0 to make sure they hash to the same value
|
// convert -0 to 0 to make sure they hash to the same value
|
||||||
i[0] = (i[0] == 0x8000000) ? 0 : i[0];
|
i[0] = (i[0] == 0x80000000) ? 0 : i[0];
|
||||||
i[1] = (i[1] == 0x8000000) ? 0 : i[1];
|
i[1] = (i[1] == 0x80000000) ? 0 : i[1];
|
||||||
i[2] = (i[2] == 0x8000000) ? 0 : i[2];
|
i[2] = (i[2] == 0x80000000) ? 0 : i[2];
|
||||||
|
|
||||||
// scramble bits to make sure that integer coordinates have entropy in lower bits
|
// scramble bits to make sure that integer coordinates have entropy in lower bits
|
||||||
i[0] ^= i[0] >> 17;
|
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);
|
unsigned int h = (i[0] * 73856093) ^ (i[1] * 19349663) ^ (i[2] * 83492791);
|
||||||
|
|
||||||
#if LUA_VECTOR_SIZE == 4
|
#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;
|
i[3] ^= i[3] >> 17;
|
||||||
h ^= i[3] * 39916801;
|
h ^= i[3] * 39916801;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -640,20 +640,16 @@ static void luau_execute(lua_State* L)
|
||||||
VM_PATCH_C(pc - 2, L->cachedslot);
|
VM_PATCH_C(pc - 2, L->cachedslot);
|
||||||
VM_NEXT();
|
VM_NEXT();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
// fall through to slow path
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// slow-path, may invoke Lua calls via __index metamethod
|
||||||
|
VM_PROTECT(luaV_gettable(L, rb, kv, ra));
|
||||||
|
VM_NEXT();
|
||||||
}
|
}
|
||||||
|
|
||||||
VM_CASE(LOP_SETTABLEKS)
|
VM_CASE(LOP_SETTABLEKS)
|
||||||
|
@ -753,19 +749,13 @@ static void luau_execute(lua_State* L)
|
||||||
setobj2s(L, ra, &h->array[unsigned(index - 1)]);
|
setobj2s(L, ra, &h->array[unsigned(index - 1)]);
|
||||||
VM_NEXT();
|
VM_NEXT();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
// fall through to slow path
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
VM_CASE(LOP_SETTABLE)
|
||||||
|
@ -790,19 +780,13 @@ static void luau_execute(lua_State* L)
|
||||||
luaC_barriert(L, h, ra);
|
luaC_barriert(L, h, ra);
|
||||||
VM_NEXT();
|
VM_NEXT();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
// fall through to slow path
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
VM_CASE(LOP_GETTABLEN)
|
||||||
|
@ -822,6 +806,8 @@ static void luau_execute(lua_State* L)
|
||||||
setobj2s(L, ra, &h->array[c]);
|
setobj2s(L, ra, &h->array[c]);
|
||||||
VM_NEXT();
|
VM_NEXT();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fall through to slow path
|
||||||
}
|
}
|
||||||
|
|
||||||
// slow-path: handles out of bounds array lookups
|
// slow-path: handles out of bounds array lookups
|
||||||
|
@ -849,6 +835,8 @@ static void luau_execute(lua_State* L)
|
||||||
luaC_barriert(L, h, ra);
|
luaC_barriert(L, h, ra);
|
||||||
VM_NEXT();
|
VM_NEXT();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fall through to slow path
|
||||||
}
|
}
|
||||||
|
|
||||||
// slow-path: handles out of bounds array lookups
|
// 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))
|
if (!ttisnumber(ra + 0) || !ttisnumber(ra + 1) || !ttisnumber(ra + 2))
|
||||||
{
|
{
|
||||||
// slow-path: can convert arguments to numbers and trigger Lua errors
|
// 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
|
// Note: this doesn't reallocate stack so we don't need to recompute ra/base
|
||||||
VM_PROTECT(luau_prepareFORN(L, ra + 0, ra + 1, ra + 2));
|
VM_PROTECT_PC();
|
||||||
|
|
||||||
|
luau_prepareFORN(L, ra + 0, ra + 1, ra + 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
double limit = nvalue(ra + 0);
|
double limit = nvalue(ra + 0);
|
||||||
|
|
|
@ -101,8 +101,10 @@ def getVmOutput(cmd):
|
||||||
elif arguments.callgrind:
|
elif arguments.callgrind:
|
||||||
try:
|
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)
|
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)
|
||||||
file = open(os.path.join(scriptdir, "callgrind.out"), "r")
|
path = os.path.join(scriptdir, "callgrind.out")
|
||||||
lines = file.readlines()
|
with open(path, "r") as file:
|
||||||
|
lines = file.readlines()
|
||||||
|
os.unlink(path)
|
||||||
return getCallgrindOutput(lines)
|
return getCallgrindOutput(lines)
|
||||||
except:
|
except:
|
||||||
return ""
|
return ""
|
||||||
|
@ -402,12 +404,12 @@ def analyzeResult(subdir, main, comparisons):
|
||||||
|
|
||||||
continue
|
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))
|
tStat = abs(main.avg - compare.avg) / (pooledStdDev * math.sqrt(2 / main.count))
|
||||||
degreesOfFreedom = 2 * main.count - 2
|
degreesOfFreedom = 2 * main.count - 2
|
||||||
|
|
||||||
if stats:
|
|
||||||
# Two-tailed distribution with 95% conf.
|
# Two-tailed distribution with 95% conf.
|
||||||
tCritical = stats.t.ppf(1 - 0.05 / 2, degreesOfFreedom)
|
tCritical = stats.t.ppf(1 - 0.05 / 2, degreesOfFreedom)
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
PCRE2-based RegEx implemention for Luau
|
PCRE2-based RegEx implemention for Luau
|
||||||
Version 1.0.0a2
|
Version 1.0.0a2
|
||||||
BSD 2-Clause Licence
|
BSD 2-Clause Licence
|
||||||
Copyright © 2020 - Blockzez (devforum.roblox.com/u/Blockzez and github.com/Blockzez)
|
Copyright © 2020 - Blockzez (devforum /u/Blockzez and github.com/Blockzez)
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
|
|
@ -213,6 +213,16 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfLea")
|
||||||
SINGLE_COMPARE(lea(rax, qword[r13 + r12 * 4 + 4]), 0x4b, 0x8d, 0x44, 0xa5, 0x04);
|
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")
|
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "ControlFlow")
|
||||||
{
|
{
|
||||||
// Jump back
|
// Jump back
|
||||||
|
@ -260,6 +270,23 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "ControlFlow")
|
||||||
{0xe9, 0x04, 0x00, 0x00, 0x00, 0x48, 0x83, 0xe7, 0x3e});
|
{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")
|
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXBinaryInstructionForms")
|
||||||
{
|
{
|
||||||
SINGLE_COMPARE(vaddpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xa9, 0x58, 0xc6);
|
SINGLE_COMPARE(vaddpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xa9, 0x58, 0xc6);
|
||||||
|
|
|
@ -105,4 +105,37 @@ if true then
|
||||||
REQUIRE(parentStat->is<AstStatIf>());
|
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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -128,6 +128,7 @@ struct Fixture
|
||||||
std::optional<TypeId> lookupImportedType(const std::string& moduleAlias, const std::string& name);
|
std::optional<TypeId> lookupImportedType(const std::string& moduleAlias, const std::string& name);
|
||||||
|
|
||||||
ScopedFastFlag sff_DebugLuauFreezeArena;
|
ScopedFastFlag sff_DebugLuauFreezeArena;
|
||||||
|
ScopedFastFlag sff_UnknownNever{"LuauUnknownAndNeverType", true};
|
||||||
|
|
||||||
TestFileResolver fileResolver;
|
TestFileResolver fileResolver;
|
||||||
TestConfigResolver configResolver;
|
TestConfigResolver configResolver;
|
||||||
|
|
|
@ -301,8 +301,6 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "any_persistance_does_not_leak")
|
TEST_CASE_FIXTURE(Fixture, "any_persistance_does_not_leak")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauNonCopyableTypeVarFields{"LuauNonCopyableTypeVarFields", true};
|
|
||||||
|
|
||||||
fileResolver.source["Module/A"] = R"(
|
fileResolver.source["Module/A"] = R"(
|
||||||
export type A = B
|
export type A = B
|
||||||
type B = A
|
type B = A
|
||||||
|
|
|
@ -1055,8 +1055,6 @@ export type t1 = { a: typeof(string.byte) }
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "intersection_combine_on_bound_self")
|
TEST_CASE_FIXTURE(Fixture, "intersection_combine_on_bound_self")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauNormalizeCombineEqFix{"LuauNormalizeCombineEqFix", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
export type t0 = (((any)&({_:l0.t0,n0:t0,_G:any,}))&({_:any,}))&(((any)&({_:l0.t0,n0:t0,_G:any,}))&({_:any,}))
|
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);
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "normalization_does_not_convert_ever")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
ScopedFastFlag sff[]{
|
||||||
|
|
|
@ -2648,7 +2648,6 @@ type Z<T> = { a: string | T..., b: number }
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "recover_function_return_type_annotations")
|
TEST_CASE_FIXTURE(Fixture, "recover_function_return_type_annotations")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauReturnTypeTokenConfusion", true};
|
|
||||||
ParseResult result = tryParse(R"(
|
ParseResult result = tryParse(R"(
|
||||||
type Custom<A, B, C> = { x: A, y: B, z: C }
|
type Custom<A, B, C> = { x: A, y: B, z: C }
|
||||||
type Packed<A...> = { x: (A...) -> () }
|
type Packed<A...> = { x: (A...) -> () }
|
||||||
|
|
|
@ -499,7 +499,7 @@ local function target(callback: nil) return callback(4, "hello") end
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERRORS(result);
|
LUAU_REQUIRE_ERRORS(result);
|
||||||
CHECK_EQ("(nil) -> (*unknown*)", toString(requireType("target")));
|
CHECK_EQ("(nil) -> (<error-type>)", toString(requireType("target")));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "toStringGenericPack")
|
TEST_CASE_FIXTURE(Fixture, "toStringGenericPack")
|
||||||
|
|
|
@ -94,7 +94,7 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_error")
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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")
|
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);
|
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")
|
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", 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")
|
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" {}
|
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")
|
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);
|
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")
|
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("number", toString(requireType("a")));
|
||||||
CHECK_EQ("string", toString(requireType("b")));
|
CHECK_EQ("string", toString(requireType("b")));
|
||||||
CHECK_EQ("boolean", toString(requireType("c")));
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "set_metatable_needs_arguments")
|
||||||
|
@ -965,8 +965,8 @@ a:b()
|
||||||
a:b({})
|
a:b({})
|
||||||
)");
|
)");
|
||||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
CHECK_EQ(result.errors[0], (TypeError{Location{{2, 0}, {2, 5}}, CountMismatch{2, 0}}));
|
CHECK_EQ(toString(result.errors[0]), "Argument count mismatch. Function expects 2 arguments, but none are specified");
|
||||||
CHECK_EQ(result.errors[1], (TypeError{Location{{3, 0}, {3, 5}}, CountMismatch{2, 1}}));
|
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")
|
TEST_CASE_FIXTURE(Fixture, "typeof_unresolved_function")
|
||||||
|
@ -1008,4 +1008,139 @@ end
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -916,13 +916,13 @@ TEST_CASE_FIXTURE(Fixture, "function_cast_error_uses_correct_language")
|
||||||
REQUIRE(tm1);
|
REQUIRE(tm1);
|
||||||
|
|
||||||
CHECK_EQ("(string) -> number", toString(tm1->wantedType));
|
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]);
|
auto tm2 = get<TypeMismatch>(result.errors[1]);
|
||||||
REQUIRE(tm2);
|
REQUIRE(tm2);
|
||||||
|
|
||||||
CHECK_EQ("(number, number) -> (number, number)", toString(tm2->wantedType));
|
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")
|
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);
|
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:
|
caused by:
|
||||||
Argument count mismatch. Function expects 1 argument, but none are specified)",
|
Argument count mismatch. Function expects 1 argument, but none are specified)",
|
||||||
toString(result.errors[0]));
|
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
|
// 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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
|
|
||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(LuauCheckGenericHOFTypes)
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("GenericsTests");
|
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");
|
std::optional<TypeFun> t0 = getMainModule()->getModuleScope()->lookupType("t0");
|
||||||
REQUIRE(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) {
|
auto it = std::find_if(result.errors.begin(), result.errors.end(), [](TypeError& err) {
|
||||||
return get<OccursCheckFailed>(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
|
local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not inferred
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERRORS(result);
|
if (FFlag::LuauCheckGenericHOFTypes)
|
||||||
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",
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
toString(result.errors[0]));
|
}
|
||||||
|
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")
|
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")));
|
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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -142,7 +142,7 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_error")
|
||||||
CHECK_EQ(2, result.errors.size());
|
CHECK_EQ(2, result.errors.size());
|
||||||
|
|
||||||
TypeId p = requireType("p");
|
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")
|
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");
|
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")
|
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);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
std::optional<TypeId> oty = requireType("ModuleA");
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_modify_imported_types")
|
||||||
|
@ -302,6 +302,30 @@ type Rename = typeof(x.x)
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "module_type_conflict")
|
||||||
{
|
{
|
||||||
fileResolver.source["game/A"] = R"(
|
fileResolver.source["game/A"] = R"(
|
||||||
|
@ -363,4 +387,21 @@ caused by:
|
||||||
Property 'x' is not compatible. Type 'number' could not be converted into 'string')");
|
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();
|
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");
|
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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -47,7 +47,7 @@ TEST_CASE_FIXTURE(Fixture, "string_index")
|
||||||
REQUIRE(nat);
|
REQUIRE(nat);
|
||||||
CHECK_EQ("string", toString(nat->ty));
|
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")
|
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})));
|
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 sffi{"LuauTarjanChildLimit", 1};
|
||||||
ScopedFastInt sffi2{"LuauTypeInferIterationLimit", 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")));
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "greedy_inference_with_shared_self_triggers_function_with_no_returns")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"DebugLuauSharedSelf", true};
|
ScopedFastFlag sff{"DebugLuauSharedSelf", true};
|
||||||
|
|
|
@ -272,8 +272,8 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_only_look_up_types_from_global_scope")
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
CHECK_EQ("*unknown*", toString(requireTypeAtPosition({8, 44})));
|
CHECK_EQ("never", toString(requireTypeAtPosition({8, 44})));
|
||||||
CHECK_EQ("*unknown*", toString(requireTypeAtPosition({9, 38})));
|
CHECK_EQ("never", toString(requireTypeAtPosition({9, 38})));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "call_a_more_specific_function_using_typeguard")
|
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);
|
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")
|
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})));
|
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"(
|
CheckResult result = check(R"(
|
||||||
local function f(t: {x: number})
|
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);
|
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")
|
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("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"
|
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);
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "falsiness_of_TruthyPredicate_narrows_into_nil")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"LuauFalsyPredicateReturnsNilInstead", true};
|
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})));
|
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();
|
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));
|
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();
|
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?
|
// TODO: Should we assert anything about these tests when DCR is being used?
|
||||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
{
|
{
|
||||||
CHECK_EQ("*unknown*", toString(requireType("c")));
|
CHECK_EQ("<error-type>", toString(requireType("c")));
|
||||||
CHECK_EQ("*unknown*", toString(requireType("d")));
|
CHECK_EQ("<error-type>", toString(requireType("d")));
|
||||||
CHECK_EQ("*unknown*", toString(requireType("e")));
|
CHECK_EQ("<error-type>", toString(requireType("e")));
|
||||||
CHECK_EQ("*unknown*", toString(requireType("f")));
|
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");
|
std::optional<TypeFun> t0 = getMainModule()->getModuleScope()->lookupType("t0");
|
||||||
REQUIRE(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) {
|
auto it = std::find_if(result.errors.begin(), result.errors.end(), [](TypeError& err) {
|
||||||
return get<OccursCheckFailed>(err);
|
return get<OccursCheckFailed>(err);
|
||||||
|
|
|
@ -121,7 +121,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "members_of_failed_typepack_unification_are_u
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
CHECK_EQ("a", toString(requireType("a")));
|
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")
|
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);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
CHECK_EQ("a", toString(requireType("a")));
|
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")));
|
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->missing[0], *bTy);
|
||||||
CHECK_EQ(mup->key, "x");
|
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")
|
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")
|
TEST_CASE("content_reassignment")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauNonCopyableTypeVarFields{"LuauNonCopyableTypeVarFields", true};
|
|
||||||
|
|
||||||
TypePackVar myError{Unifiable::Error{}, /*presistent*/ true};
|
TypePackVar myError{Unifiable::Error{}, /*presistent*/ true};
|
||||||
|
|
||||||
TypeArena arena;
|
TypeArena arena;
|
||||||
|
|
|
@ -418,8 +418,6 @@ TEST_CASE("proof_that_isBoolean_uses_all_of")
|
||||||
|
|
||||||
TEST_CASE("content_reassignment")
|
TEST_CASE("content_reassignment")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauNonCopyableTypeVarFields{"LuauNonCopyableTypeVarFields", true};
|
|
||||||
|
|
||||||
TypeVar myAny{AnyTypeVar{}, /*presistent*/ true};
|
TypeVar myAny{AnyTypeVar{}, /*presistent*/ true};
|
||||||
myAny.normal = true;
|
myAny.normal = true;
|
||||||
myAny.documentationSymbol = "@global/any";
|
myAny.documentationSymbol = "@global/any";
|
||||||
|
|
|
@ -101,4 +101,20 @@ if vector_size == 4 then
|
||||||
assert(vector(1, 2, 3, 4).W == 4)
|
assert(vector(1, 2, 3, 4).W == 4)
|
||||||
end
|
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'
|
return 'OK'
|
||||||
|
|
|
@ -137,16 +137,28 @@
|
||||||
<DisplayString>{data,s}</DisplayString>
|
<DisplayString>{data,s}</DisplayString>
|
||||||
</Type>
|
</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">
|
<Type Name ="::lua_State">
|
||||||
<DisplayString Condition="ci->func->value.gc->cl.isC">
|
<DisplayString Condition="ci">{ci,na}</DisplayString>
|
||||||
{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>thread</DisplayString>
|
<DisplayString>thread</DisplayString>
|
||||||
<Expand>
|
<Expand>
|
||||||
<Synthetic Name="[call stack]">
|
<Synthetic Name="[call stack]">
|
||||||
|
@ -156,7 +168,7 @@
|
||||||
<Size>ci-base_ci</Size>
|
<Size>ci-base_ci</Size>
|
||||||
<!-- the +1 is omitted here to avoid some issues with a blank call -->
|
<!-- the +1 is omitted here to avoid some issues with a blank call -->
|
||||||
<ValueNode>
|
<ValueNode>
|
||||||
base_ci[ci-base_ci - $i].func->value.gc->cl,view(short)
|
base_ci[ci-base_ci - $i]
|
||||||
</ValueNode>
|
</ValueNode>
|
||||||
</IndexListItems>
|
</IndexListItems>
|
||||||
</Expand>
|
</Expand>
|
||||||
|
|
Loading…
Reference in a new issue