Sync to upstream/release/595 (#1044)

* Rerun clang-format on the code
* Fix the variance on indexer result subtyping. This fixes some issues
with inconsistent error reporting.
* Fix a bug in the normalization logic for intersections of strings

New Type Solver

* New overload selection logic
* Subtype tests now correctly treat a generic as its upper bound within
that generic's scope
* Semantic subtyping for negation types
* Semantic subtyping between strings and compatible table types like
`{lower: (string) -> string}`
* Further work toward finalizing our new subtype test
* Correctly generalize module-scope symbols

Native Codegen

* Lowering statistics for assembly
* Make executable allocation size/limit configurable without a rebuild.
Use `FInt::LuauCodeGenBlockSize` and `FInt::LuauCodeGenMaxTotalSize`.

---------

Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Lily Brown <lbrown@roblox.com>
This commit is contained in:
Andy Friesen 2023-09-15 10:26:59 -07:00 committed by GitHub
parent a35d3d4588
commit 31a017c5c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
61 changed files with 1250 additions and 581 deletions

View file

@ -101,9 +101,10 @@ struct ConstraintGraphBuilder
DcrLogger* logger; DcrLogger* logger;
ConstraintGraphBuilder(ModulePtr module, NotNull<Normalizer> normalizer, NotNull<ModuleResolver> moduleResolver, NotNull<BuiltinTypes> builtinTypes, ConstraintGraphBuilder(ModulePtr module, NotNull<Normalizer> normalizer, NotNull<ModuleResolver> moduleResolver,
NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope,
DcrLogger* logger, NotNull<DataFlowGraph> dfg, std::vector<RequireCycle> requireCycles); std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, DcrLogger* logger, NotNull<DataFlowGraph> dfg,
std::vector<RequireCycle> requireCycles);
/** /**
* Fabricates a new free type belonging to a given scope. * Fabricates a new free type belonging to a given scope.

View file

@ -23,4 +23,4 @@ struct GlobalTypes
ScopePtr globalScope; // shared by all modules ScopePtr globalScope; // shared by all modules
}; };
} } // namespace Luau

View file

@ -17,8 +17,8 @@ struct TypeCheckLimits;
// A substitution which replaces generic types in a given set by free types. // A substitution which replaces generic types in a given set by free types.
struct ReplaceGenerics : Substitution struct ReplaceGenerics : Substitution
{ {
ReplaceGenerics(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope, const std::vector<TypeId>& generics, ReplaceGenerics(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope,
const std::vector<TypePackId>& genericPacks) const std::vector<TypeId>& generics, const std::vector<TypePackId>& genericPacks)
: Substitution(log, arena) : Substitution(log, arena)
, builtinTypes(builtinTypes) , builtinTypes(builtinTypes)
, level(level) , level(level)
@ -77,6 +77,7 @@ struct Instantiation : Substitution
* Instantiation fails only when processing the type causes internal recursion * Instantiation fails only when processing the type causes internal recursion
* limits to be exceeded. * limits to be exceeded.
*/ */
std::optional<TypeId> instantiate(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, TypeId ty); std::optional<TypeId> instantiate(
NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, TypeId ty);
} // namespace Luau } // namespace Luau

View file

@ -19,20 +19,18 @@ class TypeIds;
class Normalizer; class Normalizer;
struct NormalizedType; struct NormalizedType;
struct NormalizedClassType; struct NormalizedClassType;
struct NormalizedStringType;
struct NormalizedFunctionType; struct NormalizedFunctionType;
struct SubtypingResult struct SubtypingResult
{ {
// Did the test succeed?
bool isSubtype = false; bool isSubtype = false;
bool isErrorSuppressing = false; bool isErrorSuppressing = false;
bool normalizationTooComplex = false; bool normalizationTooComplex = false;
// If so, what constraints are implied by this relation? SubtypingResult& andAlso(const SubtypingResult& other);
// If not, what happened? SubtypingResult& orElse(const SubtypingResult& other);
void andAlso(const SubtypingResult& other);
void orElse(const SubtypingResult& other);
// Only negates the `isSubtype`. // Only negates the `isSubtype`.
static SubtypingResult negate(const SubtypingResult& result); static SubtypingResult negate(const SubtypingResult& result);
@ -47,6 +45,8 @@ struct Subtyping
NotNull<Normalizer> normalizer; NotNull<Normalizer> normalizer;
NotNull<InternalErrorReporter> iceReporter; NotNull<InternalErrorReporter> iceReporter;
NotNull<Scope> scope;
enum class Variance enum class Variance
{ {
Covariant, Covariant,
@ -72,6 +72,12 @@ struct Subtyping
SeenSet seenTypes; SeenSet seenTypes;
Subtyping(const Subtyping&) = delete;
Subtyping& operator=(const Subtyping&) = delete;
Subtyping(Subtyping&&) = default;
Subtyping& operator=(Subtyping&&) = default;
// TODO cache // TODO cache
// TODO cyclic types // TODO cyclic types
// TODO recursion limits // TODO recursion limits
@ -80,43 +86,61 @@ struct Subtyping
SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy); SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy);
private: private:
SubtypingResult isSubtype_(TypeId subTy, TypeId superTy); SubtypingResult isCovariantWith(TypeId subTy, TypeId superTy);
SubtypingResult isSubtype_(TypePackId subTy, TypePackId superTy); SubtypingResult isCovariantWith(TypePackId subTy, TypePackId superTy);
template<typename SubTy, typename SuperTy> template<typename SubTy, typename SuperTy>
SubtypingResult isSubtype_(const TryPair<const SubTy*, const SuperTy*>& pair); SubtypingResult isContravariantWith(SubTy&& subTy, SuperTy&& superTy);
SubtypingResult isSubtype_(TypeId subTy, const UnionType* superUnion); template<typename SubTy, typename SuperTy>
SubtypingResult isSubtype_(const UnionType* subUnion, TypeId superTy); SubtypingResult isInvariantWith(SubTy&& subTy, SuperTy&& superTy);
SubtypingResult isSubtype_(TypeId subTy, const IntersectionType* superIntersection);
SubtypingResult isSubtype_(const IntersectionType* subIntersection, TypeId superTy);
SubtypingResult isSubtype_(const PrimitiveType* subPrim, const PrimitiveType* superPrim);
SubtypingResult isSubtype_(const SingletonType* subSingleton, const PrimitiveType* superPrim);
SubtypingResult isSubtype_(const SingletonType* subSingleton, const SingletonType* superSingleton);
SubtypingResult isSubtype_(const TableType* subTable, const TableType* superTable);
SubtypingResult isSubtype_(const MetatableType* subMt, const MetatableType* superMt);
SubtypingResult isSubtype_(const MetatableType* subMt, const TableType* superTable);
SubtypingResult isSubtype_(const ClassType* subClass, const ClassType* superClass);
SubtypingResult isSubtype_(const ClassType* subClass, const TableType* superTable); // Actually a class <: shape.
SubtypingResult isSubtype_(const FunctionType* subFunction, const FunctionType* superFunction);
SubtypingResult isSubtype_(const PrimitiveType* subPrim, const TableType* superTable);
SubtypingResult isSubtype_(const SingletonType* subSingleton, const TableType* superTable);
SubtypingResult isSubtype_(const NormalizedType* subNorm, const NormalizedType* superNorm); template<typename SubTy, typename SuperTy>
SubtypingResult isSubtype_(const NormalizedClassType& subClass, const NormalizedClassType& superClass, const TypeIds& superTables); SubtypingResult isCovariantWith(const TryPair<const SubTy*, const SuperTy*>& pair);
SubtypingResult isSubtype_(const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction);
SubtypingResult isSubtype_(const TypeIds& subTypes, const TypeIds& superTypes);
SubtypingResult isSubtype_(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic); template<typename SubTy, typename SuperTy>
SubtypingResult isContravariantWith(const TryPair<const SubTy*, const SuperTy*>& pair);
template<typename SubTy, typename SuperTy>
SubtypingResult isInvariantWith(const TryPair<const SubTy*, const SuperTy*>& pair);
SubtypingResult isCovariantWith(TypeId subTy, const UnionType* superUnion);
SubtypingResult isCovariantWith(const UnionType* subUnion, TypeId superTy);
SubtypingResult isCovariantWith(TypeId subTy, const IntersectionType* superIntersection);
SubtypingResult isCovariantWith(const IntersectionType* subIntersection, TypeId superTy);
SubtypingResult isCovariantWith(const NegationType* subNegation, TypeId superTy);
SubtypingResult isCovariantWith(const TypeId subTy, const NegationType* superNegation);
SubtypingResult isCovariantWith(const PrimitiveType* subPrim, const PrimitiveType* superPrim);
SubtypingResult isCovariantWith(const SingletonType* subSingleton, const PrimitiveType* superPrim);
SubtypingResult isCovariantWith(const SingletonType* subSingleton, const SingletonType* superSingleton);
SubtypingResult isCovariantWith(const TableType* subTable, const TableType* superTable);
SubtypingResult isCovariantWith(const MetatableType* subMt, const MetatableType* superMt);
SubtypingResult isCovariantWith(const MetatableType* subMt, const TableType* superTable);
SubtypingResult isCovariantWith(const ClassType* subClass, const ClassType* superClass);
SubtypingResult isCovariantWith(const ClassType* subClass, const TableType* superTable);
SubtypingResult isCovariantWith(const FunctionType* subFunction, const FunctionType* superFunction);
SubtypingResult isCovariantWith(const PrimitiveType* subPrim, const TableType* superTable);
SubtypingResult isCovariantWith(const SingletonType* subSingleton, const TableType* superTable);
SubtypingResult isCovariantWith(const NormalizedType* subNorm, const NormalizedType* superNorm);
SubtypingResult isCovariantWith(const NormalizedClassType& subClass, const NormalizedClassType& superClass);
SubtypingResult isCovariantWith(const NormalizedClassType& subClass, const TypeIds& superTables);
SubtypingResult isCovariantWith(const NormalizedStringType& subString, const NormalizedStringType& superString);
SubtypingResult isCovariantWith(const NormalizedStringType& subString, const TypeIds& superTables);
SubtypingResult isCovariantWith(const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction);
SubtypingResult isCovariantWith(const TypeIds& subTypes, const TypeIds& superTypes);
SubtypingResult isCovariantWith(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic);
bool bindGeneric(TypeId subTp, TypeId superTp); bool bindGeneric(TypeId subTp, TypeId superTp);
bool bindGeneric(TypePackId subTp, TypePackId superTp); bool bindGeneric(TypePackId subTp, TypePackId superTp);
template <typename T, typename Container> template<typename T, typename Container>
TypeId makeAggregateType(const Container& container, TypeId orElse); TypeId makeAggregateType(const Container& container, TypeId orElse);
[[noreturn]] [[noreturn]] void unexpected(TypePackId tp);
void unexpected(TypePackId tp);
}; };
} // namespace Luau } // namespace Luau

View file

@ -849,6 +849,18 @@ bool isSubclass(const ClassType* cls, const ClassType* parent);
Type* asMutable(TypeId ty); Type* asMutable(TypeId ty);
template<typename... Ts, typename T>
bool is(T&& tv)
{
if (!tv)
return false;
if constexpr (std::is_same_v<TypeId, T> && !(std::is_same_v<BoundType, Ts> || ...))
LUAU_ASSERT(get_if<BoundType>(&tv->ty) == nullptr);
return (get<Ts>(tv) || ...);
}
template<typename T> template<typename T>
const T* get(TypeId tv) const T* get(TypeId tv)
{ {

View file

@ -14,7 +14,7 @@ struct DcrLogger;
struct TypeCheckLimits; struct TypeCheckLimits;
struct UnifierSharedState; struct UnifierSharedState;
void check(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> sharedState, NotNull<TypeCheckLimits> limits, DcrLogger* logger, const SourceModule& sourceModule, void check(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> sharedState, NotNull<TypeCheckLimits> limits, DcrLogger* logger,
Module* module); const SourceModule& sourceModule, Module* module);
} // namespace Luau } // namespace Luau

View file

@ -104,7 +104,8 @@ ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypePackId
// Similar to `std::optional<std::pair<A, B>>`, but whose `sizeof()` is the same as `std::pair<A, B>` // Similar to `std::optional<std::pair<A, B>>`, but whose `sizeof()` is the same as `std::pair<A, B>`
// and cooperates with C++'s `if (auto p = ...)` syntax without the extra fatness of `std::optional`. // and cooperates with C++'s `if (auto p = ...)` syntax without the extra fatness of `std::optional`.
template<typename A, typename B> template<typename A, typename B>
struct TryPair { struct TryPair
{
A first; A first;
B second; B second;

View file

@ -105,10 +105,12 @@ struct Unifier
* Populate the vector errors with any type errors that may arise. * Populate the vector errors with any type errors that may arise.
* Populate the transaction log with the set of TypeIds that need to be reset to undo the unification attempt. * Populate the transaction log with the set of TypeIds that need to be reset to undo the unification attempt.
*/ */
void tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr); void tryUnify(
TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
private: private:
void tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr); void tryUnify_(
TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
void tryUnifyUnionWithType(TypeId subTy, const UnionType* uv, TypeId superTy); void tryUnifyUnionWithType(TypeId subTy, const UnionType* uv, TypeId superTy);
// Traverse the two types provided and block on any BlockedTypes we find. // Traverse the two types provided and block on any BlockedTypes we find.

View file

@ -55,8 +55,8 @@ struct Unifier2
bool unify(TypePackId subTp, TypePackId superTp); bool unify(TypePackId subTp, TypePackId superTp);
std::optional<TypeId> generalize(NotNull<Scope> scope, TypeId ty); std::optional<TypeId> generalize(NotNull<Scope> scope, TypeId ty);
private:
private:
/** /**
* @returns simplify(left | right) * @returns simplify(left | right)
*/ */
@ -72,4 +72,4 @@ private:
OccursCheckResult occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack); OccursCheckResult occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
}; };
} } // namespace Luau

View file

@ -10,6 +10,7 @@
LUAU_FASTINT(LuauVisitRecursionLimit) LUAU_FASTINT(LuauVisitRecursionLimit)
LUAU_FASTFLAG(LuauBoundLazyTypes2) LUAU_FASTFLAG(LuauBoundLazyTypes2)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(DebugLuauReadWriteProperties) LUAU_FASTFLAG(DebugLuauReadWriteProperties)
namespace Luau namespace Luau
@ -220,7 +221,21 @@ struct GenericTypeVisitor
traverse(btv->boundTo); traverse(btv->boundTo);
} }
else if (auto ftv = get<FreeType>(ty)) else if (auto ftv = get<FreeType>(ty))
visit(ty, *ftv); {
if (FFlag::DebugLuauDeferredConstraintResolution)
{
if (visit(ty, *ftv))
{
LUAU_ASSERT(ftv->lowerBound);
traverse(ftv->lowerBound);
LUAU_ASSERT(ftv->upperBound);
traverse(ftv->upperBound);
}
}
else
visit(ty, *ftv);
}
else if (auto gtv = get<GenericType>(ty)) else if (auto gtv = get<GenericType>(ty))
visit(ty, *gtv); visit(ty, *gtv);
else if (auto etv = get<ErrorType>(ty)) else if (auto etv = get<ErrorType>(ty))

View file

@ -282,20 +282,8 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul
ParenthesesRecommendation parens = ParenthesesRecommendation parens =
indexType == PropIndexType::Key ? ParenthesesRecommendation::None : getParenRecommendation(type, nodes, typeCorrect); indexType == PropIndexType::Key ? ParenthesesRecommendation::None : getParenRecommendation(type, nodes, typeCorrect);
result[name] = AutocompleteEntry{ result[name] = AutocompleteEntry{AutocompleteEntryKind::Property, type, prop.deprecated, isWrongIndexer(type), typeCorrect,
AutocompleteEntryKind::Property, containingClass, &prop, prop.documentationSymbol, {}, parens, {}, indexType == PropIndexType::Colon};
type,
prop.deprecated,
isWrongIndexer(type),
typeCorrect,
containingClass,
&prop,
prop.documentationSymbol,
{},
parens,
{},
indexType == PropIndexType::Colon
};
} }
} }
}; };
@ -606,7 +594,7 @@ std::optional<TypeId> getLocalTypeInScopeAt(const Module& module, Position posit
return {}; return {};
} }
template <typename T> template<typename T>
static std::optional<std::string> tryToStringDetailed(const ScopePtr& scope, T ty, bool functionTypeArguments) static std::optional<std::string> tryToStringDetailed(const ScopePtr& scope, T ty, bool functionTypeArguments)
{ {
ToStringOptions opts; ToStringOptions opts;
@ -1461,7 +1449,8 @@ static std::string makeAnonymous(const ScopePtr& scope, const FunctionType& func
return result; return result;
} }
static std::optional<AutocompleteEntry> makeAnonymousAutofilled(const ModulePtr& module, Position position, const AstNode* node, const std::vector<AstNode*>& ancestry) static std::optional<AutocompleteEntry> makeAnonymousAutofilled(
const ModulePtr& module, Position position, const AstNode* node, const std::vector<AstNode*>& ancestry)
{ {
const AstExprCall* call = node->as<AstExprCall>(); const AstExprCall* call = node->as<AstExprCall>();
if (!call && ancestry.size() > 1) if (!call && ancestry.size() > 1)

View file

@ -780,7 +780,7 @@ void TypeCloner::operator()(const UnionType& t)
// We're just using this FreeType as a placeholder until we've finished // We're just using this FreeType as a placeholder until we've finished
// cloning the parts of this union so it is okay that its bounds are // cloning the parts of this union so it is okay that its bounds are
// nullptr. We'll never indirect them. // nullptr. We'll never indirect them.
TypeId result = dest.addType(FreeType{nullptr, /*lowerBound*/nullptr, /*upperBound*/nullptr}); TypeId result = dest.addType(FreeType{nullptr, /*lowerBound*/ nullptr, /*upperBound*/ nullptr});
seenTypes[typeId] = result; seenTypes[typeId] = result;
std::vector<TypeId> options; std::vector<TypeId> options;

View file

@ -420,17 +420,17 @@ void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location lo
{ {
switch (shouldSuppressErrors(normalizer, ty)) switch (shouldSuppressErrors(normalizer, ty))
{ {
case ErrorSuppression::DoNotSuppress: case ErrorSuppression::DoNotSuppress:
ty = simplifyIntersection(builtinTypes, arena, ty, dt).result; ty = simplifyIntersection(builtinTypes, arena, ty, dt).result;
break; break;
case ErrorSuppression::Suppress: case ErrorSuppression::Suppress:
ty = simplifyIntersection(builtinTypes, arena, ty, dt).result; ty = simplifyIntersection(builtinTypes, arena, ty, dt).result;
ty = simplifyUnion(builtinTypes, arena, ty, builtinTypes->errorType).result; ty = simplifyUnion(builtinTypes, arena, ty, builtinTypes->errorType).result;
break; break;
case ErrorSuppression::NormalizationFailed: case ErrorSuppression::NormalizationFailed:
reportError(location, NormalizationTooComplex{}); reportError(location, NormalizationTooComplex{});
ty = simplifyIntersection(builtinTypes, arena, ty, dt).result; ty = simplifyIntersection(builtinTypes, arena, ty, dt).result;
break; break;
} }
} }
} }

View file

@ -430,6 +430,35 @@ bool ConstraintSolver::isDone()
return unsolvedConstraints.empty(); return unsolvedConstraints.empty();
} }
namespace
{
struct TypeAndLocation
{
TypeId typeId;
Location location;
};
struct FreeTypeSearcher : TypeOnceVisitor
{
std::deque<TypeAndLocation>* result;
Location location;
FreeTypeSearcher(std::deque<TypeAndLocation>* result, Location location)
: result(result)
, location(location)
{
}
bool visit(TypeId ty, const FreeType&) override
{
result->push_back({ty, location});
return false;
}
};
} // namespace
void ConstraintSolver::finalizeModule() void ConstraintSolver::finalizeModule()
{ {
Anyification a{arena, rootScope, builtinTypes, &iceReporter, builtinTypes->anyType, builtinTypes->anyTypePack}; Anyification a{arena, rootScope, builtinTypes, &iceReporter, builtinTypes->anyType, builtinTypes->anyTypePack};
@ -446,12 +475,28 @@ void ConstraintSolver::finalizeModule()
Unifier2 u2{NotNull{arena}, builtinTypes, NotNull{&iceReporter}}; Unifier2 u2{NotNull{arena}, builtinTypes, NotNull{&iceReporter}};
std::deque<TypeAndLocation> queue;
for (auto& [name, binding] : rootScope->bindings) for (auto& [name, binding] : rootScope->bindings)
queue.push_back({binding.typeId, binding.location});
DenseHashSet<TypeId> seen{nullptr};
while (!queue.empty())
{ {
auto generalizedTy = u2.generalize(rootScope, binding.typeId); TypeAndLocation binding = queue.front();
if (generalizedTy) queue.pop_front();
binding.typeId = *generalizedTy;
else TypeId ty = follow(binding.typeId);
if (seen.find(ty))
continue;
seen.insert(ty);
FreeTypeSearcher fts{&queue, binding.location};
fts.traverse(ty);
auto result = u2.generalize(rootScope, ty);
if (!result)
reportError(CodeTooComplex{}, binding.location); reportError(CodeTooComplex{}, binding.location);
} }
} }
@ -2642,20 +2687,14 @@ ErrorVec ConstraintSolver::unify(NotNull<Scope> scope, Location location, TypeId
ErrorVec ConstraintSolver::unify(NotNull<Scope> scope, Location location, TypePackId subPack, TypePackId superPack) ErrorVec ConstraintSolver::unify(NotNull<Scope> scope, Location location, TypePackId subPack, TypePackId superPack)
{ {
UnifierSharedState sharedState{&iceReporter}; Unifier2 u{arena, builtinTypes, NotNull{&iceReporter}};
Unifier u{normalizer, scope, Location{}, Covariant};
u.enableNewSolver();
u.tryUnify(subPack, superPack); u.unify(subPack, superPack);
const auto [changedTypes, changedPacks] = u.log.getChanges(); unblock(subPack, Location{});
unblock(superPack, Location{});
u.log.commit(); return {};
unblock(changedTypes, Location{});
unblock(changedPacks, Location{});
return std::move(u.errors);
} }
NotNull<Constraint> ConstraintSolver::pushConstraint(NotNull<Scope> scope, const Location& location, ConstraintV cv) NotNull<Constraint> ConstraintSolver::pushConstraint(NotNull<Scope> scope, const Location& location, ConstraintV cv)

View file

@ -117,9 +117,7 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea
case DiffError::Kind::Normal: case DiffError::Kind::Normal:
{ {
checkNonMissingPropertyLeavesHaveNulloptTableProperty(); checkNonMissingPropertyLeavesHaveNulloptTableProperty();
return pathStr + conditionalNewline return pathStr + conditionalNewline + "has type" + conditionalNewline + conditionalIndent + Luau::toString(*leaf.ty);
+ "has type" + conditionalNewline
+ conditionalIndent + Luau::toString(*leaf.ty);
} }
case DiffError::Kind::MissingTableProperty: case DiffError::Kind::MissingTableProperty:
{ {
@ -127,17 +125,14 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea
{ {
if (!leaf.tableProperty.has_value()) if (!leaf.tableProperty.has_value())
throw InternalCompilerError{"leaf.tableProperty is nullopt"}; throw InternalCompilerError{"leaf.tableProperty is nullopt"};
return pathStr + "." + *leaf.tableProperty + conditionalNewline return pathStr + "." + *leaf.tableProperty + conditionalNewline + "has type" + conditionalNewline + conditionalIndent +
+ "has type" + conditionalNewline Luau::toString(*leaf.ty);
+ conditionalIndent + Luau::toString(*leaf.ty);
} }
else if (otherLeaf.ty.has_value()) else if (otherLeaf.ty.has_value())
{ {
if (!otherLeaf.tableProperty.has_value()) if (!otherLeaf.tableProperty.has_value())
throw InternalCompilerError{"otherLeaf.tableProperty is nullopt"}; throw InternalCompilerError{"otherLeaf.tableProperty is nullopt"};
return pathStr + conditionalNewline return pathStr + conditionalNewline + "is missing the property" + conditionalNewline + conditionalIndent + *otherLeaf.tableProperty;
+ "is missing the property" + conditionalNewline
+ conditionalIndent + *otherLeaf.tableProperty;
} }
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"}; throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
} }
@ -148,15 +143,11 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea
{ {
if (!leaf.unionIndex.has_value()) if (!leaf.unionIndex.has_value())
throw InternalCompilerError{"leaf.unionIndex is nullopt"}; throw InternalCompilerError{"leaf.unionIndex is nullopt"};
return pathStr + conditionalNewline return pathStr + conditionalNewline + "is a union containing type" + conditionalNewline + conditionalIndent + Luau::toString(*leaf.ty);
+ "is a union containing type" + conditionalNewline
+ conditionalIndent + Luau::toString(*leaf.ty);
} }
else if (otherLeaf.ty.has_value()) else if (otherLeaf.ty.has_value())
{ {
return pathStr + conditionalNewline return pathStr + conditionalNewline + "is a union missing type" + conditionalNewline + conditionalIndent + Luau::toString(*otherLeaf.ty);
+ "is a union missing type" + conditionalNewline
+ conditionalIndent + Luau::toString(*otherLeaf.ty);
} }
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"}; throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
} }
@ -169,15 +160,13 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea
{ {
if (!leaf.unionIndex.has_value()) if (!leaf.unionIndex.has_value())
throw InternalCompilerError{"leaf.unionIndex is nullopt"}; throw InternalCompilerError{"leaf.unionIndex is nullopt"};
return pathStr + conditionalNewline return pathStr + conditionalNewline + "is an intersection containing type" + conditionalNewline + conditionalIndent +
+ "is an intersection containing type" + conditionalNewline Luau::toString(*leaf.ty);
+ conditionalIndent + Luau::toString(*leaf.ty);
} }
else if (otherLeaf.ty.has_value()) else if (otherLeaf.ty.has_value())
{ {
return pathStr + conditionalNewline return pathStr + conditionalNewline + "is an intersection missing type" + conditionalNewline + conditionalIndent +
+ "is an intersection missing type" + conditionalNewline Luau::toString(*otherLeaf.ty);
+ conditionalIndent + Luau::toString(*otherLeaf.ty);
} }
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"}; throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
} }
@ -185,15 +174,13 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea
{ {
if (!leaf.minLength.has_value()) if (!leaf.minLength.has_value())
throw InternalCompilerError{"leaf.minLength is nullopt"}; throw InternalCompilerError{"leaf.minLength is nullopt"};
return pathStr + conditionalNewline return pathStr + conditionalNewline + "takes " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " arguments";
+ "takes " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " arguments";
} }
case DiffError::Kind::LengthMismatchInFnRets: case DiffError::Kind::LengthMismatchInFnRets:
{ {
if (!leaf.minLength.has_value()) if (!leaf.minLength.has_value())
throw InternalCompilerError{"leaf.minLength is nullopt"}; throw InternalCompilerError{"leaf.minLength is nullopt"};
return pathStr + conditionalNewline return pathStr + conditionalNewline + "returns " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " values";
+ "returns " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " values";
} }
default: default:
{ {
@ -249,17 +236,15 @@ std::string DiffError::toString(bool multiLine) const
case DiffError::Kind::IncompatibleGeneric: case DiffError::Kind::IncompatibleGeneric:
{ {
std::string diffPathStr{diffPath.toString(true)}; std::string diffPathStr{diffPath.toString(true)};
return "DiffError: these two types are not equal because the left generic at" + conditionalNewline return "DiffError: these two types are not equal because the left generic at" + conditionalNewline + conditionalIndent + leftRootName +
+ conditionalIndent + leftRootName + diffPathStr + conditionalNewline diffPathStr + conditionalNewline + "cannot be the same type parameter as the right generic at" + conditionalNewline +
+ "cannot be the same type parameter as the right generic at" + conditionalNewline conditionalIndent + rightRootName + diffPathStr;
+ conditionalIndent + rightRootName + diffPathStr;
} }
default: default:
{ {
return "DiffError: these two types are not equal because the left type at" + conditionalNewline return "DiffError: these two types are not equal because the left type at" + conditionalNewline + conditionalIndent +
+ conditionalIndent + toStringALeaf(leftRootName, left, right, multiLine) + "," + conditionalNewline + toStringALeaf(leftRootName, left, right, multiLine) + "," + conditionalNewline + "while the right type at" + conditionalNewline +
"while the right type at" + conditionalNewline conditionalIndent + toStringALeaf(rightRootName, right, left, multiLine);
+ conditionalIndent + toStringALeaf(rightRootName, right, left, multiLine);
} }
} }
} }

View file

@ -1289,7 +1289,8 @@ ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle
if (result->timeout || result->cancelled) if (result->timeout || result->cancelled)
{ {
// If solver was interrupted, skip typechecking and replace all module results with error-supressing types to avoid leaking blocked/pending types // If solver was interrupted, skip typechecking and replace all module results with error-supressing types to avoid leaking blocked/pending
// types
ScopePtr moduleScope = result->getModuleScope(); ScopePtr moduleScope = result->getModuleScope();
moduleScope->returnType = builtinTypes->errorRecoveryTypePack(); moduleScope->returnType = builtinTypes->errorRecoveryTypePack();

View file

@ -31,4 +31,4 @@ GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
} }
} }
} } // namespace Luau

View file

@ -174,7 +174,8 @@ struct Replacer : Substitution
} }
}; };
std::optional<TypeId> instantiate(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, TypeId ty) std::optional<TypeId> instantiate(
NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, TypeId ty)
{ {
ty = follow(ty); ty = follow(ty);

View file

@ -2791,8 +2791,8 @@ static void lintComments(LintContext& context, const std::vector<HotComment>& ho
else if (first == "native") else if (first == "native")
{ {
if (space != std::string::npos) if (space != std::string::npos)
emitWarning(context, LintWarning::Code_CommentDirective, hc.location, emitWarning(
"native directive has extra symbols at the end of the line"); context, LintWarning::Code_CommentDirective, hc.location, "native directive has extra symbols at the end of the line");
} }
else else
{ {

View file

@ -176,7 +176,7 @@ const NormalizedStringType NormalizedStringType::never;
bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr) bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr)
{ {
if (subStr.isUnion() && superStr.isUnion()) if (subStr.isUnion() && (superStr.isUnion() && !superStr.isNever()))
{ {
for (auto [name, ty] : subStr.singletons) for (auto [name, ty] : subStr.singletons)
{ {
@ -1983,18 +1983,68 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
void Normalizer::intersectStrings(NormalizedStringType& here, const NormalizedStringType& there) void Normalizer::intersectStrings(NormalizedStringType& here, const NormalizedStringType& there)
{ {
/* There are 9 cases to worry about here
Normalized Left | Normalized Right
C1 string | string ===> trivial
C2 string - {u_1,..} | string ===> trivial
C3 {u_1, ..} | string ===> trivial
C4 string | string - {v_1, ..} ===> string - {v_1, ..}
C5 string - {u_1,..} | string - {v_1, ..} ===> string - ({u_s} U {v_s})
C6 {u_1, ..} | string - {v_1, ..} ===> {u_s} - {v_s}
C7 string | {v_1, ..} ===> {v_s}
C8 string - {u_1,..} | {v_1, ..} ===> {v_s} - {u_s}
C9 {u_1, ..} | {v_1, ..} ===> {u_s} {v_s}
*/
// Case 1,2,3
if (there.isString()) if (there.isString())
return; return;
if (here.isString()) // Case 4, Case 7
here.resetToNever(); else if (here.isString())
for (auto it = here.singletons.begin(); it != here.singletons.end();)
{ {
if (there.singletons.count(it->first)) here.singletons.clear();
it++; for (const auto& [key, type] : there.singletons)
else here.singletons[key] = type;
it = here.singletons.erase(it); here.isCofinite = here.isCofinite && there.isCofinite;
} }
// Case 5
else if (here.isIntersection() && there.isIntersection())
{
here.isCofinite = true;
for (const auto& [key, type] : there.singletons)
here.singletons[key] = type;
}
// Case 6
else if (here.isUnion() && there.isIntersection())
{
here.isCofinite = false;
for (const auto& [key, _] : there.singletons)
here.singletons.erase(key);
}
// Case 8
else if (here.isIntersection() && there.isUnion())
{
here.isCofinite = false;
std::map<std::string, TypeId> result(there.singletons);
for (const auto& [key, _] : here.singletons)
result.erase(key);
here.singletons = result;
}
// Case 9
else if (here.isUnion() && there.isUnion())
{
here.isCofinite = false;
std::map<std::string, TypeId> result;
result.insert(here.singletons.begin(), here.singletons.end());
result.insert(there.singletons.begin(), there.singletons.end());
for (auto it = result.begin(); it != result.end();)
if (!here.singletons.count(it->first) || !there.singletons.count(it->first))
it = result.erase(it);
else
++it;
here.singletons = result;
}
else
LUAU_ASSERT(0 && "Internal Error - unrecognized case");
} }
std::optional<TypePackId> Normalizer::intersectionOfTypePacks(TypePackId here, TypePackId there) std::optional<TypePackId> Normalizer::intersectionOfTypePacks(TypePackId here, TypePackId there)

View file

@ -5,6 +5,7 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/Scope.h"
#include "Luau/StringUtils.h" #include "Luau/StringUtils.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/Type.h" #include "Luau/Type.h"
@ -28,12 +29,12 @@ struct VarianceFlipper
{ {
switch (oldValue) switch (oldValue)
{ {
case Subtyping::Variance::Covariant: case Subtyping::Variance::Covariant:
*variance = Subtyping::Variance::Contravariant; *variance = Subtyping::Variance::Contravariant;
break; break;
case Subtyping::Variance::Contravariant: case Subtyping::Variance::Contravariant:
*variance = Subtyping::Variance::Covariant; *variance = Subtyping::Variance::Covariant;
break; break;
} }
} }
@ -43,19 +44,21 @@ struct VarianceFlipper
} }
}; };
void SubtypingResult::andAlso(const SubtypingResult& other) SubtypingResult& SubtypingResult::andAlso(const SubtypingResult& other)
{ {
isSubtype &= other.isSubtype; isSubtype &= other.isSubtype;
// `|=` is intentional here, we want to preserve error related flags. // `|=` is intentional here, we want to preserve error related flags.
isErrorSuppressing |= other.isErrorSuppressing; isErrorSuppressing |= other.isErrorSuppressing;
normalizationTooComplex |= other.normalizationTooComplex; normalizationTooComplex |= other.normalizationTooComplex;
return *this;
} }
void SubtypingResult::orElse(const SubtypingResult& other) SubtypingResult& SubtypingResult::orElse(const SubtypingResult& other)
{ {
isSubtype |= other.isSubtype; isSubtype |= other.isSubtype;
isErrorSuppressing |= other.isErrorSuppressing; isErrorSuppressing |= other.isErrorSuppressing;
normalizationTooComplex |= other.normalizationTooComplex; normalizationTooComplex |= other.normalizationTooComplex;
return *this;
} }
SubtypingResult SubtypingResult::negate(const SubtypingResult& result) SubtypingResult SubtypingResult::negate(const SubtypingResult& result)
@ -88,9 +91,9 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
mappedGenerics.clear(); mappedGenerics.clear();
mappedGenericPacks.clear(); mappedGenericPacks.clear();
SubtypingResult result = isSubtype_(subTy, superTy); SubtypingResult result = isCovariantWith(subTy, superTy);
for (const auto& [subTy, bounds]: mappedGenerics) for (const auto& [subTy, bounds] : mappedGenerics)
{ {
const auto& lb = bounds.lowerBound; const auto& lb = bounds.lowerBound;
const auto& ub = bounds.upperBound; const auto& ub = bounds.upperBound;
@ -98,7 +101,7 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
TypeId lowerBound = makeAggregateType<UnionType>(lb, builtinTypes->neverType); TypeId lowerBound = makeAggregateType<UnionType>(lb, builtinTypes->neverType);
TypeId upperBound = makeAggregateType<IntersectionType>(ub, builtinTypes->unknownType); TypeId upperBound = makeAggregateType<IntersectionType>(ub, builtinTypes->unknownType);
result.andAlso(isSubtype_(lowerBound, upperBound)); result.andAlso(isCovariantWith(lowerBound, upperBound));
} }
return result; return result;
@ -106,7 +109,7 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp) SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp)
{ {
return isSubtype_(subTp, superTp); return isCovariantWith(subTp, superTp);
} }
namespace namespace
@ -119,16 +122,17 @@ struct SeenSetPopper
SeenSetPopper(Subtyping::SeenSet* seenTypes, std::pair<TypeId, TypeId> pair) SeenSetPopper(Subtyping::SeenSet* seenTypes, std::pair<TypeId, TypeId> pair)
: seenTypes(seenTypes) : seenTypes(seenTypes)
, pair(pair) , pair(pair)
{} {
}
~SeenSetPopper() ~SeenSetPopper()
{ {
seenTypes->erase(pair); seenTypes->erase(pair);
} }
}; };
} } // namespace
SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy) SubtypingResult Subtyping::isCovariantWith(TypeId subTy, TypeId superTy)
{ {
subTy = follow(subTy); subTy = follow(subTy);
superTy = follow(superTy); superTy = follow(superTy);
@ -146,19 +150,27 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy)
SeenSetPopper ssp{&seenTypes, typePair}; SeenSetPopper ssp{&seenTypes, typePair};
// Within the scope to which a generic belongs, that generic should be
// tested as though it were its upper bounds. We do not yet support bounded
// generics, so the upper bound is always unknown.
if (auto subGeneric = get<GenericType>(subTy); subGeneric && subsumes(subGeneric->scope, scope))
return isCovariantWith(builtinTypes->unknownType, superTy);
if (auto superGeneric = get<GenericType>(superTy); superGeneric && subsumes(superGeneric->scope, scope))
return isCovariantWith(subTy, builtinTypes->unknownType);
if (auto subUnion = get<UnionType>(subTy)) if (auto subUnion = get<UnionType>(subTy))
return isSubtype_(subUnion, superTy); return isCovariantWith(subUnion, superTy);
else if (auto superUnion = get<UnionType>(superTy)) else if (auto superUnion = get<UnionType>(superTy))
return isSubtype_(subTy, superUnion); return isCovariantWith(subTy, superUnion);
else if (auto superIntersection = get<IntersectionType>(superTy)) else if (auto superIntersection = get<IntersectionType>(superTy))
return isSubtype_(subTy, superIntersection); return isCovariantWith(subTy, superIntersection);
else if (auto subIntersection = get<IntersectionType>(subTy)) else if (auto subIntersection = get<IntersectionType>(subTy))
{ {
SubtypingResult result = isSubtype_(subIntersection, superTy); SubtypingResult result = isCovariantWith(subIntersection, superTy);
if (result.isSubtype || result.isErrorSuppressing || result.normalizationTooComplex) if (result.isSubtype || result.isErrorSuppressing || result.normalizationTooComplex)
return result; return result;
else else
return isSubtype_(normalizer->normalize(subTy), normalizer->normalize(superTy)); return isCovariantWith(normalizer->normalize(subTy), normalizer->normalize(superTy));
} }
else if (get<AnyType>(superTy)) else if (get<AnyType>(superTy))
return {true}; // This is always true. return {true}; // This is always true.
@ -166,14 +178,12 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy)
{ {
// any = unknown | error, so we rewrite this to match. // any = unknown | error, so we rewrite this to match.
// As per TAPL: A | B <: T iff A <: T && B <: T // As per TAPL: A | B <: T iff A <: T && B <: T
SubtypingResult result = isSubtype_(builtinTypes->unknownType, superTy); return isCovariantWith(builtinTypes->unknownType, superTy).andAlso(isCovariantWith(builtinTypes->errorType, superTy));
result.andAlso(isSubtype_(builtinTypes->errorType, superTy));
return result;
} }
else if (get<UnknownType>(superTy)) else if (get<UnknownType>(superTy))
{ {
LUAU_ASSERT(!get<AnyType>(subTy)); // TODO: replace with ice. LUAU_ASSERT(!get<AnyType>(subTy)); // TODO: replace with ice.
LUAU_ASSERT(!get<UnionType>(subTy)); // TODO: replace with ice. LUAU_ASSERT(!get<UnionType>(subTy)); // TODO: replace with ice.
LUAU_ASSERT(!get<IntersectionType>(subTy)); // TODO: replace with ice. LUAU_ASSERT(!get<IntersectionType>(subTy)); // TODO: replace with ice.
bool errorSuppressing = get<ErrorType>(subTy); bool errorSuppressing = get<ErrorType>(subTy);
@ -185,6 +195,12 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy)
return {false, true}; return {false, true};
else if (get<ErrorType>(subTy)) else if (get<ErrorType>(subTy))
return {false, true}; return {false, true};
else if (auto p = get2<NegationType, NegationType>(subTy, superTy))
return isCovariantWith(p.first->ty, p.second->ty);
else if (auto subNegation = get<NegationType>(subTy))
return isCovariantWith(subNegation, superTy);
else if (auto superNegation = get<NegationType>(superTy))
return isCovariantWith(subTy, superNegation);
else if (auto subGeneric = get<GenericType>(subTy); subGeneric && variance == Variance::Covariant) else if (auto subGeneric = get<GenericType>(subTy); subGeneric && variance == Variance::Covariant)
{ {
bool ok = bindGeneric(subTy, superTy); bool ok = bindGeneric(subTy, superTy);
@ -196,32 +212,32 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy)
return {ok}; return {ok};
} }
else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, superTy)) else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, superTy))
return isSubtype_(p); return isCovariantWith(p);
else if (auto p = get2<SingletonType, PrimitiveType>(subTy, superTy)) else if (auto p = get2<SingletonType, PrimitiveType>(subTy, superTy))
return isSubtype_(p); return isCovariantWith(p);
else if (auto p = get2<SingletonType, SingletonType>(subTy, superTy)) else if (auto p = get2<SingletonType, SingletonType>(subTy, superTy))
return isSubtype_(p); return isCovariantWith(p);
else if (auto p = get2<FunctionType, FunctionType>(subTy, superTy)) else if (auto p = get2<FunctionType, FunctionType>(subTy, superTy))
return isSubtype_(p); return isCovariantWith(p);
else if (auto p = get2<TableType, TableType>(subTy, superTy)) else if (auto p = get2<TableType, TableType>(subTy, superTy))
return isSubtype_(p); return isCovariantWith(p);
else if (auto p = get2<MetatableType, MetatableType>(subTy, superTy)) else if (auto p = get2<MetatableType, MetatableType>(subTy, superTy))
return isSubtype_(p); return isCovariantWith(p);
else if (auto p = get2<MetatableType, TableType>(subTy, superTy)) else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
return isSubtype_(p); return isCovariantWith(p);
else if (auto p = get2<ClassType, ClassType>(subTy, superTy)) else if (auto p = get2<ClassType, ClassType>(subTy, superTy))
return isSubtype_(p); return isCovariantWith(p);
else if (auto p = get2<ClassType, TableType>(subTy, superTy)) else if (auto p = get2<ClassType, TableType>(subTy, superTy))
return isSubtype_(p); return isCovariantWith(p);
else if (auto p = get2<PrimitiveType, TableType>(subTy, superTy)) else if (auto p = get2<PrimitiveType, TableType>(subTy, superTy))
return isSubtype_(p); return isCovariantWith(p);
else if (auto p = get2<SingletonType, TableType>(subTy, superTy)) else if (auto p = get2<SingletonType, TableType>(subTy, superTy))
return isSubtype_(p); return isCovariantWith(p);
return {false}; return {false};
} }
SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp) SubtypingResult Subtyping::isCovariantWith(TypePackId subTp, TypePackId superTp)
{ {
subTp = follow(subTp); subTp = follow(subTp);
superTp = follow(superTp); superTp = follow(superTp);
@ -241,7 +257,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
for (size_t i = 0; i < headSize; ++i) for (size_t i = 0; i < headSize; ++i)
{ {
results.push_back(isSubtype_(subHead[i], superHead[i])); results.push_back(isCovariantWith(subHead[i], superHead[i]));
if (!results.back().isSubtype) if (!results.back().isSubtype)
return {false}; return {false};
} }
@ -255,7 +271,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
if (auto vt = get<VariadicTypePack>(*subTail)) if (auto vt = get<VariadicTypePack>(*subTail))
{ {
for (size_t i = headSize; i < superHead.size(); ++i) for (size_t i = headSize; i < superHead.size(); ++i)
results.push_back(isSubtype_(vt->ty, superHead[i])); results.push_back(isCovariantWith(vt->ty, superHead[i]));
} }
else if (auto gt = get<GenericTypePack>(*subTail)) else if (auto gt = get<GenericTypePack>(*subTail))
{ {
@ -266,11 +282,11 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
// <X>(X) -> () <: (T) -> () // <X>(X) -> () <: (T) -> ()
// Possible optimization: If headSize == 0 then we can just use subTp as-is. // Possible optimization: If headSize == 0 then we can just use subTp as-is.
std::vector<TypeId> headSlice(begin(superHead), end(superHead) + headSize); std::vector<TypeId> headSlice(begin(superHead), begin(superHead) + headSize);
TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail); TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail);
if (TypePackId* other = mappedGenericPacks.find(*subTail)) if (TypePackId* other = mappedGenericPacks.find(*subTail))
results.push_back(isSubtype_(*other, superTailPack)); results.push_back(isCovariantWith(*other, superTailPack));
else else
mappedGenericPacks.try_insert(*subTail, superTailPack); mappedGenericPacks.try_insert(*subTail, superTailPack);
@ -300,7 +316,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
if (auto vt = get<VariadicTypePack>(*superTail)) if (auto vt = get<VariadicTypePack>(*superTail))
{ {
for (size_t i = headSize; i < subHead.size(); ++i) for (size_t i = headSize; i < subHead.size(); ++i)
results.push_back(isSubtype_(subHead[i], vt->ty)); results.push_back(isCovariantWith(subHead[i], vt->ty));
} }
else if (auto gt = get<GenericTypePack>(*superTail)) else if (auto gt = get<GenericTypePack>(*superTail))
{ {
@ -311,11 +327,11 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
// <X...>(X...) -> () <: (T) -> () // <X...>(X...) -> () <: (T) -> ()
// Possible optimization: If headSize == 0 then we can just use subTp as-is. // Possible optimization: If headSize == 0 then we can just use subTp as-is.
std::vector<TypeId> headSlice(begin(subHead), end(subHead) + headSize); std::vector<TypeId> headSlice(begin(subHead), begin(subHead) + headSize);
TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail); TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail);
if (TypePackId* other = mappedGenericPacks.find(*superTail)) if (TypePackId* other = mappedGenericPacks.find(*superTail))
results.push_back(isSubtype_(*other, subTailPack)); results.push_back(isCovariantWith(*other, subTailPack));
else else
mappedGenericPacks.try_insert(*superTail, subTailPack); mappedGenericPacks.try_insert(*superTail, subTailPack);
@ -344,7 +360,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
{ {
if (auto p = get2<VariadicTypePack, VariadicTypePack>(*subTail, *superTail)) if (auto p = get2<VariadicTypePack, VariadicTypePack>(*subTail, *superTail))
{ {
results.push_back(isSubtype_(p)); results.push_back(isCovariantWith(p));
} }
else if (auto p = get2<GenericTypePack, GenericTypePack>(*subTail, *superTail)) else if (auto p = get2<GenericTypePack, GenericTypePack>(*subTail, *superTail))
{ {
@ -380,7 +396,8 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
} }
} }
else else
iceReporter->ice(format("Subtyping::isSubtype got unexpected type packs %s and %s", toString(*subTail).c_str(), toString(*superTail).c_str())); iceReporter->ice(
format("Subtyping::isSubtype got unexpected type packs %s and %s", toString(*subTail).c_str(), toString(*superTail).c_str()));
} }
else if (subTail) else if (subTail)
{ {
@ -428,9 +445,33 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
} }
template<typename SubTy, typename SuperTy> template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isSubtype_(const TryPair<const SubTy*, const SuperTy*>& pair) SubtypingResult Subtyping::isContravariantWith(SubTy&& subTy, SuperTy&& superTy)
{ {
return isSubtype_(pair.first, pair.second); return isCovariantWith(superTy, subTy);
}
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isInvariantWith(SubTy&& subTy, SuperTy&& superTy)
{
return isCovariantWith(subTy, superTy).andAlso(isContravariantWith(subTy, superTy));
}
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isCovariantWith(const TryPair<const SubTy*, const SuperTy*>& pair)
{
return isCovariantWith(pair.first, pair.second);
}
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isContravariantWith(const TryPair<const SubTy*, const SuperTy*>& pair)
{
return isCovariantWith(pair.second, pair.first);
}
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isInvariantWith(const TryPair<const SubTy*, const SuperTy*>& pair)
{
return isCovariantWith(pair).andAlso(isContravariantWith(pair));
} }
/* /*
@ -464,48 +505,219 @@ SubtypingResult Subtyping::isSubtype_(const TryPair<const SubTy*, const SuperTy*
* other just asks for boolean ~ 'b. We can dispatch this and only commit * other just asks for boolean ~ 'b. We can dispatch this and only commit
* boolean ~ 'b. This constraint does not teach us anything about 'a. * boolean ~ 'b. This constraint does not teach us anything about 'a.
*/ */
SubtypingResult Subtyping::isSubtype_(TypeId subTy, const UnionType* superUnion) SubtypingResult Subtyping::isCovariantWith(TypeId subTy, const UnionType* superUnion)
{ {
// As per TAPL: T <: A | B iff T <: A || T <: B // As per TAPL: T <: A | B iff T <: A || T <: B
std::vector<SubtypingResult> subtypings; std::vector<SubtypingResult> subtypings;
for (TypeId ty : superUnion) for (TypeId ty : superUnion)
subtypings.push_back(isSubtype_(subTy, ty)); subtypings.push_back(isCovariantWith(subTy, ty));
return SubtypingResult::any(subtypings); return SubtypingResult::any(subtypings);
} }
SubtypingResult Subtyping::isSubtype_(const UnionType* subUnion, TypeId superTy) SubtypingResult Subtyping::isCovariantWith(const UnionType* subUnion, TypeId superTy)
{ {
// As per TAPL: A | B <: T iff A <: T && B <: T // As per TAPL: A | B <: T iff A <: T && B <: T
std::vector<SubtypingResult> subtypings; std::vector<SubtypingResult> subtypings;
for (TypeId ty : subUnion) for (TypeId ty : subUnion)
subtypings.push_back(isSubtype_(ty, superTy)); subtypings.push_back(isCovariantWith(ty, superTy));
return SubtypingResult::all(subtypings); return SubtypingResult::all(subtypings);
} }
SubtypingResult Subtyping::isSubtype_(TypeId subTy, const IntersectionType* superIntersection) SubtypingResult Subtyping::isCovariantWith(TypeId subTy, const IntersectionType* superIntersection)
{ {
// As per TAPL: T <: A & B iff T <: A && T <: B // As per TAPL: T <: A & B iff T <: A && T <: B
std::vector<SubtypingResult> subtypings; std::vector<SubtypingResult> subtypings;
for (TypeId ty : superIntersection) for (TypeId ty : superIntersection)
subtypings.push_back(isSubtype_(subTy, ty)); subtypings.push_back(isCovariantWith(subTy, ty));
return SubtypingResult::all(subtypings); return SubtypingResult::all(subtypings);
} }
SubtypingResult Subtyping::isSubtype_(const IntersectionType* subIntersection, TypeId superTy) SubtypingResult Subtyping::isCovariantWith(const IntersectionType* subIntersection, TypeId superTy)
{ {
// As per TAPL: A & B <: T iff A <: T || B <: T // As per TAPL: A & B <: T iff A <: T || B <: T
std::vector<SubtypingResult> subtypings; std::vector<SubtypingResult> subtypings;
for (TypeId ty : subIntersection) for (TypeId ty : subIntersection)
subtypings.push_back(isSubtype_(ty, superTy)); subtypings.push_back(isCovariantWith(ty, superTy));
return SubtypingResult::any(subtypings); return SubtypingResult::any(subtypings);
} }
SubtypingResult Subtyping::isSubtype_(const PrimitiveType* subPrim, const PrimitiveType* superPrim) SubtypingResult Subtyping::isCovariantWith(const NegationType* subNegation, TypeId superTy)
{
TypeId negatedTy = follow(subNegation->ty);
// In order to follow a consistent codepath, rather than folding the
// isCovariantWith test down to its conclusion here, we test the subtyping test
// of the result of negating the type for never, unknown, any, and error.
if (is<NeverType>(negatedTy))
{
// ¬never ~ unknown
return isCovariantWith(builtinTypes->unknownType, superTy);
}
else if (is<UnknownType>(negatedTy))
{
// ¬unknown ~ never
return isCovariantWith(builtinTypes->neverType, superTy);
}
else if (is<AnyType>(negatedTy))
{
// ¬any ~ any
return isCovariantWith(negatedTy, superTy);
}
else if (auto u = get<UnionType>(negatedTy))
{
// ¬(A B) ~ ¬A ∩ ¬B
// follow intersection rules: A & B <: T iff A <: T && B <: T
std::vector<SubtypingResult> subtypings;
for (TypeId ty : u)
{
NegationType negatedTmp{ty};
subtypings.push_back(isCovariantWith(&negatedTmp, superTy));
}
return SubtypingResult::all(subtypings);
}
else if (auto i = get<IntersectionType>(negatedTy))
{
// ¬(A ∩ B) ~ ¬A ¬B
// follow union rules: A | B <: T iff A <: T || B <: T
std::vector<SubtypingResult> subtypings;
for (TypeId ty : i)
{
if (auto negatedPart = get<NegationType>(follow(ty)))
subtypings.push_back(isCovariantWith(negatedPart->ty, superTy));
else
{
NegationType negatedTmp{ty};
subtypings.push_back(isCovariantWith(&negatedTmp, superTy));
}
}
return SubtypingResult::any(subtypings);
}
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
{
iceReporter->ice("attempting to negate a non-testable type");
}
// negating a different subtype will get you a very wide type that's not a
// subtype of other stuff.
else
{
return {false};
}
}
SubtypingResult Subtyping::isCovariantWith(const TypeId subTy, const NegationType* superNegation)
{
TypeId negatedTy = follow(superNegation->ty);
if (is<NeverType>(negatedTy))
{
// ¬never ~ unknown
return isCovariantWith(subTy, builtinTypes->unknownType);
}
else if (is<UnknownType>(negatedTy))
{
// ¬unknown ~ never
return isCovariantWith(subTy, builtinTypes->neverType);
}
else if (is<AnyType>(negatedTy))
{
// ¬any ~ any
return isSubtype(subTy, negatedTy);
}
else if (auto u = get<UnionType>(negatedTy))
{
// ¬(A B) ~ ¬A ∩ ¬B
// follow intersection rules: A & B <: T iff A <: T && B <: T
std::vector<SubtypingResult> subtypings;
for (TypeId ty : u)
{
if (auto negatedPart = get<NegationType>(follow(ty)))
subtypings.push_back(isCovariantWith(subTy, negatedPart->ty));
else
{
NegationType negatedTmp{ty};
subtypings.push_back(isCovariantWith(subTy, &negatedTmp));
}
}
return SubtypingResult::all(subtypings);
}
else if (auto i = get<IntersectionType>(negatedTy))
{
// ¬(A ∩ B) ~ ¬A ¬B
// follow union rules: A | B <: T iff A <: T || B <: T
std::vector<SubtypingResult> subtypings;
for (TypeId ty : i)
{
if (auto negatedPart = get<NegationType>(follow(ty)))
subtypings.push_back(isCovariantWith(subTy, negatedPart->ty));
else
{
NegationType negatedTmp{ty};
subtypings.push_back(isCovariantWith(subTy, &negatedTmp));
}
}
return SubtypingResult::any(subtypings);
}
else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, negatedTy))
{
// number <: ¬boolean
// number </: ¬number
return {p.first->type != p.second->type};
}
else if (auto p = get2<SingletonType, PrimitiveType>(subTy, negatedTy))
{
// "foo" </: ¬string
if (get<StringSingleton>(p.first) && p.second->type == PrimitiveType::String)
return {false};
// false </: ¬boolean
else if (get<BooleanSingleton>(p.first) && p.second->type == PrimitiveType::Boolean)
return {false};
// other cases are true
else
return {true};
}
else if (auto p = get2<PrimitiveType, SingletonType>(subTy, negatedTy))
{
if (p.first->type == PrimitiveType::String && get<StringSingleton>(p.second))
return {false};
else if (p.first->type == PrimitiveType::Boolean && get<BooleanSingleton>(p.second))
return {false};
else
return {true};
}
// the top class type is not actually a primitive type, so the negation of
// any one of them includes the top class type.
else if (auto p = get2<ClassType, PrimitiveType>(subTy, negatedTy))
return {true};
else if (auto p = get<PrimitiveType>(negatedTy); p && is<TableType, MetatableType>(subTy))
return {p->type != PrimitiveType::Table};
else if (auto p = get2<FunctionType, PrimitiveType>(subTy, negatedTy))
return {p.second->type != PrimitiveType::Function};
else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy))
return {*p.first != *p.second};
else if (auto p = get2<ClassType, ClassType>(subTy, negatedTy))
return SubtypingResult::negate(isCovariantWith(p.first, p.second));
else if (get2<FunctionType, ClassType>(subTy, negatedTy))
return {true};
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
iceReporter->ice("attempting to negate a non-testable type");
return {false};
}
SubtypingResult Subtyping::isCovariantWith(const PrimitiveType* subPrim, const PrimitiveType* superPrim)
{ {
return {subPrim->type == superPrim->type}; return {subPrim->type == superPrim->type};
} }
SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const PrimitiveType* superPrim) SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, const PrimitiveType* superPrim)
{ {
if (get<StringSingleton>(subSingleton) && superPrim->type == PrimitiveType::String) if (get<StringSingleton>(subSingleton) && superPrim->type == PrimitiveType::String)
return {true}; return {true};
@ -515,24 +727,20 @@ SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const P
return {false}; return {false};
} }
SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const SingletonType* superSingleton) SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, const SingletonType* superSingleton)
{ {
return {*subSingleton == *superSingleton}; return {*subSingleton == *superSingleton};
} }
SubtypingResult Subtyping::isSubtype_(const TableType* subTable, const TableType* superTable) SubtypingResult Subtyping::isCovariantWith(const TableType* subTable, const TableType* superTable)
{ {
SubtypingResult result{true}; SubtypingResult result{true};
for (const auto& [name, prop]: superTable->props) for (const auto& [name, prop] : superTable->props)
{ {
auto it = subTable->props.find(name); auto it = subTable->props.find(name);
if (it != subTable->props.end()) if (it != subTable->props.end())
{ result.andAlso(isInvariantWith(prop.type(), it->second.type()));
// Table properties are invariant
result.andAlso(isSubtype(it->second.type(), prop.type()));
result.andAlso(isSubtype(prop.type(), it->second.type()));
}
else else
return SubtypingResult{false}; return SubtypingResult{false};
} }
@ -540,17 +748,18 @@ SubtypingResult Subtyping::isSubtype_(const TableType* subTable, const TableType
return result; return result;
} }
SubtypingResult Subtyping::isSubtype_(const MetatableType* subMt, const MetatableType* superMt) SubtypingResult Subtyping::isCovariantWith(const MetatableType* subMt, const MetatableType* superMt)
{ {
return SubtypingResult::all({ return SubtypingResult::all({
isSubtype_(subMt->table, superMt->table), isCovariantWith(subMt->table, superMt->table),
isSubtype_(subMt->metatable, superMt->metatable), isCovariantWith(subMt->metatable, superMt->metatable),
}); });
} }
SubtypingResult Subtyping::isSubtype_(const MetatableType* subMt, const TableType* superTable) SubtypingResult Subtyping::isCovariantWith(const MetatableType* subMt, const TableType* superTable)
{ {
if (auto subTable = get<TableType>(subMt->table)) { if (auto subTable = get<TableType>(subMt->table))
{
// Metatables cannot erase properties from the table they're attached to, so // Metatables cannot erase properties from the table they're attached to, so
// the subtyping rule for this is just if the table component is a subtype // the subtyping rule for this is just if the table component is a subtype
// of the supertype table. // of the supertype table.
@ -560,7 +769,7 @@ SubtypingResult Subtyping::isSubtype_(const MetatableType* subMt, const TableTyp
// that the metatable isn't a subtype of the table, even though they have // that the metatable isn't a subtype of the table, even though they have
// compatible properties/shapes. We'll revisit this later when we have a // compatible properties/shapes. We'll revisit this later when we have a
// better understanding of how important this is. // better understanding of how important this is.
return isSubtype_(subTable, superTable); return isCovariantWith(subTable, superTable);
} }
else else
{ {
@ -569,23 +778,19 @@ SubtypingResult Subtyping::isSubtype_(const MetatableType* subMt, const TableTyp
} }
} }
SubtypingResult Subtyping::isSubtype_(const ClassType* subClass, const ClassType* superClass) SubtypingResult Subtyping::isCovariantWith(const ClassType* subClass, const ClassType* superClass)
{ {
return {isSubclass(subClass, superClass)}; return {isSubclass(subClass, superClass)};
} }
SubtypingResult Subtyping::isSubtype_(const ClassType* subClass, const TableType* superTable) SubtypingResult Subtyping::isCovariantWith(const ClassType* subClass, const TableType* superTable)
{ {
SubtypingResult result{true}; SubtypingResult result{true};
for (const auto& [name, prop]: superTable->props) for (const auto& [name, prop] : superTable->props)
{ {
if (auto classProp = lookupClassProp(subClass, name)) if (auto classProp = lookupClassProp(subClass, name))
{ result.andAlso(isInvariantWith(prop.type(), classProp->type()));
// Table properties are invariant
result.andAlso(isSubtype_(classProp->type(), prop.type()));
result.andAlso(isSubtype_(prop.type(), classProp->type()));
}
else else
return SubtypingResult{false}; return SubtypingResult{false};
} }
@ -593,20 +798,20 @@ SubtypingResult Subtyping::isSubtype_(const ClassType* subClass, const TableType
return result; return result;
} }
SubtypingResult Subtyping::isSubtype_(const FunctionType* subFunction, const FunctionType* superFunction) SubtypingResult Subtyping::isCovariantWith(const FunctionType* subFunction, const FunctionType* superFunction)
{ {
SubtypingResult result; SubtypingResult result;
{ {
VarianceFlipper vf{&variance}; VarianceFlipper vf{&variance};
result.orElse(isSubtype_(superFunction->argTypes, subFunction->argTypes)); result.orElse(isContravariantWith(subFunction->argTypes, superFunction->argTypes));
} }
result.andAlso(isSubtype_(subFunction->retTypes, superFunction->retTypes)); result.andAlso(isCovariantWith(subFunction->retTypes, superFunction->retTypes));
return result; return result;
} }
SubtypingResult Subtyping::isSubtype_(const PrimitiveType* subPrim, const TableType* superTable) SubtypingResult Subtyping::isCovariantWith(const PrimitiveType* subPrim, const TableType* superTable)
{ {
SubtypingResult result{false}; SubtypingResult result{false};
if (subPrim->type == PrimitiveType::String) if (subPrim->type == PrimitiveType::String)
@ -618,7 +823,7 @@ SubtypingResult Subtyping::isSubtype_(const PrimitiveType* subPrim, const TableT
if (auto it = mttv->props.find("__index"); it != mttv->props.end()) if (auto it = mttv->props.find("__index"); it != mttv->props.end())
{ {
if (auto stringTable = get<TableType>(it->second.type())) if (auto stringTable = get<TableType>(it->second.type()))
result.orElse(isSubtype_(stringTable, superTable)); result.orElse(isCovariantWith(stringTable, superTable));
} }
} }
} }
@ -627,7 +832,7 @@ SubtypingResult Subtyping::isSubtype_(const PrimitiveType* subPrim, const TableT
return result; return result;
} }
SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const TableType* superTable) SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, const TableType* superTable)
{ {
SubtypingResult result{false}; SubtypingResult result{false};
if (auto stringleton = get<StringSingleton>(subSingleton)) if (auto stringleton = get<StringSingleton>(subSingleton))
@ -639,7 +844,7 @@ SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const T
if (auto it = mttv->props.find("__index"); it != mttv->props.end()) if (auto it = mttv->props.find("__index"); it != mttv->props.end())
{ {
if (auto stringTable = get<TableType>(it->second.type())) if (auto stringTable = get<TableType>(it->second.type()))
result.orElse(isSubtype_(stringTable, superTable)); result.orElse(isCovariantWith(stringTable, superTable));
} }
} }
} }
@ -647,29 +852,27 @@ SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const T
return result; return result;
} }
SubtypingResult Subtyping::isSubtype_(const NormalizedType* subNorm, const NormalizedType* superNorm) SubtypingResult Subtyping::isCovariantWith(const NormalizedType* subNorm, const NormalizedType* superNorm)
{ {
if (!subNorm || !superNorm) if (!subNorm || !superNorm)
return {false, true, true}; return {false, true, true};
SubtypingResult result = isSubtype_(subNorm->tops, superNorm->tops); SubtypingResult result = isCovariantWith(subNorm->tops, superNorm->tops);
result.andAlso(isSubtype_(subNorm->booleans, superNorm->booleans)); result.andAlso(isCovariantWith(subNorm->booleans, superNorm->booleans));
result.andAlso(isSubtype_(subNorm->classes, superNorm->classes, superNorm->tables)); result.andAlso(isCovariantWith(subNorm->classes, superNorm->classes).orElse(isCovariantWith(subNorm->classes, superNorm->tables)));
result.andAlso(isSubtype_(subNorm->errors, superNorm->errors)); result.andAlso(isCovariantWith(subNorm->errors, superNorm->errors));
result.andAlso(isSubtype_(subNorm->nils, superNorm->nils)); result.andAlso(isCovariantWith(subNorm->nils, superNorm->nils));
result.andAlso(isSubtype_(subNorm->numbers, superNorm->numbers)); result.andAlso(isCovariantWith(subNorm->numbers, superNorm->numbers));
result.isSubtype &= Luau::isSubtype(subNorm->strings, superNorm->strings); result.andAlso(isCovariantWith(subNorm->strings, superNorm->strings));
// isSubtype_(subNorm->strings, superNorm->tables); result.andAlso(isCovariantWith(subNorm->strings, superNorm->tables));
result.andAlso(isSubtype_(subNorm->threads, superNorm->threads)); result.andAlso(isCovariantWith(subNorm->threads, superNorm->threads));
result.andAlso(isSubtype_(subNorm->tables, superNorm->tables)); result.andAlso(isCovariantWith(subNorm->tables, superNorm->tables));
// isSubtype_(subNorm->tables, superNorm->strings); result.andAlso(isCovariantWith(subNorm->functions, superNorm->functions));
// isSubtype_(subNorm->tables, superNorm->classes); // isCovariantWith(subNorm->tyvars, superNorm->tyvars);
result.andAlso(isSubtype_(subNorm->functions, superNorm->functions));
// isSubtype_(subNorm->tyvars, superNorm->tyvars);
return result; return result;
} }
SubtypingResult Subtyping::isSubtype_(const NormalizedClassType& subClass, const NormalizedClassType& superClass, const TypeIds& superTables) SubtypingResult Subtyping::isCovariantWith(const NormalizedClassType& subClass, const NormalizedClassType& superClass)
{ {
for (const auto& [subClassTy, _] : subClass.classes) for (const auto& [subClassTy, _] : subClass.classes)
{ {
@ -677,24 +880,18 @@ SubtypingResult Subtyping::isSubtype_(const NormalizedClassType& subClass, const
for (const auto& [superClassTy, superNegations] : superClass.classes) for (const auto& [superClassTy, superNegations] : superClass.classes)
{ {
result.orElse(isSubtype_(subClassTy, superClassTy)); result.orElse(isCovariantWith(subClassTy, superClassTy));
if (!result.isSubtype) if (!result.isSubtype)
continue; continue;
for (TypeId negation : superNegations) for (TypeId negation : superNegations)
{ {
result.andAlso(SubtypingResult::negate(isSubtype_(subClassTy, negation))); result.andAlso(SubtypingResult::negate(isCovariantWith(subClassTy, negation)));
if (result.isSubtype) if (result.isSubtype)
break; break;
} }
} }
if (result.isSubtype)
continue;
for (TypeId superTableTy : superTables)
result.orElse(isSubtype_(subClassTy, superTableTy));
if (!result.isSubtype) if (!result.isSubtype)
return result; return result;
} }
@ -702,17 +899,79 @@ SubtypingResult Subtyping::isSubtype_(const NormalizedClassType& subClass, const
return {true}; return {true};
} }
SubtypingResult Subtyping::isSubtype_(const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction) SubtypingResult Subtyping::isCovariantWith(const NormalizedClassType& subClass, const TypeIds& superTables)
{
for (const auto& [subClassTy, _] : subClass.classes)
{
SubtypingResult result;
for (TypeId superTableTy : superTables)
result.orElse(isCovariantWith(subClassTy, superTableTy));
if (!result.isSubtype)
return result;
}
return {true};
}
SubtypingResult Subtyping::isCovariantWith(const NormalizedStringType& subString, const NormalizedStringType& superString)
{
bool isSubtype = Luau::isSubtype(subString, superString);
return {isSubtype};
}
SubtypingResult Subtyping::isCovariantWith(const NormalizedStringType& subString, const TypeIds& superTables)
{
if (subString.isNever())
return {true};
if (subString.isCofinite)
{
SubtypingResult result;
for (const auto& superTable : superTables)
{
result.orElse(isCovariantWith(builtinTypes->stringType, superTable));
if (result.isSubtype)
return result;
}
return result;
}
// Finite case
// S = s1 | s2 | s3 ... sn <: t1 | t2 | ... | tn
// iff for some ti, S <: ti
// iff for all sj, sj <: ti
for (const auto& superTable : superTables)
{
SubtypingResult result{true};
for (const auto& [_, subString] : subString.singletons)
{
result.andAlso(isCovariantWith(subString, superTable));
if (!result.isSubtype)
break;
}
if (!result.isSubtype)
continue;
else
return result;
}
return {false};
}
SubtypingResult Subtyping::isCovariantWith(const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction)
{ {
if (subFunction.isNever()) if (subFunction.isNever())
return {true}; return {true};
else if (superFunction.isTop) else if (superFunction.isTop)
return {true}; return {true};
else else
return isSubtype_(subFunction.parts, superFunction.parts); return isCovariantWith(subFunction.parts, superFunction.parts);
} }
SubtypingResult Subtyping::isSubtype_(const TypeIds& subTypes, const TypeIds& superTypes) SubtypingResult Subtyping::isCovariantWith(const TypeIds& subTypes, const TypeIds& superTypes)
{ {
std::vector<SubtypingResult> results; std::vector<SubtypingResult> results;
@ -720,15 +979,15 @@ SubtypingResult Subtyping::isSubtype_(const TypeIds& subTypes, const TypeIds& su
{ {
results.emplace_back(); results.emplace_back();
for (TypeId superTy : superTypes) for (TypeId superTy : superTypes)
results.back().orElse(isSubtype_(subTy, superTy)); results.back().orElse(isCovariantWith(subTy, superTy));
} }
return SubtypingResult::all(results); return SubtypingResult::all(results);
} }
SubtypingResult Subtyping::isSubtype_(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic) SubtypingResult Subtyping::isCovariantWith(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic)
{ {
return isSubtype_(subVariadic->ty, superVariadic->ty); return isCovariantWith(subVariadic->ty, superVariadic->ty);
} }
bool Subtyping::bindGeneric(TypeId subTy, TypeId superTy) bool Subtyping::bindGeneric(TypeId subTy, TypeId superTy)
@ -772,7 +1031,7 @@ bool Subtyping::bindGeneric(TypePackId subTp, TypePackId superTp)
return true; return true;
} }
template <typename T, typename Container> template<typename T, typename Container>
TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse) TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse)
{ {
if (container.empty()) if (container.empty())

View file

@ -145,8 +145,7 @@ void StateDot::visitChildren(TypeId ty, int index)
startNode(index); startNode(index);
startNodeLabel(); startNodeLabel();
auto go = [&](auto&& t) auto go = [&](auto&& t) {
{
using T = std::decay_t<decltype(t)>; using T = std::decay_t<decltype(t)>;
if constexpr (std::is_same_v<T, BoundType>) if constexpr (std::is_same_v<T, BoundType>)

View file

@ -242,8 +242,8 @@ struct TypeChecker2
Normalizer normalizer; Normalizer normalizer;
TypeChecker2(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger, const SourceModule* sourceModule, TypeChecker2(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger,
Module* module) const SourceModule* sourceModule, Module* module)
: builtinTypes(builtinTypes) : builtinTypes(builtinTypes)
, logger(logger) , logger(logger)
, limits(limits) , limits(limits)
@ -1295,13 +1295,8 @@ struct TypeChecker2
else if (auto assertion = expr->as<AstExprTypeAssertion>()) else if (auto assertion = expr->as<AstExprTypeAssertion>())
return isLiteral(assertion->expr); return isLiteral(assertion->expr);
return return expr->is<AstExprConstantNil>() || expr->is<AstExprConstantBool>() || expr->is<AstExprConstantNumber>() ||
expr->is<AstExprConstantNil>() || expr->is<AstExprConstantString>() || expr->is<AstExprFunction>() || expr->is<AstExprTable>();
expr->is<AstExprConstantBool>() ||
expr->is<AstExprConstantNumber>() ||
expr->is<AstExprConstantString>() ||
expr->is<AstExprFunction>() ||
expr->is<AstExprTable>();
} }
static std::unique_ptr<LiteralProperties> buildLiteralPropertiesSet(AstExpr* expr) static std::unique_ptr<LiteralProperties> buildLiteralPropertiesSet(AstExpr* expr)
@ -1423,7 +1418,7 @@ struct TypeChecker2
LUAU_ASSERT(argOffset == args->head.size()); LUAU_ASSERT(argOffset == args->head.size());
const Location argLoc = argExprs->empty() ? Location{} // TODO const Location argLoc = argExprs->empty() ? Location{} // TODO
: argExprs->at(argExprs->size() - 1)->location; : argExprs->at(argExprs->size() - 1)->location;
if (paramIter.tail() && args->tail) if (paramIter.tail() && args->tail)
{ {
@ -2686,8 +2681,8 @@ struct TypeChecker2
} }
}; };
void check( void check(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger,
NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger, const SourceModule& sourceModule, Module* module) const SourceModule& sourceModule, Module* module)
{ {
TypeChecker2 typeChecker{builtinTypes, unifierState, limits, logger, &sourceModule, module}; TypeChecker2 typeChecker{builtinTypes, unifierState, limits, logger, &sourceModule, module};

View file

@ -25,6 +25,7 @@ LUAU_FASTFLAGVARIABLE(LuauOccursIsntAlwaysFailure, false)
LUAU_FASTFLAG(LuauNormalizeBlockedTypes) LUAU_FASTFLAG(LuauNormalizeBlockedTypes)
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls) LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering, false)
namespace Luau namespace Luau
{ {
@ -2285,7 +2286,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
variance = Invariant; variance = Invariant;
Unifier innerState = makeChildUnifier(); Unifier innerState = makeChildUnifier();
if (useNewSolver) if (useNewSolver || FFlag::LuauFixIndexerSubtypingOrdering)
innerState.tryUnify_(prop.type(), superTable->indexer->indexResultType); innerState.tryUnify_(prop.type(), superTable->indexer->indexResultType);
else else
{ {

View file

@ -26,7 +26,6 @@ Unifier2::Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes,
, ice(ice) , ice(ice)
, recursionLimit(FInt::LuauTypeInferRecursionLimit) , recursionLimit(FInt::LuauTypeInferRecursionLimit)
{ {
} }
bool Unifier2::unify(TypeId subTy, TypeId superTy) bool Unifier2::unify(TypeId subTy, TypeId superTy)
@ -99,10 +98,7 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
return true; return true;
} }
size_t maxLength = std::max( size_t maxLength = std::max(flatten(subTp).first.size(), flatten(superTp).first.size());
flatten(subTp).first.size(),
flatten(superTp).first.size()
);
auto [subTypes, subTail] = extendTypePack(*arena, builtinTypes, subTp, maxLength); auto [subTypes, subTail] = extendTypePack(*arena, builtinTypes, subTp, maxLength);
auto [superTypes, superTail] = extendTypePack(*arena, builtinTypes, superTp, maxLength); auto [superTypes, superTail] = extendTypePack(*arena, builtinTypes, superTp, maxLength);
@ -123,16 +119,25 @@ struct FreeTypeSearcher : TypeVisitor
explicit FreeTypeSearcher(NotNull<Scope> scope) explicit FreeTypeSearcher(NotNull<Scope> scope)
: TypeVisitor(/*skipBoundTypes*/ true) : TypeVisitor(/*skipBoundTypes*/ true)
, scope(scope) , scope(scope)
{} {
}
enum { Positive, Negative } polarity = Positive; enum
{
Positive,
Negative
} polarity = Positive;
void flip() void flip()
{ {
switch (polarity) switch (polarity)
{ {
case Positive: polarity = Negative; break; case Positive:
case Negative: polarity = Positive; break; polarity = Negative;
break;
case Negative:
polarity = Positive;
break;
} }
} }
@ -152,8 +157,12 @@ struct FreeTypeSearcher : TypeVisitor
switch (polarity) switch (polarity)
{ {
case Positive: positiveTypes.insert(ty); break; case Positive:
case Negative: negativeTypes.insert(ty); break; positiveTypes.insert(ty);
break;
case Negative:
negativeTypes.insert(ty);
break;
} }
return true; return true;
@ -180,13 +189,17 @@ struct MutatingGeneralizer : TypeOnceVisitor
std::unordered_set<TypeId> negativeTypes; std::unordered_set<TypeId> negativeTypes;
std::vector<TypeId> generics; std::vector<TypeId> generics;
MutatingGeneralizer(NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, std::unordered_set<TypeId> positiveTypes, std::unordered_set<TypeId> negativeTypes) bool isWithinFunction = false;
MutatingGeneralizer(
NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, std::unordered_set<TypeId> positiveTypes, std::unordered_set<TypeId> negativeTypes)
: TypeOnceVisitor(/* skipBoundTypes */ true) : TypeOnceVisitor(/* skipBoundTypes */ true)
, builtinTypes(builtinTypes) , builtinTypes(builtinTypes)
, scope(scope) , scope(scope)
, positiveTypes(std::move(positiveTypes)) , positiveTypes(std::move(positiveTypes))
, negativeTypes(std::move(negativeTypes)) , negativeTypes(std::move(negativeTypes))
{} {
}
static void replace(DenseHashSet<TypeId>& seen, TypeId haystack, TypeId needle, TypeId replacement) static void replace(DenseHashSet<TypeId>& seen, TypeId haystack, TypeId needle, TypeId replacement)
{ {
@ -211,10 +224,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
// FIXME: I bet this function has reentrancy problems // FIXME: I bet this function has reentrancy problems
option = follow(option); option = follow(option);
if (option == needle) if (option == needle)
{
LUAU_ASSERT(!seen.find(option));
option = replacement; option = replacement;
}
// TODO seen set // TODO seen set
else if (get<UnionType>(option)) else if (get<UnionType>(option))
@ -224,7 +234,21 @@ struct MutatingGeneralizer : TypeOnceVisitor
} }
} }
bool visit (TypeId ty, const FreeType&) override bool visit(TypeId ty, const FunctionType& ft) override
{
const bool oldValue = isWithinFunction;
isWithinFunction = true;
traverse(ft.argTypes);
traverse(ft.retTypes);
isWithinFunction = oldValue;
return false;
}
bool visit(TypeId ty, const FreeType&) override
{ {
const FreeType* ft = get<FreeType>(ty); const FreeType* ft = get<FreeType>(ty);
LUAU_ASSERT(ft); LUAU_ASSERT(ft);
@ -232,7 +256,8 @@ struct MutatingGeneralizer : TypeOnceVisitor
traverse(ft->lowerBound); traverse(ft->lowerBound);
traverse(ft->upperBound); traverse(ft->upperBound);
// ft is potentially invalid now. // It is possible for the above traverse() calls to cause ty to be
// transmuted. We must reaquire ft if this happens.
ty = follow(ty); ty = follow(ty);
ft = get<FreeType>(ty); ft = get<FreeType>(ty);
if (!ft) if (!ft)
@ -252,8 +277,13 @@ struct MutatingGeneralizer : TypeOnceVisitor
if (!hasLowerBound && !hasUpperBound) if (!hasLowerBound && !hasUpperBound)
{ {
emplaceType<GenericType>(asMutable(ty), scope); if (isWithinFunction)
generics.push_back(ty); {
emplaceType<GenericType>(asMutable(ty), scope);
generics.push_back(ty);
}
else
emplaceType<BoundType>(asMutable(ty), builtinTypes->unknownType);
} }
// It is possible that this free type has other free types in its upper // It is possible that this free type has other free types in its upper
@ -264,19 +294,27 @@ struct MutatingGeneralizer : TypeOnceVisitor
// If we do not do this, we get tautological bounds like a <: a <: unknown. // If we do not do this, we get tautological bounds like a <: a <: unknown.
else if (isPositive && !hasUpperBound) else if (isPositive && !hasUpperBound)
{ {
if (FreeType* lowerFree = getMutable<FreeType>(ft->lowerBound); lowerFree && lowerFree->upperBound == ty) TypeId lb = follow(ft->lowerBound);
if (FreeType* lowerFree = getMutable<FreeType>(lb); lowerFree && lowerFree->upperBound == ty)
lowerFree->upperBound = builtinTypes->unknownType; lowerFree->upperBound = builtinTypes->unknownType;
else else
replace(seen, ft->lowerBound, ty, builtinTypes->unknownType); {
emplaceType<BoundType>(asMutable(ty), ft->lowerBound); DenseHashSet<TypeId> replaceSeen{nullptr};
replace(replaceSeen, lb, ty, builtinTypes->unknownType);
}
emplaceType<BoundType>(asMutable(ty), lb);
} }
else else
{ {
if (FreeType* upperFree = getMutable<FreeType>(ft->upperBound); upperFree && upperFree->lowerBound == ty) TypeId ub = follow(ft->upperBound);
if (FreeType* upperFree = getMutable<FreeType>(ub); upperFree && upperFree->lowerBound == ty)
upperFree->lowerBound = builtinTypes->neverType; upperFree->lowerBound = builtinTypes->neverType;
else else
replace(seen, ft->upperBound, ty, builtinTypes->neverType); {
emplaceType<BoundType>(asMutable(ty), ft->upperBound); DenseHashSet<TypeId> replaceSeen{nullptr};
replace(replaceSeen, ub, ty, builtinTypes->neverType);
}
emplaceType<BoundType>(asMutable(ty), ub);
} }
return false; return false;
@ -363,4 +401,4 @@ OccursCheckResult Unifier2::occursCheck(DenseHashSet<TypePackId>& seen, TypePack
return OccursCheckResult::Pass; return OccursCheckResult::Pass;
} }
} } // namespace Luau

View file

@ -534,7 +534,7 @@ class AstStatBlock : public AstStat
public: public:
LUAU_RTTI(AstStatBlock) LUAU_RTTI(AstStatBlock)
AstStatBlock(const Location& location, const AstArray<AstStat*>& body, bool hasEnd=true); AstStatBlock(const Location& location, const AstArray<AstStat*>& body, bool hasEnd = true);
void visit(AstVisitor* visitor) override; void visit(AstVisitor* visitor) override;

View file

@ -89,13 +89,14 @@ static void reportError(const char* name, const Luau::CompileError& error)
report(name, error.getLocation(), "CompileError", error.what()); report(name, error.getLocation(), "CompileError", error.what());
} }
static std::string getCodegenAssembly(const char* name, const std::string& bytecode, Luau::CodeGen::AssemblyOptions options) static std::string getCodegenAssembly(
const char* name, const std::string& bytecode, Luau::CodeGen::AssemblyOptions options, Luau::CodeGen::LoweringStats* stats)
{ {
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close); std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
lua_State* L = globalState.get(); lua_State* L = globalState.get();
if (luau_load(L, name, bytecode.data(), bytecode.size(), 0) == 0) if (luau_load(L, name, bytecode.data(), bytecode.size(), 0) == 0)
return Luau::CodeGen::getAssembly(L, -1, options); return Luau::CodeGen::getAssembly(L, -1, options, stats);
fprintf(stderr, "Error loading bytecode %s\n", name); fprintf(stderr, "Error loading bytecode %s\n", name);
return ""; return "";
@ -119,6 +120,8 @@ struct CompileStats
double parseTime; double parseTime;
double compileTime; double compileTime;
double codegenTime; double codegenTime;
Luau::CodeGen::LoweringStats lowerStats;
}; };
static double recordDeltaTime(double& timer) static double recordDeltaTime(double& timer)
@ -213,10 +216,10 @@ static bool compileFile(const char* name, CompileFormat format, Luau::CodeGen::A
case CompileFormat::CodegenAsm: case CompileFormat::CodegenAsm:
case CompileFormat::CodegenIr: case CompileFormat::CodegenIr:
case CompileFormat::CodegenVerbose: case CompileFormat::CodegenVerbose:
printf("%s", getCodegenAssembly(name, bcb.getBytecode(), options).c_str()); printf("%s", getCodegenAssembly(name, bcb.getBytecode(), options, &stats.lowerStats).c_str());
break; break;
case CompileFormat::CodegenNull: case CompileFormat::CodegenNull:
stats.codegen += getCodegenAssembly(name, bcb.getBytecode(), options).size(); stats.codegen += getCodegenAssembly(name, bcb.getBytecode(), options, &stats.lowerStats).size();
stats.codegenTime += recordDeltaTime(currts); stats.codegenTime += recordDeltaTime(currts);
break; break;
case CompileFormat::Null: case CompileFormat::Null:
@ -355,13 +358,22 @@ int main(int argc, char** argv)
failed += !compileFile(path.c_str(), compileFormat, assemblyTarget, stats); failed += !compileFile(path.c_str(), compileFormat, assemblyTarget, stats);
if (compileFormat == CompileFormat::Null) if (compileFormat == CompileFormat::Null)
{
printf("Compiled %d KLOC into %d KB bytecode (read %.2fs, parse %.2fs, compile %.2fs)\n", int(stats.lines / 1000), int(stats.bytecode / 1024), printf("Compiled %d KLOC into %d KB bytecode (read %.2fs, parse %.2fs, compile %.2fs)\n", int(stats.lines / 1000), int(stats.bytecode / 1024),
stats.readTime, stats.parseTime, stats.compileTime); stats.readTime, stats.parseTime, stats.compileTime);
}
else if (compileFormat == CompileFormat::CodegenNull) else if (compileFormat == CompileFormat::CodegenNull)
{
printf("Compiled %d KLOC into %d KB bytecode => %d KB native code (%.2fx) (read %.2fs, parse %.2fs, compile %.2fs, codegen %.2fs)\n", printf("Compiled %d KLOC into %d KB bytecode => %d KB native code (%.2fx) (read %.2fs, parse %.2fs, compile %.2fs, codegen %.2fs)\n",
int(stats.lines / 1000), int(stats.bytecode / 1024), int(stats.codegen / 1024), int(stats.lines / 1000), int(stats.bytecode / 1024), int(stats.codegen / 1024),
stats.bytecode == 0 ? 0.0 : double(stats.codegen) / double(stats.bytecode), stats.readTime, stats.parseTime, stats.compileTime, stats.bytecode == 0 ? 0.0 : double(stats.codegen) / double(stats.bytecode), stats.readTime, stats.parseTime, stats.compileTime,
stats.codegenTime); stats.codegenTime);
printf("Lowering stats:\n");
printf("- spills to stack: %d, spills to restore: %d, max spill slot %u\n", stats.lowerStats.spillsToSlot, stats.lowerStats.spillsToRestore,
stats.lowerStats.maxSpillSlotsUsed);
printf("- regalloc failed: %d, lowering failed %d\n", stats.lowerStats.regAllocErrors, stats.lowerStats.loweringErrors);
}
return failed ? 1 : 0; return failed ? 1 : 0;
} }

View file

@ -3,6 +3,7 @@
#include <string> #include <string>
#include <stddef.h>
#include <stdint.h> #include <stdint.h>
struct lua_State; struct lua_State;
@ -74,8 +75,18 @@ struct AssemblyOptions
void* annotatorContext = nullptr; void* annotatorContext = nullptr;
}; };
struct LoweringStats
{
int spillsToSlot = 0;
int spillsToRestore = 0;
unsigned maxSpillSlotsUsed = 0;
int regAllocErrors = 0;
int loweringErrors = 0;
};
// Generates assembly for target function and all inner functions // Generates assembly for target function and all inner functions
std::string getAssembly(lua_State* L, int idx, AssemblyOptions options = {}); std::string getAssembly(lua_State* L, int idx, AssemblyOptions options = {}, LoweringStats* stats = nullptr);
using PerfLogFn = void (*)(void* context, uintptr_t addr, unsigned size, const char* symbol); using PerfLogFn = void (*)(void* context, uintptr_t addr, unsigned size, const char* symbol);

View file

@ -12,6 +12,9 @@ namespace Luau
{ {
namespace CodeGen namespace CodeGen
{ {
struct LoweringStats;
namespace X64 namespace X64
{ {
@ -33,7 +36,7 @@ struct IrSpillX64
struct IrRegAllocX64 struct IrRegAllocX64
{ {
IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function); IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function, LoweringStats* stats);
RegisterX64 allocReg(SizeX64 size, uint32_t instIdx); RegisterX64 allocReg(SizeX64 size, uint32_t instIdx);
RegisterX64 allocRegOrReuse(SizeX64 size, uint32_t instIdx, std::initializer_list<IrOp> oprefs); RegisterX64 allocRegOrReuse(SizeX64 size, uint32_t instIdx, std::initializer_list<IrOp> oprefs);
@ -70,6 +73,7 @@ struct IrRegAllocX64
AssemblyBuilderX64& build; AssemblyBuilderX64& build;
IrFunction& function; IrFunction& function;
LoweringStats* stats = nullptr;
uint32_t currInstIdx = ~0u; uint32_t currInstIdx = ~0u;

View file

@ -264,5 +264,14 @@ uint32_t getNativeContextOffset(int bfid);
// Cleans up blocks that were created with no users // Cleans up blocks that were created with no users
void killUnusedBlocks(IrFunction& function); void killUnusedBlocks(IrFunction& function);
// Get blocks in order that tries to maximize fallthrough between them during lowering
// We want to mostly preserve build order with fallbacks outlined
// But we also use hints from optimization passes that chain blocks together where there's only one out-in edge between them
std::vector<uint32_t> getSortedBlockOrder(IrFunction& function);
// Returns first non-dead block that comes after block at index 'i' in the sorted blocks array
// 'dummy' block is returned if the end of array was reached
IrBlock& getNextBlock(IrFunction& function, std::vector<uint32_t>& sortedBlocks, IrBlock& dummy, size_t i);
} // namespace CodeGen } // namespace CodeGen
} // namespace Luau } // namespace Luau

View file

@ -1091,7 +1091,7 @@ void AssemblyBuilderA64::placeER(const char* name, RegisterA64 dst, RegisterA64
LUAU_ASSERT(shift >= 0 && shift <= 4); LUAU_ASSERT(shift >= 0 && shift <= 4);
uint32_t sf = (dst.kind == KindA64::x) ? 0x80000000 : 0; // could be useful in the future for byte->word extends uint32_t sf = (dst.kind == KindA64::x) ? 0x80000000 : 0; // could be useful in the future for byte->word extends
int option = 0b010; // UXTW int option = 0b010; // UXTW
place(dst.index | (src1.index << 5) | (shift << 10) | (option << 13) | (src2.index << 16) | (1 << 21) | (op << 24) | sf); place(dst.index | (src1.index << 5) | (shift << 10) | (option << 13) | (src2.index << 16) | (1 << 21) | (op << 24) | sf);
commit(); commit();

View file

@ -106,7 +106,7 @@ static std::optional<NativeProto> createNativeFunction(AssemblyBuilder& build, M
IrBuilder ir; IrBuilder ir;
ir.buildFunctionIr(proto); ir.buildFunctionIr(proto);
if (!lowerFunction(ir, build, helpers, proto, {})) if (!lowerFunction(ir, build, helpers, proto, {}, /* stats */ nullptr))
return std::nullopt; return std::nullopt;
return createNativeProto(proto, ir); return createNativeProto(proto, ir);

View file

@ -43,7 +43,7 @@ static void logFunctionHeader(AssemblyBuilder& build, Proto* proto)
} }
template<typename AssemblyBuilder> template<typename AssemblyBuilder>
static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, AssemblyOptions options) static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, AssemblyOptions options, LoweringStats* stats)
{ {
std::vector<Proto*> protos; std::vector<Proto*> protos;
gatherFunctions(protos, clvalue(func)->l.p); gatherFunctions(protos, clvalue(func)->l.p);
@ -66,7 +66,7 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
if (options.includeAssembly || options.includeIr) if (options.includeAssembly || options.includeIr)
logFunctionHeader(build, p); logFunctionHeader(build, p);
if (!lowerFunction(ir, build, helpers, p, options)) if (!lowerFunction(ir, build, helpers, p, options, stats))
{ {
if (build.logText) if (build.logText)
build.logAppend("; skipping (can't lower)\n"); build.logAppend("; skipping (can't lower)\n");
@ -90,7 +90,7 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
unsigned int getCpuFeaturesA64(); unsigned int getCpuFeaturesA64();
#endif #endif
std::string getAssembly(lua_State* L, int idx, AssemblyOptions options) std::string getAssembly(lua_State* L, int idx, AssemblyOptions options, LoweringStats* stats)
{ {
LUAU_ASSERT(lua_isLfunction(L, idx)); LUAU_ASSERT(lua_isLfunction(L, idx));
const TValue* func = luaA_toobject(L, idx); const TValue* func = luaA_toobject(L, idx);
@ -106,35 +106,35 @@ std::string getAssembly(lua_State* L, int idx, AssemblyOptions options)
X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly); X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly);
#endif #endif
return getAssemblyImpl(build, func, options); return getAssemblyImpl(build, func, options, stats);
} }
case AssemblyOptions::A64: case AssemblyOptions::A64:
{ {
A64::AssemblyBuilderA64 build(/* logText= */ options.includeAssembly, /* features= */ A64::Feature_JSCVT); A64::AssemblyBuilderA64 build(/* logText= */ options.includeAssembly, /* features= */ A64::Feature_JSCVT);
return getAssemblyImpl(build, func, options); return getAssemblyImpl(build, func, options, stats);
} }
case AssemblyOptions::A64_NoFeatures: case AssemblyOptions::A64_NoFeatures:
{ {
A64::AssemblyBuilderA64 build(/* logText= */ options.includeAssembly, /* features= */ 0); A64::AssemblyBuilderA64 build(/* logText= */ options.includeAssembly, /* features= */ 0);
return getAssemblyImpl(build, func, options); return getAssemblyImpl(build, func, options, stats);
} }
case AssemblyOptions::X64_Windows: case AssemblyOptions::X64_Windows:
{ {
X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly, X64::ABIX64::Windows); X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly, X64::ABIX64::Windows);
return getAssemblyImpl(build, func, options); return getAssemblyImpl(build, func, options, stats);
} }
case AssemblyOptions::X64_SystemV: case AssemblyOptions::X64_SystemV:
{ {
X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly, X64::ABIX64::SystemV); X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly, X64::ABIX64::SystemV);
return getAssemblyImpl(build, func, options); return getAssemblyImpl(build, func, options, stats);
} }
default: default:

View file

@ -44,42 +44,10 @@ inline void gatherFunctions(std::vector<Proto*>& results, Proto* proto)
gatherFunctions(results, proto->p[i]); gatherFunctions(results, proto->p[i]);
} }
inline IrBlock& getNextBlock(IrFunction& function, std::vector<uint32_t>& sortedBlocks, IrBlock& dummy, size_t i)
{
for (size_t j = i + 1; j < sortedBlocks.size(); ++j)
{
IrBlock& block = function.blocks[sortedBlocks[j]];
if (block.kind != IrBlockKind::Dead)
return block;
}
return dummy;
}
template<typename AssemblyBuilder, typename IrLowering> template<typename AssemblyBuilder, typename IrLowering>
inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& function, int bytecodeid, AssemblyOptions options) inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& function, int bytecodeid, AssemblyOptions options)
{ {
// While we will need a better block ordering in the future, right now we want to mostly preserve build order with fallbacks outlined std::vector<uint32_t> sortedBlocks = getSortedBlockOrder(function);
std::vector<uint32_t> sortedBlocks;
sortedBlocks.reserve(function.blocks.size());
for (uint32_t i = 0; i < function.blocks.size(); i++)
sortedBlocks.push_back(i);
std::sort(sortedBlocks.begin(), sortedBlocks.end(), [&](uint32_t idxA, uint32_t idxB) {
const IrBlock& a = function.blocks[idxA];
const IrBlock& b = function.blocks[idxB];
// Place fallback blocks at the end
if ((a.kind == IrBlockKind::Fallback) != (b.kind == IrBlockKind::Fallback))
return (a.kind == IrBlockKind::Fallback) < (b.kind == IrBlockKind::Fallback);
// Try to order by instruction order
if (a.sortkey != b.sortkey)
return a.sortkey < b.sortkey;
// Chains of blocks are merged together by having the same sort key and consecutive chain key
return a.chainkey < b.chainkey;
});
// For each IR instruction that begins a bytecode instruction, which bytecode instruction is it? // For each IR instruction that begins a bytecode instruction, which bytecode instruction is it?
std::vector<uint32_t> bcLocations(function.instructions.size() + 1, ~0u); std::vector<uint32_t> bcLocations(function.instructions.size() + 1, ~0u);
@ -231,24 +199,26 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
return true; return true;
} }
inline bool lowerIr(X64::AssemblyBuilderX64& build, IrBuilder& ir, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options) inline bool lowerIr(
X64::AssemblyBuilderX64& build, IrBuilder& ir, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options, LoweringStats* stats)
{ {
optimizeMemoryOperandsX64(ir.function); optimizeMemoryOperandsX64(ir.function);
X64::IrLoweringX64 lowering(build, helpers, ir.function); X64::IrLoweringX64 lowering(build, helpers, ir.function, stats);
return lowerImpl(build, lowering, ir.function, proto->bytecodeid, options); return lowerImpl(build, lowering, ir.function, proto->bytecodeid, options);
} }
inline bool lowerIr(A64::AssemblyBuilderA64& build, IrBuilder& ir, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options) inline bool lowerIr(
A64::AssemblyBuilderA64& build, IrBuilder& ir, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options, LoweringStats* stats)
{ {
A64::IrLoweringA64 lowering(build, helpers, ir.function); A64::IrLoweringA64 lowering(build, helpers, ir.function, stats);
return lowerImpl(build, lowering, ir.function, proto->bytecodeid, options); return lowerImpl(build, lowering, ir.function, proto->bytecodeid, options);
} }
template<typename AssemblyBuilder> template<typename AssemblyBuilder>
inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options) inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options, LoweringStats* stats)
{ {
killUnusedBlocks(ir.function); killUnusedBlocks(ir.function);
@ -264,7 +234,7 @@ inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers&
createLinearBlocks(ir, useValueNumbering); createLinearBlocks(ir, useValueNumbering);
} }
return lowerIr(build, ir, helpers, proto, options); return lowerIr(build, ir, helpers, proto, options, stats);
} }
} // namespace CodeGen } // namespace CodeGen

View file

@ -385,7 +385,8 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
translateInstDupTable(*this, pc, i); translateInstDupTable(*this, pc, i);
break; break;
case LOP_SETLIST: case LOP_SETLIST:
inst(IrCmd::SETLIST, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), constInt(LUAU_INSN_C(*pc) - 1), constUint(pc[1]), undef()); inst(IrCmd::SETLIST, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), constInt(LUAU_INSN_C(*pc) - 1), constUint(pc[1]),
undef());
break; break;
case LOP_GETUPVAL: case LOP_GETUPVAL:
translateInstGetUpval(*this, pc, i); translateInstGetUpval(*this, pc, i);

View file

@ -240,11 +240,12 @@ static bool emitBuiltin(
} }
} }
IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, IrFunction& function) IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, IrFunction& function, LoweringStats* stats)
: build(build) : build(build)
, helpers(helpers) , helpers(helpers)
, function(function) , function(function)
, regs(function, {{x0, x15}, {x16, x17}, {q0, q7}, {q16, q31}}) , stats(stats)
, regs(function, stats, {{x0, x15}, {x16, x17}, {q0, q7}, {q16, q31}})
, valueTracker(function) , valueTracker(function)
, exitHandlerMap(~0u) , exitHandlerMap(~0u)
{ {
@ -2016,6 +2017,15 @@ void IrLoweringA64::finishFunction()
build.mov(x0, handler.pcpos * sizeof(Instruction)); build.mov(x0, handler.pcpos * sizeof(Instruction));
build.b(helpers.updatePcAndContinueInVm); build.b(helpers.updatePcAndContinueInVm);
} }
if (stats)
{
if (error)
stats->loweringErrors++;
if (regs.error)
stats->regAllocErrors++;
}
} }
bool IrLoweringA64::hasError() const bool IrLoweringA64::hasError() const

View file

@ -17,13 +17,14 @@ namespace CodeGen
struct ModuleHelpers; struct ModuleHelpers;
struct AssemblyOptions; struct AssemblyOptions;
struct LoweringStats;
namespace A64 namespace A64
{ {
struct IrLoweringA64 struct IrLoweringA64
{ {
IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, IrFunction& function); IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, IrFunction& function, LoweringStats* stats);
void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next); void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next);
void finishBlock(const IrBlock& curr, const IrBlock& next); void finishBlock(const IrBlock& curr, const IrBlock& next);
@ -74,6 +75,7 @@ struct IrLoweringA64
ModuleHelpers& helpers; ModuleHelpers& helpers;
IrFunction& function; IrFunction& function;
LoweringStats* stats = nullptr;
IrRegAllocA64 regs; IrRegAllocA64 regs;

View file

@ -22,11 +22,12 @@ namespace CodeGen
namespace X64 namespace X64
{ {
IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function) IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function, LoweringStats* stats)
: build(build) : build(build)
, helpers(helpers) , helpers(helpers)
, function(function) , function(function)
, regs(build, function) , stats(stats)
, regs(build, function, stats)
, valueTracker(function) , valueTracker(function)
, exitHandlerMap(~0u) , exitHandlerMap(~0u)
{ {
@ -1646,6 +1647,15 @@ void IrLoweringX64::finishFunction()
build.mov(edx, handler.pcpos * sizeof(Instruction)); build.mov(edx, handler.pcpos * sizeof(Instruction));
build.jmp(helpers.updatePcAndContinueInVm); build.jmp(helpers.updatePcAndContinueInVm);
} }
if (stats)
{
if (regs.maxUsedSlot > kSpillSlots)
stats->regAllocErrors++;
if (regs.maxUsedSlot > stats->maxSpillSlotsUsed)
stats->maxSpillSlotsUsed = regs.maxUsedSlot;
}
} }
bool IrLoweringX64::hasError() const bool IrLoweringX64::hasError() const

View file

@ -19,13 +19,14 @@ namespace CodeGen
struct ModuleHelpers; struct ModuleHelpers;
struct AssemblyOptions; struct AssemblyOptions;
struct LoweringStats;
namespace X64 namespace X64
{ {
struct IrLoweringX64 struct IrLoweringX64
{ {
IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function); IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function, LoweringStats* stats);
void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next); void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next);
void finishBlock(const IrBlock& curr, const IrBlock& next); void finishBlock(const IrBlock& curr, const IrBlock& next);
@ -76,6 +77,7 @@ struct IrLoweringX64
ModuleHelpers& helpers; ModuleHelpers& helpers;
IrFunction& function; IrFunction& function;
LoweringStats* stats = nullptr;
IrRegAllocX64 regs; IrRegAllocX64 regs;

View file

@ -2,6 +2,7 @@
#include "IrRegAllocA64.h" #include "IrRegAllocA64.h"
#include "Luau/AssemblyBuilderA64.h" #include "Luau/AssemblyBuilderA64.h"
#include "Luau/CodeGen.h"
#include "Luau/IrUtils.h" #include "Luau/IrUtils.h"
#include "BitUtils.h" #include "BitUtils.h"
@ -109,8 +110,9 @@ static void restoreInst(AssemblyBuilderA64& build, uint32_t& freeSpillSlots, IrF
inst.regA64 = reg; inst.regA64 = reg;
} }
IrRegAllocA64::IrRegAllocA64(IrFunction& function, std::initializer_list<std::pair<RegisterA64, RegisterA64>> regs) IrRegAllocA64::IrRegAllocA64(IrFunction& function, LoweringStats* stats, std::initializer_list<std::pair<RegisterA64, RegisterA64>> regs)
: function(function) : function(function)
, stats(stats)
{ {
for (auto& p : regs) for (auto& p : regs)
{ {
@ -329,6 +331,9 @@ size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::init
spills.push_back(s); spills.push_back(s);
def.needsReload = true; def.needsReload = true;
if (stats)
stats->spillsToRestore++;
} }
else else
{ {
@ -345,6 +350,14 @@ size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::init
spills.push_back(s); spills.push_back(s);
def.spilled = true; def.spilled = true;
if (stats)
{
stats->spillsToSlot++;
if (slot != kInvalidSpill && unsigned(slot + 1) > stats->maxSpillSlotsUsed)
stats->maxSpillSlotsUsed = slot + 1;
}
} }
def.regA64 = noreg; def.regA64 = noreg;

View file

@ -12,6 +12,9 @@ namespace Luau
{ {
namespace CodeGen namespace CodeGen
{ {
struct LoweringStats;
namespace A64 namespace A64
{ {
@ -19,7 +22,7 @@ class AssemblyBuilderA64;
struct IrRegAllocA64 struct IrRegAllocA64
{ {
IrRegAllocA64(IrFunction& function, std::initializer_list<std::pair<RegisterA64, RegisterA64>> regs); IrRegAllocA64(IrFunction& function, LoweringStats* stats, std::initializer_list<std::pair<RegisterA64, RegisterA64>> regs);
RegisterA64 allocReg(KindA64 kind, uint32_t index); RegisterA64 allocReg(KindA64 kind, uint32_t index);
RegisterA64 allocTemp(KindA64 kind); RegisterA64 allocTemp(KindA64 kind);
@ -69,6 +72,7 @@ struct IrRegAllocA64
Set& getSet(KindA64 kind); Set& getSet(KindA64 kind);
IrFunction& function; IrFunction& function;
LoweringStats* stats = nullptr;
Set gpr, simd; Set gpr, simd;
std::vector<Spill> spills; std::vector<Spill> spills;

View file

@ -1,6 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/IrRegAllocX64.h" #include "Luau/IrRegAllocX64.h"
#include "Luau/CodeGen.h"
#include "Luau/IrUtils.h" #include "Luau/IrUtils.h"
#include "EmitCommonX64.h" #include "EmitCommonX64.h"
@ -14,9 +15,10 @@ namespace X64
static const RegisterX64 kGprAllocOrder[] = {rax, rdx, rcx, rbx, rsi, rdi, r8, r9, r10, r11}; static const RegisterX64 kGprAllocOrder[] = {rax, rdx, rcx, rbx, rsi, rdi, r8, r9, r10, r11};
IrRegAllocX64::IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function) IrRegAllocX64::IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function, LoweringStats* stats)
: build(build) : build(build)
, function(function) , function(function)
, stats(stats)
, usableXmmRegCount(getXmmRegisterCount(build.abi)) , usableXmmRegCount(getXmmRegisterCount(build.abi))
{ {
freeGprMap.fill(true); freeGprMap.fill(true);
@ -225,10 +227,16 @@ void IrRegAllocX64::preserve(IrInst& inst)
spill.stackSlot = uint8_t(i); spill.stackSlot = uint8_t(i);
inst.spilled = true; inst.spilled = true;
if (stats)
stats->spillsToSlot++;
} }
else else
{ {
inst.needsReload = true; inst.needsReload = true;
if (stats)
stats->spillsToRestore++;
} }
spills.push_back(spill); spills.push_back(spill);

View file

@ -615,7 +615,7 @@ static IrOp getLoopStepK(IrBuilder& build, int ra)
{ {
IrBlock& active = build.function.blocks[build.activeBlockIdx]; IrBlock& active = build.function.blocks[build.activeBlockIdx];
if (active.start + 2 < build.function.instructions.size()) if (active.start + 2 < build.function.instructions.size())
{ {
IrInst& sv = build.function.instructions[build.function.instructions.size() - 2]; IrInst& sv = build.function.instructions[build.function.instructions.size() - 2];
IrInst& st = build.function.instructions[build.function.instructions.size() - 1]; IrInst& st = build.function.instructions[build.function.instructions.size() - 1];
@ -665,7 +665,7 @@ void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos)
IrOp step = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1)); IrOp step = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1));
// step > 0 // step > 0
// note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64 // note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64
build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::Greater), direct, reverse); build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::Greater), direct, reverse);
// Condition to start the loop: step > 0 ? idx <= limit : limit <= idx // Condition to start the loop: step > 0 ? idx <= limit : limit <= idx
@ -763,7 +763,7 @@ void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos)
IrOp reverse = build.block(IrBlockKind::Internal); IrOp reverse = build.block(IrBlockKind::Internal);
// step > 0 // step > 0
// note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64 // note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64
build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::Greater), direct, reverse); build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::Greater), direct, reverse);
// Condition to continue the loop: step > 0 ? idx <= limit : limit <= idx // Condition to continue the loop: step > 0 ? idx <= limit : limit <= idx
@ -776,8 +776,8 @@ void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos)
build.beginBlock(direct); build.beginBlock(direct);
build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::LessEqual), loopRepeat, loopExit); build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::LessEqual), loopRepeat, loopExit);
} }
else else
{ {
double stepN = build.function.doubleOp(stepK); double stepN = build.function.doubleOp(stepK);
// Condition to continue the loop: step > 0 ? idx <= limit : limit <= idx // Condition to continue the loop: step > 0 ? idx <= limit : limit <= idx
@ -785,9 +785,9 @@ void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos)
build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::LessEqual), loopRepeat, loopExit); build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::LessEqual), loopRepeat, loopExit);
else else
build.inst(IrCmd::JUMP_CMP_NUM, limit, idx, build.cond(IrCondition::LessEqual), loopRepeat, loopExit); build.inst(IrCmd::JUMP_CMP_NUM, limit, idx, build.cond(IrCondition::LessEqual), loopRepeat, loopExit);
} }
} }
else else
{ {
build.inst(IrCmd::INTERRUPT, build.constUint(pcpos)); build.inst(IrCmd::INTERRUPT, build.constUint(pcpos));

View file

@ -860,5 +860,43 @@ void killUnusedBlocks(IrFunction& function)
} }
} }
std::vector<uint32_t> getSortedBlockOrder(IrFunction& function)
{
std::vector<uint32_t> sortedBlocks;
sortedBlocks.reserve(function.blocks.size());
for (uint32_t i = 0; i < function.blocks.size(); i++)
sortedBlocks.push_back(i);
std::sort(sortedBlocks.begin(), sortedBlocks.end(), [&](uint32_t idxA, uint32_t idxB) {
const IrBlock& a = function.blocks[idxA];
const IrBlock& b = function.blocks[idxB];
// Place fallback blocks at the end
if ((a.kind == IrBlockKind::Fallback) != (b.kind == IrBlockKind::Fallback))
return (a.kind == IrBlockKind::Fallback) < (b.kind == IrBlockKind::Fallback);
// Try to order by instruction order
if (a.sortkey != b.sortkey)
return a.sortkey < b.sortkey;
// Chains of blocks are merged together by having the same sort key and consecutive chain key
return a.chainkey < b.chainkey;
});
return sortedBlocks;
}
IrBlock& getNextBlock(IrFunction& function, std::vector<uint32_t>& sortedBlocks, IrBlock& dummy, size_t i)
{
for (size_t j = i + 1; j < sortedBlocks.size(); ++j)
{
IrBlock& block = function.blocks[sortedBlocks[j]];
if (block.kind != IrBlockKind::Dead)
return block;
}
return dummy;
}
} // namespace CodeGen } // namespace CodeGen
} // namespace Luau } // namespace Luau

View file

@ -14,21 +14,21 @@
#include <math.h> #include <math.h>
#include <string.h> #include <string.h>
LUAU_FASTINTVARIABLE(LuauCodeGenBlockSize, 4 * 1024 * 1024)
LUAU_FASTINTVARIABLE(LuauCodeGenMaxTotalSize, 256 * 1024 * 1024)
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
{ {
constexpr unsigned kBlockSize = 4 * 1024 * 1024;
constexpr unsigned kMaxTotalSize = 256 * 1024 * 1024;
NativeState::NativeState() NativeState::NativeState()
: NativeState(nullptr, nullptr) : NativeState(nullptr, nullptr)
{ {
} }
NativeState::NativeState(AllocationCallback* allocationCallback, void* allocationCallbackContext) NativeState::NativeState(AllocationCallback* allocationCallback, void* allocationCallbackContext)
: codeAllocator{kBlockSize, kMaxTotalSize, allocationCallback, allocationCallbackContext} : codeAllocator{size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), allocationCallback, allocationCallbackContext}
{ {
} }

View file

@ -13,6 +13,7 @@ inline bool isFlagExperimental(const char* flag)
static const char* const kList[] = { static const char* const kList[] = {
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code "LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
"LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins "LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
// makes sure we always have at least one entry // makes sure we always have at least one entry
nullptr, nullptr,
}; };

View file

@ -275,15 +275,15 @@ static int math_randomseed(lua_State* L)
return 0; return 0;
} }
static const unsigned char kPerlinHash[257] = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, static const unsigned char kPerlinHash[257] = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8,
37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87,
20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92,
55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159,
164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58,
182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108,
79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249,
239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29,
72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 151}; 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 151};
const float kPerlinGrad[16][3] = {{1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, {0, 1, 1}, const float kPerlinGrad[16][3] = {{1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, {0, 1, 1},
{0, -1, 1}, {0, 1, -1}, {0, -1, -1}, {1, 1, 0}, {0, -1, 1}, {-1, 1, 0}, {0, -1, -1}}; {0, -1, 1}, {0, 1, -1}, {0, -1, -1}, {1, 1, 0}, {0, -1, 1}, {-1, 1, 0}, {0, -1, -1}};

View file

@ -2291,7 +2291,7 @@ reentry:
{ {
// table or userdata with __call, will be called during FORGLOOP // table or userdata with __call, will be called during FORGLOOP
// TODO: we might be able to stop supporting this depending on whether it's used in practice // TODO: we might be able to stop supporting this depending on whether it's used in practice
void (*telemetrycb)(lua_State* L, int gtt, int stt, int itt) = lua_iter_call_telemetry; void (*telemetrycb)(lua_State * L, int gtt, int stt, int itt) = lua_iter_call_telemetry;
if (telemetrycb) if (telemetrycb)
telemetrycb(L, ttype(ra), ttype(ra + 1), ttype(ra + 2)); telemetrycb(L, ttype(ra), ttype(ra + 1), ttype(ra + 2));

View file

@ -3667,8 +3667,7 @@ TEST_CASE_FIXTURE(ACFixture, "string_completion_outside_quotes")
)"); )");
StringCompletionCallback callback = [](std::string, std::optional<const ClassType*>, StringCompletionCallback callback = [](std::string, std::optional<const ClassType*>,
std::optional<std::string> contents) -> std::optional<AutocompleteEntryMap> std::optional<std::string> contents) -> std::optional<AutocompleteEntryMap> {
{
Luau::AutocompleteEntryMap results = {{"test", Luau::AutocompleteEntry{Luau::AutocompleteEntryKind::String, std::nullopt, false, false}}}; Luau::AutocompleteEntryMap results = {{"test", Luau::AutocompleteEntry{Luau::AutocompleteEntryKind::String, std::nullopt, false, false}}};
return results; return results;
}; };

View file

@ -57,8 +57,7 @@ TEST_CASE("CodeAllocationCallbacks")
AllocationData allocationData{}; AllocationData allocationData{};
const auto allocationCallback = [](void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize) const auto allocationCallback = [](void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize) {
{
AllocationData& allocationData = *static_cast<AllocationData*>(context); AllocationData& allocationData = *static_cast<AllocationData*>(context);
if (oldPointer != nullptr) if (oldPointer != nullptr)
{ {

View file

@ -74,7 +74,7 @@ TEST_CASE("BytecodeIsStable")
// Bytecode ops (serialized & in-memory) // Bytecode ops (serialized & in-memory)
CHECK(LOP_FASTCALL2K == 75); // bytecode v1 CHECK(LOP_FASTCALL2K == 75); // bytecode v1
CHECK(LOP_JUMPXEQKS == 80); // bytecode v3 CHECK(LOP_JUMPXEQKS == 80); // bytecode v3
// Bytecode fastcall ids (serialized & in-memory) // Bytecode fastcall ids (serialized & in-memory)
// Note: these aren't strictly bound to specific bytecode versions, but must monotonically increase to keep backwards compat // Note: these aren't strictly bound to specific bytecode versions, but must monotonically increase to keep backwards compat
@ -7371,7 +7371,8 @@ TEST_CASE("BuiltinFoldMathK")
function test() function test()
return math.pi * 2 return math.pi * 2
end end
)", 0, 2), )",
0, 2),
R"( R"(
LOADK R0 K0 [6.2831853071795862] LOADK R0 K0 [6.2831853071795862]
RETURN R0 1 RETURN R0 1
@ -7382,7 +7383,8 @@ RETURN R0 1
function test() function test()
return math.pi * 2 return math.pi * 2
end end
)", 0, 1), )",
0, 1),
R"( R"(
GETIMPORT R1 3 [math.pi] GETIMPORT R1 3 [math.pi]
MULK R0 R1 K0 [2] MULK R0 R1 K0 [2]
@ -7396,7 +7398,8 @@ function test()
end end
math = { pi = 4 } math = { pi = 4 }
)", 0, 2), )",
0, 2),
R"( R"(
GETGLOBAL R2 K1 ['math'] GETGLOBAL R2 K1 ['math']
GETTABLEKS R1 R2 K2 ['pi'] GETTABLEKS R1 R2 K2 ['pi']

View file

@ -12,7 +12,7 @@ class IrCallWrapperX64Fixture
public: public:
IrCallWrapperX64Fixture(ABIX64 abi = ABIX64::Windows) IrCallWrapperX64Fixture(ABIX64 abi = ABIX64::Windows)
: build(/* logText */ true, abi) : build(/* logText */ true, abi)
, regs(build, function) , regs(build, function, nullptr)
, callWrap(regs, build, ~0u) , callWrap(regs, build, ~0u)
{ {
} }

View file

@ -11,7 +11,7 @@ class IrRegAllocX64Fixture
public: public:
IrRegAllocX64Fixture() IrRegAllocX64Fixture()
: build(/* logText */ true, ABIX64::Windows) : build(/* logText */ true, ABIX64::Windows)
, regs(build, function) , regs(build, function, nullptr)
{ {
} }

View file

@ -516,6 +516,71 @@ struct NormalizeFixture : Fixture
TEST_SUITE_BEGIN("Normalize"); TEST_SUITE_BEGIN("Normalize");
TEST_CASE_FIXTURE(NormalizeFixture, "string_intersection_is_commutative")
{
auto c4 = toString(normal(R"(
string & (string & Not<"a"> & Not<"b">)
)"));
auto c4Reverse = toString(normal(R"(
(string & Not<"a"> & Not<"b">) & string
)"));
CHECK(c4 == c4Reverse);
CHECK_EQ("string & ~\"a\" & ~\"b\"", c4);
auto c5 = toString(normal(R"(
(string & Not<"a"> & Not<"b">) & (string & Not<"b"> & Not<"c">)
)"));
auto c5Reverse = toString(normal(R"(
(string & Not<"b"> & Not<"c">) & (string & Not<"a"> & Not<"c">)
)"));
CHECK(c5 == c5Reverse);
CHECK_EQ("string & ~\"a\" & ~\"b\" & ~\"c\"", c5);
auto c6 = toString(normal(R"(
("a" | "b") & (string & Not<"b"> & Not<"c">)
)"));
auto c6Reverse = toString(normal(R"(
(string & Not<"b"> & Not<"c">) & ("a" | "b")
)"));
CHECK(c6 == c6Reverse);
CHECK_EQ("\"a\"", c6);
auto c7 = toString(normal(R"(
string & ("b" | "c")
)"));
auto c7Reverse = toString(normal(R"(
("b" | "c") & string
)"));
CHECK(c7 == c7Reverse);
CHECK_EQ("\"b\" | \"c\"", c7);
auto c8 = toString(normal(R"(
(string & Not<"a"> & Not<"b">) & ("b" | "c")
)"));
auto c8Reverse = toString(normal(R"(
("b" | "c") & (string & Not<"a"> & Not<"b">)
)"));
CHECK(c8 == c8Reverse);
CHECK_EQ("\"c\"", c8);
auto c9 = toString(normal(R"(
("a" | "b") & ("b" | "c")
)"));
auto c9Reverse = toString(normal(R"(
("b" | "c") & ("a" | "b")
)"));
CHECK(c9 == c9Reverse);
CHECK_EQ("\"b\"", c9);
auto l = toString(normal(R"(
(string | number) & ("a" | true)
)"));
auto r = toString(normal(R"(
("a" | true) & (string | number)
)"));
CHECK(l == r);
CHECK_EQ("\"a\"", l);
}
TEST_CASE_FIXTURE(NormalizeFixture, "negate_string") TEST_CASE_FIXTURE(NormalizeFixture, "negate_string")
{ {
CHECK("number" == toString(normal(R"( CHECK("number" == toString(normal(R"(

View file

@ -6,6 +6,7 @@
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/Subtyping.h" #include "Luau/Subtyping.h"
#include "Luau/Type.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
using namespace Luau; using namespace Luau;
@ -17,7 +18,15 @@ struct SubtypeFixture : Fixture
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
Subtyping subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&iceReporter}}; ScopePtr rootScope{new Scope(builtinTypes->emptyTypePack)};
ScopePtr moduleScope{new Scope(rootScope)};
Subtyping subtyping = mkSubtyping(rootScope);
Subtyping mkSubtyping(const ScopePtr& scope)
{
return Subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&iceReporter}, NotNull{scope.get()}};
}
TypePackId pack(std::initializer_list<TypeId> tys) TypePackId pack(std::initializer_list<TypeId> tys)
{ {
@ -66,11 +75,18 @@ struct SubtypeFixture : Fixture
return arena.addType(UnionType{{a, b}}); return arena.addType(UnionType{{a, b}});
} }
// `~`
TypeId negate(TypeId ty) TypeId negate(TypeId ty)
{ {
return arena.addType(NegationType{ty}); return arena.addType(NegationType{ty});
} }
// "literal"
TypeId str(const char* literal)
{
return arena.addType(SingletonType{StringSingleton{literal}});
}
TypeId cls(const std::string& name, std::optional<TypeId> parent = std::nullopt) TypeId cls(const std::string& name, std::optional<TypeId> parent = std::nullopt)
{ {
return arena.addType(ClassType{name, {}, parent.value_or(builtinTypes->classType), {}, {}, nullptr, ""}); return arena.addType(ClassType{name, {}, parent.value_or(builtinTypes->classType), {}, {}, nullptr, ""});
@ -97,8 +113,8 @@ struct SubtypeFixture : Fixture
return arena.addType(MetatableType{tbl(std::move(tableProps)), tbl(std::move(metaProps))}); return arena.addType(MetatableType{tbl(std::move(tableProps)), tbl(std::move(metaProps))});
} }
TypeId genericT = arena.addType(GenericType{"T"}); TypeId genericT = arena.addType(GenericType{moduleScope.get(), "T"});
TypeId genericU = arena.addType(GenericType{"U"}); TypeId genericU = arena.addType(GenericType{moduleScope.get(), "U"});
TypePackId genericAs = arena.addTypePack(GenericTypePack{"A"}); TypePackId genericAs = arena.addTypePack(GenericTypePack{"A"});
TypePackId genericBs = arena.addTypePack(GenericTypePack{"B"}); TypePackId genericBs = arena.addTypePack(GenericTypePack{"B"});
@ -113,6 +129,10 @@ struct SubtypeFixture : Fixture
TypeId helloType2 = arena.addType(SingletonType{StringSingleton{"hello"}}); TypeId helloType2 = arena.addType(SingletonType{StringSingleton{"hello"}});
TypeId worldType = arena.addType(SingletonType{StringSingleton{"world"}}); TypeId worldType = arena.addType(SingletonType{StringSingleton{"world"}});
TypeId aType = arena.addType(SingletonType{StringSingleton{"a"}});
TypeId bType = arena.addType(SingletonType{StringSingleton{"b"}});
TypeId trueSingleton = arena.addType(SingletonType{BooleanSingleton{true}});
TypeId falseSingleton = arena.addType(SingletonType{BooleanSingleton{false}});
TypeId helloOrWorldType = join(helloType, worldType); TypeId helloOrWorldType = join(helloType, worldType);
TypeId trueOrFalseType = join(builtinTypes->trueType, builtinTypes->falseType); TypeId trueOrFalseType = join(builtinTypes->trueType, builtinTypes->falseType);
@ -128,7 +148,7 @@ struct SubtypeFixture : Fixture
* \- AnotherChild * \- AnotherChild
* |- AnotherGrandchildOne * |- AnotherGrandchildOne
* \- AnotherGrandchildTwo * \- AnotherGrandchildTwo
*/ */
TypeId rootClass = cls("Root"); TypeId rootClass = cls("Root");
TypeId childClass = cls("Child", rootClass); TypeId childClass = cls("Child", rootClass);
TypeId grandchildOneClass = cls("GrandchildOne", childClass); TypeId grandchildOneClass = cls("GrandchildOne", childClass);
@ -138,9 +158,9 @@ struct SubtypeFixture : Fixture
TypeId anotherGrandchildTwoClass = cls("AnotherGrandchildTwo", anotherChildClass); TypeId anotherGrandchildTwoClass = cls("AnotherGrandchildTwo", anotherChildClass);
TypeId vec2Class = cls("Vec2", { TypeId vec2Class = cls("Vec2", {
{"X", builtinTypes->numberType}, {"X", builtinTypes->numberType},
{"Y", builtinTypes->numberType}, {"Y", builtinTypes->numberType},
}); });
// "hello" | "hello" // "hello" | "hello"
TypeId helloOrHelloType = arena.addType(UnionType{{helloType, helloType}}); TypeId helloOrHelloType = arena.addType(UnionType{{helloType, helloType}});
@ -149,160 +169,76 @@ struct SubtypeFixture : Fixture
const TypeId nothingToNothingType = fn({}, {}); const TypeId nothingToNothingType = fn({}, {});
// (number) -> string // (number) -> string
const TypeId numberToStringType = fn( const TypeId numberToStringType = fn({builtinTypes->numberType}, {builtinTypes->stringType});
{builtinTypes->numberType},
{builtinTypes->stringType}
);
// (unknown) -> string // (unknown) -> string
const TypeId unknownToStringType = fn( const TypeId unknownToStringType = fn({builtinTypes->unknownType}, {builtinTypes->stringType});
{builtinTypes->unknownType},
{builtinTypes->stringType}
);
// (number) -> () // (number) -> ()
const TypeId numberToNothingType = fn( const TypeId numberToNothingType = fn({builtinTypes->numberType}, {});
{builtinTypes->numberType},
{}
);
// () -> number // () -> number
const TypeId nothingToNumberType = fn( const TypeId nothingToNumberType = fn({}, {builtinTypes->numberType});
{},
{builtinTypes->numberType}
);
// (number) -> number // (number) -> number
const TypeId numberToNumberType = fn( const TypeId numberToNumberType = fn({builtinTypes->numberType}, {builtinTypes->numberType});
{builtinTypes->numberType},
{builtinTypes->numberType}
);
// (number) -> unknown // (number) -> unknown
const TypeId numberToUnknownType = fn( const TypeId numberToUnknownType = fn({builtinTypes->numberType}, {builtinTypes->unknownType});
{builtinTypes->numberType},
{builtinTypes->unknownType}
);
// (number) -> (string, string) // (number) -> (string, string)
const TypeId numberToTwoStringsType = fn( const TypeId numberToTwoStringsType = fn({builtinTypes->numberType}, {builtinTypes->stringType, builtinTypes->stringType});
{builtinTypes->numberType},
{builtinTypes->stringType, builtinTypes->stringType}
);
// (number) -> (string, unknown) // (number) -> (string, unknown)
const TypeId numberToStringAndUnknownType = fn( const TypeId numberToStringAndUnknownType = fn({builtinTypes->numberType}, {builtinTypes->stringType, builtinTypes->unknownType});
{builtinTypes->numberType},
{builtinTypes->stringType, builtinTypes->unknownType}
);
// (number, number) -> string // (number, number) -> string
const TypeId numberNumberToStringType = fn( const TypeId numberNumberToStringType = fn({builtinTypes->numberType, builtinTypes->numberType}, {builtinTypes->stringType});
{builtinTypes->numberType, builtinTypes->numberType},
{builtinTypes->stringType}
);
// (unknown, number) -> string // (unknown, number) -> string
const TypeId unknownNumberToStringType = fn( const TypeId unknownNumberToStringType = fn({builtinTypes->unknownType, builtinTypes->numberType}, {builtinTypes->stringType});
{builtinTypes->unknownType, builtinTypes->numberType},
{builtinTypes->stringType}
);
// (number, string) -> string // (number, string) -> string
const TypeId numberAndStringToStringType = fn( const TypeId numberAndStringToStringType = fn({builtinTypes->numberType, builtinTypes->stringType}, {builtinTypes->stringType});
{builtinTypes->numberType, builtinTypes->stringType},
{builtinTypes->stringType}
);
// (number, ...string) -> string // (number, ...string) -> string
const TypeId numberAndStringsToStringType = fn( const TypeId numberAndStringsToStringType =
{builtinTypes->numberType}, VariadicTypePack{builtinTypes->stringType}, fn({builtinTypes->numberType}, VariadicTypePack{builtinTypes->stringType}, {builtinTypes->stringType});
{builtinTypes->stringType}
);
// (number, ...string?) -> string // (number, ...string?) -> string
const TypeId numberAndOptionalStringsToStringType = fn( const TypeId numberAndOptionalStringsToStringType =
{builtinTypes->numberType}, VariadicTypePack{builtinTypes->optionalStringType}, fn({builtinTypes->numberType}, VariadicTypePack{builtinTypes->optionalStringType}, {builtinTypes->stringType});
{builtinTypes->stringType}
);
// (...number) -> number // (...number) -> number
const TypeId numbersToNumberType = arena.addType(FunctionType{ const TypeId numbersToNumberType =
arena.addTypePack(VariadicTypePack{builtinTypes->numberType}), arena.addType(FunctionType{arena.addTypePack(VariadicTypePack{builtinTypes->numberType}), arena.addTypePack({builtinTypes->numberType})});
arena.addTypePack({builtinTypes->numberType})
});
// <T>(T) -> () // <T>(T) -> ()
const TypeId genericTToNothingType = arena.addType(FunctionType{ const TypeId genericTToNothingType = arena.addType(FunctionType{{genericT}, {}, arena.addTypePack({genericT}), builtinTypes->emptyTypePack});
{genericT},
{},
arena.addTypePack({genericT}),
builtinTypes->emptyTypePack
});
// <T>(T) -> T // <T>(T) -> T
const TypeId genericTToTType = arena.addType(FunctionType{ const TypeId genericTToTType = arena.addType(FunctionType{{genericT}, {}, arena.addTypePack({genericT}), arena.addTypePack({genericT})});
{genericT},
{},
arena.addTypePack({genericT}),
arena.addTypePack({genericT})
});
// <U>(U) -> () // <U>(U) -> ()
const TypeId genericUToNothingType = arena.addType(FunctionType{ const TypeId genericUToNothingType = arena.addType(FunctionType{{genericU}, {}, arena.addTypePack({genericU}), builtinTypes->emptyTypePack});
{genericU},
{},
arena.addTypePack({genericU}),
builtinTypes->emptyTypePack
});
// <T>() -> T // <T>() -> T
const TypeId genericNothingToTType = arena.addType(FunctionType{ const TypeId genericNothingToTType = arena.addType(FunctionType{{genericT}, {}, builtinTypes->emptyTypePack, arena.addTypePack({genericT})});
{genericT},
{},
builtinTypes->emptyTypePack,
arena.addTypePack({genericT})
});
// <A...>(A...) -> A... // <A...>(A...) -> A...
const TypeId genericAsToAsType = arena.addType(FunctionType{ const TypeId genericAsToAsType = arena.addType(FunctionType{{}, {genericAs}, genericAs, genericAs});
{},
{genericAs},
genericAs,
genericAs
});
// <A...>(A...) -> number // <A...>(A...) -> number
const TypeId genericAsToNumberType = arena.addType(FunctionType{ const TypeId genericAsToNumberType = arena.addType(FunctionType{{}, {genericAs}, genericAs, arena.addTypePack({builtinTypes->numberType})});
{},
{genericAs},
genericAs,
arena.addTypePack({builtinTypes->numberType})
});
// <B...>(B...) -> B... // <B...>(B...) -> B...
const TypeId genericBsToBsType = arena.addType(FunctionType{ const TypeId genericBsToBsType = arena.addType(FunctionType{{}, {genericBs}, genericBs, genericBs});
{},
{genericBs},
genericBs,
genericBs
});
// <B..., C...>(B...) -> C... // <B..., C...>(B...) -> C...
const TypeId genericBsToCsType = arena.addType(FunctionType{ const TypeId genericBsToCsType = arena.addType(FunctionType{{}, {genericBs, genericCs}, genericBs, genericCs});
{},
{genericBs, genericCs},
genericBs,
genericCs
});
// <A...>() -> A... // <A...>() -> A...
const TypeId genericNothingToAsType = arena.addType(FunctionType{ const TypeId genericNothingToAsType = arena.addType(FunctionType{{}, {genericAs}, builtinTypes->emptyTypePack, genericAs});
{},
{genericAs},
builtinTypes->emptyTypePack,
genericAs
});
// { lower : string -> string } // { lower : string -> string }
TypeId tableWithLower = tbl(TableType::Props{{"lower", fn({builtinTypes->stringType}, {builtinTypes->stringType})}}); TypeId tableWithLower = tbl(TableType::Props{{"lower", fn({builtinTypes->stringType}, {builtinTypes->stringType})}});
@ -728,60 +664,98 @@ TEST_CASE_FIXTURE(SubtypeFixture, "{x: number?} <!: {x: number}")
TEST_CASE_FIXTURE(SubtypeFixture, "{x: <T>(T) -> ()} <: {x: <U>(U) -> ()}") TEST_CASE_FIXTURE(SubtypeFixture, "{x: <T>(T) -> ()} <: {x: <U>(U) -> ()}")
{ {
CHECK_IS_SUBTYPE( CHECK_IS_SUBTYPE(tbl({{"x", genericTToNothingType}}), tbl({{"x", genericUToNothingType}}));
tbl({{"x", genericTToNothingType}}),
tbl({{"x", genericUToNothingType}})
);
} }
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <: { @metatable {} }") TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <: { @metatable {} }")
{ {
CHECK_IS_SUBTYPE( CHECK_IS_SUBTYPE(meta({{"x", builtinTypes->numberType}}), meta({}));
meta({{"x", builtinTypes->numberType}}),
meta({})
);
} }
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <!: { @metatable { x: boolean } }") TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <!: { @metatable { x: boolean } }")
{ {
CHECK_IS_NOT_SUBTYPE( CHECK_IS_NOT_SUBTYPE(meta({{"x", builtinTypes->numberType}}), meta({{"x", builtinTypes->booleanType}}));
meta({{"x", builtinTypes->numberType}}),
meta({{"x", builtinTypes->booleanType}})
);
} }
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } <!: { @metatable { x: boolean } }") TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } <!: { @metatable { x: boolean } }")
{ {
CHECK_IS_NOT_SUBTYPE( CHECK_IS_NOT_SUBTYPE(meta({}), meta({{"x", builtinTypes->booleanType}}));
meta({}),
meta({{"x", builtinTypes->booleanType}})
);
} }
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } <: {}") TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } <: {}")
{ {
CHECK_IS_SUBTYPE( CHECK_IS_SUBTYPE(meta({}), tbl({}));
meta({}),
tbl({})
);
} }
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { u: boolean }, x: number } <: { x: number }") TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { u: boolean }, x: number } <: { x: number }")
{ {
CHECK_IS_SUBTYPE( CHECK_IS_SUBTYPE(meta({{"u", builtinTypes->booleanType}}, {{"x", builtinTypes->numberType}}), tbl({{"x", builtinTypes->numberType}}));
meta({{"u", builtinTypes->booleanType}}, {{"x", builtinTypes->numberType}}),
tbl({{"x", builtinTypes->numberType}})
);
} }
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <!: { x: number }") TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <!: { x: number }")
{ {
CHECK_IS_NOT_SUBTYPE( CHECK_IS_NOT_SUBTYPE(meta({{"x", builtinTypes->numberType}}), tbl({{"x", builtinTypes->numberType}}));
meta({{"x", builtinTypes->numberType}}),
tbl({{"x", builtinTypes->numberType}})
);
} }
// Negated subtypes
TEST_IS_NOT_SUBTYPE(negate(builtinTypes->neverType), builtinTypes->stringType);
TEST_IS_SUBTYPE(negate(builtinTypes->unknownType), builtinTypes->stringType);
TEST_IS_NOT_SUBTYPE(negate(builtinTypes->anyType), builtinTypes->stringType);
TEST_IS_SUBTYPE(negate(meet(builtinTypes->neverType, builtinTypes->unknownType)), builtinTypes->stringType);
TEST_IS_NOT_SUBTYPE(negate(join(builtinTypes->neverType, builtinTypes->unknownType)), builtinTypes->stringType);
// Negated supertypes: never/unknown/any/error
TEST_IS_SUBTYPE(builtinTypes->stringType, negate(builtinTypes->neverType));
TEST_IS_SUBTYPE(builtinTypes->neverType, negate(builtinTypes->unknownType));
TEST_IS_NOT_SUBTYPE(builtinTypes->stringType, negate(builtinTypes->unknownType));
TEST_IS_SUBTYPE(builtinTypes->numberType, negate(builtinTypes->anyType));
TEST_IS_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->anyType));
// Negated supertypes: unions
TEST_IS_SUBTYPE(builtinTypes->booleanType, negate(join(builtinTypes->stringType, builtinTypes->numberType)));
TEST_IS_SUBTYPE(rootClass, negate(join(childClass, builtinTypes->numberType)));
TEST_IS_SUBTYPE(str("foo"), negate(join(builtinTypes->numberType, builtinTypes->booleanType)));
TEST_IS_NOT_SUBTYPE(str("foo"), negate(join(builtinTypes->stringType, builtinTypes->numberType)));
TEST_IS_NOT_SUBTYPE(childClass, negate(join(rootClass, builtinTypes->numberType)));
TEST_IS_NOT_SUBTYPE(numbersToNumberType, negate(join(builtinTypes->functionType, rootClass)));
// Negated supertypes: intersections
TEST_IS_SUBTYPE(builtinTypes->booleanType, negate(meet(builtinTypes->stringType, str("foo"))));
TEST_IS_SUBTYPE(builtinTypes->trueType, negate(meet(builtinTypes->booleanType, builtinTypes->numberType)));
TEST_IS_SUBTYPE(rootClass, negate(meet(builtinTypes->classType, childClass)));
TEST_IS_SUBTYPE(childClass, negate(meet(builtinTypes->classType, builtinTypes->numberType)));
TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(meet(builtinTypes->classType, builtinTypes->numberType)));
TEST_IS_NOT_SUBTYPE(str("foo"), negate(meet(builtinTypes->stringType, negate(str("bar")))));
// Negated supertypes: tables and metatables
TEST_IS_SUBTYPE(tbl({}), negate(builtinTypes->numberType));
TEST_IS_NOT_SUBTYPE(tbl({}), negate(builtinTypes->tableType));
TEST_IS_SUBTYPE(meta({}), negate(builtinTypes->numberType));
TEST_IS_NOT_SUBTYPE(meta({}), negate(builtinTypes->tableType));
// Negated supertypes: Functions
TEST_IS_SUBTYPE(numberToNumberType, negate(builtinTypes->classType));
TEST_IS_NOT_SUBTYPE(numberToNumberType, negate(builtinTypes->functionType));
// Negated supertypes: Primitives and singletons
TEST_IS_SUBTYPE(builtinTypes->stringType, negate(builtinTypes->numberType));
TEST_IS_SUBTYPE(str("foo"), meet(builtinTypes->stringType, negate(str("bar"))));
TEST_IS_NOT_SUBTYPE(builtinTypes->trueType, negate(builtinTypes->booleanType));
TEST_IS_NOT_SUBTYPE(str("foo"), negate(str("foo")));
TEST_IS_NOT_SUBTYPE(str("foo"), negate(builtinTypes->stringType));
TEST_IS_SUBTYPE(builtinTypes->falseType, negate(builtinTypes->trueType));
TEST_IS_SUBTYPE(builtinTypes->falseType, meet(builtinTypes->booleanType, negate(builtinTypes->trueType)));
TEST_IS_NOT_SUBTYPE(builtinTypes->stringType, meet(builtinTypes->booleanType, negate(builtinTypes->trueType)));
TEST_IS_NOT_SUBTYPE(builtinTypes->stringType, negate(str("foo")));
TEST_IS_NOT_SUBTYPE(builtinTypes->booleanType, negate(builtinTypes->falseType));
// Negated supertypes: Classes
TEST_IS_SUBTYPE(rootClass, negate(builtinTypes->tableType));
TEST_IS_NOT_SUBTYPE(rootClass, negate(builtinTypes->classType));
TEST_IS_NOT_SUBTYPE(childClass, negate(rootClass));
TEST_IS_NOT_SUBTYPE(childClass, meet(builtinTypes->classType, negate(rootClass)));
TEST_IS_SUBTYPE(anotherChildClass, meet(builtinTypes->classType, negate(childClass)));
TEST_CASE_FIXTURE(SubtypeFixture, "Root <: class") TEST_CASE_FIXTURE(SubtypeFixture, "Root <: class")
{ {
CHECK_IS_SUBTYPE(rootClass, builtinTypes->classType); CHECK_IS_SUBTYPE(rootClass, builtinTypes->classType);
@ -829,13 +803,11 @@ TEST_CASE_FIXTURE(SubtypeFixture, "Child & ~GrandchildOne <!: number")
TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <: t2 where t2 = {trim: (t2) -> string}") TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <: t2 where t2 = {trim: (t2) -> string}")
{ {
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) {
{
tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
}); });
TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) {
{
tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
}); });
@ -844,13 +816,11 @@ TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <: t2 wh
TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <!: t2 where t2 = {trim: (t2) -> t2}") TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <!: t2 where t2 = {trim: (t2) -> t2}")
{ {
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) {
{
tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
}); });
TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) {
{
tt->props["trim"] = fn({ty}, {ty}); tt->props["trim"] = fn({ty}, {ty});
}); });
@ -859,13 +829,11 @@ TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <!: t2 w
TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> t1} <!: t2 where t2 = {trim: (t2) -> string}") TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> t1} <!: t2 where t2 = {trim: (t2) -> string}")
{ {
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) {
{
tt->props["trim"] = fn({ty}, {ty}); tt->props["trim"] = fn({ty}, {ty});
}); });
TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) {
{
tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
}); });
@ -960,6 +928,50 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(string) -> number <: ~fun & (string) -> numb
CHECK_IS_NOT_SUBTYPE(numberToStringType, meet(negate(builtinTypes->functionType), numberToStringType)); CHECK_IS_NOT_SUBTYPE(numberToStringType, meet(negate(builtinTypes->functionType), numberToStringType));
} }
TEST_CASE_FIXTURE(SubtypeFixture, "~\"a\" & ~\"b\" & string <: { lower : (string) -> ()}")
{
CHECK_IS_SUBTYPE(meet(meet(negate(aType), negate(bType)), builtinTypes->stringType), tableWithLower);
}
TEST_CASE_FIXTURE(SubtypeFixture, "\"a\" | (~\"b\" & string) <: { lower : (string) -> ()}")
{
CHECK_IS_SUBTYPE(join(aType, meet(negate(bType), builtinTypes->stringType)), tableWithLower);
}
TEST_CASE_FIXTURE(SubtypeFixture, "(string | number) & (\"a\" | true) <: { lower: (string) -> string }")
{
auto base = meet(join(builtinTypes->stringType, builtinTypes->numberType), join(aType, trueSingleton));
CHECK_IS_SUBTYPE(base, tableWithLower);
}
/*
* Within the scope to which a generic belongs, that generic ought to be treated
* as its bounds.
*
* We do not yet support bounded generics, so all generics are considered to be
* bounded by unknown.
*/
TEST_CASE_FIXTURE(SubtypeFixture, "unknown <: X")
{
ScopePtr childScope{new Scope(rootScope)};
ScopePtr grandChildScope{new Scope(childScope)};
TypeId genericX = arena.addType(GenericType(childScope.get(), "X"));
SubtypingResult usingGlobalScope = subtyping.isSubtype(builtinTypes->unknownType, genericX);
CHECK_MESSAGE(!usingGlobalScope.isSubtype, "Expected " << builtinTypes->unknownType << " </: " << genericX);
Subtyping childSubtyping{mkSubtyping(childScope)};
SubtypingResult usingChildScope = childSubtyping.isSubtype(builtinTypes->unknownType, genericX);
CHECK_MESSAGE(usingChildScope.isSubtype, "Expected " << builtinTypes->unknownType << " <: " << genericX);
Subtyping grandChildSubtyping{mkSubtyping(grandChildScope)};
SubtypingResult usingGrandChildScope = grandChildSubtyping.isSubtype(builtinTypes->unknownType, genericX);
CHECK_MESSAGE(usingGrandChildScope.isSubtype, "Expected " << builtinTypes->unknownType << " <: " << genericX);
}
/* /*
* <A>(A) -> A <: <X>(X) -> X * <A>(A) -> A <: <X>(X) -> X
* A can be bound to X. * A can be bound to X.

View file

@ -1067,8 +1067,9 @@ local w = c and 1
CHECK("false | number" == toString(requireType("z"))); CHECK("false | number" == toString(requireType("z")));
else else
CHECK("boolean | number" == toString(requireType("z"))); // 'false' widened to boolean CHECK("boolean | number" == toString(requireType("z"))); // 'false' widened to boolean
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("((false?) & a) | number" == toString(requireType("w"))); CHECK("((false?) & unknown) | number" == toString(requireType("w")));
else else
CHECK("(boolean | number)?" == toString(requireType("w"))); CHECK("(boolean | number)?" == toString(requireType("w")));
} }

View file

@ -308,7 +308,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_if_condition_position")
else else
CHECK_EQ("number", toString(requireTypeAtPosition({3, 26}))); CHECK_EQ("number", toString(requireTypeAtPosition({3, 26})));
CHECK_EQ("number", toString(requireTypeAtPosition({6, 26}))); CHECK_EQ("number", toString(requireTypeAtPosition({6, 26})));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_assert_position") TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_assert_position")

View file

@ -3788,4 +3788,40 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_shifted_tables")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(Fixture, "cli_84607_missing_prop_in_array_or_dict")
{
ScopedFastFlag sff{"LuauFixIndexerSubtypingOrdering", true};
CheckResult result = check(R"(
type Thing = { name: string, prop: boolean }
local arrayOfThings : {Thing} = {
{ name = "a" }
}
local dictOfThings : {[string]: Thing} = {
a = { name = "a" }
}
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
TypeError& err1 = result.errors[0];
MissingProperties* error1 = get<MissingProperties>(err1);
REQUIRE(error1);
REQUIRE(error1->properties.size() == 1);
CHECK_EQ("prop", error1->properties[0]);
TypeError& err2 = result.errors[1];
TypeMismatch* mismatch = get<TypeMismatch>(err2);
REQUIRE(mismatch);
MissingProperties* error2 = get<MissingProperties>(*mismatch->error);
REQUIRE(error2);
REQUIRE(error2->properties.size() == 1);
CHECK_EQ("prop", error2->properties[0]);
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -1428,7 +1428,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "be_sure_to_use_active_txnlog_when_evaluating
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERRORS(result);
for (const auto& e: result.errors) for (const auto& e : result.errors)
CHECK(5 == e.location.begin.line); CHECK(5 == e.location.begin.line);
} }

View file

@ -81,18 +81,12 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "T <: U")
TEST_CASE_FIXTURE(Unifier2Fixture, "(string) -> () <: (X) -> Y...") TEST_CASE_FIXTURE(Unifier2Fixture, "(string) -> () <: (X) -> Y...")
{ {
TypeId stringToUnit = arena.addType(FunctionType{ TypeId stringToUnit = arena.addType(FunctionType{arena.addTypePack({builtinTypes.stringType}), arena.addTypePack({})});
arena.addTypePack({builtinTypes.stringType}),
arena.addTypePack({})
});
auto [x, xFree] = freshType(); auto [x, xFree] = freshType();
TypePackId y = arena.freshTypePack(&scope); TypePackId y = arena.freshTypePack(&scope);
TypeId xToY = arena.addType(FunctionType{ TypeId xToY = arena.addType(FunctionType{arena.addTypePack({x}), y});
arena.addTypePack({x}),
y
});
u2.unify(stringToUnit, xToY); u2.unify(stringToUnit, xToY);
@ -105,4 +99,54 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "(string) -> () <: (X) -> Y...")
CHECK(!yPack->tail); CHECK(!yPack->tail);
} }
TEST_CASE_FIXTURE(Unifier2Fixture, "generalize_a_type_that_is_bounded_by_another_generalizable_type")
{
auto [t1, ft1] = freshType();
auto [t2, ft2] = freshType();
// t2 <: t1 <: unknown
// unknown <: t2 <: t1
ft1->lowerBound = t2;
ft2->upperBound = t1;
ft2->lowerBound = builtinTypes.unknownType;
auto t2generalized = u2.generalize(NotNull{&scope}, t2);
REQUIRE(t2generalized);
CHECK(follow(t1) == follow(t2));
auto t1generalized = u2.generalize(NotNull{&scope}, t1);
REQUIRE(t1generalized);
CHECK(builtinTypes.unknownType == follow(t1));
CHECK(builtinTypes.unknownType == follow(t2));
}
// Same as generalize_a_type_that_is_bounded_by_another_generalizable_type
// except that we generalize the types in the opposite order
TEST_CASE_FIXTURE(Unifier2Fixture, "generalize_a_type_that_is_bounded_by_another_generalizable_type_in_reverse_order")
{
auto [t1, ft1] = freshType();
auto [t2, ft2] = freshType();
// t2 <: t1 <: unknown
// unknown <: t2 <: t1
ft1->lowerBound = t2;
ft2->upperBound = t1;
ft2->lowerBound = builtinTypes.unknownType;
auto t1generalized = u2.generalize(NotNull{&scope}, t1);
REQUIRE(t1generalized);
CHECK(follow(t1) == follow(t2));
auto t2generalized = u2.generalize(NotNull{&scope}, t2);
REQUIRE(t2generalized);
CHECK(builtinTypes.unknownType == follow(t1));
CHECK(builtinTypes.unknownType == follow(t2));
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -3,7 +3,6 @@ AnnotationTests.two_type_params
AstQuery.last_argument_function_call_type AstQuery.last_argument_function_call_type
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg
AutocompleteTest.autocomplete_if_else_regression
AutocompleteTest.autocomplete_interpolated_string_as_singleton AutocompleteTest.autocomplete_interpolated_string_as_singleton
AutocompleteTest.autocomplete_oop_implicit_self AutocompleteTest.autocomplete_oop_implicit_self
AutocompleteTest.autocomplete_response_perf1 AutocompleteTest.autocomplete_response_perf1
@ -92,8 +91,6 @@ IntersectionTypes.table_intersection_write_sealed_indirect
IntersectionTypes.table_write_sealed_indirect IntersectionTypes.table_write_sealed_indirect
Normalize.negations_of_tables Normalize.negations_of_tables
Normalize.specific_functions_cannot_be_negated Normalize.specific_functions_cannot_be_negated
ParserTests.parse_nesting_based_end_detection
ParserTests.parse_nesting_based_end_detection_single_line
ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal
ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack
ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean
@ -124,13 +121,13 @@ RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible
TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
TableTests.call_method TableTests.call_method
TableTests.call_method_with_explicit_self_argument
TableTests.cannot_augment_sealed_table TableTests.cannot_augment_sealed_table
TableTests.cannot_change_type_of_unsealed_table_prop TableTests.cannot_change_type_of_unsealed_table_prop
TableTests.casting_sealed_tables_with_props_into_table_with_indexer TableTests.casting_sealed_tables_with_props_into_table_with_indexer
TableTests.casting_tables_with_props_into_table_with_indexer4 TableTests.casting_tables_with_props_into_table_with_indexer4
TableTests.casting_unsealed_tables_with_props_into_table_with_indexer TableTests.casting_unsealed_tables_with_props_into_table_with_indexer
TableTests.checked_prop_too_early TableTests.checked_prop_too_early
TableTests.cli_84607_missing_prop_in_array_or_dict
TableTests.cyclic_shifted_tables TableTests.cyclic_shifted_tables
TableTests.defining_a_method_for_a_local_sealed_table_must_fail TableTests.defining_a_method_for_a_local_sealed_table_must_fail
TableTests.defining_a_self_method_for_a_local_sealed_table_must_fail TableTests.defining_a_self_method_for_a_local_sealed_table_must_fail
@ -139,6 +136,7 @@ TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
TableTests.dont_extend_unsealed_tables_in_rvalue_position TableTests.dont_extend_unsealed_tables_in_rvalue_position
TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index
TableTests.dont_leak_free_table_props TableTests.dont_leak_free_table_props
TableTests.dont_quantify_table_that_belongs_to_outer_scope
TableTests.dont_suggest_exact_match_keys TableTests.dont_suggest_exact_match_keys
TableTests.error_detailed_metatable_prop TableTests.error_detailed_metatable_prop
TableTests.explicitly_typed_table TableTests.explicitly_typed_table
@ -154,19 +152,21 @@ TableTests.inequality_operators_imply_exactly_matching_types
TableTests.infer_array_2 TableTests.infer_array_2
TableTests.infer_indexer_from_value_property_in_literal TableTests.infer_indexer_from_value_property_in_literal
TableTests.infer_type_when_indexing_from_a_table_indexer TableTests.infer_type_when_indexing_from_a_table_indexer
TableTests.inferred_properties_of_a_table_should_start_with_the_same_TypeLevel_of_that_table
TableTests.inferred_return_type_of_free_table TableTests.inferred_return_type_of_free_table
TableTests.instantiate_table_cloning_3 TableTests.instantiate_table_cloning_3
TableTests.leaking_bad_metatable_errors TableTests.leaking_bad_metatable_errors
TableTests.less_exponential_blowup_please TableTests.less_exponential_blowup_please
TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred
TableTests.mixed_tables_with_implicit_numbered_keys TableTests.mixed_tables_with_implicit_numbered_keys
TableTests.ok_to_add_property_to_free_table
TableTests.ok_to_provide_a_subtype_during_construction TableTests.ok_to_provide_a_subtype_during_construction
TableTests.okay_to_add_property_to_unsealed_tables_by_assignment TableTests.okay_to_add_property_to_unsealed_tables_by_assignment
TableTests.okay_to_add_property_to_unsealed_tables_by_function_call
TableTests.only_ascribe_synthetic_names_at_module_scope
TableTests.oop_indexer_works TableTests.oop_indexer_works
TableTests.oop_polymorphic TableTests.oop_polymorphic
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2
TableTests.pass_incompatible_union_to_a_generic_table_without_crashing
TableTests.passing_compatible_unions_to_a_generic_table_without_crashing
TableTests.quantify_even_that_table_was_never_exported_at_all TableTests.quantify_even_that_table_was_never_exported_at_all
TableTests.quantify_metatables_of_metatables_of_table TableTests.quantify_metatables_of_metatables_of_table
TableTests.quantifying_a_bound_var_works TableTests.quantifying_a_bound_var_works
@ -185,7 +185,6 @@ TableTests.table_unification_4
TableTests.type_mismatch_on_massive_table_is_cut_short TableTests.type_mismatch_on_massive_table_is_cut_short
TableTests.used_colon_instead_of_dot TableTests.used_colon_instead_of_dot
TableTests.used_dot_instead_of_colon TableTests.used_dot_instead_of_colon
TableTests.used_dot_instead_of_colon_but_correctly
TableTests.when_augmenting_an_unsealed_table_with_an_indexer_apply_the_correct_scope_to_the_indexer_type TableTests.when_augmenting_an_unsealed_table_with_an_indexer_apply_the_correct_scope_to_the_indexer_type
TableTests.wrong_assign_does_hit_indexer TableTests.wrong_assign_does_hit_indexer
ToDot.function ToDot.function
@ -215,6 +214,7 @@ TypeAliases.type_alias_of_an_imported_recursive_generic_type
TypeFamilyTests.family_as_fn_arg TypeFamilyTests.family_as_fn_arg
TypeFamilyTests.table_internal_families TypeFamilyTests.table_internal_families
TypeFamilyTests.unsolvable_family TypeFamilyTests.unsolvable_family
TypeInfer.be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload
TypeInfer.bidirectional_checking_of_higher_order_function TypeInfer.bidirectional_checking_of_higher_order_function
TypeInfer.check_type_infer_recursion_count TypeInfer.check_type_infer_recursion_count
TypeInfer.cli_39932_use_unifier_in_ensure_methods TypeInfer.cli_39932_use_unifier_in_ensure_methods
@ -226,21 +226,24 @@ TypeInfer.fuzz_free_table_type_change_during_index_check
TypeInfer.infer_assignment_value_types_mutable_lval TypeInfer.infer_assignment_value_types_mutable_lval
TypeInfer.infer_locals_via_assignment_from_its_call_site TypeInfer.infer_locals_via_assignment_from_its_call_site
TypeInfer.no_stack_overflow_from_isoptional TypeInfer.no_stack_overflow_from_isoptional
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2 TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
TypeInfer.type_infer_cache_limit_normalizer TypeInfer.type_infer_cache_limit_normalizer
TypeInfer.type_infer_recursion_limit_no_ice TypeInfer.type_infer_recursion_limit_no_ice
TypeInfer.type_infer_recursion_limit_normalizer TypeInfer.type_infer_recursion_limit_normalizer
TypeInferAnyError.can_subscript_any TypeInferAnyError.can_subscript_any
TypeInferAnyError.for_in_loop_iterator_is_any
TypeInferAnyError.for_in_loop_iterator_is_any2 TypeInferAnyError.for_in_loop_iterator_is_any2
TypeInferAnyError.for_in_loop_iterator_is_error
TypeInferAnyError.for_in_loop_iterator_is_error2
TypeInferAnyError.for_in_loop_iterator_returns_any TypeInferAnyError.for_in_loop_iterator_returns_any
TypeInferAnyError.for_in_loop_iterator_returns_any2
TypeInferAnyError.intersection_of_any_can_have_props TypeInferAnyError.intersection_of_any_can_have_props
TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any
TypeInferAnyError.union_of_types_regression_test TypeInferAnyError.union_of_types_regression_test
TypeInferClasses.can_read_prop_of_base_class_using_string
TypeInferClasses.class_type_mismatch_with_name_conflict TypeInferClasses.class_type_mismatch_with_name_conflict
TypeInferClasses.index_instance_property TypeInferClasses.index_instance_property
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties
TypeInferFunctions.cannot_hoist_interior_defns_into_signature TypeInferFunctions.cannot_hoist_interior_defns_into_signature
TypeInferFunctions.dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization TypeInferFunctions.dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization
TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site
@ -254,10 +257,10 @@ TypeInferFunctions.higher_order_function_2
TypeInferFunctions.higher_order_function_4 TypeInferFunctions.higher_order_function_4
TypeInferFunctions.improved_function_arg_mismatch_errors TypeInferFunctions.improved_function_arg_mismatch_errors
TypeInferFunctions.infer_anonymous_function_arguments TypeInferFunctions.infer_anonymous_function_arguments
TypeInferFunctions.infer_anonymous_function_arguments_outside_call
TypeInferFunctions.infer_generic_function_function_argument TypeInferFunctions.infer_generic_function_function_argument
TypeInferFunctions.infer_generic_function_function_argument_overloaded TypeInferFunctions.infer_generic_function_function_argument_overloaded
TypeInferFunctions.infer_generic_lib_function_function_argument TypeInferFunctions.infer_generic_lib_function_function_argument
TypeInferFunctions.infer_anonymous_function_arguments_outside_call
TypeInferFunctions.infer_that_function_does_not_return_a_table TypeInferFunctions.infer_that_function_does_not_return_a_table
TypeInferFunctions.luau_subtyping_is_np_hard TypeInferFunctions.luau_subtyping_is_np_hard
TypeInferFunctions.no_lossy_function_type TypeInferFunctions.no_lossy_function_type
@ -269,21 +272,22 @@ TypeInferFunctions.too_few_arguments_variadic_generic2
TypeInferFunctions.too_many_arguments_error_location TypeInferFunctions.too_many_arguments_error_location
TypeInferFunctions.too_many_return_values_in_parentheses TypeInferFunctions.too_many_return_values_in_parentheses
TypeInferFunctions.too_many_return_values_no_function TypeInferFunctions.too_many_return_values_no_function
TypeInferFunctions.toposort_doesnt_break_mutual_recursion
TypeInferFunctions.vararg_function_is_quantified
TypeInferLoops.cli_68448_iterators_need_not_accept_nil TypeInferLoops.cli_68448_iterators_need_not_accept_nil
TypeInferLoops.dcr_iteration_on_never_gives_never TypeInferLoops.dcr_iteration_on_never_gives_never
TypeInferLoops.for_in_loop TypeInferLoops.for_in_loop
TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values
TypeInferLoops.for_in_loop_on_error
TypeInferLoops.for_in_loop_with_custom_iterator TypeInferLoops.for_in_loop_with_custom_iterator
TypeInferLoops.for_in_loop_with_incompatible_args_to_iterator TypeInferLoops.for_in_loop_with_incompatible_args_to_iterator
TypeInferLoops.for_in_loop_with_next TypeInferLoops.for_in_loop_with_next
TypeInferLoops.ipairs_produces_integral_indices TypeInferLoops.ipairs_produces_integral_indices
TypeInferLoops.iteration_regression_issue_69967_alt TypeInferLoops.iteration_regression_issue_69967_alt
TypeInferLoops.loop_iter_basic
TypeInferLoops.loop_iter_metamethod_nil TypeInferLoops.loop_iter_metamethod_nil
TypeInferLoops.loop_iter_metamethod_ok_with_inference TypeInferLoops.loop_iter_metamethod_ok_with_inference
TypeInferLoops.loop_iter_trailing_nil TypeInferLoops.loop_iter_trailing_nil
TypeInferLoops.unreachable_code_after_infinite_loop TypeInferLoops.unreachable_code_after_infinite_loop
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
TypeInferModules.do_not_modify_imported_types_5 TypeInferModules.do_not_modify_imported_types_5
TypeInferModules.module_type_conflict TypeInferModules.module_type_conflict
TypeInferModules.module_type_conflict_instantiated TypeInferModules.module_type_conflict_instantiated
@ -294,7 +298,6 @@ TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
TypeInferOOP.methods_are_topologically_sorted TypeInferOOP.methods_are_topologically_sorted
TypeInferOperators.and_binexps_dont_unify TypeInferOperators.and_binexps_dont_unify
TypeInferOperators.cli_38355_recursive_union TypeInferOperators.cli_38355_recursive_union
TypeInferOperators.compound_assign_mismatch_metatable
TypeInferOperators.concat_op_on_string_lhs_and_free_rhs TypeInferOperators.concat_op_on_string_lhs_and_free_rhs
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
TypeInferOperators.luau_polyfill_is_array TypeInferOperators.luau_polyfill_is_array