mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-13 13:30:40 +00:00
Sync to upstream/release/611 (#1160)
# What's changed? ### Native Code Generation * Fixed an UAF relating to reusing a hash key after a weak table has undergone some GC. * Fixed a bounds check on arm64 to allow access to the last byte of a buffer. ### New Type Solver * Type states now preserves error-suppression, i.e. `local x: any = 5` and `x.foo` does not error. * Made error-suppression logic in subtyping more accurate. * Subtyping now knows how to reduce type families. * Fixed function call overload resolution so that the return type resolves to the correct overload. * Fixed a case where we attempted to reduce irreducible type families a few too many times, leading to duplicate errors. * Type checker needs to type check annotations in function signatures to be able to report errors relating to those annotations. * Fixed an UAF from a pointer to stack-allocated data in Subtyping's `explainReasonings`. ### Nonstrict Type Checker * Fixed a crash when calling a checked function of the form `math.abs` with an incorrect argument type. * Fixed a crash when calling a checked function with a number of arguments that did not exactly match the number of parameters required. --- ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
parent
974963a870
commit
67ce75e870
35 changed files with 1264 additions and 567 deletions
|
@ -143,6 +143,18 @@ private:
|
||||||
*/
|
*/
|
||||||
TypePackId freshTypePack(const ScopePtr& scope);
|
TypePackId freshTypePack(const ScopePtr& scope);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate a new TypePack with the given head and tail.
|
||||||
|
*
|
||||||
|
* Avoids allocating 0-length type packs:
|
||||||
|
*
|
||||||
|
* If the head is non-empty, allocate and return a type pack with the given
|
||||||
|
* head and tail.
|
||||||
|
* If the head is empty and tail is non-empty, return *tail.
|
||||||
|
* If both the head and tail are empty, return an empty type pack.
|
||||||
|
*/
|
||||||
|
TypePackId addTypePack(std::vector<TypeId> head, std::optional<TypePackId> tail);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fabricates a scope that is a child of another scope.
|
* Fabricates a scope that is a child of another scope.
|
||||||
* @param node the lexical node that the scope belongs to.
|
* @param node the lexical node that the scope belongs to.
|
||||||
|
|
|
@ -254,7 +254,6 @@ struct ConstraintSolver
|
||||||
bool hasUnresolvedConstraints(TypeId ty);
|
bool hasUnresolvedConstraints(TypeId ty);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/** Helper used by tryDispatch(SubtypeConstraint) and
|
/** Helper used by tryDispatch(SubtypeConstraint) and
|
||||||
* tryDispatch(PackSubtypeConstraint)
|
* tryDispatch(PackSubtypeConstraint)
|
||||||
*
|
*
|
||||||
|
@ -265,7 +264,7 @@ private:
|
||||||
*
|
*
|
||||||
* If unification succeeds, unblock every type changed by the unification.
|
* If unification succeeds, unblock every type changed by the unification.
|
||||||
*/
|
*/
|
||||||
template <typename TID>
|
template<typename TID>
|
||||||
bool tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy);
|
bool tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -380,13 +380,22 @@ struct NonStrictFunctionDefinitionError
|
||||||
bool operator==(const NonStrictFunctionDefinitionError& rhs) const;
|
bool operator==(const NonStrictFunctionDefinitionError& rhs) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
using TypeErrorData = Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods,
|
struct CheckedFunctionIncorrectArgs
|
||||||
DuplicateTypeDefinition, CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire,
|
{
|
||||||
IncorrectGenericParameterCount, SyntaxError, CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, InternalError,
|
std::string functionName;
|
||||||
CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning,
|
size_t expected;
|
||||||
DuplicateGenericParameter, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty,
|
size_t actual;
|
||||||
TypesAreUnrelated, NormalizationTooComplex, TypePackMismatch, DynamicPropertyLookupOnClassesUnsafe, UninhabitedTypeFamily,
|
bool operator==(const CheckedFunctionIncorrectArgs& rhs) const;
|
||||||
UninhabitedTypePackFamily, WhereClauseNeeded, PackWhereClauseNeeded, CheckedFunctionCallError, NonStrictFunctionDefinitionError>;
|
};
|
||||||
|
|
||||||
|
using TypeErrorData =
|
||||||
|
Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods, DuplicateTypeDefinition,
|
||||||
|
CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire, IncorrectGenericParameterCount, SyntaxError,
|
||||||
|
CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, InternalError, CannotCallNonFunction, ExtraInformation,
|
||||||
|
DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning, DuplicateGenericParameter,
|
||||||
|
CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty, TypesAreUnrelated,
|
||||||
|
NormalizationTooComplex, TypePackMismatch, DynamicPropertyLookupOnClassesUnsafe, UninhabitedTypeFamily, UninhabitedTypePackFamily,
|
||||||
|
WhereClauseNeeded, PackWhereClauseNeeded, CheckedFunctionCallError, NonStrictFunctionDefinitionError, CheckedFunctionIncorrectArgs>;
|
||||||
|
|
||||||
struct TypeErrorSummary
|
struct TypeErrorSummary
|
||||||
{
|
{
|
||||||
|
|
70
Analysis/include/Luau/OverloadResolution.h
Normal file
70
Analysis/include/Luau/OverloadResolution.h
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Ast.h"
|
||||||
|
#include "Luau/InsertionOrderedMap.h"
|
||||||
|
#include "Luau/NotNull.h"
|
||||||
|
#include "Luau/TypeFwd.h"
|
||||||
|
#include "Luau/Location.h"
|
||||||
|
#include "Luau/Error.h"
|
||||||
|
#include "Luau/Subtyping.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
struct BuiltinTypes;
|
||||||
|
struct TypeArena;
|
||||||
|
struct Scope;
|
||||||
|
struct InternalErrorReporter;
|
||||||
|
struct TypeCheckLimits;
|
||||||
|
struct Subtyping;
|
||||||
|
|
||||||
|
class Normalizer;
|
||||||
|
|
||||||
|
struct OverloadResolver
|
||||||
|
{
|
||||||
|
enum Analysis
|
||||||
|
{
|
||||||
|
Ok,
|
||||||
|
TypeIsNotAFunction,
|
||||||
|
ArityMismatch,
|
||||||
|
OverloadIsNonviable, // Arguments were incompatible with the overloads parameters but were otherwise compatible by arity
|
||||||
|
};
|
||||||
|
|
||||||
|
OverloadResolver(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<Normalizer> normalizer, NotNull<Scope> scope,
|
||||||
|
NotNull<InternalErrorReporter> reporter, NotNull<TypeCheckLimits> limits, Location callLocation);
|
||||||
|
|
||||||
|
NotNull<BuiltinTypes> builtinTypes;
|
||||||
|
NotNull<TypeArena> arena;
|
||||||
|
NotNull<Normalizer> normalizer;
|
||||||
|
NotNull<Scope> scope;
|
||||||
|
NotNull<InternalErrorReporter> ice;
|
||||||
|
NotNull<TypeCheckLimits> limits;
|
||||||
|
Subtyping subtyping;
|
||||||
|
Location callLoc;
|
||||||
|
|
||||||
|
// Resolver results
|
||||||
|
std::vector<TypeId> ok;
|
||||||
|
std::vector<TypeId> nonFunctions;
|
||||||
|
std::vector<std::pair<TypeId, ErrorVec>> arityMismatches;
|
||||||
|
std::vector<std::pair<TypeId, ErrorVec>> nonviableOverloads;
|
||||||
|
InsertionOrderedMap<TypeId, std::pair<OverloadResolver::Analysis, size_t>> resolution;
|
||||||
|
|
||||||
|
|
||||||
|
std::pair<OverloadResolver::Analysis, TypeId> selectOverload(TypeId ty, TypePackId args);
|
||||||
|
void resolve(TypeId fnTy, const TypePack* args, AstExpr* selfExpr, const std::vector<AstExpr*>* argExprs);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<ErrorVec> testIsSubtype(const Location& location, TypeId subTy, TypeId superTy);
|
||||||
|
std::optional<ErrorVec> testIsSubtype(const Location& location, TypePackId subTy, TypePackId superTy);
|
||||||
|
std::pair<Analysis, ErrorVec> checkOverload(
|
||||||
|
TypeId fnTy, const TypePack* args, AstExpr* fnLoc, const std::vector<AstExpr*>* argExprs, bool callMetamethodOk = true);
|
||||||
|
static bool isLiteral(AstExpr* expr);
|
||||||
|
LUAU_NOINLINE
|
||||||
|
std::pair<Analysis, ErrorVec> checkOverload_(
|
||||||
|
TypeId fnTy, const FunctionType* fn, const TypePack* args, AstExpr* fnExpr, const std::vector<AstExpr*>* argExprs);
|
||||||
|
size_t indexof(Analysis analysis);
|
||||||
|
void add(Analysis analysis, TypeId ty, ErrorVec&& errors);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -45,6 +45,8 @@ struct Scope
|
||||||
|
|
||||||
TypeLevel level;
|
TypeLevel level;
|
||||||
|
|
||||||
|
Location location; // the spanning location associated with this scope
|
||||||
|
|
||||||
std::unordered_map<Name, TypeFun> exportedTypeBindings;
|
std::unordered_map<Name, TypeFun> exportedTypeBindings;
|
||||||
std::unordered_map<Name, TypeFun> privateTypeBindings;
|
std::unordered_map<Name, TypeFun> privateTypeBindings;
|
||||||
std::unordered_map<Name, Location> typeAliasLocations;
|
std::unordered_map<Name, Location> typeAliasLocations;
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
#include "Luau/TypeFwd.h"
|
#include "Luau/TypeFwd.h"
|
||||||
#include "Luau/TypePairHash.h"
|
#include "Luau/TypePairHash.h"
|
||||||
#include "Luau/TypePath.h"
|
#include "Luau/TypePath.h"
|
||||||
|
#include "Luau/TypeFamily.h"
|
||||||
|
#include "Luau/TypeCheckLimits.h"
|
||||||
#include "Luau/DenseHash.h"
|
#include "Luau/DenseHash.h"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -24,6 +26,7 @@ struct NormalizedClassType;
|
||||||
struct NormalizedStringType;
|
struct NormalizedStringType;
|
||||||
struct NormalizedFunctionType;
|
struct NormalizedFunctionType;
|
||||||
struct TypeArena;
|
struct TypeArena;
|
||||||
|
struct TypeCheckLimits;
|
||||||
struct Scope;
|
struct Scope;
|
||||||
struct TableIndexer;
|
struct TableIndexer;
|
||||||
|
|
||||||
|
@ -60,7 +63,6 @@ static const SubtypingReasoning kEmptyReasoning = SubtypingReasoning{TypePath::k
|
||||||
struct SubtypingResult
|
struct SubtypingResult
|
||||||
{
|
{
|
||||||
bool isSubtype = false;
|
bool isSubtype = false;
|
||||||
bool isErrorSuppressing = false;
|
|
||||||
bool normalizationTooComplex = false;
|
bool normalizationTooComplex = false;
|
||||||
bool isCacheable = true;
|
bool isCacheable = true;
|
||||||
|
|
||||||
|
@ -109,6 +111,7 @@ struct Subtyping
|
||||||
NotNull<InternalErrorReporter> iceReporter;
|
NotNull<InternalErrorReporter> iceReporter;
|
||||||
|
|
||||||
NotNull<Scope> scope;
|
NotNull<Scope> scope;
|
||||||
|
TypeCheckLimits limits;
|
||||||
|
|
||||||
enum class Variance
|
enum class Variance
|
||||||
{
|
{
|
||||||
|
@ -199,6 +202,8 @@ private:
|
||||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes);
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes);
|
||||||
|
|
||||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic);
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic);
|
||||||
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeFamilyInstanceType* subFamilyInstance, const TypeId superTy);
|
||||||
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFamilyInstanceType* superFamilyInstance);
|
||||||
|
|
||||||
bool bindGeneric(SubtypingEnvironment& env, TypeId subTp, TypeId superTp);
|
bool bindGeneric(SubtypingEnvironment& env, TypeId subTp, TypeId superTp);
|
||||||
bool bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp);
|
bool bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp);
|
||||||
|
@ -206,6 +211,21 @@ private:
|
||||||
template<typename T, typename Container>
|
template<typename T, typename Container>
|
||||||
TypeId makeAggregateType(const Container& container, TypeId orElse);
|
TypeId makeAggregateType(const Container& container, TypeId orElse);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T handleTypeFamilyReductionResult(const TypeFamilyInstanceType* tf)
|
||||||
|
{
|
||||||
|
TypeFamilyContext context{arena, builtinTypes, scope, normalizer, iceReporter, NotNull{&limits}};
|
||||||
|
TypeFamilyReductionResult<TypeId> result = tf->family->reducer(tf->typeArguments, tf->packArguments, NotNull{&context});
|
||||||
|
if (!result.blockedTypes.empty())
|
||||||
|
unexpected(result.blockedTypes[0]);
|
||||||
|
else if (!result.blockedPacks.empty())
|
||||||
|
unexpected(result.blockedPacks[0]);
|
||||||
|
else if (result.uninhabited || result.result == std::nullopt)
|
||||||
|
return builtinTypes->neverType;
|
||||||
|
return *result.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void unexpected(TypeId ty);
|
||||||
[[noreturn]] void unexpected(TypePackId tp);
|
[[noreturn]] void unexpected(TypePackId tp);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -56,11 +56,33 @@ std::vector<TypeId> reduceUnion(const std::vector<TypeId>& types);
|
||||||
*/
|
*/
|
||||||
TypeId stripNil(NotNull<BuiltinTypes> builtinTypes, TypeArena& arena, TypeId ty);
|
TypeId stripNil(NotNull<BuiltinTypes> builtinTypes, TypeArena& arena, TypeId ty);
|
||||||
|
|
||||||
enum class ErrorSuppression
|
struct ErrorSuppression
|
||||||
{
|
{
|
||||||
Suppress,
|
enum Value
|
||||||
DoNotSuppress,
|
{
|
||||||
NormalizationFailed
|
Suppress,
|
||||||
|
DoNotSuppress,
|
||||||
|
NormalizationFailed,
|
||||||
|
};
|
||||||
|
|
||||||
|
ErrorSuppression() = default;
|
||||||
|
constexpr ErrorSuppression(Value enumValue) : value(enumValue) { }
|
||||||
|
|
||||||
|
constexpr operator Value() const { return value; }
|
||||||
|
explicit operator bool() const = delete;
|
||||||
|
|
||||||
|
ErrorSuppression orElse(const ErrorSuppression& other) const
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case DoNotSuppress:
|
||||||
|
return other;
|
||||||
|
default:
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
Value value;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,6 +140,8 @@ struct TryPair
|
||||||
template<typename A, typename B, typename Ty>
|
template<typename A, typename B, typename Ty>
|
||||||
TryPair<const A*, const B*> get2(Ty one, Ty two)
|
TryPair<const A*, const B*> get2(Ty one, Ty two)
|
||||||
{
|
{
|
||||||
|
static_assert(std::is_pointer_v<Ty>, "argument must be a pointer type");
|
||||||
|
|
||||||
const A* a = get<A>(one);
|
const A* a = get<A>(one);
|
||||||
const B* b = get<B>(two);
|
const B* b = get<B>(two);
|
||||||
if (a && b)
|
if (a && b)
|
||||||
|
|
|
@ -167,6 +167,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
|
||||||
ScopePtr scope = std::make_shared<Scope>(globalScope);
|
ScopePtr scope = std::make_shared<Scope>(globalScope);
|
||||||
rootScope = scope.get();
|
rootScope = scope.get();
|
||||||
scopes.emplace_back(block->location, scope);
|
scopes.emplace_back(block->location, scope);
|
||||||
|
rootScope->location = block->location;
|
||||||
module->astScopes[block] = NotNull{scope.get()};
|
module->astScopes[block] = NotNull{scope.get()};
|
||||||
|
|
||||||
rootScope->returnType = freshTypePack(scope);
|
rootScope->returnType = freshTypePack(scope);
|
||||||
|
@ -192,10 +193,24 @@ TypePackId ConstraintGenerator::freshTypePack(const ScopePtr& scope)
|
||||||
return arena->addTypePack(TypePackVar{std::move(f)});
|
return arena->addTypePack(TypePackVar{std::move(f)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TypePackId ConstraintGenerator::addTypePack(std::vector<TypeId> head, std::optional<TypePackId> tail)
|
||||||
|
{
|
||||||
|
if (head.empty())
|
||||||
|
{
|
||||||
|
if (tail)
|
||||||
|
return *tail;
|
||||||
|
else
|
||||||
|
return builtinTypes->emptyTypePack;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return arena->addTypePack(TypePack{std::move(head), tail});
|
||||||
|
}
|
||||||
|
|
||||||
ScopePtr ConstraintGenerator::childScope(AstNode* node, const ScopePtr& parent)
|
ScopePtr ConstraintGenerator::childScope(AstNode* node, const ScopePtr& parent)
|
||||||
{
|
{
|
||||||
auto scope = std::make_shared<Scope>(parent);
|
auto scope = std::make_shared<Scope>(parent);
|
||||||
scopes.emplace_back(node->location, scope);
|
scopes.emplace_back(node->location, scope);
|
||||||
|
scope->location = node->location;
|
||||||
|
|
||||||
scope->returnType = parent->returnType;
|
scope->returnType = parent->returnType;
|
||||||
scope->varargPack = parent->varargPack;
|
scope->varargPack = parent->varargPack;
|
||||||
|
@ -1278,7 +1293,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
|
||||||
if (FunctionType* ftv = getMutable<FunctionType>(propTy))
|
if (FunctionType* ftv = getMutable<FunctionType>(propTy))
|
||||||
{
|
{
|
||||||
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
|
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
|
||||||
ftv->argTypes = arena->addTypePack(TypePack{{classTy}, ftv->argTypes});
|
ftv->argTypes = addTypePack({classTy}, ftv->argTypes);
|
||||||
|
|
||||||
ftv->hasSelf = true;
|
ftv->hasSelf = true;
|
||||||
}
|
}
|
||||||
|
@ -1407,10 +1422,7 @@ InferencePack ConstraintGenerator::checkPack(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (head.empty() && tail)
|
return InferencePack{addTypePack(std::move(head), tail)};
|
||||||
return InferencePack{*tail};
|
|
||||||
else
|
|
||||||
return InferencePack{arena->addTypePack(TypePack{std::move(head), tail})};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InferencePack ConstraintGenerator::checkPack(
|
InferencePack ConstraintGenerator::checkPack(
|
||||||
|
@ -1611,7 +1623,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
||||||
|
|
||||||
// TODO: How do expectedTypes play into this? Do they?
|
// TODO: How do expectedTypes play into this? Do they?
|
||||||
TypePackId rets = arena->addTypePack(BlockedTypePack{});
|
TypePackId rets = arena->addTypePack(BlockedTypePack{});
|
||||||
TypePackId argPack = arena->addTypePack(TypePack{args, argTail});
|
TypePackId argPack = addTypePack(std::move(args), argTail);
|
||||||
FunctionType ftv(TypeLevel{}, scope.get(), argPack, rets, std::nullopt, call->self);
|
FunctionType ftv(TypeLevel{}, scope.get(), argPack, rets, std::nullopt, call->self);
|
||||||
|
|
||||||
NotNull<Constraint> fcc = addConstraint(scope, call->func->location,
|
NotNull<Constraint> fcc = addConstraint(scope, call->func->location,
|
||||||
|
@ -2283,12 +2295,33 @@ std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, As
|
||||||
{
|
{
|
||||||
if (auto lt = getMutable<LocalType>(*ty))
|
if (auto lt = getMutable<LocalType>(*ty))
|
||||||
++lt->blockCount;
|
++lt->blockCount;
|
||||||
|
else if (auto ut = getMutable<UnionType>(*ty))
|
||||||
|
{
|
||||||
|
for (TypeId optTy : ut->options)
|
||||||
|
if (auto lt = getMutable<LocalType>(optTy))
|
||||||
|
++lt->blockCount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ty = arena->addType(LocalType{builtinTypes->neverType, /* blockCount */ 1, local->local->name.value});
|
ty = arena->addType(LocalType{builtinTypes->neverType, /* blockCount */ 1, local->local->name.value});
|
||||||
|
|
||||||
|
if (annotatedTy)
|
||||||
|
{
|
||||||
|
switch (shouldSuppressErrors(normalizer, *annotatedTy))
|
||||||
|
{
|
||||||
|
case ErrorSuppression::DoNotSuppress:
|
||||||
|
break;
|
||||||
|
case ErrorSuppression::Suppress:
|
||||||
|
ty = simplifyUnion(builtinTypes, arena, *ty, builtinTypes->errorType).result;
|
||||||
|
break;
|
||||||
|
case ErrorSuppression::NormalizationFailed:
|
||||||
|
reportError(local->local->annotation->location, NormalizationTooComplex{});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scope->lvalueTypes[defId] = *ty;
|
scope->lvalueTypes[defId] = *ty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2546,6 +2579,22 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
|
||||||
|
|
||||||
TypeId itemTy = check(scope, item.value, checkExpectedIndexResultType).ty;
|
TypeId itemTy = check(scope, item.value, checkExpectedIndexResultType).ty;
|
||||||
|
|
||||||
|
// we should preserve error-suppressingness from the expected value type if we have one
|
||||||
|
if (expectedValueType)
|
||||||
|
{
|
||||||
|
switch (shouldSuppressErrors(normalizer, *expectedValueType))
|
||||||
|
{
|
||||||
|
case ErrorSuppression::DoNotSuppress:
|
||||||
|
break;
|
||||||
|
case ErrorSuppression::Suppress:
|
||||||
|
itemTy = simplifyUnion(builtinTypes, arena, itemTy, builtinTypes->errorType).result;
|
||||||
|
break;
|
||||||
|
case ErrorSuppression::NormalizationFailed:
|
||||||
|
reportError(item.value->location, NormalizationTooComplex{});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isIndexedResultType && !pinnedIndexResultType)
|
if (isIndexedResultType && !pinnedIndexResultType)
|
||||||
pinnedIndexResultType = itemTy;
|
pinnedIndexResultType = itemTy;
|
||||||
|
|
||||||
|
@ -3071,7 +3120,7 @@ TypePackId ConstraintGenerator::resolveTypePack(const ScopePtr& scope, const Ast
|
||||||
tail = resolveTypePack(scope, list.tailType, inTypeArguments, replaceErrorWithFresh);
|
tail = resolveTypePack(scope, list.tailType, inTypeArguments, replaceErrorWithFresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
return arena->addTypePack(TypePack{head, tail});
|
return addTypePack(std::move(head), tail);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGenerator::createGenerics(
|
std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGenerator::createGenerics(
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "Luau/Instantiation.h"
|
#include "Luau/Instantiation.h"
|
||||||
#include "Luau/Location.h"
|
#include "Luau/Location.h"
|
||||||
#include "Luau/ModuleResolver.h"
|
#include "Luau/ModuleResolver.h"
|
||||||
|
#include "Luau/OverloadResolution.h"
|
||||||
#include "Luau/Quantify.h"
|
#include "Luau/Quantify.h"
|
||||||
#include "Luau/Simplify.h"
|
#include "Luau/Simplify.h"
|
||||||
#include "Luau/TimeTrace.h"
|
#include "Luau/TimeTrace.h"
|
||||||
|
@ -1098,10 +1099,18 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
||||||
*asMutable(follow(*ty)) = BoundType{builtinTypes->anyType};
|
*asMutable(follow(*ty)) = BoundType{builtinTypes->anyType};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OverloadResolver resolver{
|
||||||
|
builtinTypes, NotNull{arena}, normalizer, constraint->scope, NotNull{&iceReporter}, NotNull{&limits}, c.callSite->location};
|
||||||
|
auto [status, overload] = resolver.selectOverload(fn, argsPack);
|
||||||
|
TypeId overloadToUse = fn;
|
||||||
|
if (status == OverloadResolver::Analysis::Ok)
|
||||||
|
overloadToUse = overload;
|
||||||
|
|
||||||
|
|
||||||
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope.get(), argsPack, c.result});
|
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope.get(), argsPack, c.result});
|
||||||
Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}};
|
Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}};
|
||||||
|
|
||||||
const bool occursCheckPassed = u2.unify(fn, inferredTy);
|
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
|
||||||
|
|
||||||
for (const auto& [expanded, additions] : u2.expandedFreeTypes)
|
for (const auto& [expanded, additions] : u2.expandedFreeTypes)
|
||||||
{
|
{
|
||||||
|
@ -1115,7 +1124,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
||||||
unblock(c.result, constraint->location);
|
unblock(c.result, constraint->location);
|
||||||
|
|
||||||
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
|
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
|
||||||
queuer.traverse(fn);
|
queuer.traverse(overloadToUse);
|
||||||
queuer.traverse(inferredTy);
|
queuer.traverse(inferredTy);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -1482,6 +1491,41 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
||||||
auto resultIter = begin(resultPack);
|
auto resultIter = begin(resultPack);
|
||||||
auto resultEnd = end(resultPack);
|
auto resultEnd = end(resultPack);
|
||||||
|
|
||||||
|
auto apply = [&](TypeId resultTy, TypeId srcTy) {
|
||||||
|
if (auto lt = getMutable<LocalType>(resultTy); c.resultIsLValue && lt)
|
||||||
|
{
|
||||||
|
lt->domain = simplifyUnion(builtinTypes, arena, lt->domain, srcTy).result;
|
||||||
|
LUAU_ASSERT(lt->blockCount > 0);
|
||||||
|
--lt->blockCount;
|
||||||
|
|
||||||
|
LUAU_ASSERT(0 <= lt->blockCount);
|
||||||
|
|
||||||
|
if (0 == lt->blockCount)
|
||||||
|
asMutable(resultTy)->ty.emplace<BoundType>(lt->domain);
|
||||||
|
}
|
||||||
|
else if (get<BlockedType>(resultTy))
|
||||||
|
{
|
||||||
|
if (follow(srcTy) == resultTy)
|
||||||
|
{
|
||||||
|
// It is sometimes the case that we find that a blocked type
|
||||||
|
// is only blocked on itself. This doesn't actually
|
||||||
|
// constitute any meaningful constraint, so we replace it
|
||||||
|
// with a free type.
|
||||||
|
TypeId f = freshType(arena, builtinTypes, constraint->scope);
|
||||||
|
asMutable(resultTy)->ty.emplace<BoundType>(f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
asMutable(resultTy)->ty.emplace<BoundType>(srcTy);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(c.resultIsLValue);
|
||||||
|
unify(constraint->scope, constraint->location, resultTy, srcTy);
|
||||||
|
}
|
||||||
|
|
||||||
|
unblock(resultTy, constraint->location);
|
||||||
|
};
|
||||||
|
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
while (resultIter != resultEnd)
|
while (resultIter != resultEnd)
|
||||||
{
|
{
|
||||||
|
@ -1493,38 +1537,15 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
||||||
|
|
||||||
if (resultTy)
|
if (resultTy)
|
||||||
{
|
{
|
||||||
if (auto lt = getMutable<LocalType>(resultTy); c.resultIsLValue && lt)
|
// when we preserve the error-suppression of types through typestate,
|
||||||
|
// we introduce a union with the error type, so we need to find the local type in those options to update.
|
||||||
|
if (auto ut = getMutable<UnionType>(resultTy))
|
||||||
{
|
{
|
||||||
lt->domain = simplifyUnion(builtinTypes, arena, lt->domain, srcTy).result;
|
for (auto opt : ut->options)
|
||||||
LUAU_ASSERT(lt->blockCount > 0);
|
apply(opt, srcTy);
|
||||||
--lt->blockCount;
|
|
||||||
|
|
||||||
LUAU_ASSERT(0 <= lt->blockCount);
|
|
||||||
|
|
||||||
if (0 == lt->blockCount)
|
|
||||||
asMutable(resultTy)->ty.emplace<BoundType>(lt->domain);
|
|
||||||
}
|
|
||||||
else if (get<BlockedType>(resultTy))
|
|
||||||
{
|
|
||||||
if (follow(srcTy) == resultTy)
|
|
||||||
{
|
|
||||||
// It is sometimes the case that we find that a blocked type
|
|
||||||
// is only blocked on itself. This doesn't actually
|
|
||||||
// constitute any meaningful constraint, so we replace it
|
|
||||||
// with a free type.
|
|
||||||
TypeId f = freshType(arena, builtinTypes, constraint->scope);
|
|
||||||
asMutable(resultTy)->ty.emplace<BoundType>(f);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
asMutable(resultTy)->ty.emplace<BoundType>(srcTy);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
apply(resultTy, srcTy);
|
||||||
LUAU_ASSERT(c.resultIsLValue);
|
|
||||||
unify(constraint->scope, constraint->location, resultTy, srcTy);
|
|
||||||
}
|
|
||||||
|
|
||||||
unblock(resultTy, constraint->location);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
unify(constraint->scope, constraint->location, resultTy, srcTy);
|
unify(constraint->scope, constraint->location, resultTy, srcTy);
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
|
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
|
||||||
|
@ -539,6 +540,12 @@ struct ErrorConverter
|
||||||
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
|
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
|
||||||
"' is used in a way that will run time error";
|
"' is used in a way that will run time error";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string operator()(const CheckedFunctionIncorrectArgs& e) const
|
||||||
|
{
|
||||||
|
return "Checked Function " + e.functionName + " expects " + std::to_string(e.expected) + " arguments, but received " +
|
||||||
|
std::to_string(e.actual);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct InvalidNameChecker
|
struct InvalidNameChecker
|
||||||
|
@ -872,6 +879,11 @@ bool NonStrictFunctionDefinitionError::operator==(const NonStrictFunctionDefinit
|
||||||
return functionName == rhs.functionName && argument == rhs.argument && argumentType == rhs.argumentType;
|
return functionName == rhs.functionName && argument == rhs.argument && argumentType == rhs.argumentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CheckedFunctionIncorrectArgs::operator==(const CheckedFunctionIncorrectArgs& rhs) const
|
||||||
|
{
|
||||||
|
return functionName == rhs.functionName && expected == rhs.expected && actual == rhs.actual;
|
||||||
|
}
|
||||||
|
|
||||||
std::string toString(const TypeError& error)
|
std::string toString(const TypeError& error)
|
||||||
{
|
{
|
||||||
return toString(error, TypeErrorToStringOptions{});
|
return toString(error, TypeErrorToStringOptions{});
|
||||||
|
@ -1047,6 +1059,9 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
|
||||||
{
|
{
|
||||||
e.argumentType = clone(e.argumentType);
|
e.argumentType = clone(e.argumentType);
|
||||||
}
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, CheckedFunctionIncorrectArgs>)
|
||||||
|
{
|
||||||
|
}
|
||||||
else
|
else
|
||||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,6 @@ LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauRethrowSingleModuleIce, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
|
@ -677,7 +676,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptio
|
||||||
sendItemTask(i);
|
sendItemTask(i);
|
||||||
nextItems.clear();
|
nextItems.clear();
|
||||||
|
|
||||||
if (FFlag::LuauRethrowSingleModuleIce && processing == 0)
|
if (processing == 0)
|
||||||
{
|
{
|
||||||
// Typechecking might have been cancelled by user, don't return partial results
|
// Typechecking might have been cancelled by user, don't return partial results
|
||||||
if (cancelled)
|
if (cancelled)
|
||||||
|
@ -690,23 +689,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptio
|
||||||
|
|
||||||
// If we aren't done, but don't have anything processing, we hit a cycle
|
// If we aren't done, but don't have anything processing, we hit a cycle
|
||||||
if (remaining != 0 && processing == 0)
|
if (remaining != 0 && processing == 0)
|
||||||
{
|
|
||||||
if (!FFlag::LuauRethrowSingleModuleIce)
|
|
||||||
{
|
|
||||||
// Typechecking might have been cancelled by user, don't return partial results
|
|
||||||
if (cancelled)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
// We might have stopped because of a pending exception
|
|
||||||
if (itemWithException)
|
|
||||||
{
|
|
||||||
recordItemResult(buildQueueItems[*itemWithException]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendCycleItemTask();
|
sendCycleItemTask();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ModuleName> checkedModules;
|
std::vector<ModuleName> checkedModules;
|
||||||
|
|
|
@ -207,6 +207,9 @@ static void errorToString(std::ostream& stream, const T& err)
|
||||||
else if constexpr (std::is_same_v<T, NonStrictFunctionDefinitionError>)
|
else if constexpr (std::is_same_v<T, NonStrictFunctionDefinitionError>)
|
||||||
stream << "NonStrictFunctionDefinitionError { functionName = '" + err.functionName + "', argument = '" + err.argument +
|
stream << "NonStrictFunctionDefinitionError { functionName = '" + err.functionName + "', argument = '" + err.argument +
|
||||||
"', argumentType = '" + toString(err.argumentType) + "' }";
|
"', argumentType = '" + toString(err.argumentType) + "' }";
|
||||||
|
else if constexpr (std::is_same_v<T, CheckedFunctionIncorrectArgs>)
|
||||||
|
stream << "CheckedFunction { functionName = '" + err.functionName + ", expected = " + std::to_string(err.expected) +
|
||||||
|
", actual = " + std::to_string(err.actual) + "}";
|
||||||
else
|
else
|
||||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "Luau/TypeArena.h"
|
#include "Luau/TypeArena.h"
|
||||||
#include "Luau/TypeFamily.h"
|
#include "Luau/TypeFamily.h"
|
||||||
#include "Luau/Def.h"
|
#include "Luau/Def.h"
|
||||||
|
#include "Luau/ToString.h"
|
||||||
#include "Luau/TypeFwd.h"
|
#include "Luau/TypeFwd.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
@ -140,7 +141,6 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unordered_map<const Def*, TypeId> context;
|
std::unordered_map<const Def*, TypeId> context;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct NonStrictTypeChecker
|
struct NonStrictTypeChecker
|
||||||
|
@ -543,7 +543,14 @@ struct NonStrictTypeChecker
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// For a checked function, these gotta be the same size
|
// For a checked function, these gotta be the same size
|
||||||
LUAU_ASSERT(call->args.size == argTypes.size());
|
|
||||||
|
std::string functionName = getFunctionNameAsString(*call->func).value_or("");
|
||||||
|
if (call->args.size != argTypes.size())
|
||||||
|
{
|
||||||
|
reportError(CheckedFunctionIncorrectArgs{functionName, argTypes.size(), call->args.size}, call->location);
|
||||||
|
return fresh;
|
||||||
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < call->args.size; i++)
|
for (size_t i = 0; i < call->args.size; i++)
|
||||||
{
|
{
|
||||||
// For example, if the arg is "hi"
|
// For example, if the arg is "hi"
|
||||||
|
@ -559,12 +566,11 @@ struct NonStrictTypeChecker
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate the context and now iterate through each of the arguments to the call to find out if we satisfy the types
|
// Populate the context and now iterate through each of the arguments to the call to find out if we satisfy the types
|
||||||
AstName name = getIdentifier(call->func);
|
|
||||||
for (size_t i = 0; i < call->args.size; i++)
|
for (size_t i = 0; i < call->args.size; i++)
|
||||||
{
|
{
|
||||||
AstExpr* arg = call->args.data[i];
|
AstExpr* arg = call->args.data[i];
|
||||||
if (auto runTimeFailureType = willRunTimeError(arg, fresh))
|
if (auto runTimeFailureType = willRunTimeError(arg, fresh))
|
||||||
reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, name.value, i}, arg->location);
|
reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, functionName, i}, arg->location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
349
Analysis/src/OverloadResolution.cpp
Normal file
349
Analysis/src/OverloadResolution.cpp
Normal file
|
@ -0,0 +1,349 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#include "Luau/OverloadResolution.h"
|
||||||
|
|
||||||
|
#include "Luau/Subtyping.h"
|
||||||
|
#include "Luau/TxnLog.h"
|
||||||
|
#include "Luau/Type.h"
|
||||||
|
#include "Luau/TypePack.h"
|
||||||
|
#include "Luau/TypeUtils.h"
|
||||||
|
#include "Luau/TypeFamily.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
OverloadResolver::OverloadResolver(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<Normalizer> normalizer, NotNull<Scope> scope,
|
||||||
|
NotNull<InternalErrorReporter> reporter, NotNull<TypeCheckLimits> limits, Location callLocation)
|
||||||
|
: builtinTypes(builtinTypes)
|
||||||
|
, arena(arena)
|
||||||
|
, normalizer(normalizer)
|
||||||
|
, scope(scope)
|
||||||
|
, ice(reporter)
|
||||||
|
, limits(limits)
|
||||||
|
, subtyping({builtinTypes, arena, normalizer, ice, scope})
|
||||||
|
, callLoc(callLocation)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<OverloadResolver::Analysis, TypeId> OverloadResolver::selectOverload(TypeId ty, TypePackId argsPack)
|
||||||
|
{
|
||||||
|
TypeId t = follow(ty);
|
||||||
|
if (auto it = get<IntersectionType>(t))
|
||||||
|
{
|
||||||
|
for (TypeId component : it)
|
||||||
|
{
|
||||||
|
if (auto ftv = get<FunctionType>(component))
|
||||||
|
{
|
||||||
|
SubtypingResult r = subtyping.isSubtype(argsPack, ftv->argTypes);
|
||||||
|
if (r.isSubtype)
|
||||||
|
return {Analysis::Ok, component};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {Analysis::OverloadIsNonviable, ty};
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverloadResolver::resolve(TypeId fnTy, const TypePack* args, AstExpr* selfExpr, const std::vector<AstExpr*>* argExprs)
|
||||||
|
{
|
||||||
|
fnTy = follow(fnTy);
|
||||||
|
|
||||||
|
auto it = get<IntersectionType>(fnTy);
|
||||||
|
if (!it)
|
||||||
|
{
|
||||||
|
auto [analysis, errors] = checkOverload(fnTy, args, selfExpr, argExprs);
|
||||||
|
add(analysis, fnTy, std::move(errors));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TypeId ty : it)
|
||||||
|
{
|
||||||
|
if (resolution.find(ty) != resolution.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto [analysis, errors] = checkOverload(ty, args, selfExpr, argExprs);
|
||||||
|
add(analysis, ty, std::move(errors));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ErrorVec> OverloadResolver::testIsSubtype(const Location& location, TypeId subTy, TypeId superTy)
|
||||||
|
{
|
||||||
|
auto r = subtyping.isSubtype(subTy, superTy);
|
||||||
|
ErrorVec errors;
|
||||||
|
|
||||||
|
if (r.normalizationTooComplex)
|
||||||
|
errors.emplace_back(location, NormalizationTooComplex{});
|
||||||
|
|
||||||
|
if (!r.isSubtype)
|
||||||
|
{
|
||||||
|
switch (shouldSuppressErrors(normalizer, subTy).orElse(shouldSuppressErrors(normalizer, superTy)))
|
||||||
|
{
|
||||||
|
case ErrorSuppression::Suppress:
|
||||||
|
break;
|
||||||
|
case ErrorSuppression::NormalizationFailed:
|
||||||
|
errors.emplace_back(location, NormalizationTooComplex{});
|
||||||
|
// intentionally fallthrough here since we couldn't prove this was error-suppressing
|
||||||
|
case ErrorSuppression::DoNotSuppress:
|
||||||
|
errors.emplace_back(location, TypeMismatch{superTy, subTy});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.empty())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ErrorVec> OverloadResolver::testIsSubtype(const Location& location, TypePackId subTy, TypePackId superTy)
|
||||||
|
{
|
||||||
|
auto r = subtyping.isSubtype(subTy, superTy);
|
||||||
|
ErrorVec errors;
|
||||||
|
|
||||||
|
if (r.normalizationTooComplex)
|
||||||
|
errors.emplace_back(location, NormalizationTooComplex{});
|
||||||
|
|
||||||
|
if (!r.isSubtype)
|
||||||
|
{
|
||||||
|
switch (shouldSuppressErrors(normalizer, subTy).orElse(shouldSuppressErrors(normalizer, superTy)))
|
||||||
|
{
|
||||||
|
case ErrorSuppression::Suppress:
|
||||||
|
break;
|
||||||
|
case ErrorSuppression::NormalizationFailed:
|
||||||
|
errors.emplace_back(location, NormalizationTooComplex{});
|
||||||
|
// intentionally fallthrough here since we couldn't prove this was error-suppressing
|
||||||
|
case ErrorSuppression::DoNotSuppress:
|
||||||
|
errors.emplace_back(location, TypePackMismatch{superTy, subTy});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.empty())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload(
|
||||||
|
TypeId fnTy, const TypePack* args, AstExpr* fnLoc, const std::vector<AstExpr*>* argExprs, bool callMetamethodOk)
|
||||||
|
{
|
||||||
|
fnTy = follow(fnTy);
|
||||||
|
|
||||||
|
ErrorVec discard;
|
||||||
|
if (get<AnyType>(fnTy) || get<ErrorType>(fnTy) || get<NeverType>(fnTy))
|
||||||
|
return {Ok, {}};
|
||||||
|
else if (auto fn = get<FunctionType>(fnTy))
|
||||||
|
return checkOverload_(fnTy, fn, args, fnLoc, argExprs); // Intentionally split to reduce the stack pressure of this function.
|
||||||
|
else if (auto callMm = findMetatableEntry(builtinTypes, discard, fnTy, "__call", callLoc); callMm && callMetamethodOk)
|
||||||
|
{
|
||||||
|
// Calling a metamethod forwards the `fnTy` as self.
|
||||||
|
TypePack withSelf = *args;
|
||||||
|
withSelf.head.insert(withSelf.head.begin(), fnTy);
|
||||||
|
|
||||||
|
std::vector<AstExpr*> withSelfExprs = *argExprs;
|
||||||
|
withSelfExprs.insert(withSelfExprs.begin(), fnLoc);
|
||||||
|
|
||||||
|
return checkOverload(*callMm, &withSelf, fnLoc, &withSelfExprs, /*callMetamethodOk=*/false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return {TypeIsNotAFunction, {}}; // Intentionally empty. We can just fabricate the type error later on.
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OverloadResolver::isLiteral(AstExpr* expr)
|
||||||
|
{
|
||||||
|
if (auto group = expr->as<AstExprGroup>())
|
||||||
|
return isLiteral(group->expr);
|
||||||
|
else if (auto assertion = expr->as<AstExprTypeAssertion>())
|
||||||
|
return isLiteral(assertion->expr);
|
||||||
|
|
||||||
|
return expr->is<AstExprConstantNil>() || expr->is<AstExprConstantBool>() || expr->is<AstExprConstantNumber>() ||
|
||||||
|
expr->is<AstExprConstantString>() || expr->is<AstExprFunction>() || expr->is<AstExprTable>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_(
|
||||||
|
TypeId fnTy, const FunctionType* fn, const TypePack* args, AstExpr* fnExpr, const std::vector<AstExpr*>* argExprs)
|
||||||
|
{
|
||||||
|
FamilyGraphReductionResult result =
|
||||||
|
reduceFamilies(fnTy, callLoc, TypeFamilyContext{arena, builtinTypes, scope, normalizer, ice, limits}, /*force=*/true);
|
||||||
|
if (!result.errors.empty())
|
||||||
|
return {OverloadIsNonviable, result.errors};
|
||||||
|
|
||||||
|
ErrorVec argumentErrors;
|
||||||
|
|
||||||
|
TypeId prospectiveFunction = arena->addType(FunctionType{arena->addTypePack(*args), builtinTypes->anyTypePack});
|
||||||
|
SubtypingResult sr = subtyping.isSubtype(fnTy, prospectiveFunction);
|
||||||
|
|
||||||
|
if (sr.isSubtype)
|
||||||
|
return {Analysis::Ok, {}};
|
||||||
|
|
||||||
|
if (1 == sr.reasoning.size())
|
||||||
|
{
|
||||||
|
const SubtypingReasoning& reason = *sr.reasoning.begin();
|
||||||
|
|
||||||
|
const TypePath::Path justArguments{TypePath::PackField::Arguments};
|
||||||
|
|
||||||
|
if (reason.subPath == justArguments && reason.superPath == justArguments)
|
||||||
|
{
|
||||||
|
// If the subtype test failed only due to an arity mismatch,
|
||||||
|
// it is still possible that this function call is okay.
|
||||||
|
// Subtype testing does not know anything about optional
|
||||||
|
// function arguments.
|
||||||
|
//
|
||||||
|
// This can only happen if the actual function call has a
|
||||||
|
// finite set of arguments which is too short for the
|
||||||
|
// function being called. If all of those unsatisfied
|
||||||
|
// function arguments are options, then this function call
|
||||||
|
// is ok.
|
||||||
|
|
||||||
|
const size_t firstUnsatisfiedArgument = argExprs->size();
|
||||||
|
const auto [requiredHead, _requiredTail] = flatten(fn->argTypes);
|
||||||
|
|
||||||
|
// If too many arguments were supplied, this overload
|
||||||
|
// definitely does not match.
|
||||||
|
if (args->head.size() > requiredHead.size())
|
||||||
|
{
|
||||||
|
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
|
||||||
|
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, false}};
|
||||||
|
|
||||||
|
return {Analysis::ArityMismatch, {error}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any of the unsatisfied arguments are not supertypes of
|
||||||
|
// nil, then this overload does not match.
|
||||||
|
for (size_t i = firstUnsatisfiedArgument; i < requiredHead.size(); ++i)
|
||||||
|
{
|
||||||
|
if (!subtyping.isSubtype(builtinTypes->nilType, requiredHead[i]).isSubtype)
|
||||||
|
{
|
||||||
|
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
|
||||||
|
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, false}};
|
||||||
|
|
||||||
|
return {Analysis::ArityMismatch, {error}};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {Analysis::Ok, {}};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorVec errors;
|
||||||
|
|
||||||
|
for (const SubtypingReasoning& reason : sr.reasoning)
|
||||||
|
{
|
||||||
|
/* The return type of our prospective function is always
|
||||||
|
* any... so any subtype failures here can only arise from
|
||||||
|
* argument type mismatches.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Location argLocation;
|
||||||
|
|
||||||
|
if (const Luau::TypePath::Index* pathIndexComponent = get_if<Luau::TypePath::Index>(&reason.superPath.components.at(1)))
|
||||||
|
{
|
||||||
|
size_t nthArgument = pathIndexComponent->index;
|
||||||
|
argLocation = argExprs->at(nthArgument)->location;
|
||||||
|
|
||||||
|
std::optional<TypeId> failedSubTy = traverseForType(fnTy, reason.subPath, builtinTypes);
|
||||||
|
std::optional<TypeId> failedSuperTy = traverseForType(prospectiveFunction, reason.superPath, builtinTypes);
|
||||||
|
|
||||||
|
if (failedSubTy && failedSuperTy)
|
||||||
|
{
|
||||||
|
|
||||||
|
switch (shouldSuppressErrors(normalizer, *failedSubTy).orElse(shouldSuppressErrors(normalizer, *failedSuperTy)))
|
||||||
|
{
|
||||||
|
case ErrorSuppression::Suppress:
|
||||||
|
break;
|
||||||
|
case ErrorSuppression::NormalizationFailed:
|
||||||
|
errors.emplace_back(argLocation, NormalizationTooComplex{});
|
||||||
|
// intentionally fallthrough here since we couldn't prove this was error-suppressing
|
||||||
|
case ErrorSuppression::DoNotSuppress:
|
||||||
|
// TODO extract location from the SubtypingResult path and argExprs
|
||||||
|
switch (reason.variance)
|
||||||
|
{
|
||||||
|
case SubtypingVariance::Covariant:
|
||||||
|
case SubtypingVariance::Contravariant:
|
||||||
|
errors.emplace_back(argLocation, TypeMismatch{*failedSubTy, *failedSuperTy, TypeMismatch::CovariantContext});
|
||||||
|
break;
|
||||||
|
case SubtypingVariance::Invariant:
|
||||||
|
errors.emplace_back(argLocation, TypeMismatch{*failedSubTy, *failedSuperTy, TypeMismatch::InvariantContext});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LUAU_ASSERT(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<TypePackId> failedSubPack = traverseForPack(fnTy, reason.subPath, builtinTypes);
|
||||||
|
std::optional<TypePackId> failedSuperPack = traverseForPack(prospectiveFunction, reason.superPath, builtinTypes);
|
||||||
|
|
||||||
|
if (failedSubPack && failedSuperPack)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(!argExprs->empty());
|
||||||
|
argLocation = argExprs->at(argExprs->size() - 1)->location;
|
||||||
|
|
||||||
|
// TODO extract location from the SubtypingResult path and argExprs
|
||||||
|
switch (reason.variance)
|
||||||
|
{
|
||||||
|
case SubtypingVariance::Covariant:
|
||||||
|
errors.emplace_back(argLocation, TypePackMismatch{*failedSubPack, *failedSuperPack});
|
||||||
|
break;
|
||||||
|
case SubtypingVariance::Contravariant:
|
||||||
|
errors.emplace_back(argLocation, TypePackMismatch{*failedSuperPack, *failedSubPack});
|
||||||
|
break;
|
||||||
|
case SubtypingVariance::Invariant:
|
||||||
|
errors.emplace_back(argLocation, TypePackMismatch{*failedSubPack, *failedSuperPack});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LUAU_ASSERT(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {Analysis::OverloadIsNonviable, std::move(errors)};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t OverloadResolver::indexof(Analysis analysis)
|
||||||
|
{
|
||||||
|
switch (analysis)
|
||||||
|
{
|
||||||
|
case Ok:
|
||||||
|
return ok.size();
|
||||||
|
case TypeIsNotAFunction:
|
||||||
|
return nonFunctions.size();
|
||||||
|
case ArityMismatch:
|
||||||
|
return arityMismatches.size();
|
||||||
|
case OverloadIsNonviable:
|
||||||
|
return nonviableOverloads.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
ice->ice("Inexhaustive switch in FunctionCallResolver::indexof");
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverloadResolver::add(Analysis analysis, TypeId ty, ErrorVec&& errors)
|
||||||
|
{
|
||||||
|
resolution.insert(ty, {analysis, indexof(analysis)});
|
||||||
|
|
||||||
|
switch (analysis)
|
||||||
|
{
|
||||||
|
case Ok:
|
||||||
|
LUAU_ASSERT(errors.empty());
|
||||||
|
ok.push_back(ty);
|
||||||
|
break;
|
||||||
|
case TypeIsNotAFunction:
|
||||||
|
LUAU_ASSERT(errors.empty());
|
||||||
|
nonFunctions.push_back(ty);
|
||||||
|
break;
|
||||||
|
case ArityMismatch:
|
||||||
|
LUAU_ASSERT(!errors.empty());
|
||||||
|
arityMismatches.emplace_back(ty, std::move(errors));
|
||||||
|
break;
|
||||||
|
case OverloadIsNonviable:
|
||||||
|
nonviableOverloads.emplace_back(ty, std::move(errors));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -10,6 +10,8 @@
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
#include "Luau/TypeArena.h"
|
#include "Luau/TypeArena.h"
|
||||||
|
#include "Luau/TypeCheckLimits.h"
|
||||||
|
#include "Luau/TypeFamily.h"
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
#include "Luau/TypePath.h"
|
#include "Luau/TypePath.h"
|
||||||
#include "Luau/TypeUtils.h"
|
#include "Luau/TypeUtils.h"
|
||||||
|
@ -122,8 +124,6 @@ SubtypingResult& SubtypingResult::andAlso(const SubtypingResult& other)
|
||||||
reasoning = mergeReasonings(reasoning, other.reasoning);
|
reasoning = mergeReasonings(reasoning, other.reasoning);
|
||||||
|
|
||||||
isSubtype &= other.isSubtype;
|
isSubtype &= other.isSubtype;
|
||||||
// `|=` is intentional here, we want to preserve error related flags.
|
|
||||||
isErrorSuppressing |= other.isErrorSuppressing;
|
|
||||||
normalizationTooComplex |= other.normalizationTooComplex;
|
normalizationTooComplex |= other.normalizationTooComplex;
|
||||||
isCacheable &= other.isCacheable;
|
isCacheable &= other.isCacheable;
|
||||||
|
|
||||||
|
@ -145,7 +145,6 @@ SubtypingResult& SubtypingResult::orElse(const SubtypingResult& other)
|
||||||
}
|
}
|
||||||
|
|
||||||
isSubtype |= other.isSubtype;
|
isSubtype |= other.isSubtype;
|
||||||
isErrorSuppressing |= other.isErrorSuppressing;
|
|
||||||
normalizationTooComplex |= other.normalizationTooComplex;
|
normalizationTooComplex |= other.normalizationTooComplex;
|
||||||
isCacheable &= other.isCacheable;
|
isCacheable &= other.isCacheable;
|
||||||
|
|
||||||
|
@ -218,14 +217,13 @@ SubtypingResult SubtypingResult::negate(const SubtypingResult& result)
|
||||||
{
|
{
|
||||||
return SubtypingResult{
|
return SubtypingResult{
|
||||||
!result.isSubtype,
|
!result.isSubtype,
|
||||||
result.isErrorSuppressing,
|
|
||||||
result.normalizationTooComplex,
|
result.normalizationTooComplex,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult SubtypingResult::all(const std::vector<SubtypingResult>& results)
|
SubtypingResult SubtypingResult::all(const std::vector<SubtypingResult>& results)
|
||||||
{
|
{
|
||||||
SubtypingResult acc{true, false};
|
SubtypingResult acc{true};
|
||||||
for (const SubtypingResult& current : results)
|
for (const SubtypingResult& current : results)
|
||||||
acc.andAlso(current);
|
acc.andAlso(current);
|
||||||
return acc;
|
return acc;
|
||||||
|
@ -233,7 +231,7 @@ SubtypingResult SubtypingResult::all(const std::vector<SubtypingResult>& results
|
||||||
|
|
||||||
SubtypingResult SubtypingResult::any(const std::vector<SubtypingResult>& results)
|
SubtypingResult SubtypingResult::any(const std::vector<SubtypingResult>& results)
|
||||||
{
|
{
|
||||||
SubtypingResult acc{false, false};
|
SubtypingResult acc{false};
|
||||||
for (const SubtypingResult& current : results)
|
for (const SubtypingResult& current : results)
|
||||||
acc.orElse(current);
|
acc.orElse(current);
|
||||||
return acc;
|
return acc;
|
||||||
|
@ -408,7 +406,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||||
else if (auto superUnion = get<UnionType>(superTy))
|
else if (auto superUnion = get<UnionType>(superTy))
|
||||||
{
|
{
|
||||||
result = isCovariantWith(env, subTy, superUnion);
|
result = isCovariantWith(env, subTy, superUnion);
|
||||||
if (!result.isSubtype && !result.isErrorSuppressing && !result.normalizationTooComplex)
|
if (!result.isSubtype && !result.normalizationTooComplex)
|
||||||
{
|
{
|
||||||
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
|
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
|
||||||
if (semantic.isSubtype)
|
if (semantic.isSubtype)
|
||||||
|
@ -423,7 +421,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||||
else if (auto subIntersection = get<IntersectionType>(subTy))
|
else if (auto subIntersection = get<IntersectionType>(subTy))
|
||||||
{
|
{
|
||||||
result = isCovariantWith(env, subIntersection, superTy);
|
result = isCovariantWith(env, subIntersection, superTy);
|
||||||
if (!result.isSubtype && !result.isErrorSuppressing && !result.normalizationTooComplex)
|
if (!result.isSubtype && !result.normalizationTooComplex)
|
||||||
{
|
{
|
||||||
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
|
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
|
||||||
if (semantic.isSubtype)
|
if (semantic.isSubtype)
|
||||||
|
@ -450,20 +448,20 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||||
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);
|
||||||
result = {!errorSuppressing, errorSuppressing};
|
result = {!errorSuppressing};
|
||||||
}
|
}
|
||||||
else if (get<NeverType>(subTy))
|
else if (get<NeverType>(subTy))
|
||||||
result = {true};
|
result = {true};
|
||||||
else if (get<ErrorType>(superTy))
|
else if (get<ErrorType>(superTy))
|
||||||
result = {false, true};
|
result = {false};
|
||||||
else if (get<ErrorType>(subTy))
|
else if (get<ErrorType>(subTy))
|
||||||
result = {false, true};
|
result = {false};
|
||||||
else if (auto p = get2<NegationType, NegationType>(subTy, superTy))
|
else if (auto p = get2<NegationType, NegationType>(subTy, superTy))
|
||||||
result = isCovariantWith(env, p.first->ty, p.second->ty).withBothComponent(TypePath::TypeField::Negated);
|
result = isCovariantWith(env, p.first->ty, p.second->ty).withBothComponent(TypePath::TypeField::Negated);
|
||||||
else if (auto subNegation = get<NegationType>(subTy))
|
else if (auto subNegation = get<NegationType>(subTy))
|
||||||
{
|
{
|
||||||
result = isCovariantWith(env, subNegation, superTy);
|
result = isCovariantWith(env, subNegation, superTy);
|
||||||
if (!result.isSubtype && !result.isErrorSuppressing && !result.normalizationTooComplex)
|
if (!result.isSubtype && !result.normalizationTooComplex)
|
||||||
{
|
{
|
||||||
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
|
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
|
||||||
if (semantic.isSubtype)
|
if (semantic.isSubtype)
|
||||||
|
@ -476,7 +474,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||||
else if (auto superNegation = get<NegationType>(superTy))
|
else if (auto superNegation = get<NegationType>(superTy))
|
||||||
{
|
{
|
||||||
result = isCovariantWith(env, subTy, superNegation);
|
result = isCovariantWith(env, subTy, superNegation);
|
||||||
if (!result.isSubtype && !result.isErrorSuppressing && !result.normalizationTooComplex)
|
if (!result.isSubtype && !result.normalizationTooComplex)
|
||||||
{
|
{
|
||||||
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
|
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
|
||||||
if (semantic.isSubtype)
|
if (semantic.isSubtype)
|
||||||
|
@ -486,6 +484,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (auto subTypeFamilyInstance = get<TypeFamilyInstanceType>(subTy))
|
||||||
|
result = isCovariantWith(env, subTypeFamilyInstance, superTy);
|
||||||
|
else if (auto superTypeFamilyInstance = get<TypeFamilyInstanceType>(superTy))
|
||||||
|
result = isCovariantWith(env, subTy, superTypeFamilyInstance);
|
||||||
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(env, subTy, superTy);
|
bool ok = bindGeneric(env, subTy, superTy);
|
||||||
|
@ -1256,7 +1258,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
|
||||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedType* subNorm, const NormalizedType* superNorm)
|
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedType* subNorm, const NormalizedType* superNorm)
|
||||||
{
|
{
|
||||||
if (!subNorm || !superNorm)
|
if (!subNorm || !superNorm)
|
||||||
return {false, true, true};
|
return {false, true};
|
||||||
|
|
||||||
SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops);
|
SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops);
|
||||||
result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans));
|
result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans));
|
||||||
|
@ -1412,6 +1414,20 @@ bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId supe
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeFamilyInstanceType* subFamilyInstance, const TypeId superTy)
|
||||||
|
{
|
||||||
|
// Reduce the typefamily instance
|
||||||
|
TypeId reduced = handleTypeFamilyReductionResult<TypeId>(subFamilyInstance);
|
||||||
|
return isCovariantWith(env, reduced, superTy);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFamilyInstanceType* superFamilyInstance)
|
||||||
|
{
|
||||||
|
// Reduce the typefamily instance
|
||||||
|
TypeId reduced = handleTypeFamilyReductionResult<TypeId>(superFamilyInstance);
|
||||||
|
return isCovariantWith(env, subTy, reduced);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If, when performing a subtyping test, we encounter a generic on the left
|
* If, when performing a subtyping test, we encounter a generic on the left
|
||||||
* side, it is permissible to tentatively bind that generic to the right side
|
* side, it is permissible to tentatively bind that generic to the right side
|
||||||
|
@ -1444,6 +1460,11 @@ TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse)
|
||||||
return arena->addType(T{std::vector<TypeId>(begin(container), end(container))});
|
return arena->addType(T{std::vector<TypeId>(begin(container), end(container))});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Subtyping::unexpected(TypeId ty)
|
||||||
|
{
|
||||||
|
iceReporter->ice(format("Unexpected type %s", toString(ty).c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
void Subtyping::unexpected(TypePackId tp)
|
void Subtyping::unexpected(TypePackId tp)
|
||||||
{
|
{
|
||||||
iceReporter->ice(format("Unexpected type pack %s", toString(tp).c_str()));
|
iceReporter->ice(format("Unexpected type pack %s", toString(tp).c_str()));
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "Luau/Instantiation.h"
|
#include "Luau/Instantiation.h"
|
||||||
#include "Luau/Metamethods.h"
|
#include "Luau/Metamethods.h"
|
||||||
#include "Luau/Normalize.h"
|
#include "Luau/Normalize.h"
|
||||||
|
#include "Luau/OverloadResolution.h"
|
||||||
#include "Luau/Subtyping.h"
|
#include "Luau/Subtyping.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
#include "Luau/TxnLog.h"
|
#include "Luau/TxnLog.h"
|
||||||
|
@ -240,7 +241,7 @@ struct TypeChecker2
|
||||||
std::vector<NotNull<Scope>> stack;
|
std::vector<NotNull<Scope>> stack;
|
||||||
std::vector<TypeId> functionDeclStack;
|
std::vector<TypeId> functionDeclStack;
|
||||||
|
|
||||||
DenseHashSet<TypeId> noTypeFamilyErrors{nullptr};
|
DenseHashSet<TypeId> seenTypeFamilyInstances{nullptr};
|
||||||
|
|
||||||
Normalizer normalizer;
|
Normalizer normalizer;
|
||||||
Subtyping _subtyping;
|
Subtyping _subtyping;
|
||||||
|
@ -377,7 +378,7 @@ struct TypeChecker2
|
||||||
if (const AstStatExpr* stat = node->as<AstStatExpr>())
|
if (const AstStatExpr* stat = node->as<AstStatExpr>())
|
||||||
{
|
{
|
||||||
if (AstExprCall* call = stat->expr->as<AstExprCall>(); call && isErrorCall(call))
|
if (AstExprCall* call = stat->expr->as<AstExprCall>(); call && isErrorCall(call))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
return stat;
|
return stat;
|
||||||
}
|
}
|
||||||
|
@ -432,18 +433,16 @@ struct TypeChecker2
|
||||||
|
|
||||||
TypeId checkForFamilyInhabitance(TypeId instance, Location location)
|
TypeId checkForFamilyInhabitance(TypeId instance, Location location)
|
||||||
{
|
{
|
||||||
if (noTypeFamilyErrors.find(instance))
|
if (seenTypeFamilyInstances.find(instance))
|
||||||
return instance;
|
return instance;
|
||||||
|
seenTypeFamilyInstances.insert(instance);
|
||||||
|
|
||||||
ErrorVec errors = reduceFamilies(
|
ErrorVec errors = reduceFamilies(
|
||||||
instance, location, TypeFamilyContext{NotNull{&testArena}, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true)
|
instance, location, TypeFamilyContext{NotNull{&testArena}, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true)
|
||||||
.errors;
|
.errors;
|
||||||
|
|
||||||
if (errors.empty())
|
|
||||||
noTypeFamilyErrors.insert(instance);
|
|
||||||
|
|
||||||
if (!isErrorSuppressing(location, instance))
|
if (!isErrorSuppressing(location, instance))
|
||||||
reportErrors(std::move(errors));
|
reportErrors(std::move(errors));
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -538,20 +537,21 @@ struct TypeChecker2
|
||||||
Scope* findInnermostScope(Location location)
|
Scope* findInnermostScope(Location location)
|
||||||
{
|
{
|
||||||
Scope* bestScope = module->getModuleScope().get();
|
Scope* bestScope = module->getModuleScope().get();
|
||||||
Location bestLocation = module->scopes[0].first;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < module->scopes.size(); ++i)
|
bool didNarrow;
|
||||||
|
do
|
||||||
{
|
{
|
||||||
auto& [scopeBounds, scope] = module->scopes[i];
|
didNarrow = false;
|
||||||
if (scopeBounds.encloses(location))
|
for (auto scope : bestScope->children)
|
||||||
{
|
{
|
||||||
if (scopeBounds.begin > bestLocation.begin || scopeBounds.end < bestLocation.end)
|
if (scope->location.encloses(location))
|
||||||
{
|
{
|
||||||
bestScope = scope.get();
|
bestScope = scope.get();
|
||||||
bestLocation = scopeBounds;
|
didNarrow = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} while (didNarrow && bestScope->children.size() > 0);
|
||||||
|
|
||||||
return bestScope;
|
return bestScope;
|
||||||
}
|
}
|
||||||
|
@ -1192,7 +1192,7 @@ struct TypeChecker2
|
||||||
TypeId expectedType = builtinTypes->nilType;
|
TypeId expectedType = builtinTypes->nilType;
|
||||||
|
|
||||||
SubtypingResult r = subtyping->isSubtype(actualType, expectedType);
|
SubtypingResult r = subtyping->isSubtype(actualType, expectedType);
|
||||||
LUAU_ASSERT(r.isSubtype || r.isErrorSuppressing);
|
LUAU_ASSERT(r.isSubtype || isErrorSuppressing(expr->location, actualType));
|
||||||
}
|
}
|
||||||
|
|
||||||
void visit(AstExprConstantBool* expr)
|
void visit(AstExprConstantBool* expr)
|
||||||
|
@ -1201,7 +1201,7 @@ struct TypeChecker2
|
||||||
TypeId expectedType = builtinTypes->booleanType;
|
TypeId expectedType = builtinTypes->booleanType;
|
||||||
|
|
||||||
SubtypingResult r = subtyping->isSubtype(actualType, expectedType);
|
SubtypingResult r = subtyping->isSubtype(actualType, expectedType);
|
||||||
LUAU_ASSERT(r.isSubtype || r.isErrorSuppressing);
|
LUAU_ASSERT(r.isSubtype || isErrorSuppressing(expr->location, actualType));
|
||||||
}
|
}
|
||||||
|
|
||||||
void visit(AstExprConstantNumber* expr)
|
void visit(AstExprConstantNumber* expr)
|
||||||
|
@ -1210,7 +1210,7 @@ struct TypeChecker2
|
||||||
TypeId expectedType = builtinTypes->numberType;
|
TypeId expectedType = builtinTypes->numberType;
|
||||||
|
|
||||||
SubtypingResult r = subtyping->isSubtype(actualType, expectedType);
|
SubtypingResult r = subtyping->isSubtype(actualType, expectedType);
|
||||||
LUAU_ASSERT(r.isSubtype || r.isErrorSuppressing);
|
LUAU_ASSERT(r.isSubtype || isErrorSuppressing(expr->location, actualType));
|
||||||
}
|
}
|
||||||
|
|
||||||
void visit(AstExprConstantString* expr)
|
void visit(AstExprConstantString* expr)
|
||||||
|
@ -1219,7 +1219,7 @@ struct TypeChecker2
|
||||||
TypeId expectedType = builtinTypes->stringType;
|
TypeId expectedType = builtinTypes->stringType;
|
||||||
|
|
||||||
SubtypingResult r = subtyping->isSubtype(actualType, expectedType);
|
SubtypingResult r = subtyping->isSubtype(actualType, expectedType);
|
||||||
LUAU_ASSERT(r.isSubtype || r.isErrorSuppressing);
|
LUAU_ASSERT(r.isSubtype || isErrorSuppressing(expr->location, actualType));
|
||||||
}
|
}
|
||||||
|
|
||||||
void visit(AstExprLocal* expr)
|
void visit(AstExprLocal* expr)
|
||||||
|
@ -1309,14 +1309,13 @@ struct TypeChecker2
|
||||||
args.head.push_back(builtinTypes->anyType);
|
args.head.push_back(builtinTypes->anyType);
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionCallResolver resolver{
|
OverloadResolver resolver{
|
||||||
builtinTypes,
|
builtinTypes,
|
||||||
NotNull{&testArena},
|
NotNull{&testArena},
|
||||||
NotNull{&normalizer},
|
NotNull{&normalizer},
|
||||||
NotNull{stack.back()},
|
NotNull{stack.back()},
|
||||||
ice,
|
ice,
|
||||||
limits,
|
limits,
|
||||||
subtyping,
|
|
||||||
call->location,
|
call->location,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1369,7 +1368,7 @@ struct TypeChecker2
|
||||||
{
|
{
|
||||||
for (const auto& [ty, p] : resolver.resolution)
|
for (const auto& [ty, p] : resolver.resolution)
|
||||||
{
|
{
|
||||||
if (p.first == FunctionCallResolver::TypeIsNotAFunction)
|
if (p.first == OverloadResolver::TypeIsNotAFunction)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
overloads.push_back(ty);
|
overloads.push_back(ty);
|
||||||
|
@ -1393,305 +1392,6 @@ struct TypeChecker2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FunctionCallResolver
|
|
||||||
{
|
|
||||||
enum Analysis
|
|
||||||
{
|
|
||||||
Ok,
|
|
||||||
TypeIsNotAFunction,
|
|
||||||
ArityMismatch,
|
|
||||||
OverloadIsNonviable, // Arguments were incompatible with the overload's parameters, but were otherwise compatible by arity.
|
|
||||||
};
|
|
||||||
|
|
||||||
NotNull<BuiltinTypes> builtinTypes;
|
|
||||||
NotNull<TypeArena> arena;
|
|
||||||
NotNull<Normalizer> normalizer;
|
|
||||||
NotNull<Scope> scope;
|
|
||||||
NotNull<InternalErrorReporter> ice;
|
|
||||||
NotNull<TypeCheckLimits> limits;
|
|
||||||
NotNull<Subtyping> subtyping;
|
|
||||||
Location callLoc;
|
|
||||||
|
|
||||||
std::vector<TypeId> ok;
|
|
||||||
std::vector<TypeId> nonFunctions;
|
|
||||||
std::vector<std::pair<TypeId, ErrorVec>> arityMismatches;
|
|
||||||
std::vector<std::pair<TypeId, ErrorVec>> nonviableOverloads;
|
|
||||||
InsertionOrderedMap<TypeId, std::pair<Analysis, size_t>> resolution;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::optional<ErrorVec> testIsSubtype(const Location& location, TypeId subTy, TypeId superTy)
|
|
||||||
{
|
|
||||||
auto r = subtyping->isSubtype(subTy, superTy);
|
|
||||||
ErrorVec errors;
|
|
||||||
|
|
||||||
if (r.normalizationTooComplex)
|
|
||||||
errors.push_back(TypeError{location, NormalizationTooComplex{}});
|
|
||||||
|
|
||||||
if (!r.isSubtype && !r.isErrorSuppressing)
|
|
||||||
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
|
||||||
|
|
||||||
if (errors.empty())
|
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<ErrorVec> testIsSubtype(const Location& location, TypePackId subTy, TypePackId superTy)
|
|
||||||
{
|
|
||||||
auto r = subtyping->isSubtype(subTy, superTy);
|
|
||||||
ErrorVec errors;
|
|
||||||
|
|
||||||
if (r.normalizationTooComplex)
|
|
||||||
errors.push_back(TypeError{location, NormalizationTooComplex{}});
|
|
||||||
|
|
||||||
if (!r.isSubtype && !r.isErrorSuppressing)
|
|
||||||
errors.push_back(TypeError{location, TypePackMismatch{superTy, subTy}});
|
|
||||||
|
|
||||||
if (errors.empty())
|
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<Analysis, ErrorVec> checkOverload(
|
|
||||||
TypeId fnTy, const TypePack* args, AstExpr* fnLoc, const std::vector<AstExpr*>* argExprs, bool callMetamethodOk = true)
|
|
||||||
{
|
|
||||||
fnTy = follow(fnTy);
|
|
||||||
|
|
||||||
ErrorVec discard;
|
|
||||||
if (get<AnyType>(fnTy) || get<ErrorType>(fnTy) || get<NeverType>(fnTy))
|
|
||||||
return {Ok, {}};
|
|
||||||
else if (auto fn = get<FunctionType>(fnTy))
|
|
||||||
return checkOverload_(fnTy, fn, args, fnLoc, argExprs); // Intentionally split to reduce the stack pressure of this function.
|
|
||||||
else if (auto callMm = findMetatableEntry(builtinTypes, discard, fnTy, "__call", callLoc); callMm && callMetamethodOk)
|
|
||||||
{
|
|
||||||
// Calling a metamethod forwards the `fnTy` as self.
|
|
||||||
TypePack withSelf = *args;
|
|
||||||
withSelf.head.insert(withSelf.head.begin(), fnTy);
|
|
||||||
|
|
||||||
std::vector<AstExpr*> withSelfExprs = *argExprs;
|
|
||||||
withSelfExprs.insert(withSelfExprs.begin(), fnLoc);
|
|
||||||
|
|
||||||
return checkOverload(*callMm, &withSelf, fnLoc, &withSelfExprs, /*callMetamethodOk=*/false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return {TypeIsNotAFunction, {}}; // Intentionally empty. We can just fabricate the type error later on.
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isLiteral(AstExpr* expr)
|
|
||||||
{
|
|
||||||
if (auto group = expr->as<AstExprGroup>())
|
|
||||||
return isLiteral(group->expr);
|
|
||||||
else if (auto assertion = expr->as<AstExprTypeAssertion>())
|
|
||||||
return isLiteral(assertion->expr);
|
|
||||||
|
|
||||||
return expr->is<AstExprConstantNil>() || expr->is<AstExprConstantBool>() || expr->is<AstExprConstantNumber>() ||
|
|
||||||
expr->is<AstExprConstantString>() || expr->is<AstExprFunction>() || expr->is<AstExprTable>();
|
|
||||||
}
|
|
||||||
|
|
||||||
LUAU_NOINLINE
|
|
||||||
std::pair<Analysis, ErrorVec> checkOverload_(
|
|
||||||
TypeId fnTy, const FunctionType* fn, const TypePack* args, AstExpr* fnExpr, const std::vector<AstExpr*>* argExprs)
|
|
||||||
{
|
|
||||||
FamilyGraphReductionResult result =
|
|
||||||
reduceFamilies(fnTy, callLoc, TypeFamilyContext{arena, builtinTypes, scope, normalizer, ice, limits}, /*force=*/true);
|
|
||||||
if (!result.errors.empty())
|
|
||||||
return {OverloadIsNonviable, result.errors};
|
|
||||||
|
|
||||||
ErrorVec argumentErrors;
|
|
||||||
|
|
||||||
TypeId prospectiveFunction = arena->addType(FunctionType{arena->addTypePack(*args), builtinTypes->anyTypePack});
|
|
||||||
SubtypingResult sr = subtyping->isSubtype(fnTy, prospectiveFunction);
|
|
||||||
|
|
||||||
if (sr.isSubtype)
|
|
||||||
return {Analysis::Ok, {}};
|
|
||||||
|
|
||||||
if (1 == sr.reasoning.size())
|
|
||||||
{
|
|
||||||
const SubtypingReasoning& reason = *sr.reasoning.begin();
|
|
||||||
|
|
||||||
const TypePath::Path justArguments{TypePath::PackField::Arguments};
|
|
||||||
|
|
||||||
if (reason.subPath == justArguments && reason.superPath == justArguments)
|
|
||||||
{
|
|
||||||
// If the subtype test failed only due to an arity mismatch,
|
|
||||||
// it is still possible that this function call is okay.
|
|
||||||
// Subtype testing does not know anything about optional
|
|
||||||
// function arguments.
|
|
||||||
//
|
|
||||||
// This can only happen if the actual function call has a
|
|
||||||
// finite set of arguments which is too short for the
|
|
||||||
// function being called. If all of those unsatisfied
|
|
||||||
// function arguments are options, then this function call
|
|
||||||
// is ok.
|
|
||||||
|
|
||||||
const size_t firstUnsatisfiedArgument = argExprs->size();
|
|
||||||
const auto [requiredHead, _requiredTail] = flatten(fn->argTypes);
|
|
||||||
|
|
||||||
// If too many arguments were supplied, this overload
|
|
||||||
// definitely does not match.
|
|
||||||
if (args->head.size() > requiredHead.size())
|
|
||||||
{
|
|
||||||
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
|
|
||||||
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, false}};
|
|
||||||
|
|
||||||
return {Analysis::ArityMismatch, {error}};
|
|
||||||
}
|
|
||||||
|
|
||||||
// If any of the unsatisfied arguments are not supertypes of
|
|
||||||
// nil, then this overload does not match.
|
|
||||||
for (size_t i = firstUnsatisfiedArgument; i < requiredHead.size(); ++i)
|
|
||||||
{
|
|
||||||
if (!subtyping->isSubtype(builtinTypes->nilType, requiredHead[i]).isSubtype)
|
|
||||||
{
|
|
||||||
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
|
|
||||||
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, false}};
|
|
||||||
|
|
||||||
return {Analysis::ArityMismatch, {error}};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {Analysis::Ok, {}};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorVec errors;
|
|
||||||
|
|
||||||
if (!sr.isErrorSuppressing)
|
|
||||||
{
|
|
||||||
for (const SubtypingReasoning& reason : sr.reasoning)
|
|
||||||
{
|
|
||||||
/* The return type of our prospective function is always
|
|
||||||
* any... so any subtype failures here can only arise from
|
|
||||||
* argument type mismatches.
|
|
||||||
*/
|
|
||||||
|
|
||||||
Location argLocation;
|
|
||||||
|
|
||||||
if (const Luau::TypePath::Index* pathIndexComponent = get_if<Luau::TypePath::Index>(&reason.superPath.components.at(1)))
|
|
||||||
{
|
|
||||||
size_t nthArgument = pathIndexComponent->index;
|
|
||||||
argLocation = argExprs->at(nthArgument)->location;
|
|
||||||
|
|
||||||
std::optional<TypeId> failedSubTy = traverseForType(fnTy, reason.subPath, builtinTypes);
|
|
||||||
std::optional<TypeId> failedSuperTy = traverseForType(prospectiveFunction, reason.superPath, builtinTypes);
|
|
||||||
|
|
||||||
if (failedSubTy && failedSuperTy)
|
|
||||||
{
|
|
||||||
// TODO extract location from the SubtypingResult path and argExprs
|
|
||||||
switch (reason.variance)
|
|
||||||
{
|
|
||||||
case SubtypingVariance::Covariant:
|
|
||||||
case SubtypingVariance::Contravariant:
|
|
||||||
errors.emplace_back(argLocation, TypeMismatch{*failedSubTy, *failedSuperTy, TypeMismatch::CovariantContext});
|
|
||||||
break;
|
|
||||||
case SubtypingVariance::Invariant:
|
|
||||||
errors.emplace_back(argLocation, TypeMismatch{*failedSubTy, *failedSuperTy, TypeMismatch::InvariantContext});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
LUAU_ASSERT(0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<TypePackId> failedSubPack = traverseForPack(fnTy, reason.subPath, builtinTypes);
|
|
||||||
std::optional<TypePackId> failedSuperPack = traverseForPack(prospectiveFunction, reason.superPath, builtinTypes);
|
|
||||||
|
|
||||||
if (failedSubPack && failedSuperPack)
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(!argExprs->empty());
|
|
||||||
argLocation = argExprs->at(argExprs->size() - 1)->location;
|
|
||||||
|
|
||||||
// TODO extract location from the SubtypingResult path and argExprs
|
|
||||||
switch (reason.variance)
|
|
||||||
{
|
|
||||||
case SubtypingVariance::Covariant:
|
|
||||||
errors.emplace_back(argLocation, TypePackMismatch{*failedSubPack, *failedSuperPack});
|
|
||||||
break;
|
|
||||||
case SubtypingVariance::Contravariant:
|
|
||||||
errors.emplace_back(argLocation, TypePackMismatch{*failedSuperPack, *failedSubPack});
|
|
||||||
break;
|
|
||||||
case SubtypingVariance::Invariant:
|
|
||||||
errors.emplace_back(argLocation, TypePackMismatch{*failedSubPack, *failedSuperPack});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
LUAU_ASSERT(0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {Analysis::OverloadIsNonviable, std::move(errors)};
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t indexof(Analysis analysis)
|
|
||||||
{
|
|
||||||
switch (analysis)
|
|
||||||
{
|
|
||||||
case Ok:
|
|
||||||
return ok.size();
|
|
||||||
case TypeIsNotAFunction:
|
|
||||||
return nonFunctions.size();
|
|
||||||
case ArityMismatch:
|
|
||||||
return arityMismatches.size();
|
|
||||||
case OverloadIsNonviable:
|
|
||||||
return nonviableOverloads.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
ice->ice("Inexhaustive switch in FunctionCallResolver::indexof");
|
|
||||||
}
|
|
||||||
|
|
||||||
void add(Analysis analysis, TypeId ty, ErrorVec&& errors)
|
|
||||||
{
|
|
||||||
resolution.insert(ty, {analysis, indexof(analysis)});
|
|
||||||
|
|
||||||
switch (analysis)
|
|
||||||
{
|
|
||||||
case Ok:
|
|
||||||
LUAU_ASSERT(errors.empty());
|
|
||||||
ok.push_back(ty);
|
|
||||||
break;
|
|
||||||
case TypeIsNotAFunction:
|
|
||||||
LUAU_ASSERT(errors.empty());
|
|
||||||
nonFunctions.push_back(ty);
|
|
||||||
break;
|
|
||||||
case ArityMismatch:
|
|
||||||
LUAU_ASSERT(!errors.empty());
|
|
||||||
arityMismatches.emplace_back(ty, std::move(errors));
|
|
||||||
break;
|
|
||||||
case OverloadIsNonviable:
|
|
||||||
nonviableOverloads.emplace_back(ty, std::move(errors));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
void resolve(TypeId fnTy, const TypePack* args, AstExpr* selfExpr, const std::vector<AstExpr*>* argExprs)
|
|
||||||
{
|
|
||||||
fnTy = follow(fnTy);
|
|
||||||
|
|
||||||
auto it = get<IntersectionType>(fnTy);
|
|
||||||
if (!it)
|
|
||||||
{
|
|
||||||
auto [analysis, errors] = checkOverload(fnTy, args, selfExpr, argExprs);
|
|
||||||
add(analysis, fnTy, std::move(errors));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (TypeId ty : it)
|
|
||||||
{
|
|
||||||
if (resolution.find(ty) != resolution.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto [analysis, errors] = checkOverload(ty, args, selfExpr, argExprs);
|
|
||||||
add(analysis, ty, std::move(errors));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void visit(AstExprCall* call)
|
void visit(AstExprCall* call)
|
||||||
{
|
{
|
||||||
visit(call->func, ValueContext::RValue);
|
visit(call->func, ValueContext::RValue);
|
||||||
|
@ -1738,7 +1438,17 @@ struct TypeChecker2
|
||||||
|
|
||||||
if (std::optional<TypeId> strippedUnion = tryStripUnionFromNil(ty))
|
if (std::optional<TypeId> strippedUnion = tryStripUnionFromNil(ty))
|
||||||
{
|
{
|
||||||
reportError(OptionalValueAccess{ty}, location);
|
switch (shouldSuppressErrors(NotNull{&normalizer}, ty))
|
||||||
|
{
|
||||||
|
case ErrorSuppression::Suppress:
|
||||||
|
break;
|
||||||
|
case ErrorSuppression::NormalizationFailed:
|
||||||
|
reportError(NormalizationTooComplex{}, location);
|
||||||
|
// fallthrough intentional
|
||||||
|
case ErrorSuppression::DoNotSuppress:
|
||||||
|
reportError(OptionalValueAccess{ty}, location);
|
||||||
|
}
|
||||||
|
|
||||||
return follow(*strippedUnion);
|
return follow(*strippedUnion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1784,7 +1494,18 @@ struct TypeChecker2
|
||||||
else if (auto cls = get<ClassType>(exprType); cls && cls->indexer)
|
else if (auto cls = get<ClassType>(exprType); cls && cls->indexer)
|
||||||
testIsSubtype(indexType, cls->indexer->indexType, indexExpr->index->location);
|
testIsSubtype(indexType, cls->indexer->indexType, indexExpr->index->location);
|
||||||
else if (get<UnionType>(exprType) && isOptional(exprType))
|
else if (get<UnionType>(exprType) && isOptional(exprType))
|
||||||
reportError(OptionalValueAccess{exprType}, indexExpr->location);
|
{
|
||||||
|
switch (shouldSuppressErrors(NotNull{&normalizer}, exprType))
|
||||||
|
{
|
||||||
|
case ErrorSuppression::Suppress:
|
||||||
|
break;
|
||||||
|
case ErrorSuppression::NormalizationFailed:
|
||||||
|
reportError(NormalizationTooComplex{}, indexExpr->location);
|
||||||
|
// fallthrough intentional
|
||||||
|
case ErrorSuppression::DoNotSuppress:
|
||||||
|
reportError(OptionalValueAccess{exprType}, indexExpr->location);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void visit(AstExprFunction* fn)
|
void visit(AstExprFunction* fn)
|
||||||
|
@ -1832,6 +1553,9 @@ struct TypeChecker2
|
||||||
|
|
||||||
if (arg->annotation)
|
if (arg->annotation)
|
||||||
{
|
{
|
||||||
|
// we need to typecheck any argument annotations themselves.
|
||||||
|
visit(arg->annotation);
|
||||||
|
|
||||||
TypeId annotatedArgTy = lookupAnnotation(arg->annotation);
|
TypeId annotatedArgTy = lookupAnnotation(arg->annotation);
|
||||||
|
|
||||||
testIsSubtype(inferredArgTy, annotatedArgTy, arg->location);
|
testIsSubtype(inferredArgTy, annotatedArgTy, arg->location);
|
||||||
|
@ -1873,6 +1597,10 @@ struct TypeChecker2
|
||||||
++argIt;
|
++argIt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we need to typecheck the vararg annotation, if it exists.
|
||||||
|
if (fn->vararg && fn->varargAnnotation)
|
||||||
|
visit(fn->varargAnnotation);
|
||||||
|
|
||||||
bool reachesImplicitReturn = getFallthrough(fn->body) != nullptr;
|
bool reachesImplicitReturn = getFallthrough(fn->body) != nullptr;
|
||||||
if (reachesImplicitReturn && !allowsNoReturnValues(follow(inferredFtv->retTypes)))
|
if (reachesImplicitReturn && !allowsNoReturnValues(follow(inferredFtv->retTypes)))
|
||||||
reportError(FunctionExitsWithoutReturning{inferredFtv->retTypes}, getEndLocation(fn));
|
reportError(FunctionExitsWithoutReturning{inferredFtv->retTypes}, getEndLocation(fn));
|
||||||
|
@ -1880,6 +1608,10 @@ struct TypeChecker2
|
||||||
|
|
||||||
visit(fn->body);
|
visit(fn->body);
|
||||||
|
|
||||||
|
// we need to typecheck the return annotation itself, if it exists.
|
||||||
|
if (fn->returnAnnotation)
|
||||||
|
visit(*fn->returnAnnotation);
|
||||||
|
|
||||||
functionDeclStack.pop_back();
|
functionDeclStack.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2272,12 +2004,22 @@ struct TypeChecker2
|
||||||
TypeId computedType = lookupType(expr->expr);
|
TypeId computedType = lookupType(expr->expr);
|
||||||
|
|
||||||
// Note: As an optimization, we try 'number <: number | string' first, as that is the more likely case.
|
// Note: As an optimization, we try 'number <: number | string' first, as that is the more likely case.
|
||||||
if (auto r = subtyping->isSubtype(annotationType, computedType); r.isSubtype || r.isErrorSuppressing)
|
if (subtyping->isSubtype(annotationType, computedType).isSubtype)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (auto r = subtyping->isSubtype(computedType, annotationType); r.isSubtype || r.isErrorSuppressing)
|
if (subtyping->isSubtype(computedType, annotationType).isSubtype)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
switch (shouldSuppressErrors(NotNull{&normalizer}, computedType).orElse(shouldSuppressErrors(NotNull{&normalizer}, annotationType)))
|
||||||
|
{
|
||||||
|
case ErrorSuppression::Suppress:
|
||||||
|
return;
|
||||||
|
case ErrorSuppression::NormalizationFailed:
|
||||||
|
reportError(NormalizationTooComplex{}, expr->location);
|
||||||
|
case ErrorSuppression::DoNotSuppress:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
reportError(TypesAreUnrelated{computedType, annotationType}, expr->location);
|
reportError(TypesAreUnrelated{computedType, annotationType}, expr->location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2615,25 +2357,65 @@ struct TypeChecker2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Reasonings
|
||||||
|
{
|
||||||
|
// the list of reasons
|
||||||
|
std::vector<std::string> reasons;
|
||||||
|
|
||||||
|
// this should be true if _all_ of the reasons have an error suppressing type, and false otherwise.
|
||||||
|
bool suppressed;
|
||||||
|
|
||||||
|
std::string toString()
|
||||||
|
{
|
||||||
|
// DenseHashSet ordering is entirely undefined, so we want to
|
||||||
|
// sort the reasons here to achieve a stable error
|
||||||
|
// stringification.
|
||||||
|
std::sort(reasons.begin(), reasons.end());
|
||||||
|
std::string allReasons;
|
||||||
|
bool first = true;
|
||||||
|
for (const std::string& reason : reasons)
|
||||||
|
{
|
||||||
|
if (first)
|
||||||
|
first = false;
|
||||||
|
else
|
||||||
|
allReasons += "\n\t";
|
||||||
|
|
||||||
|
allReasons += reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
return allReasons;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template<typename TID>
|
template<typename TID>
|
||||||
std::optional<std::string> explainReasonings(TID subTy, TID superTy, Location location, const SubtypingResult& r)
|
Reasonings explainReasonings(TID subTy, TID superTy, Location location, const SubtypingResult& r)
|
||||||
{
|
{
|
||||||
if (r.reasoning.empty())
|
if (r.reasoning.empty())
|
||||||
return std::nullopt;
|
return {};
|
||||||
|
|
||||||
std::vector<std::string> reasons;
|
std::vector<std::string> reasons;
|
||||||
|
bool suppressed = true;
|
||||||
for (const SubtypingReasoning& reasoning : r.reasoning)
|
for (const SubtypingReasoning& reasoning : r.reasoning)
|
||||||
{
|
{
|
||||||
if (reasoning.subPath.empty() && reasoning.superPath.empty())
|
if (reasoning.subPath.empty() && reasoning.superPath.empty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
std::optional<TypeOrPack> subLeaf = traverse(subTy, reasoning.subPath, builtinTypes);
|
std::optional<TypeOrPack> optSubLeaf = traverse(subTy, reasoning.subPath, builtinTypes);
|
||||||
std::optional<TypeOrPack> superLeaf = traverse(superTy, reasoning.superPath, builtinTypes);
|
std::optional<TypeOrPack> optSuperLeaf = traverse(superTy, reasoning.superPath, builtinTypes);
|
||||||
|
|
||||||
if (!subLeaf || !superLeaf)
|
if (!optSubLeaf || !optSuperLeaf)
|
||||||
ice->ice("Subtyping test returned a reasoning with an invalid path", location);
|
ice->ice("Subtyping test returned a reasoning with an invalid path", location);
|
||||||
|
|
||||||
if (!get2<TypeId, TypeId>(*subLeaf, *superLeaf) && !get2<TypePackId, TypePackId>(*subLeaf, *superLeaf))
|
const TypeOrPack& subLeaf = *optSubLeaf;
|
||||||
|
const TypeOrPack& superLeaf = *optSuperLeaf;
|
||||||
|
|
||||||
|
auto subLeafTy = get<TypeId>(subLeaf);
|
||||||
|
auto superLeafTy = get<TypeId>(superLeaf);
|
||||||
|
|
||||||
|
auto subLeafTp = get<TypePackId>(subLeaf);
|
||||||
|
auto superLeafTp = get<TypePackId>(superLeaf);
|
||||||
|
|
||||||
|
if (!subLeafTy && !superLeafTy && !subLeafTp && !superLeafTp)
|
||||||
ice->ice("Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack.", location);
|
ice->ice("Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack.", location);
|
||||||
|
|
||||||
std::string relation = "a subtype of";
|
std::string relation = "a subtype of";
|
||||||
|
@ -2644,41 +2426,61 @@ struct TypeChecker2
|
||||||
|
|
||||||
std::string reason;
|
std::string reason;
|
||||||
if (reasoning.subPath == reasoning.superPath)
|
if (reasoning.subPath == reasoning.superPath)
|
||||||
reason = "at " + toString(reasoning.subPath) + ", " + toString(*subLeaf) + " is not " + relation + " " + toString(*superLeaf);
|
reason = "at " + toString(reasoning.subPath) + ", " + toString(subLeaf) + " is not " + relation + " " + toString(superLeaf);
|
||||||
else
|
else
|
||||||
reason = "type " + toString(subTy) + toString(reasoning.subPath, /* prefixDot */ true) + " (" + toString(*subLeaf) + ") is not " +
|
reason = "type " + toString(subTy) + toString(reasoning.subPath, /* prefixDot */ true) + " (" + toString(subLeaf) + ") is not " +
|
||||||
relation + " " + toString(superTy) + toString(reasoning.superPath, /* prefixDot */ true) + " (" + toString(*superLeaf) + ")";
|
relation + " " + toString(superTy) + toString(reasoning.superPath, /* prefixDot */ true) + " (" + toString(superLeaf) + ")";
|
||||||
|
|
||||||
reasons.push_back(reason);
|
reasons.push_back(reason);
|
||||||
|
|
||||||
|
// if we haven't already proved this isn't suppressing, we have to keep checking.
|
||||||
|
if (suppressed)
|
||||||
|
{
|
||||||
|
if (subLeafTy && superLeafTy)
|
||||||
|
suppressed &= isErrorSuppressing(location, *subLeafTy) || isErrorSuppressing(location, *superLeafTy);
|
||||||
|
else
|
||||||
|
suppressed &= isErrorSuppressing(location, *subLeafTp) || isErrorSuppressing(location, *superLeafTp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DenseHashSet ordering is entirely undefined, so we want to
|
return {std::move(reasons), suppressed};
|
||||||
// sort the reasons here to achieve a stable error
|
|
||||||
// stringification.
|
|
||||||
std::sort(reasons.begin(), reasons.end());
|
|
||||||
std::string allReasons;
|
|
||||||
bool first = true;
|
|
||||||
for (const std::string& reason : reasons)
|
|
||||||
{
|
|
||||||
if (first)
|
|
||||||
first = false;
|
|
||||||
else
|
|
||||||
allReasons += "\n\t";
|
|
||||||
|
|
||||||
allReasons += reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
return allReasons;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void explainError(TypeId subTy, TypeId superTy, Location location, const SubtypingResult& result)
|
void explainError(TypeId subTy, TypeId superTy, Location location, const SubtypingResult& result)
|
||||||
{
|
{
|
||||||
reportError(TypeMismatch{superTy, subTy, explainReasonings(subTy, superTy, location, result).value_or("")}, location);
|
switch (shouldSuppressErrors(NotNull{&normalizer}, subTy).orElse(shouldSuppressErrors(NotNull{&normalizer}, superTy)))
|
||||||
|
{
|
||||||
|
case ErrorSuppression::Suppress:
|
||||||
|
return;
|
||||||
|
case ErrorSuppression::NormalizationFailed:
|
||||||
|
reportError(NormalizationTooComplex{}, location);
|
||||||
|
case ErrorSuppression::DoNotSuppress:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reasonings reasonings = explainReasonings(subTy, superTy, location, result);
|
||||||
|
|
||||||
|
if (!reasonings.suppressed)
|
||||||
|
reportError(TypeMismatch{superTy, subTy, reasonings.toString()}, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
void explainError(TypePackId subTy, TypePackId superTy, Location location, const SubtypingResult& result)
|
void explainError(TypePackId subTy, TypePackId superTy, Location location, const SubtypingResult& result)
|
||||||
{
|
{
|
||||||
reportError(TypePackMismatch{superTy, subTy, explainReasonings(subTy, superTy, location, result).value_or("")}, location);
|
switch (shouldSuppressErrors(NotNull{&normalizer}, subTy).orElse(shouldSuppressErrors(NotNull{&normalizer}, superTy)))
|
||||||
|
{
|
||||||
|
case ErrorSuppression::Suppress:
|
||||||
|
return;
|
||||||
|
case ErrorSuppression::NormalizationFailed:
|
||||||
|
reportError(NormalizationTooComplex{}, location);
|
||||||
|
case ErrorSuppression::DoNotSuppress:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reasonings reasonings = explainReasonings(subTy, superTy, location, result);
|
||||||
|
|
||||||
|
if (!reasonings.suppressed)
|
||||||
|
reportError(TypePackMismatch{superTy, subTy, reasonings.toString()}, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool testIsSubtype(TypeId subTy, TypeId superTy, Location location)
|
bool testIsSubtype(TypeId subTy, TypeId superTy, Location location)
|
||||||
|
@ -2688,7 +2490,7 @@ struct TypeChecker2
|
||||||
if (r.normalizationTooComplex)
|
if (r.normalizationTooComplex)
|
||||||
reportError(NormalizationTooComplex{}, location);
|
reportError(NormalizationTooComplex{}, location);
|
||||||
|
|
||||||
if (!r.isSubtype && !r.isErrorSuppressing)
|
if (!r.isSubtype)
|
||||||
explainError(subTy, superTy, location, r);
|
explainError(subTy, superTy, location, r);
|
||||||
|
|
||||||
return r.isSubtype;
|
return r.isSubtype;
|
||||||
|
@ -2701,7 +2503,7 @@ struct TypeChecker2
|
||||||
if (r.normalizationTooComplex)
|
if (r.normalizationTooComplex)
|
||||||
reportError(NormalizationTooComplex{}, location);
|
reportError(NormalizationTooComplex{}, location);
|
||||||
|
|
||||||
if (!r.isSubtype && !r.isErrorSuppressing)
|
if (!r.isSubtype)
|
||||||
explainError(subTy, superTy, location, r);
|
explainError(subTy, superTy, location, r);
|
||||||
|
|
||||||
return r.isSubtype;
|
return r.isSubtype;
|
||||||
|
|
|
@ -6,6 +6,10 @@
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauFollowEmptyTypePacks, false);
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -267,6 +271,8 @@ TypePackId follow(TypePackId tp, const void* context, TypePackId (*mapper)(const
|
||||||
|
|
||||||
if (const Unifiable::Bound<TypePackId>* btv = get<Unifiable::Bound<TypePackId>>(mapped))
|
if (const Unifiable::Bound<TypePackId>* btv = get<Unifiable::Bound<TypePackId>>(mapped))
|
||||||
return btv->boundTo;
|
return btv->boundTo;
|
||||||
|
else if (const TypePack* tp = get<TypePack>(mapped); (FFlag::DebugLuauDeferredConstraintResolution || FFlag::LuauFollowEmptyTypePacks) && tp && tp->head.empty())
|
||||||
|
return tp->tail;
|
||||||
else
|
else
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
#include "lstate.h"
|
#include "lstate.h"
|
||||||
#include "lgc.h"
|
#include "lgc.h"
|
||||||
|
|
||||||
|
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauCodeGenFixBufferLenCheckA64, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
namespace CodeGen
|
namespace CodeGen
|
||||||
|
@ -1533,11 +1535,15 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// fails if offset + size >= len; we compute it as len - offset <= size
|
// fails if offset + size > len; we compute it as len - offset < size
|
||||||
RegisterA64 tempx = castReg(KindA64::x, temp);
|
RegisterA64 tempx = castReg(KindA64::x, temp);
|
||||||
build.sub(tempx, tempx, regOp(inst.b)); // implicit uxtw
|
build.sub(tempx, tempx, regOp(inst.b)); // implicit uxtw
|
||||||
build.cmp(tempx, uint16_t(accessSize));
|
build.cmp(tempx, uint16_t(accessSize));
|
||||||
build.b(ConditionA64::LessEqual, target); // note: this is a signed 64-bit comparison so that out of bounds offset fails
|
|
||||||
|
if (DFFlag::LuauCodeGenFixBufferLenCheckA64)
|
||||||
|
build.b(ConditionA64::Less, target); // note: this is a signed 64-bit comparison so that out of bounds offset fails
|
||||||
|
else
|
||||||
|
build.b(ConditionA64::LessEqual, target); // note: this is a signed 64-bit comparison so that out of bounds offset fails
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (inst.b.kind == IrOpKind::Constant)
|
else if (inst.b.kind == IrOpKind::Constant)
|
||||||
|
|
|
@ -19,6 +19,7 @@ LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauReuseBufferChecks, false)
|
LUAU_FASTFLAGVARIABLE(LuauReuseBufferChecks, false)
|
||||||
LUAU_FASTFLAG(LuauCodegenVector)
|
LUAU_FASTFLAG(LuauCodegenVector)
|
||||||
|
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauCodeGenCheckGcEffectFix, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -1037,9 +1038,19 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||||
case IrCmd::CHECK_GC:
|
case IrCmd::CHECK_GC:
|
||||||
// It is enough to perform a GC check once in a block
|
// It is enough to perform a GC check once in a block
|
||||||
if (state.checkedGc)
|
if (state.checkedGc)
|
||||||
|
{
|
||||||
kill(function, inst);
|
kill(function, inst);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
state.checkedGc = true;
|
state.checkedGc = true;
|
||||||
|
|
||||||
|
if (DFFlag::LuauCodeGenCheckGcEffectFix)
|
||||||
|
{
|
||||||
|
// GC assist might modify table data (hash part)
|
||||||
|
state.invalidateHeapTableData();
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case IrCmd::BARRIER_OBJ:
|
case IrCmd::BARRIER_OBJ:
|
||||||
case IrCmd::BARRIER_TABLE_FORWARD:
|
case IrCmd::BARRIER_TABLE_FORWARD:
|
||||||
|
|
|
@ -184,6 +184,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||||
Analysis/include/Luau/ModuleResolver.h
|
Analysis/include/Luau/ModuleResolver.h
|
||||||
Analysis/include/Luau/NonStrictTypeChecker.h
|
Analysis/include/Luau/NonStrictTypeChecker.h
|
||||||
Analysis/include/Luau/Normalize.h
|
Analysis/include/Luau/Normalize.h
|
||||||
|
Analysis/include/Luau/OverloadResolution.h
|
||||||
Analysis/include/Luau/Predicate.h
|
Analysis/include/Luau/Predicate.h
|
||||||
Analysis/include/Luau/Quantify.h
|
Analysis/include/Luau/Quantify.h
|
||||||
Analysis/include/Luau/RecursionCounter.h
|
Analysis/include/Luau/RecursionCounter.h
|
||||||
|
@ -247,6 +248,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||||
Analysis/src/Module.cpp
|
Analysis/src/Module.cpp
|
||||||
Analysis/src/NonStrictTypeChecker.cpp
|
Analysis/src/NonStrictTypeChecker.cpp
|
||||||
Analysis/src/Normalize.cpp
|
Analysis/src/Normalize.cpp
|
||||||
|
Analysis/src/OverloadResolution.cpp
|
||||||
Analysis/src/Quantify.cpp
|
Analysis/src/Quantify.cpp
|
||||||
Analysis/src/Refinement.cpp
|
Analysis/src/Refinement.cpp
|
||||||
Analysis/src/RequireTracer.cpp
|
Analysis/src/RequireTracer.cpp
|
||||||
|
|
|
@ -49,7 +49,9 @@ void luaL_sandbox(lua_State* L)
|
||||||
lua_pop(L, 2);
|
lua_pop(L, 2);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
|
||||||
// set globals to readonly and activate safeenv since the env is immutable
|
// set globals to readonly and activate safeenv since the env is immutable
|
||||||
lua_setreadonly(L, LUA_GLOBALSINDEX, true);
|
lua_setreadonly(L, LUA_GLOBALSINDEX, true);
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
unsigned int luaS_hash(const char* str, size_t len)
|
unsigned int luaS_hash(const char* str, size_t len)
|
||||||
{
|
{
|
||||||
// Note that this hashing algorithm is replicated in BytecodeBuilder.cpp, BytecodeBuilder::getStringHash
|
// Note that this hashing algorithm is replicated in BytecodeBuilder.cpp, BytecodeBuilder::getStringHash
|
||||||
|
|
|
@ -30,6 +30,7 @@ LUAU_FASTFLAG(LuauTaggedLuData)
|
||||||
LUAU_FASTFLAG(LuauSciNumberSkipTrailDot)
|
LUAU_FASTFLAG(LuauSciNumberSkipTrailDot)
|
||||||
LUAU_DYNAMIC_FASTFLAG(LuauInterruptablePatternMatch)
|
LUAU_DYNAMIC_FASTFLAG(LuauInterruptablePatternMatch)
|
||||||
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
|
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
|
||||||
|
LUAU_DYNAMIC_FASTFLAG(LuauCodeGenFixBufferLenCheckA64)
|
||||||
|
|
||||||
static lua_CompileOptions defaultOptions()
|
static lua_CompileOptions defaultOptions()
|
||||||
{
|
{
|
||||||
|
@ -281,6 +282,45 @@ static void* limitedRealloc(void* ud, void* ptr, size_t osize, size_t nsize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setupVectorHelpers(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushcfunction(L, lua_vector, "vector");
|
||||||
|
lua_setglobal(L, "vector");
|
||||||
|
|
||||||
|
#if LUA_VECTOR_SIZE == 4
|
||||||
|
lua_pushvector(L, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
#else
|
||||||
|
lua_pushvector(L, 0.0f, 0.0f, 0.0f);
|
||||||
|
#endif
|
||||||
|
luaL_newmetatable(L, "vector");
|
||||||
|
|
||||||
|
lua_pushstring(L, "__index");
|
||||||
|
lua_pushcfunction(L, lua_vector_index, nullptr);
|
||||||
|
lua_settable(L, -3);
|
||||||
|
|
||||||
|
lua_pushstring(L, "__namecall");
|
||||||
|
lua_pushcfunction(L, lua_vector_namecall, nullptr);
|
||||||
|
lua_settable(L, -3);
|
||||||
|
|
||||||
|
lua_setreadonly(L, -1, true);
|
||||||
|
lua_setmetatable(L, -2);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setupNativeHelpers(lua_State* L)
|
||||||
|
{
|
||||||
|
lua_pushcclosurek(
|
||||||
|
L,
|
||||||
|
[](lua_State* L) -> int {
|
||||||
|
extern int luaG_isnative(lua_State * L, int level);
|
||||||
|
|
||||||
|
lua_pushboolean(L, luaG_isnative(L, 1));
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
"is_native", 0, nullptr);
|
||||||
|
lua_setglobal(L, "is_native");
|
||||||
|
}
|
||||||
|
|
||||||
static std::vector<Luau::CodeGen::FunctionBytecodeSummary> analyzeFile(const char* source, const unsigned nestingLimit)
|
static std::vector<Luau::CodeGen::FunctionBytecodeSummary> analyzeFile(const char* source, const unsigned nestingLimit)
|
||||||
{
|
{
|
||||||
Luau::BytecodeBuilder bcb;
|
Luau::BytecodeBuilder bcb;
|
||||||
|
@ -490,27 +530,7 @@ TEST_CASE("Vector")
|
||||||
runConformance(
|
runConformance(
|
||||||
"vector.lua",
|
"vector.lua",
|
||||||
[](lua_State* L) {
|
[](lua_State* L) {
|
||||||
lua_pushcfunction(L, lua_vector, "vector");
|
setupVectorHelpers(L);
|
||||||
lua_setglobal(L, "vector");
|
|
||||||
|
|
||||||
#if LUA_VECTOR_SIZE == 4
|
|
||||||
lua_pushvector(L, 0.0f, 0.0f, 0.0f, 0.0f);
|
|
||||||
#else
|
|
||||||
lua_pushvector(L, 0.0f, 0.0f, 0.0f);
|
|
||||||
#endif
|
|
||||||
luaL_newmetatable(L, "vector");
|
|
||||||
|
|
||||||
lua_pushstring(L, "__index");
|
|
||||||
lua_pushcfunction(L, lua_vector_index, nullptr);
|
|
||||||
lua_settable(L, -3);
|
|
||||||
|
|
||||||
lua_pushstring(L, "__namecall");
|
|
||||||
lua_pushcfunction(L, lua_vector_namecall, nullptr);
|
|
||||||
lua_settable(L, -3);
|
|
||||||
|
|
||||||
lua_setreadonly(L, -1, true);
|
|
||||||
lua_setmetatable(L, -2);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
},
|
},
|
||||||
nullptr, nullptr, nullptr);
|
nullptr, nullptr, nullptr);
|
||||||
}
|
}
|
||||||
|
@ -2019,7 +2039,15 @@ TEST_CASE("SafeEnv")
|
||||||
|
|
||||||
TEST_CASE("Native")
|
TEST_CASE("Native")
|
||||||
{
|
{
|
||||||
runConformance("native.lua");
|
ScopedFastFlag luauCodeGenFixBufferLenCheckA64{DFFlag::LuauCodeGenFixBufferLenCheckA64, true};
|
||||||
|
|
||||||
|
// This tests requires code to run natively, otherwise all 'is_native' checks will fail
|
||||||
|
if (!codegen || !luau_codegen_supported())
|
||||||
|
return;
|
||||||
|
|
||||||
|
runConformance("native.lua", [](lua_State* L) {
|
||||||
|
setupNativeHelpers(L);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("NativeTypeAnnotations")
|
TEST_CASE("NativeTypeAnnotations")
|
||||||
|
@ -2028,37 +2056,10 @@ TEST_CASE("NativeTypeAnnotations")
|
||||||
if (!codegen || !luau_codegen_supported())
|
if (!codegen || !luau_codegen_supported())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
runConformance(
|
runConformance("native_types.lua", [](lua_State* L) {
|
||||||
"native_types.lua",
|
setupNativeHelpers(L);
|
||||||
[](lua_State* L) {
|
setupVectorHelpers(L);
|
||||||
// add is_native() function
|
});
|
||||||
lua_pushcclosurek(
|
|
||||||
L,
|
|
||||||
[](lua_State* L) -> int {
|
|
||||||
extern int luaG_isnative(lua_State * L, int level);
|
|
||||||
|
|
||||||
lua_pushboolean(L, luaG_isnative(L, 1));
|
|
||||||
return 1;
|
|
||||||
},
|
|
||||||
"is_native", 0, nullptr);
|
|
||||||
lua_setglobal(L, "is_native");
|
|
||||||
|
|
||||||
// for vector tests
|
|
||||||
lua_pushcfunction(L, lua_vector, "vector");
|
|
||||||
lua_setglobal(L, "vector");
|
|
||||||
|
|
||||||
#if LUA_VECTOR_SIZE == 4
|
|
||||||
lua_pushvector(L, 0.0f, 0.0f, 0.0f, 0.0f);
|
|
||||||
#else
|
|
||||||
lua_pushvector(L, 0.0f, 0.0f, 0.0f);
|
|
||||||
#endif
|
|
||||||
luaL_newmetatable(L, "vector");
|
|
||||||
|
|
||||||
lua_setreadonly(L, -1, true);
|
|
||||||
lua_setmetatable(L, -2);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
},
|
|
||||||
nullptr, nullptr, nullptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("HugeFunction")
|
TEST_CASE("HugeFunction")
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
|
|
||||||
using namespace Luau::CodeGen;
|
using namespace Luau::CodeGen;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauReuseBufferChecks);
|
LUAU_FASTFLAG(LuauReuseBufferChecks)
|
||||||
|
LUAU_DYNAMIC_FASTFLAG(LuauCodeGenCheckGcEffectFix)
|
||||||
|
|
||||||
class IrBuilderFixture
|
class IrBuilderFixture
|
||||||
{
|
{
|
||||||
|
@ -2058,6 +2059,68 @@ bb_fallback_1:
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecksInvalidation")
|
||||||
|
{
|
||||||
|
ScopedFastFlag luauCodeGenCheckGcEffectFix{DFFlag::LuauCodeGenCheckGcEffectFix, true};
|
||||||
|
|
||||||
|
IrOp block = build.block(IrBlockKind::Internal);
|
||||||
|
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||||
|
|
||||||
|
build.beginBlock(block);
|
||||||
|
|
||||||
|
// This roughly corresponds to 'return t.a + t.a' with a stange GC assist in the middle
|
||||||
|
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
|
||||||
|
IrOp slot1 = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(3), build.vmConst(1));
|
||||||
|
build.inst(IrCmd::CHECK_SLOT_MATCH, slot1, build.vmConst(1), fallback);
|
||||||
|
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, slot1, build.constInt(0));
|
||||||
|
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
|
||||||
|
|
||||||
|
build.inst(IrCmd::CHECK_GC);
|
||||||
|
|
||||||
|
IrOp slot1b = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(8), build.vmConst(1));
|
||||||
|
build.inst(IrCmd::CHECK_SLOT_MATCH, slot1b, build.vmConst(1), fallback);
|
||||||
|
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, slot1b, build.constInt(0));
|
||||||
|
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
|
||||||
|
|
||||||
|
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
|
||||||
|
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
|
||||||
|
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
|
||||||
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
|
||||||
|
|
||||||
|
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
|
||||||
|
|
||||||
|
build.beginBlock(fallback);
|
||||||
|
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||||
|
|
||||||
|
updateUseCounts(build.function);
|
||||||
|
constPropInBlockChains(build, true);
|
||||||
|
|
||||||
|
// In the future, we might even see duplicate identical TValue loads go away
|
||||||
|
// In the future, we might even see loads of different VM regs with the same value go away
|
||||||
|
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||||
|
bb_0:
|
||||||
|
%0 = LOAD_POINTER R1
|
||||||
|
%1 = GET_SLOT_NODE_ADDR %0, 3u, K1
|
||||||
|
CHECK_SLOT_MATCH %1, K1, bb_fallback_1
|
||||||
|
%3 = LOAD_TVALUE %1, 0i
|
||||||
|
STORE_TVALUE R3, %3
|
||||||
|
CHECK_GC
|
||||||
|
%6 = GET_SLOT_NODE_ADDR %0, 8u, K1
|
||||||
|
CHECK_SLOT_MATCH %6, K1, bb_fallback_1
|
||||||
|
%8 = LOAD_TVALUE %6, 0i
|
||||||
|
STORE_TVALUE R4, %8
|
||||||
|
%10 = LOAD_DOUBLE R3
|
||||||
|
%11 = LOAD_DOUBLE R4
|
||||||
|
%12 = ADD_NUM %10, %11
|
||||||
|
STORE_DOUBLE R2, %12
|
||||||
|
RETURN R2, 1u
|
||||||
|
|
||||||
|
bb_fallback_1:
|
||||||
|
RETURN R0, 1u
|
||||||
|
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameIndex")
|
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameIndex")
|
||||||
{
|
{
|
||||||
IrOp block = build.block(IrBlockKind::Internal);
|
IrOp block = build.block(IrBlockKind::Internal);
|
||||||
|
|
|
@ -86,6 +86,9 @@ declare function @checked contrived(n : Not<number>) : number
|
||||||
declare function @checked onlyNums(...: number) : number
|
declare function @checked onlyNums(...: number) : number
|
||||||
declare function @checked mixedArgs(x: string, ...: number) : number
|
declare function @checked mixedArgs(x: string, ...: number) : number
|
||||||
declare function @checked optionalArg(x: string?) : number
|
declare function @checked optionalArg(x: string?) : number
|
||||||
|
declare foo: {
|
||||||
|
bar: @checked (number) -> number,
|
||||||
|
}
|
||||||
)BUILTIN_SRC";
|
)BUILTIN_SRC";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -427,7 +430,7 @@ lower(x) -- phi {x1, x2}
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
} //
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "phi_node_assignment_err")
|
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "phi_node_assignment_err")
|
||||||
{
|
{
|
||||||
|
@ -447,4 +450,28 @@ end
|
||||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(8, 10), "lower", result);
|
NONSTRICT_REQUIRE_CHECKED_ERR(Position(8, 10), "lower", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "tblprop_is_checked")
|
||||||
|
{
|
||||||
|
CheckResult result = checkNonStrict(R"(
|
||||||
|
foo.bar("hi")
|
||||||
|
)");
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
NONSTRICT_REQUIRE_CHECKED_ERR(Position(1, 8), "foo.bar", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "incorrect_arg_count")
|
||||||
|
{
|
||||||
|
CheckResult result = checkNonStrict(R"(
|
||||||
|
foo.bar(1,2,3)
|
||||||
|
abs(3, "hi");
|
||||||
|
)");
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
|
auto r1 = get<CheckedFunctionIncorrectArgs>(result.errors[0]);
|
||||||
|
auto r2 = get<CheckedFunctionIncorrectArgs>(result.errors[1]);
|
||||||
|
LUAU_ASSERT(r1);
|
||||||
|
LUAU_ASSERT(r2);
|
||||||
|
CHECK_EQ("abs", r1->functionName);
|
||||||
|
CHECK_EQ("foo.bar", r2->functionName);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
// 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/TypeFwd.h"
|
||||||
#include "Luau/TypePath.h"
|
#include "Luau/TypePath.h"
|
||||||
|
|
||||||
#include "Luau/Normalize.h"
|
#include "Luau/Normalize.h"
|
||||||
#include "Luau/Subtyping.h"
|
#include "Luau/Subtyping.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
|
#include "Luau/TypeFamily.h"
|
||||||
|
|
||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
#include "Fixture.h"
|
#include "Fixture.h"
|
||||||
|
@ -67,6 +69,7 @@ struct SubtypeFixture : Fixture
|
||||||
ScopePtr moduleScope{new Scope(rootScope)};
|
ScopePtr moduleScope{new Scope(rootScope)};
|
||||||
|
|
||||||
Subtyping subtyping = mkSubtyping(rootScope);
|
Subtyping subtyping = mkSubtyping(rootScope);
|
||||||
|
BuiltinTypeFamilies builtinTypeFamilies{};
|
||||||
|
|
||||||
Subtyping mkSubtyping(const ScopePtr& scope)
|
Subtyping mkSubtyping(const ScopePtr& scope)
|
||||||
{
|
{
|
||||||
|
@ -319,24 +322,6 @@ struct SubtypeFixture : Fixture
|
||||||
CHECK_MESSAGE(!result.isSubtype, "Expected " << leftTy << " </: " << rightTy); \
|
CHECK_MESSAGE(!result.isSubtype, "Expected " << leftTy << " </: " << rightTy); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#define CHECK_IS_ERROR_SUPPRESSING(left, right) \
|
|
||||||
do \
|
|
||||||
{ \
|
|
||||||
const auto& leftTy = (left); \
|
|
||||||
const auto& rightTy = (right); \
|
|
||||||
SubtypingResult result = isSubtype(leftTy, rightTy); \
|
|
||||||
CHECK_MESSAGE(result.isErrorSuppressing, "Expected " << leftTy << " to error-suppress " << rightTy); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define CHECK_IS_NOT_ERROR_SUPPRESSING(left, right) \
|
|
||||||
do \
|
|
||||||
{ \
|
|
||||||
const auto& leftTy = (left); \
|
|
||||||
const auto& rightTy = (right); \
|
|
||||||
SubtypingResult result = isSubtype(leftTy, rightTy); \
|
|
||||||
CHECK_MESSAGE(!result.isErrorSuppressing, "Expected " << leftTy << " to error-suppress " << rightTy); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
/// Internal macro for registering a generated test case.
|
/// Internal macro for registering a generated test case.
|
||||||
///
|
///
|
||||||
/// @param der the name of the derived fixture struct
|
/// @param der the name of the derived fixture struct
|
||||||
|
@ -404,11 +389,61 @@ TEST_SUITE_BEGIN("Subtyping");
|
||||||
TEST_IS_SUBTYPE(builtinTypes->numberType, builtinTypes->anyType);
|
TEST_IS_SUBTYPE(builtinTypes->numberType, builtinTypes->anyType);
|
||||||
TEST_IS_NOT_SUBTYPE(builtinTypes->numberType, builtinTypes->stringType);
|
TEST_IS_NOT_SUBTYPE(builtinTypes->numberType, builtinTypes->stringType);
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(SubtypeFixture, "basic_reducible_sub_typefamily")
|
||||||
|
{
|
||||||
|
// add<number, number> <: number
|
||||||
|
TypeId typeFamilyNum =
|
||||||
|
arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.addFamily}, {builtinTypes->numberType, builtinTypes->numberType}, {}});
|
||||||
|
TypeId superTy = builtinTypes->numberType;
|
||||||
|
SubtypingResult result = isSubtype(typeFamilyNum, superTy);
|
||||||
|
CHECK(result.isSubtype);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(SubtypeFixture, "basic_reducible_super_typefamily")
|
||||||
|
{
|
||||||
|
// number <: add<number, number> ~ number
|
||||||
|
TypeId typeFamilyNum =
|
||||||
|
arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.addFamily}, {builtinTypes->numberType, builtinTypes->numberType}, {}});
|
||||||
|
TypeId subTy = builtinTypes->numberType;
|
||||||
|
SubtypingResult result = isSubtype(subTy, typeFamilyNum);
|
||||||
|
CHECK(result.isSubtype);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(SubtypeFixture, "basic_irreducible_sub_typefamily")
|
||||||
|
{
|
||||||
|
// add<string, boolean> ~ never <: number
|
||||||
|
TypeId typeFamilyNum =
|
||||||
|
arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.addFamily}, {builtinTypes->stringType, builtinTypes->booleanType}, {}});
|
||||||
|
TypeId superTy = builtinTypes->numberType;
|
||||||
|
SubtypingResult result = isSubtype(typeFamilyNum, superTy);
|
||||||
|
CHECK(result.isSubtype);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(SubtypeFixture, "basic_irreducible_super_typefamily")
|
||||||
|
{
|
||||||
|
// number <\: add<string, boolean> ~ irreducible/never
|
||||||
|
TypeId typeFamilyNum =
|
||||||
|
arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.addFamily}, {builtinTypes->stringType, builtinTypes->booleanType}, {}});
|
||||||
|
TypeId subTy = builtinTypes->numberType;
|
||||||
|
SubtypingResult result = isSubtype(subTy, typeFamilyNum);
|
||||||
|
CHECK(!result.isSubtype);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(SubtypeFixture, "basic_typefamily_with_generics")
|
||||||
|
{
|
||||||
|
// <T,U>(x: T, x: U) -> add<T,U> <: (number, number) -> number
|
||||||
|
TypeId addFamily = arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.addFamily}, {genericT, genericU}, {}});
|
||||||
|
FunctionType ft{{genericT, genericU}, {}, arena.addTypePack({genericT, genericU}), arena.addTypePack({addFamily})};
|
||||||
|
TypeId functionType = arena.addType(std::move(ft));
|
||||||
|
FunctionType superFt{arena.addTypePack({builtinTypes->numberType, builtinTypes->numberType}), arena.addTypePack({builtinTypes->numberType})};
|
||||||
|
TypeId superFunction = arena.addType(std::move(superFt));
|
||||||
|
SubtypingResult result = isSubtype(functionType, superFunction);
|
||||||
|
CHECK(result.isSubtype);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(SubtypeFixture, "any <!: unknown")
|
TEST_CASE_FIXTURE(SubtypeFixture, "any <!: unknown")
|
||||||
{
|
{
|
||||||
SubtypingResult result = isSubtype(builtinTypes->anyType, builtinTypes->unknownType);
|
CHECK_IS_NOT_SUBTYPE(builtinTypes->anyType, builtinTypes->unknownType);
|
||||||
CHECK(!result.isSubtype);
|
|
||||||
CHECK(result.isErrorSuppressing);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(SubtypeFixture, "number? <: unknown")
|
TEST_CASE_FIXTURE(SubtypeFixture, "number? <: unknown")
|
||||||
|
@ -785,7 +820,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <!: { x: number
|
||||||
// Negated subtypes
|
// Negated subtypes
|
||||||
TEST_IS_NOT_SUBTYPE(negate(builtinTypes->neverType), builtinTypes->stringType);
|
TEST_IS_NOT_SUBTYPE(negate(builtinTypes->neverType), builtinTypes->stringType);
|
||||||
TEST_IS_SUBTYPE(negate(builtinTypes->unknownType), builtinTypes->stringType);
|
TEST_IS_SUBTYPE(negate(builtinTypes->unknownType), builtinTypes->stringType);
|
||||||
TEST_IS_NOT_SUBTYPE(negate(builtinTypes->anyType), builtinTypes->stringType);
|
TEST_IS_SUBTYPE(negate(builtinTypes->anyType), builtinTypes->stringType);
|
||||||
TEST_IS_SUBTYPE(negate(meet(builtinTypes->neverType, builtinTypes->unknownType)), builtinTypes->stringType);
|
TEST_IS_SUBTYPE(negate(meet(builtinTypes->neverType, builtinTypes->unknownType)), builtinTypes->stringType);
|
||||||
TEST_IS_SUBTYPE(negate(join(builtinTypes->neverType, builtinTypes->unknownType)), builtinTypes->stringType);
|
TEST_IS_SUBTYPE(negate(join(builtinTypes->neverType, builtinTypes->unknownType)), builtinTypes->stringType);
|
||||||
|
|
||||||
|
@ -1163,18 +1198,10 @@ TEST_CASE_FIXTURE(SubtypeFixture, "dont_cache_tests_involving_cycles")
|
||||||
TEST_CASE_FIXTURE(SubtypeFixture, "<T>({ x: T }) -> T <: ({ method: <T>({ x: T }) -> T, x: number }) -> number")
|
TEST_CASE_FIXTURE(SubtypeFixture, "<T>({ x: T }) -> T <: ({ method: <T>({ x: T }) -> T, x: number }) -> number")
|
||||||
{
|
{
|
||||||
// <T>({ x: T }) -> T
|
// <T>({ x: T }) -> T
|
||||||
TypeId tableToPropType = arena.addType(FunctionType{
|
TypeId tableToPropType = arena.addType(FunctionType{{genericT}, {}, arena.addTypePack({tbl({{"x", genericT}})}), arena.addTypePack({genericT})});
|
||||||
{genericT},
|
|
||||||
{},
|
|
||||||
arena.addTypePack({tbl({{"x", genericT}})}),
|
|
||||||
arena.addTypePack({genericT})
|
|
||||||
});
|
|
||||||
|
|
||||||
// ({ method: <T>({ x: T }) -> T, x: number }) -> number
|
// ({ method: <T>({ x: T }) -> T, x: number }) -> number
|
||||||
TypeId otherType = fn(
|
TypeId otherType = fn({tbl({{"method", tableToPropType}, {"x", builtinTypes->numberType}})}, {builtinTypes->numberType});
|
||||||
{tbl({{"method", tableToPropType}, {"x", builtinTypes->numberType}})},
|
|
||||||
{builtinTypes->numberType}
|
|
||||||
);
|
|
||||||
|
|
||||||
CHECK_IS_SUBTYPE(tableToPropType, otherType);
|
CHECK_IS_SUBTYPE(tableToPropType, otherType);
|
||||||
}
|
}
|
||||||
|
|
|
@ -221,20 +221,18 @@ n2 [label="number"];
|
||||||
n1 -> n3 [label="y"];
|
n1 -> n3 [label="y"];
|
||||||
n3 [label="FunctionType 3"];
|
n3 [label="FunctionType 3"];
|
||||||
n3 -> n4 [label="arg"];
|
n3 -> n4 [label="arg"];
|
||||||
n4 [label="TypePack 4"];
|
n4 [label="VariadicTypePack 4"];
|
||||||
n4 -> n5 [label="tail"];
|
n4 -> n5;
|
||||||
n5 [label="VariadicTypePack 5"];
|
n5 [label="string"];
|
||||||
n5 -> n6;
|
n3 -> n6 [label="ret"];
|
||||||
n6 [label="string"];
|
n6 [label="TypePack 6"];
|
||||||
n3 -> n7 [label="ret"];
|
n1 -> n7 [label="[index]"];
|
||||||
n7 [label="TypePack 7"];
|
n7 [label="string"];
|
||||||
n1 -> n8 [label="[index]"];
|
n1 -> n8 [label="[value]"];
|
||||||
n8 [label="string"];
|
n8 [label="any"];
|
||||||
n1 -> n9 [label="[value]"];
|
n1 -> n9 [label="typeParam"];
|
||||||
n9 [label="any"];
|
n9 [label="number"];
|
||||||
n1 -> n10 [label="typeParam"];
|
n1 -> n4 [label="typePackParam"];
|
||||||
n10 [label="number"];
|
|
||||||
n1 -> n5 [label="typePackParam"];
|
|
||||||
})",
|
})",
|
||||||
toDot(requireType("a"), opts));
|
toDot(requireType("a"), opts));
|
||||||
}
|
}
|
||||||
|
|
|
@ -956,11 +956,11 @@ caused by:
|
||||||
Property 'd' is not compatible.
|
Property 'd' is not compatible.
|
||||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
Type 'string' could not be converted into 'number' in an invariant context)";
|
||||||
//clang-format on
|
//clang-format on
|
||||||
//
|
|
||||||
std::string actual = toString(result.errors[0]);
|
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
|
std::string actual = toString(result.errors[0]);
|
||||||
|
|
||||||
CHECK(expected == actual);
|
CHECK(expected == actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -332,11 +332,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_errors_if_it_has_nontable_
|
||||||
local function err(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
local function err(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
||||||
)");
|
)");
|
||||||
|
|
||||||
// FIXME: we should actually only report the type family being uninhabited error at its first use, I think?
|
// FIXME(CLI-95289): we should actually only report the type family being uninhabited error at its first use, I think?
|
||||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
CHECK(toString(result.errors[0]) == "Type family instance keyof<MyObject | boolean> is uninhabited");
|
CHECK(toString(result.errors[0]) == "Type family instance keyof<MyObject | boolean> is uninhabited");
|
||||||
CHECK(toString(result.errors[1]) == "Type family instance keyof<MyObject | boolean> is uninhabited");
|
CHECK(toString(result.errors[1]) == "Type family instance keyof<MyObject | boolean> is uninhabited");
|
||||||
CHECK(toString(result.errors[2]) == "Type family instance keyof<MyObject | boolean> is uninhabited");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_string_indexer")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_string_indexer")
|
||||||
|
@ -459,11 +458,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_errors_if_it_has_nontab
|
||||||
local function err(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
local function err(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
||||||
)");
|
)");
|
||||||
|
|
||||||
// FIXME: we should actually only report the type family being uninhabited error at its first use, I think?
|
// FIXME(CLI-95289): we should actually only report the type family being uninhabited error at its first use, I think?
|
||||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
CHECK(toString(result.errors[0]) == "Type family instance rawkeyof<MyObject | boolean> is uninhabited");
|
CHECK(toString(result.errors[0]) == "Type family instance rawkeyof<MyObject | boolean> is uninhabited");
|
||||||
CHECK(toString(result.errors[1]) == "Type family instance rawkeyof<MyObject | boolean> is uninhabited");
|
CHECK(toString(result.errors[1]) == "Type family instance rawkeyof<MyObject | boolean> is uninhabited");
|
||||||
CHECK(toString(result.errors[2]) == "Type family instance rawkeyof<MyObject | boolean> is uninhabited");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_common_subset_if_union_of_differing_tables")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_common_subset_if_union_of_differing_tables")
|
||||||
|
@ -533,11 +531,10 @@ TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_errors_if_it_has_nonclass_par
|
||||||
local function err(idx: KeysOfMyObject): "BaseMethod" | "BaseField" return idx end
|
local function err(idx: KeysOfMyObject): "BaseMethod" | "BaseField" return idx end
|
||||||
)");
|
)");
|
||||||
|
|
||||||
// FIXME: we should actually only report the type family being uninhabited error at its first use, I think?
|
// FIXME(CLI-95289): we should actually only report the type family being uninhabited error at its first use, I think?
|
||||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
CHECK(toString(result.errors[0]) == "Type family instance keyof<BaseClass | boolean> is uninhabited");
|
CHECK(toString(result.errors[0]) == "Type family instance keyof<BaseClass | boolean> is uninhabited");
|
||||||
CHECK(toString(result.errors[1]) == "Type family instance keyof<BaseClass | boolean> is uninhabited");
|
CHECK(toString(result.errors[1]) == "Type family instance keyof<BaseClass | boolean> is uninhabited");
|
||||||
CHECK(toString(result.errors[2]) == "Type family instance keyof<BaseClass | boolean> is uninhabited");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_common_subset_if_union_of_differing_classes")
|
TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_common_subset_if_union_of_differing_classes")
|
||||||
|
|
|
@ -21,6 +21,24 @@ LUAU_FASTINT(LuauTarjanChildLimit);
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TypeInferFunctions");
|
TEST_SUITE_BEGIN("TypeInferFunctions");
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "overload_resolution")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type A = (number) -> string
|
||||||
|
type B = (string) -> number
|
||||||
|
|
||||||
|
local function foo(f: A & B)
|
||||||
|
return f(1), f("five")
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
TypeId t = requireType("foo");
|
||||||
|
const FunctionType* fooType = get<FunctionType>(requireType("foo"));
|
||||||
|
REQUIRE(fooType != nullptr);
|
||||||
|
|
||||||
|
CHECK(toString(t) == "(((number) -> string) & ((string) -> number)) -> (string, number)");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "tc_function")
|
TEST_CASE_FIXTURE(Fixture, "tc_function")
|
||||||
{
|
{
|
||||||
CheckResult result = check("function five() return 5 end");
|
CheckResult result = check("function five() return 5 end");
|
||||||
|
@ -2198,4 +2216,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "regex_benchmark_string_format_minimization")
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "subgeneric_typefamily_super_monomorphic")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local a: (number, number) -> number = function(a, b) return a - b end
|
||||||
|
|
||||||
|
a = function(a, b) return a + b end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -1307,4 +1307,16 @@ TEST_CASE_FIXTURE(Fixture, "bidirectional_checking_and_generalization_play_nice"
|
||||||
CHECK("string" == toString(requireType("b")));
|
CHECK("string" == toString(requireType("b")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "missing_generic_type_parameter")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
function f(x: T): T return x end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
|
|
||||||
|
REQUIRE(get<UnknownSymbol>(result.errors[0]));
|
||||||
|
REQUIRE(get<UnknownSymbol>(result.errors[1]));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -4026,4 +4026,37 @@ TEST_CASE_FIXTURE(Fixture, "read_ond_write_only_indexers_are_unsupported")
|
||||||
CHECK(Location{{2, 18}, {2, 23}} == result.errors[1].location);
|
CHECK(Location{{2, 18}, {2, 23}} == result.errors[1].location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "table_subtyping_error_suppression")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
function one(tbl: {x: any}) end
|
||||||
|
function two(tbl: {x: string}) one(tbl) end -- ok, string <: any and any <: string
|
||||||
|
|
||||||
|
function three(tbl: {x: any, y: string}) end
|
||||||
|
function four(tbl: {x: string, y: string}) three(tbl) end -- ok, string <: any, any <: string, string <: string
|
||||||
|
function five(tbl: {x: string, y: number}) three(tbl) end -- error, string <: any, any <: string, but number </: string
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
|
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||||
|
REQUIRE(tm);
|
||||||
|
|
||||||
|
|
||||||
|
// the new solver reports specifically the inner mismatch, rather than the whole table
|
||||||
|
// honestly not sure which of these is a better developer experience.
|
||||||
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
{
|
||||||
|
CHECK_EQ(*tm->wantedType, *builtinTypes->stringType);
|
||||||
|
CHECK_EQ(*tm->givenType, *builtinTypes->numberType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_EQ("{| x: any, y: string |}", toString(tm->wantedType));
|
||||||
|
CHECK_EQ("{| x: string, y: number |}", toString(tm->givenType));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -422,4 +422,34 @@ TEST_CASE_FIXTURE(TypeStateFixture, "multiple_assignments_in_loops")
|
||||||
CHECK("(number | string)?" == toString(requireType("x")));
|
CHECK("(number | string)?" == toString(requireType("x")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(TypeStateFixture, "typestates_preserve_error_suppression")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local a: any = 51
|
||||||
|
a = "pickles" -- We'll have a new DefId for this iteration of `a`. Its type must also be error-suppressing
|
||||||
|
print(a)
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK("*error-type* | string" == toString(requireTypeAtPosition({3, 14}), {true}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "typestates_preserve_error_suppression_properties")
|
||||||
|
{
|
||||||
|
// early return if the flag isn't set since this is blocking gated commits
|
||||||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local a: {x: any} = {x = 51}
|
||||||
|
a.x = "pickles" -- We'll have a new DefId for this iteration of `a.x`. Its type must also be error-suppressing
|
||||||
|
print(a.x)
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK("*error-type* | string" == toString(requireTypeAtPosition({3, 16}), {true}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -167,7 +167,7 @@ end
|
||||||
assert(pcall(fuzzfail17) == false)
|
assert(pcall(fuzzfail17) == false)
|
||||||
|
|
||||||
local function fuzzfail18()
|
local function fuzzfail18()
|
||||||
return bit32.extract(7890276,0)
|
return bit32.extract(7890276,0)
|
||||||
end
|
end
|
||||||
|
|
||||||
assert(pcall(fuzzfail18) == true)
|
assert(pcall(fuzzfail18) == true)
|
||||||
|
@ -317,4 +317,76 @@ local function vec3mulconst(a: vector) return a * 4 end
|
||||||
assert(vec3mulnum(vector(10, 20, 40), 4) == vector(40, 80, 160))
|
assert(vec3mulnum(vector(10, 20, 40), 4) == vector(40, 80, 160))
|
||||||
assert(vec3mulconst(vector(10, 20, 40), 4) == vector(40, 80, 160))
|
assert(vec3mulconst(vector(10, 20, 40), 4) == vector(40, 80, 160))
|
||||||
|
|
||||||
|
local function bufferbounds(zero)
|
||||||
|
local b1 = buffer.create(1)
|
||||||
|
local b2 = buffer.create(2)
|
||||||
|
local b4 = buffer.create(4)
|
||||||
|
local b8 = buffer.create(8)
|
||||||
|
local b10 = buffer.create(10)
|
||||||
|
|
||||||
|
-- only one valid position and size for a 1 byte buffer
|
||||||
|
buffer.writei8(b1, zero + 0, buffer.readi8(b1, zero + 0))
|
||||||
|
buffer.writeu8(b1, zero + 0, buffer.readu8(b1, zero + 0))
|
||||||
|
|
||||||
|
-- 2 byte buffer
|
||||||
|
buffer.writei8(b2, zero + 0, buffer.readi8(b2, zero + 0))
|
||||||
|
buffer.writeu8(b2, zero + 0, buffer.readu8(b2, zero + 0))
|
||||||
|
buffer.writei8(b2, zero + 1, buffer.readi8(b2, zero + 1))
|
||||||
|
buffer.writeu8(b2, zero + 1, buffer.readu8(b2, zero + 1))
|
||||||
|
buffer.writei16(b2, zero + 0, buffer.readi16(b2, zero + 0))
|
||||||
|
buffer.writeu16(b2, zero + 0, buffer.readu16(b2, zero + 0))
|
||||||
|
|
||||||
|
-- 4 byte buffer
|
||||||
|
buffer.writei8(b4, zero + 0, buffer.readi8(b4, zero + 0))
|
||||||
|
buffer.writeu8(b4, zero + 0, buffer.readu8(b4, zero + 0))
|
||||||
|
buffer.writei8(b4, zero + 3, buffer.readi8(b4, zero + 3))
|
||||||
|
buffer.writeu8(b4, zero + 3, buffer.readu8(b4, zero + 3))
|
||||||
|
buffer.writei16(b4, zero + 0, buffer.readi16(b4, zero + 0))
|
||||||
|
buffer.writeu16(b4, zero + 0, buffer.readu16(b4, zero + 0))
|
||||||
|
buffer.writei16(b4, zero + 2, buffer.readi16(b4, zero + 2))
|
||||||
|
buffer.writeu16(b4, zero + 2, buffer.readu16(b4, zero + 2))
|
||||||
|
buffer.writei32(b4, zero + 0, buffer.readi32(b4, zero + 0))
|
||||||
|
buffer.writeu32(b4, zero + 0, buffer.readu32(b4, zero + 0))
|
||||||
|
buffer.writef32(b4, zero + 0, buffer.readf32(b4, zero + 0))
|
||||||
|
|
||||||
|
-- 8 byte buffer
|
||||||
|
buffer.writei8(b8, zero + 0, buffer.readi8(b8, zero + 0))
|
||||||
|
buffer.writeu8(b8, zero + 0, buffer.readu8(b8, zero + 0))
|
||||||
|
buffer.writei8(b8, zero + 7, buffer.readi8(b8, zero + 7))
|
||||||
|
buffer.writeu8(b8, zero + 7, buffer.readu8(b8, zero + 7))
|
||||||
|
buffer.writei16(b8, zero + 0, buffer.readi16(b8, zero + 0))
|
||||||
|
buffer.writeu16(b8, zero + 0, buffer.readu16(b8, zero + 0))
|
||||||
|
buffer.writei16(b8, zero + 6, buffer.readi16(b8, zero + 6))
|
||||||
|
buffer.writeu16(b8, zero + 6, buffer.readu16(b8, zero + 6))
|
||||||
|
buffer.writei32(b8, zero + 0, buffer.readi32(b8, zero + 0))
|
||||||
|
buffer.writeu32(b8, zero + 0, buffer.readu32(b8, zero + 0))
|
||||||
|
buffer.writef32(b8, zero + 0, buffer.readf32(b8, zero + 0))
|
||||||
|
buffer.writei32(b8, zero + 4, buffer.readi32(b8, zero + 4))
|
||||||
|
buffer.writeu32(b8, zero + 4, buffer.readu32(b8, zero + 4))
|
||||||
|
buffer.writef32(b8, zero + 4, buffer.readf32(b8, zero + 4))
|
||||||
|
buffer.writef64(b8, zero + 0, buffer.readf64(b8, zero + 0))
|
||||||
|
|
||||||
|
-- 'any' size buffer
|
||||||
|
buffer.writei8(b10, zero + 0, buffer.readi8(b10, zero + 0))
|
||||||
|
buffer.writeu8(b10, zero + 0, buffer.readu8(b10, zero + 0))
|
||||||
|
buffer.writei8(b10, zero + 9, buffer.readi8(b10, zero + 9))
|
||||||
|
buffer.writeu8(b10, zero + 9, buffer.readu8(b10, zero + 9))
|
||||||
|
buffer.writei16(b10, zero + 0, buffer.readi16(b10, zero + 0))
|
||||||
|
buffer.writeu16(b10, zero + 0, buffer.readu16(b10, zero + 0))
|
||||||
|
buffer.writei16(b10, zero + 8, buffer.readi16(b10, zero + 8))
|
||||||
|
buffer.writeu16(b10, zero + 8, buffer.readu16(b10, zero + 8))
|
||||||
|
buffer.writei32(b10, zero + 0, buffer.readi32(b10, zero + 0))
|
||||||
|
buffer.writeu32(b10, zero + 0, buffer.readu32(b10, zero + 0))
|
||||||
|
buffer.writef32(b10, zero + 0, buffer.readf32(b10, zero + 0))
|
||||||
|
buffer.writei32(b10, zero + 6, buffer.readi32(b10, zero + 6))
|
||||||
|
buffer.writeu32(b10, zero + 6, buffer.readu32(b10, zero + 6))
|
||||||
|
buffer.writef32(b10, zero + 6, buffer.readf32(b10, zero + 6))
|
||||||
|
buffer.writef64(b10, zero + 0, buffer.readf64(b10, zero + 0))
|
||||||
|
buffer.writef64(b10, zero + 2, buffer.readf64(b10, zero + 2))
|
||||||
|
|
||||||
|
assert(is_native())
|
||||||
|
end
|
||||||
|
|
||||||
|
bufferbounds(0)
|
||||||
|
|
||||||
return('OK')
|
return('OK')
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
AnnotationTests.typeof_expr
|
AnnotationTests.typeof_expr
|
||||||
AstQuery.last_argument_function_call_type
|
AstQuery.last_argument_function_call_type
|
||||||
AstQuery::getDocumentationSymbolAtPosition.table_overloaded_function_prop
|
|
||||||
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_interpolated_string_as_singleton
|
AutocompleteTest.autocomplete_interpolated_string_as_singleton
|
||||||
|
@ -110,7 +109,6 @@ GenericsTests.better_mismatch_error_messages
|
||||||
GenericsTests.bound_tables_do_not_clone_original_fields
|
GenericsTests.bound_tables_do_not_clone_original_fields
|
||||||
GenericsTests.check_generic_function
|
GenericsTests.check_generic_function
|
||||||
GenericsTests.check_generic_local_function
|
GenericsTests.check_generic_local_function
|
||||||
GenericsTests.check_generic_typepack_function
|
|
||||||
GenericsTests.check_mutual_generic_functions
|
GenericsTests.check_mutual_generic_functions
|
||||||
GenericsTests.check_nested_generic_function
|
GenericsTests.check_nested_generic_function
|
||||||
GenericsTests.check_recursive_generic_function
|
GenericsTests.check_recursive_generic_function
|
||||||
|
@ -176,10 +174,9 @@ IntersectionTypes.overloadeded_functions_with_weird_typepacks_1
|
||||||
IntersectionTypes.overloadeded_functions_with_weird_typepacks_2
|
IntersectionTypes.overloadeded_functions_with_weird_typepacks_2
|
||||||
IntersectionTypes.overloadeded_functions_with_weird_typepacks_3
|
IntersectionTypes.overloadeded_functions_with_weird_typepacks_3
|
||||||
IntersectionTypes.overloadeded_functions_with_weird_typepacks_4
|
IntersectionTypes.overloadeded_functions_with_weird_typepacks_4
|
||||||
IntersectionTypes.select_correct_union_fn
|
|
||||||
IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions
|
|
||||||
IntersectionTypes.table_write_sealed_indirect
|
IntersectionTypes.table_write_sealed_indirect
|
||||||
IntersectionTypes.union_saturate_overloaded_functions
|
IntersectionTypes.union_saturate_overloaded_functions
|
||||||
|
Linter.CleanCode
|
||||||
Linter.DeprecatedApiFenv
|
Linter.DeprecatedApiFenv
|
||||||
Linter.FormatStringTyped
|
Linter.FormatStringTyped
|
||||||
Linter.TableOperationsIndexer
|
Linter.TableOperationsIndexer
|
||||||
|
@ -197,6 +194,8 @@ NonstrictModeTests.table_props_are_any
|
||||||
Normalize.higher_order_function_with_annotation
|
Normalize.higher_order_function_with_annotation
|
||||||
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.discriminate_from_x_not_equal_to_nil
|
ProvisionalTests.discriminate_from_x_not_equal_to_nil
|
||||||
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
|
||||||
|
@ -219,6 +218,7 @@ ProvisionalTests.table_unification_infinite_recursion
|
||||||
ProvisionalTests.typeguard_inference_incomplete
|
ProvisionalTests.typeguard_inference_incomplete
|
||||||
ProvisionalTests.while_body_are_also_refined
|
ProvisionalTests.while_body_are_also_refined
|
||||||
RefinementTest.assert_a_to_be_truthy_then_assert_a_to_be_number
|
RefinementTest.assert_a_to_be_truthy_then_assert_a_to_be_number
|
||||||
|
RefinementTest.call_an_incompatible_function_after_using_typeguard
|
||||||
RefinementTest.correctly_lookup_property_whose_base_was_previously_refined
|
RefinementTest.correctly_lookup_property_whose_base_was_previously_refined
|
||||||
RefinementTest.dataflow_analysis_can_tell_refinements_when_its_appropriate_to_refine_into_nil_or_never
|
RefinementTest.dataflow_analysis_can_tell_refinements_when_its_appropriate_to_refine_into_nil_or_never
|
||||||
RefinementTest.discriminate_from_isa_of_x
|
RefinementTest.discriminate_from_isa_of_x
|
||||||
|
@ -362,7 +362,6 @@ ToString.primitive
|
||||||
ToString.tostring_unsee_ttv_if_array
|
ToString.tostring_unsee_ttv_if_array
|
||||||
ToString.toStringDetailed2
|
ToString.toStringDetailed2
|
||||||
ToString.toStringErrorPack
|
ToString.toStringErrorPack
|
||||||
ToString.toStringNamedFunction_generic_pack
|
|
||||||
ToString.toStringNamedFunction_map
|
ToString.toStringNamedFunction_map
|
||||||
TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
|
TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
|
||||||
TryUnifyTests.result_of_failed_typepack_unification_is_constrained
|
TryUnifyTests.result_of_failed_typepack_unification_is_constrained
|
||||||
|
@ -415,7 +414,6 @@ TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parame
|
||||||
TypeInfer.statements_are_topologically_sorted
|
TypeInfer.statements_are_topologically_sorted
|
||||||
TypeInfer.stringify_nested_unions_with_optionals
|
TypeInfer.stringify_nested_unions_with_optionals
|
||||||
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
||||||
TypeInfer.tc_if_else_expressions_expected_type_3
|
|
||||||
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
|
||||||
TypeInfer.unify_nearly_identical_recursive_types
|
TypeInfer.unify_nearly_identical_recursive_types
|
||||||
|
@ -446,9 +444,7 @@ TypeInferClasses.table_indexers_are_invariant
|
||||||
TypeInferClasses.unions_of_intersections_of_classes
|
TypeInferClasses.unions_of_intersections_of_classes
|
||||||
TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class
|
TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class
|
||||||
TypeInferFunctions.another_other_higher_order_function
|
TypeInferFunctions.another_other_higher_order_function
|
||||||
TypeInferFunctions.apply_of_lambda_with_inferred_and_explicit_types
|
|
||||||
TypeInferFunctions.calling_function_with_anytypepack_doesnt_leak_free_types
|
TypeInferFunctions.calling_function_with_anytypepack_doesnt_leak_free_types
|
||||||
TypeInferFunctions.cannot_hoist_interior_defns_into_signature
|
|
||||||
TypeInferFunctions.check_function_bodies
|
TypeInferFunctions.check_function_bodies
|
||||||
TypeInferFunctions.complicated_return_types_require_an_explicit_annotation
|
TypeInferFunctions.complicated_return_types_require_an_explicit_annotation
|
||||||
TypeInferFunctions.concrete_functions_are_not_supertypes_of_function
|
TypeInferFunctions.concrete_functions_are_not_supertypes_of_function
|
||||||
|
@ -586,7 +582,6 @@ TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_neve
|
||||||
TypeInferUnknownNever.length_of_never
|
TypeInferUnknownNever.length_of_never
|
||||||
TypeInferUnknownNever.math_operators_and_never
|
TypeInferUnknownNever.math_operators_and_never
|
||||||
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable
|
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable
|
||||||
TypePackTests.detect_cyclic_typepacks2
|
|
||||||
TypePackTests.fuzz_typepack_iter_follow_2
|
TypePackTests.fuzz_typepack_iter_follow_2
|
||||||
TypePackTests.pack_tail_unification_check
|
TypePackTests.pack_tail_unification_check
|
||||||
TypePackTests.type_alias_backwards_compatible
|
TypePackTests.type_alias_backwards_compatible
|
||||||
|
@ -597,10 +592,12 @@ TypePackTests.unify_variadic_tails_in_arguments
|
||||||
TypeSingletons.enums_using_singletons_mismatch
|
TypeSingletons.enums_using_singletons_mismatch
|
||||||
TypeSingletons.error_detailed_tagged_union_mismatch_bool
|
TypeSingletons.error_detailed_tagged_union_mismatch_bool
|
||||||
TypeSingletons.error_detailed_tagged_union_mismatch_string
|
TypeSingletons.error_detailed_tagged_union_mismatch_string
|
||||||
|
TypeSingletons.overloaded_function_call_with_singletons_mismatch
|
||||||
TypeSingletons.return_type_of_f_is_not_widened
|
TypeSingletons.return_type_of_f_is_not_widened
|
||||||
TypeSingletons.table_properties_type_error_escapes
|
TypeSingletons.table_properties_type_error_escapes
|
||||||
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
|
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
|
||||||
TypeStatesTest.prototyped_recursive_functions_but_has_future_assignments
|
TypeStatesTest.prototyped_recursive_functions_but_has_future_assignments
|
||||||
|
TypeStatesTest.typestates_preserve_error_suppression_properties
|
||||||
UnionTypes.error_detailed_optional
|
UnionTypes.error_detailed_optional
|
||||||
UnionTypes.error_detailed_union_all
|
UnionTypes.error_detailed_union_all
|
||||||
UnionTypes.generic_function_with_optional_arg
|
UnionTypes.generic_function_with_optional_arg
|
||||||
|
|
Loading…
Reference in a new issue