Sync to upstream/release/640 (#1374)

### What's new

* Fixed many of the false positive errors in indexing of table unions
and table intersections
* It is now possible to run custom checks over Luau AST during
typechecking by setting `customModuleCheck` in `FrontendOptions`
* Fixed codegen issue on arm, where number->vector cast could corrupt
that number value for the next time it's read

### New Solver

* `error` type now behaves as the bottom type during subtyping checks
* Fixed the scope that is used in subtyping with generic types
* Fixed `astOriginalCallTypes` table often used by LSP to match the old
solver

---

### Internal Contributors

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
vegorov-rbx 2024-08-23 09:35:30 -07:00 committed by GitHub
parent 1dde4de793
commit d518d14b92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 1377 additions and 968 deletions

View File

@ -108,6 +108,10 @@ struct FrontendOptions
// When true, some internal complexity limits will be scaled down for modules that miss the limit set by moduleTimeLimitSec
bool applyInternalLimitScaling = false;
// An optional callback which is called for every *dirty* module was checked
// Is multi-threaded typechecking is used, this callback might be called from multiple threads and has to be thread-safe
std::function<void(const SourceModule& sourceModule, const Luau::Module& module)> customModuleCheck;
};
struct CheckResult

View File

@ -125,7 +125,6 @@ struct Subtyping
NotNull<Normalizer> normalizer;
NotNull<InternalErrorReporter> iceReporter;
NotNull<Scope> scope;
TypeCheckLimits limits;
enum class Variance
@ -144,8 +143,7 @@ struct Subtyping
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> typeArena,
NotNull<Normalizer> normalizer,
NotNull<InternalErrorReporter> iceReporter,
NotNull<Scope> scope
NotNull<InternalErrorReporter> iceReporter
);
Subtyping(const Subtyping&) = delete;
@ -164,75 +162,125 @@ struct Subtyping
// TODO cyclic types
// TODO recursion limits
SubtypingResult isSubtype(TypeId subTy, TypeId superTy);
SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy);
SubtypingResult isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope);
SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy, NotNull<Scope> scope);
private:
DenseHashMap<std::pair<TypeId, TypeId>, SubtypingResult, TypePairHash> resultCache{{}};
SubtypingResult cache(SubtypingEnvironment& env, SubtypingResult res, TypeId subTy, TypeId superTy);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypePackId subTy, TypePackId superTy);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp, NotNull<Scope> scope);
template<typename SubTy, typename SuperTy>
SubtypingResult isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy);
SubtypingResult isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy, NotNull<Scope> scope);
template<typename SubTy, typename SuperTy>
SubtypingResult isInvariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy);
SubtypingResult isInvariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy, NotNull<Scope> scope);
template<typename SubTy, typename SuperTy>
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair, NotNull<Scope> scope);
template<typename SubTy, typename SuperTy>
SubtypingResult isContravariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair);
SubtypingResult isContravariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair, NotNull<Scope>);
template<typename SubTy, typename SuperTy>
SubtypingResult isInvariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair);
SubtypingResult isInvariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair, NotNull<Scope>);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const UnionType* superUnion);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const UnionType* subUnion, TypeId superTy);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const IntersectionType* superIntersection);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const IntersectionType* subIntersection, TypeId superTy);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const UnionType* superUnion, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const UnionType* subUnion, TypeId superTy, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const IntersectionType* superIntersection, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const IntersectionType* subIntersection, TypeId superTy, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NegationType* subNegation, TypeId superTy);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const NegationType* superNegation);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NegationType* subNegation, TypeId superTy, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const NegationType* superNegation, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const PrimitiveType* superPrim);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const PrimitiveType* superPrim);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const SingletonType* superSingleton);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const FunctionType* subFunction, const FunctionType* superFunction);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const PrimitiveType* superPrim);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const TableType* superTable);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const TableType* superTable);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const PrimitiveType* superPrim, NotNull<Scope> scope);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const SingletonType* subSingleton,
const PrimitiveType* superPrim,
NotNull<Scope> scope
);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const SingletonType* subSingleton,
const SingletonType* superSingleton,
NotNull<Scope> scope
);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull<Scope> scope);
SubtypingResult
isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable, NotNull<Scope>);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const FunctionType* subFunction,
const FunctionType* superFunction,
NotNull<Scope> scope
);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const PrimitiveType* superPrim, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const TableType* superTable, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const TableType* superTable, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableIndexer& subIndexer, const TableIndexer& superIndexer);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const Property& subProperty, const Property& superProperty, const std::string& name);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const TableIndexer& subIndexer,
const TableIndexer& superIndexer,
NotNull<Scope> scope
);
SubtypingResult
isCovariantWith(SubtypingEnvironment& env, const Property& subProperty, const Property& superProperty, const std::string& name, NotNull<Scope>);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const std::shared_ptr<const NormalizedType>& subNorm,
const std::shared_ptr<const NormalizedType>& superNorm
const std::shared_ptr<const NormalizedType>& superNorm,
NotNull<Scope> scope
);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const NormalizedClassType& superClass);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const NormalizedStringType& superString);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const TypeIds& superTables);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const NormalizedFunctionType& subFunction,
const NormalizedFunctionType& superFunction
const NormalizedClassType& subClass,
const NormalizedClassType& superClass,
NotNull<Scope> scope
);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables, NotNull<Scope> scope);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const NormalizedStringType& subString,
const NormalizedStringType& superString,
NotNull<Scope> scope
);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const NormalizedStringType& subString,
const TypeIds& superTables,
NotNull<Scope> scope
);
SubtypingResult
isCovariantWith(SubtypingEnvironment& env, const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction, NotNull<Scope>);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeFunctionInstanceType* subFunctionInstance, const TypeId superTy);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFunctionInstanceType* superFunctionInstance);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const VariadicTypePack* subVariadic,
const VariadicTypePack* superVariadic,
NotNull<Scope> scope
);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const TypeFunctionInstanceType* subFunctionInstance,
const TypeId superTy,
NotNull<Scope> scope
);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const TypeId subTy,
const TypeFunctionInstanceType* superFunctionInstance,
NotNull<Scope> scope
);
bool bindGeneric(SubtypingEnvironment& env, TypeId subTp, TypeId superTp);
bool bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp);
@ -240,7 +288,7 @@ private:
template<typename T, typename Container>
TypeId makeAggregateType(const Container& container, TypeId orElse);
std::pair<TypeId, ErrorVec> handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance);
std::pair<TypeId, ErrorVec> handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance, NotNull<Scope> scope);
[[noreturn]] void unexpected(TypeId ty);
[[noreturn]] void unexpected(TypePackId tp);

View File

@ -38,7 +38,7 @@
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(StudioReportLuauAny, false);
LUAU_FASTFLAGVARIABLE(StudioReportLuauAny2, false);
LUAU_FASTINTVARIABLE(LuauAnySummaryRecursionLimit, 300);
LUAU_FASTFLAG(DebugLuauMagicTypes);
@ -211,14 +211,17 @@ void AnyTypeSummary::visit(const Scope* scope, AstStatLocal* local, const Module
if (!maybeRequire)
continue;
if (isAnyCast(scope, local->values.data[posn], module, builtinTypes))
if (std::min(local->values.size - 1, posn) < head.size())
{
TelemetryTypePair types;
if (isAnyCast(scope, local->values.data[posn], module, builtinTypes))
{
TelemetryTypePair types;
types.inferredType = toString(head[std::min(local->values.size - 1, posn)]);
types.inferredType = toString(head[std::min(local->values.size - 1, posn)]);
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
typeInfo.push_back(ti);
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
else
@ -292,7 +295,7 @@ void AnyTypeSummary::visit(const Scope* scope, AstStatAssign* assign, const Modu
types.annotatedType = toString(tp);
auto loc = std::min(assign->vars.size - 1, posn);
if (head.size() >= assign->vars.size)
if (head.size() >= assign->vars.size && posn < head.size())
{
types.inferredType = toString(head[posn]);
}

View File

@ -144,9 +144,9 @@ static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull<Scope> scope, T
if (FFlag::DebugLuauDeferredConstraintResolution)
{
Subtyping subtyping{builtinTypes, NotNull{typeArena}, NotNull{&normalizer}, NotNull{&iceReporter}, scope};
Subtyping subtyping{builtinTypes, NotNull{typeArena}, NotNull{&normalizer}, NotNull{&iceReporter}};
return subtyping.isSubtype(subTy, superTy).isSubtype;
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
}
else
{

View File

@ -1793,7 +1793,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
std::vector<std::optional<TypeId>> expectedTypesForCall = getExpectedCallTypesForFunctionOverloads(fnType);
module->astOriginalCallTypes[call] = fnType;
module->astOriginalCallTypes[call->func] = fnType;
Checkpoint argBeginCheckpoint = checkpoint(this);

View File

@ -45,8 +45,9 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes, false)
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode, false)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode, false)
LUAU_FASTFLAGVARIABLE(LuauSourceModuleUpdatedWithSelectedMode, false)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
LUAU_FASTFLAG(StudioReportLuauAny)
LUAU_FASTFLAG(StudioReportLuauAny2)
namespace Luau
{
@ -511,7 +512,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
if (item.name == name)
checkResult.lintResult = item.module->lintResult;
if (FFlag::StudioReportLuauAny && item.options.retainFullTypeGraphs)
if (FFlag::StudioReportLuauAny2 && item.options.retainFullTypeGraphs)
{
if (item.module)
{
@ -1040,6 +1041,9 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
item.stats.timeCheck += duration;
item.stats.filesStrict += 1;
if (DFFlag::LuauRunCustomModuleChecks && item.options.customModuleCheck)
item.options.customModuleCheck(sourceModule, *moduleForAutocomplete);
item.module = moduleForAutocomplete;
return;
}
@ -1057,8 +1061,8 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
item.stats.filesStrict += mode == Mode::Strict;
item.stats.filesNonstrict += mode == Mode::Nonstrict;
if (module == nullptr)
throw InternalCompilerError("Frontend::check produced a nullptr module for " + item.name, item.name);
if (DFFlag::LuauRunCustomModuleChecks && item.options.customModuleCheck)
item.options.customModuleCheck(sourceModule, *module);
if (FFlag::DebugLuauDeferredConstraintResolution && mode == Mode::NoCheck)
module->errors.clear();

View File

@ -182,7 +182,7 @@ struct NonStrictTypeChecker
, arena(arena)
, module(module)
, normalizer{arena, builtinTypes, unifierState, /* cache inhabitance */ true}
, subtyping{builtinTypes, arena, NotNull(&normalizer), ice, NotNull{module->getModuleScope().get()}}
, subtyping{builtinTypes, arena, NotNull(&normalizer), ice}
, dfg(dfg)
, limits(limits)
{
@ -531,7 +531,7 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstExprCall* call)
{
NonStrictContext fresh{};
TypeId* originalCallTy = module->astOriginalCallTypes.find(call);
TypeId* originalCallTy = module->astOriginalCallTypes.find(call->func);
if (!originalCallTy)
return fresh;
@ -699,6 +699,7 @@ struct NonStrictTypeChecker
// If this fragment of the ast will run time error, return the type that causes this
std::optional<TypeId> willRunTimeError(AstExpr* fragment, const NonStrictContext& context)
{
NotNull<Scope> scope{Luau::findScopeAtPosition(*module, fragment->location.end).get()};
DefId def = dfg->getDef(fragment);
std::vector<DefId> defs;
collectOperands(def, &defs);
@ -708,7 +709,7 @@ struct NonStrictTypeChecker
{
TypeId actualType = lookupType(fragment);
SubtypingResult r = subtyping.isSubtype(actualType, *contextTy);
SubtypingResult r = subtyping.isSubtype(actualType, *contextTy, scope);
if (r.normalizationTooComplex)
reportError(NormalizationTooComplex{}, fragment->location);
if (r.isSubtype)
@ -721,6 +722,7 @@ struct NonStrictTypeChecker
std::optional<TypeId> willRunTimeErrorFunctionDefinition(AstLocal* fragment, const NonStrictContext& context)
{
NotNull<Scope> scope{Luau::findScopeAtPosition(*module, fragment->location.end).get()};
DefId def = dfg->getDef(fragment);
std::vector<DefId> defs;
collectOperands(def, &defs);
@ -728,8 +730,8 @@ struct NonStrictTypeChecker
{
if (std::optional<TypeId> contextTy = context.find(def))
{
SubtypingResult r1 = subtyping.isSubtype(builtinTypes->unknownType, *contextTy);
SubtypingResult r2 = subtyping.isSubtype(*contextTy, builtinTypes->unknownType);
SubtypingResult r1 = subtyping.isSubtype(builtinTypes->unknownType, *contextTy, scope);
SubtypingResult r2 = subtyping.isSubtype(*contextTy, builtinTypes->unknownType, scope);
if (r1.normalizationTooComplex || r2.normalizationTooComplex)
reportError(NormalizationTooComplex{}, fragment->location);
bool isUnknown = r1.isSubtype && r2.isSubtype;
@ -747,7 +749,7 @@ private:
if (!cachedResult)
cachedResult = arena->addType(NegationType{baseType});
return cachedResult;
};
}
};
void checkNonStrict(

View File

@ -3429,9 +3429,9 @@ bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<Built
// Subtyping under DCR is not implemented using unification!
if (FFlag::DebugLuauDeferredConstraintResolution)
{
Subtyping subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&ice}, scope};
Subtyping subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&ice}};
return subtyping.isSubtype(subTy, superTy).isSubtype;
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
}
else
{
@ -3451,9 +3451,9 @@ bool isSubtype(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope, N
// Subtyping under DCR is not implemented using unification!
if (FFlag::DebugLuauDeferredConstraintResolution)
{
Subtyping subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&ice}, scope};
Subtyping subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&ice}};
return subtyping.isSubtype(subPack, superPack).isSubtype;
return subtyping.isSubtype(subPack, superPack, scope).isSubtype;
}
else
{

View File

@ -28,7 +28,7 @@ OverloadResolver::OverloadResolver(
, scope(scope)
, ice(reporter)
, limits(limits)
, subtyping({builtinTypes, arena, normalizer, ice, scope})
, subtyping({builtinTypes, arena, normalizer, ice})
, callLoc(callLocation)
{
}
@ -41,7 +41,7 @@ std::pair<OverloadResolver::Analysis, TypeId> OverloadResolver::selectOverload(T
{
Subtyping::Variance variance = subtyping.variance;
subtyping.variance = Subtyping::Variance::Contravariant;
SubtypingResult r = subtyping.isSubtype(argsPack, ftv->argTypes);
SubtypingResult r = subtyping.isSubtype(argsPack, ftv->argTypes, scope);
subtyping.variance = variance;
if (r.isSubtype)
@ -92,7 +92,7 @@ void OverloadResolver::resolve(TypeId fnTy, const TypePack* args, AstExpr* selfE
std::optional<ErrorVec> OverloadResolver::testIsSubtype(const Location& location, TypeId subTy, TypeId superTy)
{
auto r = subtyping.isSubtype(subTy, superTy);
auto r = subtyping.isSubtype(subTy, superTy, scope);
ErrorVec errors;
if (r.normalizationTooComplex)
@ -121,7 +121,7 @@ std::optional<ErrorVec> OverloadResolver::testIsSubtype(const Location& location
std::optional<ErrorVec> OverloadResolver::testIsSubtype(const Location& location, TypePackId subTy, TypePackId superTy)
{
auto r = subtyping.isSubtype(subTy, superTy);
auto r = subtyping.isSubtype(subTy, superTy, scope);
ErrorVec errors;
if (r.normalizationTooComplex)
@ -206,7 +206,7 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
TypePackId typ = arena->addTypePack(*args);
TypeId prospectiveFunction = arena->addType(FunctionType{typ, builtinTypes->anyTypePack});
SubtypingResult sr = subtyping.isSubtype(fnTy, prospectiveFunction);
SubtypingResult sr = subtyping.isSubtype(fnTy, prospectiveFunction, scope);
if (sr.isSubtype)
return {Analysis::Ok, {}};
@ -250,7 +250,7 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
// nil, then this overload does not match.
for (size_t i = firstUnsatisfiedArgument; i < requiredHead.size(); ++i)
{
if (!subtyping.isSubtype(builtinTypes->nilType, requiredHead[i]).isSubtype)
if (!subtyping.isSubtype(builtinTypes->nilType, requiredHead[i], scope).isSubtype)
{
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, isVariadic}};

View File

@ -333,28 +333,25 @@ Subtyping::Subtyping(
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> typeArena,
NotNull<Normalizer> normalizer,
NotNull<InternalErrorReporter> iceReporter,
NotNull<Scope> scope
NotNull<InternalErrorReporter> iceReporter
)
: builtinTypes(builtinTypes)
, arena(typeArena)
, normalizer(normalizer)
, iceReporter(iceReporter)
, scope(scope)
{
}
SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope)
{
SubtypingEnvironment env;
SubtypingResult result = isCovariantWith(env, subTy, superTy);
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope);
for (const auto& [subTy, bounds] : env.mappedGenerics)
{
const auto& lb = bounds.lowerBound;
const auto& ub = bounds.upperBound;
TypeId lowerBound = makeAggregateType<UnionType>(lb, builtinTypes->neverType);
TypeId upperBound = makeAggregateType<IntersectionType>(ub, builtinTypes->unknownType);
@ -382,7 +379,7 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
result.isSubtype = false;
}
SubtypingResult boundsResult = isCovariantWith(env, lowerBound, upperBound);
SubtypingResult boundsResult = isCovariantWith(env, lowerBound, upperBound, scope);
boundsResult.reasoning.clear();
result.andAlso(boundsResult);
@ -406,10 +403,10 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
return result;
}
SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp)
SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp, NotNull<Scope> scope)
{
SubtypingEnvironment env;
return isCovariantWith(env, subTp, superTp);
return isCovariantWith(env, subTp, superTp, scope);
}
SubtypingResult Subtyping::cache(SubtypingEnvironment& env, SubtypingResult result, TypeId subTy, TypeId superTy)
@ -443,7 +440,7 @@ struct SeenSetPopper
};
} // namespace
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy, NotNull<Scope> scope)
{
subTy = follow(subTy);
superTy = follow(superTy);
@ -501,20 +498,20 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
// tested as though it were its upper bounds. We do not yet support bounded
// generics, so the upper bound is always unknown.
if (auto subGeneric = get<GenericType>(subTy); subGeneric && subsumes(subGeneric->scope, scope))
return isCovariantWith(env, builtinTypes->neverType, superTy);
return isCovariantWith(env, builtinTypes->neverType, superTy, scope);
if (auto superGeneric = get<GenericType>(superTy); superGeneric && subsumes(superGeneric->scope, scope))
return isCovariantWith(env, subTy, builtinTypes->unknownType);
return isCovariantWith(env, subTy, builtinTypes->unknownType, scope);
SubtypingResult result;
if (auto subUnion = get<UnionType>(subTy))
result = isCovariantWith(env, subUnion, superTy);
result = isCovariantWith(env, subUnion, superTy, scope);
else if (auto superUnion = get<UnionType>(superTy))
{
result = isCovariantWith(env, subTy, superUnion);
result = isCovariantWith(env, subTy, superUnion, scope);
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), scope);
if (semantic.isSubtype)
{
semantic.reasoning.clear();
@ -523,13 +520,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
}
}
else if (auto superIntersection = get<IntersectionType>(superTy))
result = isCovariantWith(env, subTy, superIntersection);
result = isCovariantWith(env, subTy, superIntersection, scope);
else if (auto subIntersection = get<IntersectionType>(subTy))
{
result = isCovariantWith(env, subIntersection, superTy);
result = isCovariantWith(env, subIntersection, superTy, scope);
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), scope);
if (semantic.isSubtype)
{
// Clear the semantic reasoning, as any reasonings within
@ -550,7 +547,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
{
// any = unknown | error, so we rewrite this to match.
// As per TAPL: A | B <: T iff A <: T && B <: T
result = isCovariantWith(env, builtinTypes->unknownType, superTy).andAlso(isCovariantWith(env, builtinTypes->errorType, superTy));
result =
isCovariantWith(env, builtinTypes->unknownType, superTy, scope).andAlso(isCovariantWith(env, builtinTypes->errorType, superTy, scope));
}
else if (get<UnknownType>(superTy))
{
@ -566,15 +564,15 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
else if (get<ErrorType>(superTy))
result = {false};
else if (get<ErrorType>(subTy))
result = {false};
result = {true};
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, scope).withBothComponent(TypePath::TypeField::Negated);
else if (auto subNegation = get<NegationType>(subTy))
{
result = isCovariantWith(env, subNegation, superTy);
result = isCovariantWith(env, subNegation, superTy, scope);
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), scope);
if (semantic.isSubtype)
{
semantic.reasoning.clear();
@ -584,10 +582,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
}
else if (auto superNegation = get<NegationType>(superTy))
{
result = isCovariantWith(env, subTy, superNegation);
result = isCovariantWith(env, subTy, superNegation, scope);
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), scope);
if (semantic.isSubtype)
{
semantic.reasoning.clear();
@ -600,14 +598,14 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
if (auto substSubTy = env.applyMappedGenerics(builtinTypes, arena, subTy))
subTypeFunctionInstance = get<TypeFunctionInstanceType>(*substSubTy);
result = isCovariantWith(env, subTypeFunctionInstance, superTy);
result = isCovariantWith(env, subTypeFunctionInstance, superTy, scope);
}
else if (auto superTypeFunctionInstance = get<TypeFunctionInstanceType>(superTy))
{
if (auto substSuperTy = env.applyMappedGenerics(builtinTypes, arena, superTy))
superTypeFunctionInstance = get<TypeFunctionInstanceType>(*substSuperTy);
result = isCovariantWith(env, subTy, superTypeFunctionInstance);
result = isCovariantWith(env, subTy, superTypeFunctionInstance, scope);
}
else if (auto subGeneric = get<GenericType>(subTy); subGeneric && variance == Variance::Covariant)
{
@ -622,41 +620,41 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result.isCacheable = false;
}
else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, superTy))
result = isCovariantWith(env, p);
result = isCovariantWith(env, p, scope);
else if (auto p = get2<SingletonType, PrimitiveType>(subTy, superTy))
result = isCovariantWith(env, p);
result = isCovariantWith(env, p, scope);
else if (auto p = get2<SingletonType, SingletonType>(subTy, superTy))
result = isCovariantWith(env, p);
result = isCovariantWith(env, p, scope);
else if (auto p = get2<FunctionType, PrimitiveType>(subTy, superTy))
{
auto [subFunction, superPrimitive] = p;
result.isSubtype = superPrimitive->type == PrimitiveType::Function;
}
else if (auto p = get2<FunctionType, FunctionType>(subTy, superTy))
result = isCovariantWith(env, p);
result = isCovariantWith(env, p, scope);
else if (auto p = get2<TableType, TableType>(subTy, superTy))
result = isCovariantWith(env, p);
result = isCovariantWith(env, p, scope);
else if (auto p = get2<MetatableType, MetatableType>(subTy, superTy))
result = isCovariantWith(env, p);
result = isCovariantWith(env, p, scope);
else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
result = isCovariantWith(env, p);
result = isCovariantWith(env, p, scope);
else if (auto p = get2<ClassType, ClassType>(subTy, superTy))
result = isCovariantWith(env, p);
result = isCovariantWith(env, p, scope);
else if (auto p = get2<ClassType, TableType>(subTy, superTy))
result = isCovariantWith(env, subTy, p.first, superTy, p.second);
result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope);
else if (auto p = get2<TableType, PrimitiveType>(subTy, superTy))
result = isCovariantWith(env, p);
result = isCovariantWith(env, p, scope);
else if (auto p = get2<PrimitiveType, TableType>(subTy, superTy))
result = isCovariantWith(env, p);
result = isCovariantWith(env, p, scope);
else if (auto p = get2<SingletonType, TableType>(subTy, superTy))
result = isCovariantWith(env, p);
result = isCovariantWith(env, p, scope);
assertReasoningValid(subTy, superTy, result, builtinTypes);
return cache(env, result, subTy, superTy);
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp, NotNull<Scope> scope)
{
subTp = follow(subTp);
superTp = follow(superTp);
@ -675,7 +673,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
// Match head types pairwise
for (size_t i = 0; i < headSize; ++i)
results.push_back(isCovariantWith(env, subHead[i], superHead[i]).withBothComponent(TypePath::Index{i}));
results.push_back(isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i}));
// Handle mismatched head sizes
@ -686,7 +684,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
if (auto vt = get<VariadicTypePack>(*subTail))
{
for (size_t i = headSize; i < superHead.size(); ++i)
results.push_back(isCovariantWith(env, vt->ty, superHead[i])
results.push_back(isCovariantWith(env, vt->ty, superHead[i], scope)
.withSubPath(TypePath::PathBuilder().tail().variadic().build())
.withSuperComponent(TypePath::Index{i}));
}
@ -704,7 +702,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
if (TypePackId* other = env.mappedGenericPacks.find(*subTail))
// TODO: TypePath can't express "slice of a pack + its tail".
results.push_back(isCovariantWith(env, *other, superTailPack).withSubComponent(TypePath::PackField::Tail));
results.push_back(isCovariantWith(env, *other, superTailPack, scope).withSubComponent(TypePath::PackField::Tail));
else
env.mappedGenericPacks.try_insert(*subTail, superTailPack);
@ -741,7 +739,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
if (auto vt = get<VariadicTypePack>(*superTail))
{
for (size_t i = headSize; i < subHead.size(); ++i)
results.push_back(isCovariantWith(env, subHead[i], vt->ty)
results.push_back(isCovariantWith(env, subHead[i], vt->ty, scope)
.withSubComponent(TypePath::Index{i})
.withSuperPath(TypePath::PathBuilder().tail().variadic().build()));
}
@ -759,7 +757,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
if (TypePackId* other = env.mappedGenericPacks.find(*superTail))
// TODO: TypePath can't express "slice of a pack + its tail".
results.push_back(isContravariantWith(env, subTailPack, *other).withSuperComponent(TypePath::PackField::Tail));
results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail));
else
env.mappedGenericPacks.try_insert(*superTail, subTailPack);
@ -794,7 +792,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
{
// Variadic component is added by the isCovariantWith
// implementation; no need to add it here.
results.push_back(isCovariantWith(env, p).withBothComponent(TypePath::PackField::Tail));
results.push_back(isCovariantWith(env, p, scope).withBothComponent(TypePath::PackField::Tail));
}
else if (auto p = get2<GenericTypePack, GenericTypePack>(*subTail, *superTail))
{
@ -899,11 +897,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
}
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy)
SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy, NotNull<Scope> scope)
{
VarianceFlipper vf{&variance};
SubtypingResult result = isCovariantWith(env, superTy, subTy);
SubtypingResult result = isCovariantWith(env, superTy, subTy, scope);
if (result.reasoning.empty())
result.reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Contravariant});
else
@ -931,9 +929,9 @@ SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy&
}
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy)
SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy, NotNull<Scope> scope)
{
SubtypingResult result = isCovariantWith(env, subTy, superTy).andAlso(isContravariantWith(env, subTy, superTy));
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope).andAlso(isContravariantWith(env, subTy, superTy, scope));
if (result.reasoning.empty())
result.reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invariant});
@ -948,19 +946,19 @@ SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& su
}
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair, NotNull<Scope> scope)
{
return isCovariantWith(env, pair.first, pair.second);
return isCovariantWith(env, pair.first, pair.second, scope);
}
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair)
SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair, NotNull<Scope> scope)
{
return isContravariantWith(env, pair.first, pair.second);
return isContravariantWith(env, pair.first, pair.second, scope);
}
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair)
SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair, NotNull<Scope> scope)
{
return isInvariantWith(env, pair.first, pair.second);
}
@ -996,13 +994,13 @@ SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, const TryP
* other just asks for boolean ~ 'b. We can dispatch this and only commit
* boolean ~ 'b. This constraint does not teach us anything about 'a.
*/
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const UnionType* superUnion)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const UnionType* superUnion, NotNull<Scope> scope)
{
// As per TAPL: T <: A | B iff T <: A || T <: B
for (TypeId ty : superUnion)
{
SubtypingResult next = isCovariantWith(env, subTy, ty);
SubtypingResult next = isCovariantWith(env, subTy, ty, scope);
if (next.isSubtype)
return SubtypingResult{true};
}
@ -1015,37 +1013,37 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
return SubtypingResult{false};
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const UnionType* subUnion, TypeId superTy)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const UnionType* subUnion, TypeId superTy, NotNull<Scope> scope)
{
// As per TAPL: A | B <: T iff A <: T && B <: T
std::vector<SubtypingResult> subtypings;
size_t i = 0;
for (TypeId ty : subUnion)
subtypings.push_back(isCovariantWith(env, ty, superTy).withSubComponent(TypePath::Index{i++}));
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++}));
return SubtypingResult::all(subtypings);
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const IntersectionType* superIntersection)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const IntersectionType* superIntersection, NotNull<Scope> scope)
{
// As per TAPL: T <: A & B iff T <: A && T <: B
std::vector<SubtypingResult> subtypings;
size_t i = 0;
for (TypeId ty : superIntersection)
subtypings.push_back(isCovariantWith(env, subTy, ty).withSuperComponent(TypePath::Index{i++}));
subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++}));
return SubtypingResult::all(subtypings);
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const IntersectionType* subIntersection, TypeId superTy)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const IntersectionType* subIntersection, TypeId superTy, NotNull<Scope> scope)
{
// As per TAPL: A & B <: T iff A <: T || B <: T
std::vector<SubtypingResult> subtypings;
size_t i = 0;
for (TypeId ty : subIntersection)
subtypings.push_back(isCovariantWith(env, ty, superTy).withSubComponent(TypePath::Index{i++}));
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++}));
return SubtypingResult::any(subtypings);
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NegationType* subNegation, TypeId superTy)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NegationType* subNegation, TypeId superTy, NotNull<Scope> scope)
{
TypeId negatedTy = follow(subNegation->ty);
@ -1057,17 +1055,17 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Nega
if (is<NeverType>(negatedTy))
{
// ¬never ~ unknown
result = isCovariantWith(env, builtinTypes->unknownType, superTy).withSubComponent(TypePath::TypeField::Negated);
result = isCovariantWith(env, builtinTypes->unknownType, superTy, scope).withSubComponent(TypePath::TypeField::Negated);
}
else if (is<UnknownType>(negatedTy))
{
// ¬unknown ~ never
result = isCovariantWith(env, builtinTypes->neverType, superTy).withSubComponent(TypePath::TypeField::Negated);
result = isCovariantWith(env, builtinTypes->neverType, superTy, scope).withSubComponent(TypePath::TypeField::Negated);
}
else if (is<AnyType>(negatedTy))
{
// ¬any ~ any
result = isCovariantWith(env, negatedTy, superTy).withSubComponent(TypePath::TypeField::Negated);
result = isCovariantWith(env, negatedTy, superTy, scope).withSubComponent(TypePath::TypeField::Negated);
}
else if (auto u = get<UnionType>(negatedTy))
{
@ -1078,11 +1076,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Nega
for (TypeId ty : u)
{
if (auto negatedPart = get<NegationType>(follow(ty)))
subtypings.push_back(isCovariantWith(env, negatedPart->ty, superTy).withSubComponent(TypePath::TypeField::Negated));
subtypings.push_back(isCovariantWith(env, negatedPart->ty, superTy, scope).withSubComponent(TypePath::TypeField::Negated));
else
{
NegationType negatedTmp{ty};
subtypings.push_back(isCovariantWith(env, &negatedTmp, superTy));
subtypings.push_back(isCovariantWith(env, &negatedTmp, superTy, scope));
}
}
@ -1097,11 +1095,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Nega
for (TypeId ty : i)
{
if (auto negatedPart = get<NegationType>(follow(ty)))
subtypings.push_back(isCovariantWith(env, negatedPart->ty, superTy).withSubComponent(TypePath::TypeField::Negated));
subtypings.push_back(isCovariantWith(env, negatedPart->ty, superTy, scope).withSubComponent(TypePath::TypeField::Negated));
else
{
NegationType negatedTmp{ty};
subtypings.push_back(isCovariantWith(env, &negatedTmp, superTy));
subtypings.push_back(isCovariantWith(env, &negatedTmp, superTy, scope));
}
}
@ -1121,7 +1119,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Nega
return result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const NegationType* superNegation)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const NegationType* superNegation, NotNull<Scope> scope)
{
TypeId negatedTy = follow(superNegation->ty);
@ -1130,17 +1128,17 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
if (is<NeverType>(negatedTy))
{
// ¬never ~ unknown
result = isCovariantWith(env, subTy, builtinTypes->unknownType);
result = isCovariantWith(env, subTy, builtinTypes->unknownType, scope);
}
else if (is<UnknownType>(negatedTy))
{
// ¬unknown ~ never
result = isCovariantWith(env, subTy, builtinTypes->neverType);
result = isCovariantWith(env, subTy, builtinTypes->neverType, scope);
}
else if (is<AnyType>(negatedTy))
{
// ¬any ~ any
result = isSubtype(subTy, negatedTy);
result = isSubtype(subTy, negatedTy, scope);
}
else if (auto u = get<UnionType>(negatedTy))
{
@ -1151,11 +1149,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
for (TypeId ty : u)
{
if (auto negatedPart = get<NegationType>(follow(ty)))
subtypings.push_back(isCovariantWith(env, subTy, negatedPart->ty));
subtypings.push_back(isCovariantWith(env, subTy, negatedPart->ty, scope));
else
{
NegationType negatedTmp{ty};
subtypings.push_back(isCovariantWith(env, subTy, &negatedTmp));
subtypings.push_back(isCovariantWith(env, subTy, &negatedTmp, scope));
}
}
@ -1170,11 +1168,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
for (TypeId ty : i)
{
if (auto negatedPart = get<NegationType>(follow(ty)))
subtypings.push_back(isCovariantWith(env, subTy, negatedPart->ty));
subtypings.push_back(isCovariantWith(env, subTy, negatedPart->ty, scope));
else
{
NegationType negatedTmp{ty};
subtypings.push_back(isCovariantWith(env, subTy, &negatedTmp));
subtypings.push_back(isCovariantWith(env, subTy, &negatedTmp, scope));
}
}
@ -1218,7 +1216,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy))
result = {*p.first != *p.second};
else if (auto p = get2<ClassType, ClassType>(subTy, negatedTy))
result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second));
result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second, scope));
else if (get2<FunctionType, ClassType>(subTy, negatedTy))
result = {true};
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
@ -1229,12 +1227,22 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
return result.withSuperComponent(TypePath::TypeField::Negated);
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const PrimitiveType* superPrim)
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const PrimitiveType* subPrim,
const PrimitiveType* superPrim,
NotNull<Scope> scope
)
{
return {subPrim->type == superPrim->type};
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const PrimitiveType* superPrim)
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const SingletonType* subSingleton,
const PrimitiveType* superPrim,
NotNull<Scope> scope
)
{
if (get<StringSingleton>(subSingleton) && superPrim->type == PrimitiveType::String)
return {true};
@ -1244,12 +1252,17 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Sing
return {false};
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const SingletonType* superSingleton)
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const SingletonType* subSingleton,
const SingletonType* superSingleton,
NotNull<Scope> scope
)
{
return {*subSingleton == *superSingleton};
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, NotNull<Scope> scope)
{
SubtypingResult result{true};
@ -1260,23 +1273,23 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
{
std::vector<SubtypingResult> results;
if (auto subIter = subTable->props.find(name); subIter != subTable->props.end())
results.push_back(isCovariantWith(env, subIter->second, superProp, name));
results.push_back(isCovariantWith(env, subIter->second, superProp, name, scope));
else if (subTable->indexer)
{
if (isCovariantWith(env, builtinTypes->stringType, subTable->indexer->indexType).isSubtype)
if (isCovariantWith(env, builtinTypes->stringType, subTable->indexer->indexType, scope).isSubtype)
{
if (superProp.isShared())
results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, superProp.type())
results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, superProp.type(), scope)
.withSubComponent(TypePath::TypeField::IndexResult)
.withSuperComponent(TypePath::Property::read(name)));
else
{
if (superProp.readTy)
results.push_back(isCovariantWith(env, subTable->indexer->indexResultType, *superProp.readTy)
results.push_back(isCovariantWith(env, subTable->indexer->indexResultType, *superProp.readTy, scope)
.withSubComponent(TypePath::TypeField::IndexResult)
.withSuperComponent(TypePath::Property::read(name)));
if (superProp.writeTy)
results.push_back(isContravariantWith(env, subTable->indexer->indexResultType, *superProp.writeTy)
results.push_back(isContravariantWith(env, subTable->indexer->indexResultType, *superProp.writeTy, scope)
.withSubComponent(TypePath::TypeField::IndexResult)
.withSuperComponent(TypePath::Property::write(name)));
}
@ -1292,7 +1305,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
if (superTable->indexer)
{
if (subTable->indexer)
result.andAlso(isInvariantWith(env, *subTable->indexer, *superTable->indexer));
result.andAlso(isInvariantWith(env, *subTable->indexer, *superTable->indexer, scope));
else
return {false};
}
@ -1300,13 +1313,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
return result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope)
{
return isCovariantWith(env, subMt->table, superMt->table)
.andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable).withBothComponent(TypePath::TypeField::Metatable));
return isCovariantWith(env, subMt->table, superMt->table, scope)
.andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable));
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope)
{
if (auto subTable = get<TableType>(follow(subMt->table)))
{
@ -1319,7 +1332,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta
// that the metatable isn't a subtype of the table, even though they have
// compatible properties/shapes. We'll revisit this later when we have a
// better understanding of how important this is.
return isCovariantWith(env, subTable, superTable);
return isCovariantWith(env, subTable, superTable, scope);
}
else
{
@ -1328,7 +1341,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta
}
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull<Scope> scope)
{
return {isSubclass(subClass, superClass)};
}
@ -1338,7 +1351,8 @@ SubtypingResult Subtyping::isCovariantWith(
TypeId subTy,
const ClassType* subClass,
TypeId superTy,
const TableType* superTable
const TableType* superTable,
NotNull<Scope> scope
)
{
SubtypingResult result{true};
@ -1349,7 +1363,7 @@ SubtypingResult Subtyping::isCovariantWith(
{
if (auto classProp = lookupClassProp(subClass, name))
{
result.andAlso(isCovariantWith(env, *classProp, prop, name));
result.andAlso(isCovariantWith(env, *classProp, prop, name, scope));
}
else
{
@ -1363,19 +1377,26 @@ SubtypingResult Subtyping::isCovariantWith(
return result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const FunctionType* subFunction, const FunctionType* superFunction)
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const FunctionType* subFunction,
const FunctionType* superFunction,
NotNull<Scope> scope
)
{
SubtypingResult result;
{
result.orElse(isContravariantWith(env, subFunction->argTypes, superFunction->argTypes).withBothComponent(TypePath::PackField::Arguments));
result.orElse(
isContravariantWith(env, subFunction->argTypes, superFunction->argTypes, scope).withBothComponent(TypePath::PackField::Arguments)
);
}
result.andAlso(isCovariantWith(env, subFunction->retTypes, superFunction->retTypes).withBothComponent(TypePath::PackField::Returns));
result.andAlso(isCovariantWith(env, subFunction->retTypes, superFunction->retTypes, scope).withBothComponent(TypePath::PackField::Returns));
return result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const PrimitiveType* superPrim)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const PrimitiveType* superPrim, NotNull<Scope> scope)
{
SubtypingResult result{false};
if (superPrim->type == PrimitiveType::Table)
@ -1384,7 +1405,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
return result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const TableType* superTable)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const TableType* superTable, NotNull<Scope> scope)
{
SubtypingResult result{false};
if (subPrim->type == PrimitiveType::String)
@ -1397,7 +1418,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prim
{
if (auto stringTable = get<TableType>(it->second.type()))
result.orElse(
isCovariantWith(env, stringTable, superTable).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build())
isCovariantWith(env, stringTable, superTable, scope).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build())
);
}
}
@ -1412,7 +1433,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prim
return result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const TableType* superTable)
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const SingletonType* subSingleton,
const TableType* superTable,
NotNull<Scope> scope
)
{
SubtypingResult result{false};
if (auto stringleton = get<StringSingleton>(subSingleton))
@ -1425,7 +1451,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Sing
{
if (auto stringTable = get<TableType>(it->second.type()))
result.orElse(
isCovariantWith(env, stringTable, superTable).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build())
isCovariantWith(env, stringTable, superTable, scope).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build())
);
}
}
@ -1434,25 +1460,38 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Sing
return result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TableIndexer& subIndexer, const TableIndexer& superIndexer)
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const TableIndexer& subIndexer,
const TableIndexer& superIndexer,
NotNull<Scope> scope
)
{
return isInvariantWith(env, subIndexer.indexType, superIndexer.indexType)
return isInvariantWith(env, subIndexer.indexType, superIndexer.indexType, scope)
.withBothComponent(TypePath::TypeField::IndexLookup)
.andAlso(isInvariantWith(env, subIndexer.indexResultType, superIndexer.indexResultType).withBothComponent(TypePath::TypeField::IndexResult));
.andAlso(
isInvariantWith(env, subIndexer.indexResultType, superIndexer.indexResultType, scope).withBothComponent(TypePath::TypeField::IndexResult)
);
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Property& subProp, const Property& superProp, const std::string& name)
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const Property& subProp,
const Property& superProp,
const std::string& name,
NotNull<Scope> scope
)
{
SubtypingResult res{true};
if (superProp.isShared() && subProp.isShared())
res.andAlso(isInvariantWith(env, subProp.type(), superProp.type()).withBothComponent(TypePath::Property::read(name)));
res.andAlso(isInvariantWith(env, subProp.type(), superProp.type(), scope).withBothComponent(TypePath::Property::read(name)));
else
{
if (superProp.readTy.has_value() && subProp.readTy.has_value())
res.andAlso(isCovariantWith(env, *subProp.readTy, *superProp.readTy).withBothComponent(TypePath::Property::read(name)));
res.andAlso(isCovariantWith(env, *subProp.readTy, *superProp.readTy, scope).withBothComponent(TypePath::Property::read(name)));
if (superProp.writeTy.has_value() && subProp.writeTy.has_value())
res.andAlso(isContravariantWith(env, *subProp.writeTy, *superProp.writeTy).withBothComponent(TypePath::Property::write(name)));
res.andAlso(isContravariantWith(env, *subProp.writeTy, *superProp.writeTy, scope).withBothComponent(TypePath::Property::write(name)));
if (superProp.isReadWrite())
{
@ -1469,29 +1508,37 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prop
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const std::shared_ptr<const NormalizedType>& subNorm,
const std::shared_ptr<const NormalizedType>& superNorm
const std::shared_ptr<const NormalizedType>& superNorm,
NotNull<Scope> scope
)
{
if (!subNorm || !superNorm)
return {false, true};
SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops);
result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans));
result.andAlso(isCovariantWith(env, subNorm->classes, superNorm->classes).orElse(isCovariantWith(env, subNorm->classes, superNorm->tables)));
result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors));
result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils));
result.andAlso(isCovariantWith(env, subNorm->numbers, superNorm->numbers));
result.andAlso(isCovariantWith(env, subNorm->strings, superNorm->strings));
result.andAlso(isCovariantWith(env, subNorm->strings, superNorm->tables));
result.andAlso(isCovariantWith(env, subNorm->threads, superNorm->threads));
result.andAlso(isCovariantWith(env, subNorm->buffers, superNorm->buffers));
result.andAlso(isCovariantWith(env, subNorm->tables, superNorm->tables));
result.andAlso(isCovariantWith(env, subNorm->functions, superNorm->functions));
SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope);
result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope));
result.andAlso(
isCovariantWith(env, subNorm->classes, superNorm->classes, scope).orElse(isCovariantWith(env, subNorm->classes, superNorm->tables, scope))
);
result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope));
result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope));
result.andAlso(isCovariantWith(env, subNorm->numbers, superNorm->numbers, scope));
result.andAlso(isCovariantWith(env, subNorm->strings, superNorm->strings, scope));
result.andAlso(isCovariantWith(env, subNorm->strings, superNorm->tables, scope));
result.andAlso(isCovariantWith(env, subNorm->threads, superNorm->threads, scope));
result.andAlso(isCovariantWith(env, subNorm->buffers, superNorm->buffers, scope));
result.andAlso(isCovariantWith(env, subNorm->tables, superNorm->tables, scope));
result.andAlso(isCovariantWith(env, subNorm->functions, superNorm->functions, scope));
// isCovariantWith(subNorm->tyvars, superNorm->tyvars);
return result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const NormalizedClassType& superClass)
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const NormalizedClassType& subClass,
const NormalizedClassType& superClass,
NotNull<Scope> scope
)
{
for (const auto& [subClassTy, _] : subClass.classes)
{
@ -1499,13 +1546,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Norm
for (const auto& [superClassTy, superNegations] : superClass.classes)
{
result.orElse(isCovariantWith(env, subClassTy, superClassTy));
result.orElse(isCovariantWith(env, subClassTy, superClassTy, scope));
if (!result.isSubtype)
continue;
for (TypeId negation : superNegations)
{
result.andAlso(SubtypingResult::negate(isCovariantWith(env, subClassTy, negation)));
result.andAlso(SubtypingResult::negate(isCovariantWith(env, subClassTy, negation, scope)));
if (result.isSubtype)
break;
}
@ -1518,14 +1565,19 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Norm
return {true};
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables)
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const NormalizedClassType& subClass,
const TypeIds& superTables,
NotNull<Scope> scope
)
{
for (const auto& [subClassTy, _] : subClass.classes)
{
SubtypingResult result;
for (TypeId superTableTy : superTables)
result.orElse(isCovariantWith(env, subClassTy, superTableTy));
result.orElse(isCovariantWith(env, subClassTy, superTableTy, scope));
if (!result.isSubtype)
return result;
@ -1534,13 +1586,23 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Norm
return {true};
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const NormalizedStringType& superString)
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const NormalizedStringType& subString,
const NormalizedStringType& superString,
NotNull<Scope> scope
)
{
bool isSubtype = Luau::isSubtype(subString, superString);
return {isSubtype};
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const TypeIds& superTables)
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const NormalizedStringType& subString,
const TypeIds& superTables,
NotNull<Scope> scope
)
{
if (subString.isNever())
return {true};
@ -1550,7 +1612,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Norm
SubtypingResult result;
for (const auto& superTable : superTables)
{
result.orElse(isCovariantWith(env, builtinTypes->stringType, superTable));
result.orElse(isCovariantWith(env, builtinTypes->stringType, superTable, scope));
if (result.isSubtype)
return result;
}
@ -1566,7 +1628,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Norm
SubtypingResult result{true};
for (const auto& [_, subString] : subString.singletons)
{
result.andAlso(isCovariantWith(env, subString, superTable));
result.andAlso(isCovariantWith(env, subString, superTable, scope));
if (!result.isSubtype)
break;
}
@ -1583,7 +1645,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Norm
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const NormalizedFunctionType& subFunction,
const NormalizedFunctionType& superFunction
const NormalizedFunctionType& superFunction,
NotNull<Scope> scope
)
{
if (subFunction.isNever())
@ -1591,10 +1654,10 @@ SubtypingResult Subtyping::isCovariantWith(
else if (superFunction.isTop)
return {true};
else
return isCovariantWith(env, subFunction.parts, superFunction.parts);
return isCovariantWith(env, subFunction.parts, superFunction.parts, scope);
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes, NotNull<Scope> scope)
{
std::vector<SubtypingResult> results;
@ -1602,15 +1665,20 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
{
results.emplace_back();
for (TypeId superTy : superTypes)
results.back().orElse(isCovariantWith(env, subTy, superTy));
results.back().orElse(isCovariantWith(env, subTy, superTy, scope));
}
return SubtypingResult::all(results);
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic)
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const VariadicTypePack* subVariadic,
const VariadicTypePack* superVariadic,
NotNull<Scope> scope
)
{
return isCovariantWith(env, subVariadic->ty, superVariadic->ty).withBothComponent(TypePath::TypeField::Variadic);
return isCovariantWith(env, subVariadic->ty, superVariadic->ty, scope).withBothComponent(TypePath::TypeField::Variadic);
}
bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId superTy)
@ -1633,20 +1701,30 @@ bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId supe
return true;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeFunctionInstanceType* subFunctionInstance, const TypeId superTy)
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const TypeFunctionInstanceType* subFunctionInstance,
const TypeId superTy,
NotNull<Scope> scope
)
{
// Reduce the type function instance
auto [ty, errors] = handleTypeFunctionReductionResult(subFunctionInstance);
auto [ty, errors] = handleTypeFunctionReductionResult(subFunctionInstance, scope);
// If we return optional, that means the type function was irreducible - we can reduce that to never
return isCovariantWith(env, ty, superTy).withErrors(errors).withSubComponent(TypePath::Reduction{ty});
return isCovariantWith(env, ty, superTy, scope).withErrors(errors).withSubComponent(TypePath::Reduction{ty});
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFunctionInstanceType* superFunctionInstance)
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const TypeId subTy,
const TypeFunctionInstanceType* superFunctionInstance,
NotNull<Scope> scope
)
{
// Reduce the type function instance
auto [ty, errors] = handleTypeFunctionReductionResult(superFunctionInstance);
return isCovariantWith(env, subTy, ty).withErrors(errors).withSuperComponent(TypePath::Reduction{ty});
auto [ty, errors] = handleTypeFunctionReductionResult(superFunctionInstance, scope);
return isCovariantWith(env, subTy, ty, scope).withErrors(errors).withSuperComponent(TypePath::Reduction{ty});
}
/*
@ -1681,7 +1759,7 @@ TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse)
return arena->addType(T{std::vector<TypeId>(begin(container), end(container))});
}
std::pair<TypeId, ErrorVec> Subtyping::handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance)
std::pair<TypeId, ErrorVec> Subtyping::handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance, NotNull<Scope> scope)
{
TypeFunctionContext context{arena, builtinTypes, scope, normalizer, iceReporter, NotNull{&limits}};
TypeId function = arena->addType(*functionInstance);

View File

@ -252,8 +252,14 @@ struct TypeChecker2
Subtyping _subtyping;
NotNull<Subtyping> subtyping;
TypeChecker2(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger,
const SourceModule* sourceModule, Module* module)
TypeChecker2(
NotNull<BuiltinTypes> builtinTypes,
NotNull<UnifierSharedState> unifierState,
NotNull<TypeCheckLimits> limits,
DcrLogger* logger,
const SourceModule* sourceModule,
Module* module
)
: builtinTypes(builtinTypes)
, logger(logger)
, limits(limits)
@ -261,8 +267,7 @@ struct TypeChecker2
, sourceModule(sourceModule)
, module(module)
, normalizer{&module->internalTypes, builtinTypes, unifierState, /* cacheInhabitance */ true}
, _subtyping{builtinTypes, NotNull{&module->internalTypes}, NotNull{&normalizer}, NotNull{unifierState->iceHandler},
NotNull{module->getModuleScope().get()}}
, _subtyping{builtinTypes, NotNull{&module->internalTypes}, NotNull{&normalizer}, NotNull{unifierState->iceHandler}}
, subtyping(&_subtyping)
{
}
@ -1255,8 +1260,9 @@ struct TypeChecker2
#if defined(LUAU_ENABLE_ASSERT)
TypeId actualType = lookupType(expr);
TypeId expectedType = builtinTypes->nilType;
NotNull<Scope> scope{findInnermostScope(expr->location)};
SubtypingResult r = subtyping->isSubtype(actualType, expectedType);
SubtypingResult r = subtyping->isSubtype(actualType, expectedType, scope);
LUAU_ASSERT(r.isSubtype || isErrorSuppressing(expr->location, actualType));
#endif
}
@ -1267,8 +1273,9 @@ struct TypeChecker2
const TypeId bestType = expr->value ? builtinTypes->trueType : builtinTypes->falseType;
const TypeId inferredType = lookupType(expr);
NotNull<Scope> scope{findInnermostScope(expr->location)};
const SubtypingResult r = subtyping->isSubtype(bestType, inferredType);
const SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope);
if (!r.isSubtype && !isErrorSuppressing(expr->location, inferredType))
reportError(TypeMismatch{inferredType, bestType}, expr->location);
}
@ -1278,8 +1285,9 @@ struct TypeChecker2
#if defined(LUAU_ENABLE_ASSERT)
const TypeId bestType = builtinTypes->numberType;
const TypeId inferredType = lookupType(expr);
NotNull<Scope> scope{findInnermostScope(expr->location)};
const SubtypingResult r = subtyping->isSubtype(bestType, inferredType);
const SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope);
LUAU_ASSERT(r.isSubtype || isErrorSuppressing(expr->location, inferredType));
#endif
}
@ -1290,8 +1298,9 @@ struct TypeChecker2
const TypeId bestType = module->internalTypes.addType(SingletonType{StringSingleton{std::string{expr->value.data, expr->value.size}}});
const TypeId inferredType = lookupType(expr);
NotNull<Scope> scope{findInnermostScope(expr->location)};
const SubtypingResult r = subtyping->isSubtype(bestType, inferredType);
const SubtypingResult r = subtyping->isSubtype(bestType, inferredType, scope);
if (!r.isSubtype && !isErrorSuppressing(expr->location, inferredType))
reportError(TypeMismatch{inferredType, bestType}, expr->location);
}
@ -1320,7 +1329,7 @@ struct TypeChecker2
std::vector<AstExpr*> argExprs;
argExprs.reserve(call->args.size + 1);
TypeId* originalCallTy = module->astOriginalCallTypes.find(call);
TypeId* originalCallTy = module->astOriginalCallTypes.find(call->func);
TypeId* selectedOverloadTy = module->astOverloadResolvedTypes.find(call);
if (!originalCallTy)
return;
@ -1347,7 +1356,8 @@ struct TypeChecker2
if (selectedOverloadTy)
{
SubtypingResult result = subtyping->isSubtype(*originalCallTy, *selectedOverloadTy);
NotNull<Scope> scope{findInnermostScope(call->location)};
SubtypingResult result = subtyping->isSubtype(*originalCallTy, *selectedOverloadTy, scope);
if (result.isSubtype)
fnTy = follow(*selectedOverloadTy);
@ -1622,12 +1632,17 @@ struct TypeChecker2
reportError(OptionalValueAccess{exprType}, indexExpr->location);
}
}
else if (auto exprIntersection = get<IntersectionType>(exprType))
else if (auto ut = get<UnionType>(exprType))
{
for (TypeId part : exprIntersection)
{
(void)part;
}
// if all of the types are a table type, the union must be a table, and so we shouldn't error.
if (!std::all_of(begin(ut), end(ut), getTableType))
reportError(NotATable{exprType}, indexExpr->location);
}
else if (auto it = get<IntersectionType>(exprType))
{
// if any of the types are a table type, the intersection must be a table, and so we shouldn't error.
if (!std::any_of(begin(it), end(it), getTableType))
reportError(NotATable{exprType}, indexExpr->location);
}
else if (get<NeverType>(exprType) || isErrorSuppressing(indexExpr->location, exprType))
{
@ -2726,7 +2741,8 @@ struct TypeChecker2
bool testIsSubtype(TypeId subTy, TypeId superTy, Location location)
{
SubtypingResult r = subtyping->isSubtype(subTy, superTy);
NotNull<Scope> scope{findInnermostScope(location)};
SubtypingResult r = subtyping->isSubtype(subTy, superTy, scope);
if (r.normalizationTooComplex)
reportError(NormalizationTooComplex{}, location);
@ -2739,7 +2755,8 @@ struct TypeChecker2
bool testIsSubtype(TypePackId subTy, TypePackId superTy, Location location)
{
SubtypingResult r = subtyping->isSubtype(subTy, superTy);
NotNull<Scope> scope{findInnermostScope(location)};
SubtypingResult r = subtyping->isSubtype(subTy, superTy, scope);
if (r.normalizationTooComplex)
reportError(NormalizationTooComplex{}, location);

View File

@ -701,8 +701,8 @@ TypeFunctionReductionResult<TypeId> lenTypeFunction(
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
return {std::nullopt, true, {}, {}}; // occurs check failed
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope};
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance?
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice};
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance?
return {std::nullopt, true, {}, {}};
// `len` must return a `number`.
@ -790,8 +790,8 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
return {std::nullopt, true, {}, {}}; // occurs check failed
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope};
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance?
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice};
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance?
return {std::nullopt, true, {}, {}};
if (std::optional<TypeId> ret = first(instantiatedMmFtv->retTypes))
@ -1138,8 +1138,8 @@ TypeFunctionReductionResult<TypeId> concatTypeFunction(
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
return {std::nullopt, true, {}, {}}; // occurs check failed
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope};
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance?
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice};
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance?
return {std::nullopt, true, {}, {}};
return {ctx->builtins->stringType, false, {}, {}};
@ -1392,8 +1392,8 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
return {std::nullopt, true, {}, {}}; // occurs check failed
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope};
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance?
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice};
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance?
return {std::nullopt, true, {}, {}};
return {ctx->builtins->booleanType, false, {}, {}};
@ -1536,8 +1536,8 @@ TypeFunctionReductionResult<TypeId> eqTypeFunction(
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
return {std::nullopt, true, {}, {}}; // occurs check failed
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope};
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance?
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice};
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance?
return {std::nullopt, true, {}, {}};
return {ctx->builtins->booleanType, false, {}, {}};

View File

@ -34,6 +34,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false)
LUAU_FASTFLAGVARIABLE(LuauOkWithIteratingOverTableProperties, false)
LUAU_FASTFLAGVARIABLE(LuauAcceptIndexingTableUnionsIntersections, false)
namespace Luau
{
@ -3506,54 +3507,207 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
}
}
TableType* exprTable = getMutableTableType(exprType);
if (!exprTable)
if (FFlag::LuauAcceptIndexingTableUnionsIntersections)
{
reportError(TypeError{expr.expr->location, NotATable{exprType}});
return errorRecoveryType(scope);
}
// We're going to have a whole vector.
std::vector<TableType*> tableTypes{};
bool isUnion = true;
if (value)
{
const auto& it = exprTable->props.find(value->value.data);
if (it != exprTable->props.end())
// We'd like for normalization eventually to deal with this sort of thing, but as a tactical affordance, we will
// attempt to deal with _one_ level of unions or intersections.
if (auto exprUnion = get<UnionType>(exprType))
{
return it->second.type();
tableTypes.reserve(exprUnion->options.size());
for (auto option : exprUnion)
{
TableType* optionTable = getMutableTableType(option);
if (!optionTable)
{
// TODO: we could do better here and report `option` is not a table as reasoning for the error
reportError(TypeError{expr.expr->location, NotATable{exprType}});
return errorRecoveryType(scope);
}
tableTypes.push_back(optionTable);
}
}
else if ((ctx == ValueContext::LValue && exprTable->state == TableState::Unsealed) || exprTable->state == TableState::Free)
else if (auto exprIntersection = get<IntersectionType>(exprType))
{
TypeId resultType = freshType(scope);
Property& property = exprTable->props[value->value.data];
property.setType(resultType);
property.location = expr.index->location;
return resultType;
tableTypes.reserve(exprIntersection->parts.size());
isUnion = false;
for (auto part : exprIntersection)
{
TableType* partTable = getMutableTableType(part);
if (!partTable)
{
// TODO: we could do better here and report `part` is not a table as reasoning for the error
reportError(TypeError{expr.expr->location, NotATable{exprType}});
return errorRecoveryType(scope);
}
tableTypes.push_back(partTable);
}
}
else if (auto exprTable = getMutableTableType(exprType))
{
tableTypes.push_back(exprTable);
}
else
{
reportError(TypeError{expr.expr->location, NotATable{exprType}});
return errorRecoveryType(scope);
}
}
if (exprTable->indexer)
{
const TableIndexer& indexer = *exprTable->indexer;
unify(indexType, indexer.indexType, scope, expr.index->location);
return indexer.indexResultType;
}
else if ((ctx == ValueContext::LValue && exprTable->state == TableState::Unsealed) || exprTable->state == TableState::Free)
{
TypeId indexerType = freshType(exprTable->level);
unify(indexType, indexerType, scope, expr.location);
TypeId indexResultType = freshType(exprTable->level);
if (value)
{
DenseHashSet<TypeId> propTypes{{}};
exprTable->indexer = TableIndexer{anyIfNonstrict(indexerType), anyIfNonstrict(indexResultType)};
return indexResultType;
for (auto table : tableTypes)
{
const auto& it = table->props.find(value->value.data);
if (it != table->props.end())
{
propTypes.insert(it->second.type());
}
else if ((ctx == ValueContext::LValue && table->state == TableState::Unsealed) || table->state == TableState::Free)
{
TypeId resultType = freshType(scope);
Property& property = table->props[value->value.data];
property.setType(resultType);
property.location = expr.index->location;
propTypes.insert(resultType);
}
}
if (propTypes.size() == 1)
return *propTypes.begin();
if (!propTypes.empty())
{
if (isUnion)
{
std::vector<TypeId> options = reduceUnion({propTypes.begin(), propTypes.end()});
if (options.empty())
return neverType;
if (options.size() == 1)
return options[0];
return addType(UnionType{options});
}
return addType(IntersectionType{{propTypes.begin(), propTypes.end()}});
}
}
DenseHashSet<TypeId> resultTypes{{}};
for (auto table : tableTypes)
{
if (table->indexer)
{
const TableIndexer& indexer = *table->indexer;
unify(indexType, indexer.indexType, scope, expr.index->location);
resultTypes.insert(indexer.indexResultType);
}
else if ((ctx == ValueContext::LValue && table->state == TableState::Unsealed) || table->state == TableState::Free)
{
TypeId indexerType = freshType(table->level);
unify(indexType, indexerType, scope, expr.location);
TypeId indexResultType = freshType(table->level);
table->indexer = TableIndexer{anyIfNonstrict(indexerType), anyIfNonstrict(indexResultType)};
resultTypes.insert(indexResultType);
}
else
{
/*
* If we use [] indexing to fetch a property from a sealed table that
* has no indexer, we have no idea if it will work so we just return any
* and hope for the best.
*/
// if this is a union, it's going to be equivalent to `any` no matter what at this point, so we'll just call it done.
if (isUnion)
return anyType;
resultTypes.insert(anyType);
}
}
if (resultTypes.size() == 1)
return *resultTypes.begin();
if (isUnion)
{
std::vector<TypeId> options = reduceUnion({resultTypes.begin(), resultTypes.end()});
if (options.empty())
return neverType;
if (options.size() == 1)
return options[0];
return addType(UnionType{options});
}
return addType(IntersectionType{{resultTypes.begin(), resultTypes.end()}});
}
else
{
/*
* If we use [] indexing to fetch a property from a sealed table that
* has no indexer, we have no idea if it will work so we just return any
* and hope for the best.
*/
return anyType;
TableType* exprTable = getMutableTableType(exprType);
if (!exprTable)
{
reportError(TypeError{expr.expr->location, NotATable{exprType}});
return errorRecoveryType(scope);
}
if (value)
{
const auto& it = exprTable->props.find(value->value.data);
if (it != exprTable->props.end())
{
return it->second.type();
}
else if ((ctx == ValueContext::LValue && exprTable->state == TableState::Unsealed) || exprTable->state == TableState::Free)
{
TypeId resultType = freshType(scope);
Property& property = exprTable->props[value->value.data];
property.setType(resultType);
property.location = expr.index->location;
return resultType;
}
}
if (exprTable->indexer)
{
const TableIndexer& indexer = *exprTable->indexer;
unify(indexType, indexer.indexType, scope, expr.index->location);
return indexer.indexResultType;
}
else if ((ctx == ValueContext::LValue && exprTable->state == TableState::Unsealed) || exprTable->state == TableState::Free)
{
TypeId indexerType = freshType(exprTable->level);
unify(indexType, indexerType, scope, expr.location);
TypeId indexResultType = freshType(exprTable->level);
exprTable->indexer = TableIndexer{anyIfNonstrict(indexerType), anyIfNonstrict(indexResultType)};
return indexResultType;
}
else
{
/*
* If we use [] indexing to fetch a property from a sealed table that
* has no indexer, we have no idea if it will work so we just return any
* and hope for the best.
*/
return anyType;
}
}
}

View File

@ -301,6 +301,8 @@ TypePack extendTypePack(
TypePack newPack;
newPack.tail = arena.freshTypePack(ftp->scope);
if (FFlag::DebugLuauDeferredConstraintResolution)
result.tail = newPack.tail;
size_t overridesIndex = 0;
while (result.head.size() < length)
{

View File

@ -4,8 +4,6 @@
#include "Luau/Common.h"
#include "Luau/IrData.h"
LUAU_FASTFLAG(LuauCodegenFastcall3)
namespace Luau
{
namespace CodeGen
@ -113,47 +111,16 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i
break;
case IrCmd::FASTCALL:
if (FFlag::LuauCodegenFastcall3)
{
visitor.use(inst.c);
visitor.use(inst.c);
if (int nresults = function.intOp(inst.d); nresults != -1)
visitor.defRange(vmRegOp(inst.b), nresults);
}
else
{
if (int count = function.intOp(inst.e); count != -1)
{
if (count >= 3)
{
CODEGEN_ASSERT(inst.d.kind == IrOpKind::VmReg && vmRegOp(inst.d) == vmRegOp(inst.c) + 1);
visitor.useRange(vmRegOp(inst.c), count);
}
else
{
if (count >= 1)
visitor.use(inst.c);
if (count >= 2)
visitor.maybeUse(inst.d); // Argument can also be a VmConst
}
}
else
{
visitor.useVarargs(vmRegOp(inst.c));
}
// Multiple return sequences (count == -1) are defined by ADJUST_STACK_TO_REG
if (int count = function.intOp(inst.f); count != -1)
visitor.defRange(vmRegOp(inst.b), count);
}
if (int nresults = function.intOp(inst.d); nresults != -1)
visitor.defRange(vmRegOp(inst.b), nresults);
break;
case IrCmd::INVOKE_FASTCALL:
if (int count = function.intOp(FFlag::LuauCodegenFastcall3 ? inst.f : inst.e); count != -1)
if (int count = function.intOp(inst.f); count != -1)
{
// Only LOP_FASTCALL3 lowering is allowed to have third optional argument
if (count >= 3 && (!FFlag::LuauCodegenFastcall3 || inst.e.kind == IrOpKind::Undef))
if (count >= 3 && inst.e.kind == IrOpKind::Undef)
{
CODEGEN_ASSERT(inst.d.kind == IrOpKind::VmReg && vmRegOp(inst.d) == vmRegOp(inst.c) + 1);
@ -167,7 +134,7 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i
if (count >= 2)
visitor.maybeUse(inst.d); // Argument can also be a VmConst
if (FFlag::LuauCodegenFastcall3 && count >= 3)
if (count >= 3)
visitor.maybeUse(inst.e); // Argument can also be a VmConst
}
}
@ -177,7 +144,7 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i
}
// Multiple return sequences (count == -1) are defined by ADJUST_STACK_TO_REG
if (int count = function.intOp(FFlag::LuauCodegenFastcall3 ? inst.g : inst.f); count != -1)
if (int count = function.intOp(inst.g); count != -1)
visitor.defRange(vmRegOp(inst.b), count);
break;
case IrCmd::FORGLOOP:

View File

@ -11,8 +11,6 @@
#include <algorithm>
LUAU_FASTFLAGVARIABLE(LuauCodegenFastcall3, false)
namespace Luau
{
namespace CodeGen
@ -1101,8 +1099,6 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
}
case LOP_FASTCALL3:
{
CODEGEN_ASSERT(FFlag::LuauCodegenFastcall3);
int bfid = LUAU_INSN_A(*pc);
int skip = LUAU_INSN_C(*pc);
int aux = pc[1];

View File

@ -149,7 +149,10 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
Proto* root = clvalue(func)->l.p;
if ((options.compilationOptions.flags & CodeGen_OnlyNativeModules) != 0 && (root->flags & LPF_NATIVE_MODULE) == 0)
{
build.finalize();
return std::string();
}
std::vector<Proto*> protos;
if (FFlag::LuauNativeAttribute)
@ -174,7 +177,7 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
if (protos.empty())
{
build.finalize(); // to avoid assertion in AssemblyBuilder dtor
build.finalize();
return std::string();
}

View File

@ -12,8 +12,6 @@
#include "lstate.h"
LUAU_FASTFLAG(LuauCodegenMathSign)
namespace Luau
{
namespace CodeGen
@ -57,36 +55,6 @@ static void emitBuiltinMathModf(IrRegAllocX64& regs, AssemblyBuilderX64& build,
}
}
static void emitBuiltinMathSign(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int arg)
{
CODEGEN_ASSERT(!FFlag::LuauCodegenMathSign);
ScopedRegX64 tmp0{regs, SizeX64::xmmword};
ScopedRegX64 tmp1{regs, SizeX64::xmmword};
ScopedRegX64 tmp2{regs, SizeX64::xmmword};
ScopedRegX64 tmp3{regs, SizeX64::xmmword};
build.vmovsd(tmp0.reg, luauRegValue(arg));
build.vxorpd(tmp1.reg, tmp1.reg, tmp1.reg);
// Set tmp2 to -1 if arg < 0, else 0
build.vcmpltsd(tmp2.reg, tmp0.reg, tmp1.reg);
build.vmovsd(tmp3.reg, build.f64(-1));
build.vandpd(tmp2.reg, tmp2.reg, tmp3.reg);
// Set mask bit to 1 if 0 < arg, else 0
build.vcmpltsd(tmp0.reg, tmp1.reg, tmp0.reg);
// Result = (mask-bit == 1) ? 1.0 : tmp2
// If arg < 0 then tmp2 is -1 and mask-bit is 0, result is -1
// If arg == 0 then tmp2 is 0 and mask-bit is 0, result is 0
// If arg > 0 then tmp2 is 0 and mask-bit is 1, result is 1
build.vblendvpd(tmp0.reg, tmp2.reg, build.f64x2(1, 1), tmp0.reg);
build.vmovsd(luauRegValue(ra), tmp0.reg);
build.mov(luauRegTag(ra), LUA_TNUMBER);
}
void emitBuiltin(IrRegAllocX64& regs, AssemblyBuilderX64& build, int bfid, int ra, int arg, int nresults)
{
switch (bfid)
@ -97,10 +65,6 @@ void emitBuiltin(IrRegAllocX64& regs, AssemblyBuilderX64& build, int bfid, int r
case LBF_MATH_MODF:
CODEGEN_ASSERT(nresults == 1 || nresults == 2);
return emitBuiltinMathModf(regs, build, ra, arg, nresults);
case LBF_MATH_SIGN:
CODEGEN_ASSERT(!FFlag::LuauCodegenMathSign);
CODEGEN_ASSERT(nresults == 1);
return emitBuiltinMathSign(regs, build, ra, arg);
default:
CODEGEN_ASSERT(!"Missing x64 lowering");
}

View File

@ -13,8 +13,6 @@
#include <string.h>
LUAU_FASTFLAG(LuauCodegenFastcall3)
namespace Luau
{
namespace CodeGen
@ -458,8 +456,6 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
handleFastcallFallback(translateFastCallN(*this, pc, i, true, 2, vmConst(pc[1]), undef()), pc, i);
break;
case LOP_FASTCALL3:
CODEGEN_ASSERT(FFlag::LuauCodegenFastcall3);
handleFastcallFallback(translateFastCallN(*this, pc, i, true, 3, vmReg(pc[1] & 0xff), vmReg((pc[1] >> 8) & 0xff)), pc, i);
break;
case LOP_FORNPREP:

View File

@ -11,8 +11,7 @@
#include "lstate.h"
#include "lgc.h"
LUAU_FASTFLAG(LuauCodegenFastcall3)
LUAU_FASTFLAG(LuauCodegenMathSign)
LUAU_FASTFLAGVARIABLE(LuauCodegenArmNumToVecFix, false)
namespace Luau
{
@ -235,25 +234,6 @@ static bool emitBuiltin(AssemblyBuilderA64& build, IrFunction& function, IrRegAl
}
return true;
}
case LBF_MATH_SIGN:
{
CODEGEN_ASSERT(!FFlag::LuauCodegenMathSign);
CODEGEN_ASSERT(nresults == 1);
build.ldr(d0, mem(rBase, arg * sizeof(TValue) + offsetof(TValue, value.n)));
build.fcmpz(d0);
build.fmov(d0, 0.0);
build.fmov(d1, 1.0);
build.fcsel(d0, d1, d0, getConditionFP(IrCondition::Greater));
build.fmov(d1, -1.0);
build.fcsel(d0, d1, d0, getConditionFP(IrCondition::Less));
build.str(d0, mem(rBase, res * sizeof(TValue) + offsetof(TValue, value.n)));
RegisterA64 temp = regs.allocTemp(KindA64::w);
build.mov(temp, LUA_TNUMBER);
build.str(temp, mem(rBase, res * sizeof(TValue) + offsetof(TValue, tt)));
return true;
}
default:
CODEGEN_ASSERT(!"Missing A64 lowering");
@ -701,8 +681,6 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
}
case IrCmd::SIGN_NUM:
{
CODEGEN_ASSERT(FFlag::LuauCodegenMathSign);
inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a});
RegisterA64 temp = tempDouble(inst.a);
@ -1143,7 +1121,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
else
{
RegisterA64 tempd = tempDouble(inst.a);
RegisterA64 temps = castReg(KindA64::s, tempd);
RegisterA64 temps = FFlag::LuauCodegenArmNumToVecFix ? regs.allocTemp(KindA64::s) : castReg(KindA64::s, tempd);
build.fcvt(temps, tempd);
build.dup_4s(inst.regA64, castReg(KindA64::q, temps), 0);
@ -1194,88 +1172,54 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
case IrCmd::FASTCALL:
regs.spill(build, index);
if (FFlag::LuauCodegenFastcall3)
error |= !emitBuiltin(build, function, regs, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d));
else
error |= !emitBuiltin(build, function, regs, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.f));
error |= !emitBuiltin(build, function, regs, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d));
break;
case IrCmd::INVOKE_FASTCALL:
{
if (FFlag::LuauCodegenFastcall3)
// We might need a temporary and we have to preserve it over the spill
RegisterA64 temp = regs.allocTemp(KindA64::q);
regs.spill(build, index, {temp});
build.mov(x0, rState);
build.add(x1, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue)));
build.add(x2, rBase, uint16_t(vmRegOp(inst.c) * sizeof(TValue)));
build.mov(w3, intOp(inst.g)); // nresults
// 'E' argument can only be produced by LOP_FASTCALL3 lowering
if (inst.e.kind != IrOpKind::Undef)
{
// We might need a temporary and we have to preserve it over the spill
RegisterA64 temp = regs.allocTemp(KindA64::q);
regs.spill(build, index, {temp});
CODEGEN_ASSERT(intOp(inst.f) == 3);
build.mov(x0, rState);
build.add(x1, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue)));
build.add(x2, rBase, uint16_t(vmRegOp(inst.c) * sizeof(TValue)));
build.mov(w3, intOp(inst.g)); // nresults
build.ldr(x4, mem(rState, offsetof(lua_State, top)));
// 'E' argument can only be produced by LOP_FASTCALL3 lowering
if (inst.e.kind != IrOpKind::Undef)
{
CODEGEN_ASSERT(intOp(inst.f) == 3);
build.ldr(temp, mem(rBase, vmRegOp(inst.d) * sizeof(TValue)));
build.str(temp, mem(x4, 0));
build.ldr(x4, mem(rState, offsetof(lua_State, top)));
build.ldr(temp, mem(rBase, vmRegOp(inst.d) * sizeof(TValue)));
build.str(temp, mem(x4, 0));
build.ldr(temp, mem(rBase, vmRegOp(inst.e) * sizeof(TValue)));
build.str(temp, mem(x4, sizeof(TValue)));
}
else
{
if (inst.d.kind == IrOpKind::VmReg)
build.add(x4, rBase, uint16_t(vmRegOp(inst.d) * sizeof(TValue)));
else if (inst.d.kind == IrOpKind::VmConst)
emitAddOffset(build, x4, rConstants, vmConstOp(inst.d) * sizeof(TValue));
else
CODEGEN_ASSERT(inst.d.kind == IrOpKind::Undef);
}
// nparams
if (intOp(inst.f) == LUA_MULTRET)
{
// L->top - (ra + 1)
build.ldr(x5, mem(rState, offsetof(lua_State, top)));
build.sub(x5, x5, rBase);
build.sub(x5, x5, uint16_t((vmRegOp(inst.b) + 1) * sizeof(TValue)));
build.lsr(x5, x5, kTValueSizeLog2);
}
else
build.mov(w5, intOp(inst.f));
build.ldr(temp, mem(rBase, vmRegOp(inst.e) * sizeof(TValue)));
build.str(temp, mem(x4, sizeof(TValue)));
}
else
{
regs.spill(build, index);
build.mov(x0, rState);
build.add(x1, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue)));
build.add(x2, rBase, uint16_t(vmRegOp(inst.c) * sizeof(TValue)));
build.mov(w3, intOp(inst.f)); // nresults
if (inst.d.kind == IrOpKind::VmReg)
build.add(x4, rBase, uint16_t(vmRegOp(inst.d) * sizeof(TValue)));
else if (inst.d.kind == IrOpKind::VmConst)
emitAddOffset(build, x4, rConstants, vmConstOp(inst.d) * sizeof(TValue));
else
CODEGEN_ASSERT(inst.d.kind == IrOpKind::Undef);
// nparams
if (intOp(inst.e) == LUA_MULTRET)
{
// L->top - (ra + 1)
build.ldr(x5, mem(rState, offsetof(lua_State, top)));
build.sub(x5, x5, rBase);
build.sub(x5, x5, uint16_t((vmRegOp(inst.b) + 1) * sizeof(TValue)));
build.lsr(x5, x5, kTValueSizeLog2);
}
else
build.mov(w5, intOp(inst.e));
}
// nparams
if (intOp(inst.f) == LUA_MULTRET)
{
// L->top - (ra + 1)
build.ldr(x5, mem(rState, offsetof(lua_State, top)));
build.sub(x5, x5, rBase);
build.sub(x5, x5, uint16_t((vmRegOp(inst.b) + 1) * sizeof(TValue)));
build.lsr(x5, x5, kTValueSizeLog2);
}
else
build.mov(w5, intOp(inst.f));
build.ldr(x6, mem(rNativeContext, offsetof(NativeContext, luauF_table) + uintOp(inst.a) * sizeof(luau_FastFunction)));
build.blr(x6);

View File

@ -15,9 +15,6 @@
#include "lstate.h"
#include "lgc.h"
LUAU_FASTFLAG(LuauCodegenFastcall3)
LUAU_FASTFLAG(LuauCodegenMathSign)
namespace Luau
{
namespace CodeGen
@ -596,8 +593,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
break;
case IrCmd::SIGN_NUM:
{
CODEGEN_ASSERT(FFlag::LuauCodegenMathSign);
inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a});
ScopedRegX64 tmp0{regs, SizeX64::xmmword};
@ -1038,10 +1033,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
case IrCmd::FASTCALL:
{
if (FFlag::LuauCodegenFastcall3)
emitBuiltin(regs, build, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d));
else
emitBuiltin(regs, build, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.f));
emitBuiltin(regs, build, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d));
break;
}
case IrCmd::INVOKE_FASTCALL:
@ -1052,7 +1044,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
ScopedRegX64 argsAlt{regs};
// 'E' argument can only be produced by LOP_FASTCALL3
if (FFlag::LuauCodegenFastcall3 && inst.e.kind != IrOpKind::Undef)
if (inst.e.kind != IrOpKind::Undef)
{
CODEGEN_ASSERT(intOp(inst.f) == 3);
@ -1079,8 +1071,8 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
int ra = vmRegOp(inst.b);
int arg = vmRegOp(inst.c);
int nparams = intOp(FFlag::LuauCodegenFastcall3 ? inst.f : inst.e);
int nresults = intOp(FFlag::LuauCodegenFastcall3 ? inst.g : inst.f);
int nparams = intOp(inst.f);
int nresults = intOp(inst.g);
IrCallWrapperX64 callWrap(regs, build, index);
callWrap.addArgument(SizeX64::qword, rState);
@ -1088,7 +1080,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
callWrap.addArgument(SizeX64::qword, luauRegAddress(arg));
callWrap.addArgument(SizeX64::dword, nresults);
if (FFlag::LuauCodegenFastcall3 && inst.e.kind != IrOpKind::Undef)
if (inst.e.kind != IrOpKind::Undef)
callWrap.addArgument(SizeX64::qword, argsAlt);
else
callWrap.addArgument(SizeX64::qword, args);

View File

@ -8,9 +8,6 @@
#include <math.h>
LUAU_FASTFLAG(LuauCodegenFastcall3)
LUAU_FASTFLAGVARIABLE(LuauCodegenMathSign, false)
// TODO: when nresults is less than our actual result count, we can skip computing/writing unused results
static const int kMinMaxUnrolledParams = 5;
@ -39,33 +36,6 @@ static IrOp builtinLoadDouble(IrBuilder& build, IrOp arg)
// Wrapper code for all builtins with a fixed signature and manual assembly lowering of the body
// (number, ...) -> number
static BuiltinImplResult translateBuiltinNumberToNumber(
IrBuilder& build,
LuauBuiltinFunction bfid,
int nparams,
int ra,
int arg,
IrOp args,
int nresults,
int pcpos
)
{
CODEGEN_ASSERT(!FFlag::LuauCodegenMathSign);
if (nparams < 1 || nresults > 1)
return {BuiltinImplType::None, -1};
builtinCheckDouble(build, build.vmReg(arg), pcpos);
if (FFlag::LuauCodegenFastcall3)
build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), build.constInt(1));
else
build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), args, build.constInt(1), build.constInt(1));
return {BuiltinImplType::Full, 1};
}
static BuiltinImplResult translateBuiltinNumberToNumberLibm(
IrBuilder& build,
LuauBuiltinFunction bfid,
@ -142,18 +112,7 @@ static BuiltinImplResult translateBuiltinNumberTo2Number(
builtinCheckDouble(build, build.vmReg(arg), pcpos);
if (FFlag::LuauCodegenFastcall3)
build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), build.constInt(nresults == 1 ? 1 : 2));
else
build.inst(
IrCmd::FASTCALL,
build.constUint(bfid),
build.vmReg(ra),
build.vmReg(arg),
build.undef(),
build.constInt(1),
build.constInt(nresults == 1 ? 1 : 2)
);
build.inst(IrCmd::FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), build.constInt(nresults == 1 ? 1 : 2));
return {BuiltinImplType::Full, 2};
}
@ -250,10 +209,10 @@ static BuiltinImplResult translateBuiltinMathMinMax(
builtinCheckDouble(build, build.vmReg(arg), pcpos);
builtinCheckDouble(build, args, pcpos);
if (FFlag::LuauCodegenFastcall3 && nparams >= 3)
if (nparams >= 3)
builtinCheckDouble(build, arg3, pcpos);
for (int i = (FFlag::LuauCodegenFastcall3 ? 4 : 3); i <= nparams; ++i)
for (int i = 4; i <= nparams; ++i)
builtinCheckDouble(build, build.vmReg(vmRegOp(args) + (i - 2)), pcpos);
IrOp varg1 = builtinLoadDouble(build, build.vmReg(arg));
@ -261,13 +220,13 @@ static BuiltinImplResult translateBuiltinMathMinMax(
IrOp res = build.inst(cmd, varg2, varg1); // Swapped arguments are required for consistency with VM builtins
if (FFlag::LuauCodegenFastcall3 && nparams >= 3)
if (nparams >= 3)
{
IrOp arg = builtinLoadDouble(build, arg3);
res = build.inst(cmd, arg, res);
}
for (int i = (FFlag::LuauCodegenFastcall3 ? 4 : 3); i <= nparams; ++i)
for (int i = 4; i <= nparams; ++i)
{
IrOp arg = builtinLoadDouble(build, build.vmReg(vmRegOp(args) + (i - 2)));
res = build.inst(cmd, arg, res);
@ -302,10 +261,10 @@ static BuiltinImplResult translateBuiltinMathClamp(
builtinCheckDouble(build, build.vmReg(arg), pcpos);
builtinCheckDouble(build, args, pcpos);
builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1), pcpos);
builtinCheckDouble(build, arg3, pcpos);
IrOp min = builtinLoadDouble(build, args);
IrOp max = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1));
IrOp max = builtinLoadDouble(build, arg3);
build.inst(IrCmd::JUMP_CMP_NUM, min, max, build.cond(IrCondition::NotLessEqual), fallback, block);
build.beginBlock(block);
@ -386,10 +345,10 @@ static BuiltinImplResult translateBuiltinBit32BinaryOp(
builtinCheckDouble(build, build.vmReg(arg), pcpos);
builtinCheckDouble(build, args, pcpos);
if (FFlag::LuauCodegenFastcall3 && nparams >= 3)
if (nparams >= 3)
builtinCheckDouble(build, arg3, pcpos);
for (int i = (FFlag::LuauCodegenFastcall3 ? 4 : 3); i <= nparams; ++i)
for (int i = 4; i <= nparams; ++i)
builtinCheckDouble(build, build.vmReg(vmRegOp(args) + (i - 2)), pcpos);
IrOp va = builtinLoadDouble(build, build.vmReg(arg));
@ -400,7 +359,7 @@ static BuiltinImplResult translateBuiltinBit32BinaryOp(
IrOp res = build.inst(cmd, vaui, vbui);
if (FFlag::LuauCodegenFastcall3 && nparams >= 3)
if (nparams >= 3)
{
IrOp vc = builtinLoadDouble(build, arg3);
IrOp arg = build.inst(IrCmd::NUM_TO_UINT, vc);
@ -408,7 +367,7 @@ static BuiltinImplResult translateBuiltinBit32BinaryOp(
res = build.inst(cmd, res, arg);
}
for (int i = (FFlag::LuauCodegenFastcall3 ? 4 : 3); i <= nparams; ++i)
for (int i = 4; i <= nparams; ++i)
{
IrOp vc = builtinLoadDouble(build, build.vmReg(vmRegOp(args) + (i - 2)));
IrOp arg = build.inst(IrCmd::NUM_TO_UINT, vc);
@ -599,8 +558,8 @@ static BuiltinImplResult translateBuiltinBit32Extract(
{
IrOp f = build.inst(IrCmd::NUM_TO_INT, vb);
builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(args.index + 1), pcpos);
IrOp vc = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(args.index + 1));
builtinCheckDouble(build, arg3, pcpos);
IrOp vc = builtinLoadDouble(build, arg3);
IrOp w = build.inst(IrCmd::NUM_TO_INT, vc);
IrOp block1 = build.block(IrBlockKind::Internal);
@ -705,11 +664,11 @@ static BuiltinImplResult translateBuiltinBit32Replace(
builtinCheckDouble(build, build.vmReg(arg), pcpos);
builtinCheckDouble(build, args, pcpos);
builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(args.index + 1), pcpos);
builtinCheckDouble(build, arg3, pcpos);
IrOp va = builtinLoadDouble(build, build.vmReg(arg));
IrOp vb = builtinLoadDouble(build, args);
IrOp vc = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(args.index + 1));
IrOp vc = builtinLoadDouble(build, arg3);
IrOp n = build.inst(IrCmd::NUM_TO_UINT, va);
IrOp v = build.inst(IrCmd::NUM_TO_UINT, vb);
@ -734,8 +693,8 @@ static BuiltinImplResult translateBuiltinBit32Replace(
}
else
{
builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? build.vmReg(vmRegOp(args) + 2) : build.vmReg(args.index + 2), pcpos);
IrOp vd = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? build.vmReg(vmRegOp(args) + 2) : build.vmReg(args.index + 2));
builtinCheckDouble(build, build.vmReg(vmRegOp(args) + 2), pcpos);
IrOp vd = builtinLoadDouble(build, build.vmReg(vmRegOp(args) + 2));
IrOp w = build.inst(IrCmd::NUM_TO_INT, vd);
IrOp block1 = build.block(IrBlockKind::Internal);
@ -781,11 +740,11 @@ static BuiltinImplResult translateBuiltinVector(IrBuilder& build, int nparams, i
builtinCheckDouble(build, build.vmReg(arg), pcpos);
builtinCheckDouble(build, args, pcpos);
builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1), pcpos);
builtinCheckDouble(build, arg3, pcpos);
IrOp x = builtinLoadDouble(build, build.vmReg(arg));
IrOp y = builtinLoadDouble(build, args);
IrOp z = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1));
IrOp z = builtinLoadDouble(build, arg3);
build.inst(IrCmd::STORE_VECTOR, build.vmReg(ra), x, y, z);
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TVECTOR));
@ -863,7 +822,7 @@ static void translateBufferArgsAndCheckBounds(
builtinCheckDouble(build, args, pcpos);
if (nparams == 3)
builtinCheckDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1), pcpos);
builtinCheckDouble(build, arg3, pcpos);
buf = build.inst(IrCmd::LOAD_POINTER, build.vmReg(arg));
@ -920,7 +879,7 @@ static BuiltinImplResult translateBuiltinBufferWrite(
IrOp buf, intIndex;
translateBufferArgsAndCheckBounds(build, nparams, arg, args, arg3, size, pcpos, buf, intIndex);
IrOp numValue = builtinLoadDouble(build, FFlag::LuauCodegenFastcall3 ? arg3 : build.vmReg(vmRegOp(args) + 1));
IrOp numValue = builtinLoadDouble(build, arg3);
build.inst(writeCmd, buf, intIndex, convCmd == IrCmd::NOP ? numValue : build.inst(convCmd, numValue));
return {BuiltinImplType::Full, 0};
@ -982,10 +941,7 @@ BuiltinImplResult translateBuiltin(
case LBF_MATH_LOG10:
return translateBuiltinNumberToNumberLibm(build, LuauBuiltinFunction(bfid), nparams, ra, arg, nresults, pcpos);
case LBF_MATH_SIGN:
if (FFlag::LuauCodegenMathSign)
return translateBuiltinMathUnary(build, IrCmd::SIGN_NUM, nparams, ra, arg, nresults, pcpos);
else
return translateBuiltinNumberToNumber(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, pcpos);
return translateBuiltinMathUnary(build, IrCmd::SIGN_NUM, nparams, ra, arg, nresults, pcpos);
case LBF_MATH_POW:
case LBF_MATH_FMOD:
case LBF_MATH_ATAN2:

View File

@ -13,8 +13,6 @@
#include "lstate.h"
#include "ltm.h"
LUAU_FASTFLAG(LuauCodegenFastcall3)
namespace Luau
{
namespace CodeGen
@ -767,7 +765,7 @@ IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool
builtinArgs = build.constDouble(protok.value.n);
}
IrOp builtinArg3 = FFlag::LuauCodegenFastcall3 ? (customParams ? customArg3 : build.vmReg(ra + 3)) : IrOp{};
IrOp builtinArg3 = customParams ? customArg3 : build.vmReg(ra + 3);
IrOp fallback = build.block(IrBlockKind::Fallback);
@ -793,7 +791,7 @@ IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool
return build.undef();
}
}
else if (FFlag::LuauCodegenFastcall3)
else
{
IrOp arg3 = customParams ? customArg3 : build.undef();
@ -817,21 +815,6 @@ IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool
else if (nparams == LUA_MULTRET)
build.inst(IrCmd::ADJUST_STACK_TO_TOP);
}
else
{
// TODO: we can skip saving pc for some well-behaved builtins which we didn't inline
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + getOpLength(opcode)));
IrOp res = build.inst(
IrCmd::INVOKE_FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), args, build.constInt(nparams), build.constInt(nresults)
);
build.inst(IrCmd::CHECK_FASTCALL_RES, res, fallback);
if (nresults == LUA_MULTRET)
build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(ra), res);
else if (nparams == LUA_MULTRET)
build.inst(IrCmd::ADJUST_STACK_TO_TOP);
}
return fallback;
}

View File

@ -3,8 +3,6 @@
#include "Luau/IrUtils.h"
LUAU_FASTFLAG(LuauCodegenFastcall3)
namespace Luau
{
namespace CodeGen
@ -46,11 +44,11 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst)
invalidateRestoreVmRegs(vmRegOp(inst.a), -1);
break;
case IrCmd::FASTCALL:
invalidateRestoreVmRegs(vmRegOp(inst.b), function.intOp(FFlag::LuauCodegenFastcall3 ? inst.d : inst.f));
invalidateRestoreVmRegs(vmRegOp(inst.b), function.intOp(inst.d));
break;
case IrCmd::INVOKE_FASTCALL:
// Multiple return sequences (count == -1) are defined by ADJUST_STACK_TO_REG
if (int count = function.intOp(FFlag::LuauCodegenFastcall3 ? inst.g : inst.f); count != -1)
if (int count = function.intOp(inst.g); count != -1)
invalidateRestoreVmRegs(vmRegOp(inst.b), count);
break;
case IrCmd::DO_ARITH:

View File

@ -18,8 +18,6 @@ LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3)
LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64)
LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64)
LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks, false)
LUAU_FASTFLAG(LuauCodegenFastcall3)
LUAU_FASTFLAG(LuauCodegenMathSign)
namespace Luau
{
@ -1119,7 +1117,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
{
LuauBuiltinFunction bfid = LuauBuiltinFunction(function.uintOp(inst.a));
int firstReturnReg = vmRegOp(inst.b);
int nresults = function.intOp(FFlag::LuauCodegenFastcall3 ? inst.d : inst.f);
int nresults = function.intOp(inst.d);
// TODO: FASTCALL is more restrictive than INVOKE_FASTCALL; we should either determine the exact semantics, or rework it
handleBuiltinEffects(state, bfid, firstReturnReg, nresults);
@ -1133,19 +1131,13 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
if (nresults > 1)
state.updateTag(IrOp{IrOpKind::VmReg, uint8_t(firstReturnReg + 1)}, LUA_TNUMBER);
break;
case LBF_MATH_SIGN:
CODEGEN_ASSERT(!FFlag::LuauCodegenMathSign);
state.updateTag(IrOp{IrOpKind::VmReg, uint8_t(firstReturnReg)}, LUA_TNUMBER);
break;
default:
break;
}
break;
}
case IrCmd::INVOKE_FASTCALL:
handleBuiltinEffects(
state, LuauBuiltinFunction(function.uintOp(inst.a)), vmRegOp(inst.b), function.intOp(FFlag::LuauCodegenFastcall3 ? inst.g : inst.f)
);
handleBuiltinEffects(state, LuauBuiltinFunction(function.uintOp(inst.a)), vmRegOp(inst.b), function.intOp(inst.g));
break;
// These instructions don't have an effect on register/memory state we are tracking

View File

@ -13,7 +13,7 @@ inline bool isFlagExperimental(const char* flag)
static const char* const kList[] = {
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
"StudioReportLuauAny", // takes telemetry data for usage of any types
"StudioReportLuauAny2", // takes telemetry data for usage of any types
// makes sure we always have at least one entry
nullptr,
};

View File

@ -5,7 +5,6 @@
#include "Luau/Id.h"
#include "Luau/Language.h"
#include "Luau/UnionFind.h"
#include "Luau/VecDeque.h"
#include <optional>
#include <unordered_map>
@ -145,7 +144,7 @@ private:
/// The hashcons 𝐻 is a map from e-nodes to e-class ids.
std::unordered_map<L, Id, typename L::Hash> hashcons;
VecDeque<std::pair<L, Id>> worklist;
std::vector<std::pair<L, Id>> worklist;
private:
void canonicalize(L& enode)
@ -183,7 +182,7 @@ private:
for (Id operand : enode.operands())
get(operand).parents.push_back({enode, id});
worklist.push_back({enode, id});
worklist.emplace_back(enode, id);
hashcons.insert_or_assign(enode, id);
return id;

View File

@ -7,7 +7,6 @@
#include "Luau/Variant.h"
#include <array>
#include <algorithm>
#include <type_traits>
#include <utility>
@ -233,12 +232,6 @@ struct Language final
{
}
Language(const Language&) noexcept = default;
Language& operator=(const Language&) noexcept = default;
Language(Language&&) noexcept = default;
Language& operator=(Language&&) noexcept = default;
int index() const noexcept
{
return v.index();

View File

@ -3,6 +3,7 @@
#include <cstddef>
#include <functional>
#include <vector>
namespace Luau::EqSat
{

View File

@ -11,6 +11,7 @@ Id UnionFind::makeSet()
Id id{parents.size()};
parents.push_back(id);
ranks.push_back(0);
return id;
}
@ -32,6 +33,7 @@ Id UnionFind::find(Id id)
parents[size_t(id)] = set;
id = parent;
}
return set;
}
@ -47,6 +49,7 @@ void UnionFind::merge(Id a, Id b)
std::swap(aSet, bSet);
parents[size_t(bSet)] = aSet;
if (ranks[size_t(aSet)] == ranks[size_t(bSet)])
ranks[size_t(aSet)]++;
}

View File

@ -181,8 +181,7 @@ coverage: $(TESTS_TARGET) $(COMPILE_CLI_TARGET)
mv default.profraw tests.profraw
$(TESTS_TARGET) --fflags=true
mv default.profraw tests-flags.profraw
# new solver is expected to fail tests at the moment, remove '!' once tests are fixed and this starts to fail
! $(TESTS_TARGET) --fflags=true,DebugLuauDeferredConstraintResolution=true
$(TESTS_TARGET) --fflags=true,DebugLuauDeferredConstraintResolution=true
mv default.profraw tests-dcr.profraw
$(TESTS_TARGET) -ts=Conformance --codegen
mv default.profraw codegen.profraw

View File

@ -5,6 +5,7 @@
#include "Fixture.h"
#include "ScopedFlags.h"
#include "doctest.h"
#include <algorithm>
@ -14,14 +15,13 @@ using namespace Luau;
using Pattern = AnyTypeSummary::Pattern;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(DebugLuauFreezeArena);
LUAU_FASTFLAG(DebugLuauMagicTypes);
LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(StudioReportLuauAny2)
LUAU_FASTFLAG(StudioReportLuauAny);
struct ATSFixture : BuiltinsFixture
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
ATSFixture()
{
@ -34,6 +34,11 @@ TEST_SUITE_BEGIN("AnyTypeSummaryTest");
TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
type A = (number, string) -> ...any
)";
@ -43,16 +48,18 @@ type A = (number, string) -> ...any
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
LUAU_ASSERT(module->ats.typeInfo[0].node == "type A = (number, string)->( ...any)");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
LUAU_ASSERT(module->ats.typeInfo[0].node == "type A = (number, string)->( ...any)");
}
TEST_CASE_FIXTURE(ATSFixture, "export_alias")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))
)";
@ -62,16 +69,18 @@ export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))");
}
TEST_CASE_FIXTURE(ATSFixture, "typepacks")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
local function fallible(t: number): ...any
if t > 0 then
@ -86,16 +95,18 @@ end
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::TypePk);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function fallible(t: number): ...any\n if t > 0 then\n return true, t\n end\n return false, 'must be positive'\nend");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::TypePk);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function fallible(t: number): ...any\n if t > 0 then\n return true, t\n end\n return false, 'must be positive'\nend");
}
TEST_CASE_FIXTURE(ATSFixture, "typepacks_no_ret")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
-- TODO: if partially typed, we'd want to know too
local function fallible(t: number)
@ -111,14 +122,16 @@ end
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
}
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
}
TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any_gen_table")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
type Pair<T> = {first: T, second: any}
)";
@ -128,16 +141,18 @@ type Pair<T> = {first: T, second: any}
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Pair<T> = {first: T, second: any}");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Pair<T> = {first: T, second: any}");
}
TEST_CASE_FIXTURE(ATSFixture, "assign_uneq")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/B"] = R"(
local function greetings(name: string)
return "Hello, " .. name, nil
@ -152,14 +167,16 @@ local x, y, z = greetings("Dibri") -- mismatch
LUAU_REQUIRE_ERROR_COUNT(1, result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/B");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
}
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
}
TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any_gen")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
-- type Pair<T> = (boolean, string, ...any) -> {T} -- type aliases with generics/pack do not seem to be processed?
type Pair<T> = (boolean, T) -> ...any
@ -170,16 +187,18 @@ type Pair<T> = (boolean, T) -> ...any
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Pair<T> = (boolean, T)->( ...any)");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Pair<T> = (boolean, T)->( ...any)");
}
TEST_CASE_FIXTURE(ATSFixture, "typeof_any_in_func")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
local function f()
local a: any = 1
@ -192,16 +211,18 @@ TEST_CASE_FIXTURE(ATSFixture, "typeof_any_in_func")
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f()\n local a: any = 1\n local b: typeof(a) = 1\n end");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f()\n local a: any = 1\n local b: typeof(a) = 1\n end");
}
TEST_CASE_FIXTURE(ATSFixture, "generic_types")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
local function foo<A>(a: (...A) -> any, ...: A)
return a(...)
@ -220,16 +241,18 @@ foo(addNumbers)
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::FuncApp);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function foo<A>(a: (...A)->( any),...: A)\n return a(...)\nend");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::FuncApp);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function foo<A>(a: (...A)->( any),...: A)\n return a(...)\nend");
}
TEST_CASE_FIXTURE(ATSFixture, "no_annot")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
local character = script.Parent
)";
@ -239,14 +262,16 @@ local character = script.Parent
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
}
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
}
TEST_CASE_FIXTURE(ATSFixture, "if_any")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
function f(x: any)
if not x then
@ -266,22 +291,24 @@ end
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
LUAU_ASSERT(
module->ats.typeInfo[0].node == "function f(x: any)\nif not x then\nx = {\n y = math.random(0, 2^31-1),\n left = nil,\n right = "
"nil\n}\nelse\n local expected = x * 5\nend\nend"
);
}
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
LUAU_ASSERT(
module->ats.typeInfo[0].node == "function f(x: any)\nif not x then\nx = {\n y = math.random(0, 2^31-1),\n left = nil,\n right = "
"nil\n}\nelse\n local expected = x * 5\nend\nend"
);
}
TEST_CASE_FIXTURE(ATSFixture, "variadic_any")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
local function f(): (number, ...any)
return 1, 5
return 1, 5 --catching this
end
local x, y, z = f() -- not catching this any because no annot
@ -292,16 +319,18 @@ TEST_CASE_FIXTURE(ATSFixture, "variadic_any")
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncRet);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(): (number, ...any)\n return 1, 5\n end");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncRet);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(): (number, ...any)\n return 1, 5\n end");
}
TEST_CASE_FIXTURE(ATSFixture, "type_alias_intersection")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
type XCoord = {x: number}
type YCoord = {y: any}
@ -314,16 +343,18 @@ TEST_CASE_FIXTURE(ATSFixture, "type_alias_intersection")
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
LUAU_ASSERT(module->ats.typeInfo[2].code == Pattern::VarAnnot);
LUAU_ASSERT(module->ats.typeInfo[2].node == "local vec2: Vector2 = {x = 1, y = 2}");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
LUAU_ASSERT(module->ats.typeInfo[2].code == Pattern::VarAnnot);
LUAU_ASSERT(module->ats.typeInfo[2].node == "local vec2: Vector2 = {x = 1, y = 2}");
}
TEST_CASE_FIXTURE(ATSFixture, "var_func_arg")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
local function f(...: any)
end
@ -340,16 +371,18 @@ TEST_CASE_FIXTURE(ATSFixture, "var_func_arg")
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 4);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAny);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(...: any)\n end");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 4);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAny);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(...: any)\n end");
}
TEST_CASE_FIXTURE(ATSFixture, "var_func_apps")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
local function f(...: any)
end
@ -362,17 +395,19 @@ TEST_CASE_FIXTURE(ATSFixture, "var_func_apps")
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAny);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(...: any)\n end");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAny);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(...: any)\n end");
}
TEST_CASE_FIXTURE(ATSFixture, "CannotExtendTable")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
local CAR_COLLISION_GROUP = "Car"
@ -390,14 +425,16 @@ end
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
}
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
}
TEST_CASE_FIXTURE(ATSFixture, "unknown_symbol")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
local function manageRace(raceContainer: Model)
RaceManager.new(raceContainer)
@ -410,16 +447,18 @@ end
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend");
}
TEST_CASE_FIXTURE(ATSFixture, "racing_3_short")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
local CollectionService = game:GetService("CollectionService")
@ -449,16 +488,18 @@ initialize()
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 5);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 5);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend");
}
TEST_CASE_FIXTURE(ATSFixture, "racing_collision_2")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
local PhysicsService = game:GetService("PhysicsService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
@ -524,22 +565,24 @@ initialize()
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 11);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
LUAU_ASSERT(
module->ats.typeInfo[0].node ==
"local function onCharacterAdded(character: Model)\n\n character.DescendantAdded:Connect(function(descendant)\n if "
"descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n end)\n\n\n for _, descendant in "
"character:GetDescendants()do\n if descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n "
"end\nend"
);
}
LUAU_ASSERT(module->ats.typeInfo.size() == 11);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
LUAU_ASSERT(
module->ats.typeInfo[0].node ==
"local function onCharacterAdded(character: Model)\n\n character.DescendantAdded:Connect(function(descendant)\n if "
"descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n end)\n\n\n for _, descendant in "
"character:GetDescendants()do\n if descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n "
"end\nend"
);
}
TEST_CASE_FIXTURE(ATSFixture, "racing_spawning_1")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")
@ -593,23 +636,25 @@ initialize()
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 7);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
LUAU_ASSERT(
module->ats.typeInfo[0].node ==
"local function setupKiosk(kiosk: Model)\n local spawnLocation = kiosk:FindFirstChild('SpawnLocation')\n assert(spawnLocation, "
"`{kiosk:GetFullName()} has no SpawnLocation part`)\n local promptPart = kiosk:FindFirstChild('Prompt')\n assert(promptPart, "
"`{kiosk:GetFullName()} has no Prompt part`)\n\n\n spawnLocation.Transparency = 1\n\n\n local spawnPrompt = "
"spawnPromptTemplate:Clone()\n spawnPrompt.Parent = promptPart\n\n spawnPrompt.Triggered:Connect(function(player: Player)\n\n "
"destroyPlayerCars(player)\n\n spawnCar(spawnLocation.CFrame, player)\n end)\nend"
);
}
LUAU_ASSERT(module->ats.typeInfo.size() == 7);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
LUAU_ASSERT(
module->ats.typeInfo[0].node ==
"local function setupKiosk(kiosk: Model)\n local spawnLocation = kiosk:FindFirstChild('SpawnLocation')\n assert(spawnLocation, "
"`{kiosk:GetFullName()} has no SpawnLocation part`)\n local promptPart = kiosk:FindFirstChild('Prompt')\n assert(promptPart, "
"`{kiosk:GetFullName()} has no Prompt part`)\n\n\n spawnLocation.Transparency = 1\n\n\n local spawnPrompt = "
"spawnPromptTemplate:Clone()\n spawnPrompt.Parent = promptPart\n\n spawnPrompt.Triggered:Connect(function(player: Player)\n\n "
"destroyPlayerCars(player)\n\n spawnCar(spawnLocation.CFrame, player)\n end)\nend"
);
}
TEST_CASE_FIXTURE(ATSFixture, "mutually_recursive_generic")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
--!strict
type T<a> = { f: a, g: U<a> }
@ -625,14 +670,16 @@ TEST_CASE_FIXTURE(ATSFixture, "mutually_recursive_generic")
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
}
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
}
TEST_CASE_FIXTURE(ATSFixture, "explicit_pack")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
type Foo<T...> = (T...) -> () -- also want to see how these are used.
type Bar = Foo<(number, any)>
@ -643,16 +690,39 @@ type Bar = Foo<(number, any)>
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Bar = Foo<(number, any)>");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Bar = Foo<(number, any)>");
}
TEST_CASE_FIXTURE(ATSFixture, "local_val")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
local a, b, c = 1 :: any
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Casts);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local a, b, c = 1 :: any");
}
TEST_CASE_FIXTURE(ATSFixture, "var_any_local")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
local x = 2
local x: any = 2, 3
@ -665,16 +735,18 @@ local x: number, y: any, z, h: nil = 1, nil
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: any = 2, 3");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: any = 2, 3");
}
TEST_CASE_FIXTURE(ATSFixture, "table_uses_any")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
local x: any = 0
local y: number
@ -686,16 +758,18 @@ TEST_CASE_FIXTURE(ATSFixture, "table_uses_any")
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: any = 0");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: any = 0");
}
TEST_CASE_FIXTURE(ATSFixture, "typeof_any")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
local x: any = 0
function some1(x: typeof(x))
@ -707,16 +781,18 @@ TEST_CASE_FIXTURE(ATSFixture, "typeof_any")
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::FuncArg);
LUAU_ASSERT(module->ats.typeInfo[0].node == "function some1(x: typeof(x))\n end");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::FuncArg);
LUAU_ASSERT(module->ats.typeInfo[0].node == "function some1(x: typeof(x))\n end");
}
TEST_CASE_FIXTURE(ATSFixture, "table_type_assigned")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
local x: { x: any?} = {x = 1}
local z: { x : any, y : number? } -- not catching this
@ -729,16 +805,18 @@ TEST_CASE_FIXTURE(ATSFixture, "table_type_assigned")
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::Assign);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: { x: any?} = {x = 1}");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::Assign);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: { x: any?} = {x = 1}");
}
TEST_CASE_FIXTURE(ATSFixture, "simple_func_wo_ret")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
function some(x: any)
end
@ -749,16 +827,18 @@ TEST_CASE_FIXTURE(ATSFixture, "simple_func_wo_ret")
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
LUAU_ASSERT(module->ats.typeInfo[0].node == "function some(x: any)\n end");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
LUAU_ASSERT(module->ats.typeInfo[0].node == "function some(x: any)\n end");
}
TEST_CASE_FIXTURE(ATSFixture, "simple_func_w_ret")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
function other(y: number): any
return "gotcha!"
@ -770,16 +850,18 @@ TEST_CASE_FIXTURE(ATSFixture, "simple_func_w_ret")
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncRet);
LUAU_ASSERT(module->ats.typeInfo[0].node == "function other(y: number): any\n return 'gotcha!'\n end");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncRet);
LUAU_ASSERT(module->ats.typeInfo[0].node == "function other(y: number): any\n return 'gotcha!'\n end");
}
TEST_CASE_FIXTURE(ATSFixture, "nested_local")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
function cool(y: number): number
local g: any = "gratatataaa"
@ -792,16 +874,18 @@ TEST_CASE_FIXTURE(ATSFixture, "nested_local")
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
LUAU_ASSERT(module->ats.typeInfo[0].node == "function cool(y: number): number\n local g: any = 'gratatataaa'\n return y\n end");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
LUAU_ASSERT(module->ats.typeInfo[0].node == "function cool(y: number): number\n local g: any = 'gratatataaa'\n return y\n end");
}
TEST_CASE_FIXTURE(ATSFixture, "generic_func")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
function reverse<T>(a: {T}, b: any): {T}
return a
@ -813,16 +897,18 @@ TEST_CASE_FIXTURE(ATSFixture, "generic_func")
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
LUAU_ASSERT(module->ats.typeInfo[0].node == "function reverse<T>(a: {T}, b: any): {T}\n return a\n end");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
LUAU_ASSERT(module->ats.typeInfo[0].node == "function reverse<T>(a: {T}, b: any): {T}\n return a\n end");
}
TEST_CASE_FIXTURE(ATSFixture, "type_alias_any")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/Gui/Modules/A"] = R"(
type Clear = any
local z: Clear = "zip"
@ -833,16 +919,18 @@ TEST_CASE_FIXTURE(ATSFixture, "type_alias_any")
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Clear = any");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Clear = any");
}
TEST_CASE_FIXTURE(ATSFixture, "multi_module_any")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/A"] = R"(
export type MyFunction = (number, string) -> (any)
)";
@ -864,16 +952,18 @@ TEST_CASE_FIXTURE(ATSFixture, "multi_module_any")
ModulePtr module = frontend.moduleResolver.getModule("game/B");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Clear = any");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Clear = any");
}
TEST_CASE_FIXTURE(ATSFixture, "cast_on_cyclic_req")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::StudioReportLuauAny2, true},
};
fileResolver.source["game/A"] = R"(
local a = require(script.Parent.B) -- not resolving this module
export type MyFunction = (number, string) -> (any)
@ -890,12 +980,9 @@ TEST_CASE_FIXTURE(ATSFixture, "cast_on_cyclic_req")
ModulePtr module = frontend.moduleResolver.getModule("game/B");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::Alias);
LUAU_ASSERT(module->ats.typeInfo[1].node == "type Clear = any");
}
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::Alias);
LUAU_ASSERT(module->ats.typeInfo[1].node == "type Clear = any");
}

View File

@ -1607,6 +1607,9 @@ return target(a.@1
TEST_CASE_FIXTURE(ACFixture, "type_correct_suggestion_in_table")
{
if (FFlag::DebugLuauDeferredConstraintResolution) // CLI-116815 Autocomplete cannot suggest keys while autocompleting inside of a table
return;
check(R"(
type Foo = { a: number, b: string }
local a = { one = 4, two = "hello" }
@ -2261,6 +2264,9 @@ local ec = e(f@5)
TEST_CASE_FIXTURE(ACFixture, "type_correct_suggestion_for_overloads")
{
if (FFlag::DebugLuauDeferredConstraintResolution) // CLI-116814 Autocomplete needs to populate expected types for function arguments correctly
// (overloads and singletons)
return;
check(R"(
local target: ((number) -> string) & ((string) -> number))
@ -2608,6 +2614,10 @@ end
TEST_CASE_FIXTURE(ACFixture, "suggest_table_keys")
{
if (FFlag::DebugLuauDeferredConstraintResolution) // CLI-116812 AutocompleteTest.suggest_table_keys needs to populate expected types for nested
// tables without an annotation
return;
check(R"(
type Test = { first: number, second: number }
local t: Test = { f@1 }
@ -3091,6 +3101,10 @@ TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_on_string_singletons")
TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singletons")
{
if (FFlag::DebugLuauDeferredConstraintResolution) // CLI-116814 Autocomplete needs to populate expected types for function arguments correctly
// (overloads and singletons)
return;
check(R"(
type tag = "cat" | "dog"
local function f(a: tag) end
@ -4247,6 +4261,9 @@ foo(@1)
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_generic_type_pack_vararg")
{
// CLI-116932 - Autocomplete on a anonymous function in a function argument should not recommend a function with a generic parameter.
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
check(R"(
local function foo<A>(a: (...A) -> number, ...: A)
return a(...)
@ -4276,7 +4293,8 @@ end
foo(@1)
)");
const std::optional<std::string> EXPECTED_INSERT = "function(...): number end";
const std::optional<std::string> EXPECTED_INSERT =
FFlag::DebugLuauDeferredConstraintResolution ? "function(...: number): number end" : "function(...): number end";
auto ac = autocomplete('1');

View File

@ -35,6 +35,7 @@ LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_FASTFLAG(LuauNativeAttribute)
LUAU_FASTFLAG(LuauPreserveLudataRenaming)
LUAU_FASTFLAG(LuauCodegenArmNumToVecFix)
static lua_CompileOptions defaultOptions()
{
@ -809,6 +810,8 @@ TEST_CASE("Pack")
TEST_CASE("Vector")
{
ScopedFastFlag luauCodegenArmNumToVecFix{FFlag::LuauCodegenArmNumToVecFix, true};
lua_CompileOptions copts = defaultOptions();
Luau::CodeGen::CompilationOptions nativeOpts = defaultCodegenOptions();

View File

@ -13,8 +13,6 @@
#include <limits.h>
LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTFLAG(LuauCodegenFastcall3)
LUAU_FASTFLAG(LuauCodegenMathSign)
using namespace Luau::CodeGen;
@ -334,8 +332,6 @@ TEST_SUITE_BEGIN("ConstantFolding");
TEST_CASE_FIXTURE(IrBuilderFixture, "Numeric")
{
ScopedFastFlag luauCodegenMathSign{FFlag::LuauCodegenMathSign, true};
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
@ -2631,8 +2627,6 @@ bb_1:
TEST_CASE_FIXTURE(IrBuilderFixture, "FastCallEffects1")
{
ScopedFastFlag luauCodegenFastcall3{FFlag::LuauCodegenFastcall3, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -2656,8 +2650,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "FastCallEffects2")
{
ScopedFastFlag luauCodegenFastcall3{FFlag::LuauCodegenFastcall3, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -2852,8 +2844,6 @@ bb_1:
TEST_CASE_FIXTURE(IrBuilderFixture, "ExplicitUseOfRegisterInVarargSequence")
{
ScopedFastFlag luauCodegenFastcall3{FFlag::LuauCodegenFastcall3, true};
IrOp entry = build.block(IrBlockKind::Internal);
IrOp exit = build.block(IrBlockKind::Internal);

View File

@ -17,7 +17,6 @@
LUAU_FASTFLAG(LuauCompileUserdataInfo)
LUAU_FASTFLAG(LuauCompileFastcall3)
LUAU_FASTFLAG(LuauCodegenFastcall3)
static std::string getCodegenAssembly(const char* source, bool includeIrTypes = false, int debugLevel = 1)
{
@ -425,8 +424,6 @@ bb_5:
TEST_CASE("DseInitialStackState2")
{
ScopedFastFlag luauCodegenFastcall3{FFlag::LuauCodegenFastcall3, true};
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function foo(a)
@ -945,7 +942,7 @@ bb_bytecode_0:
TEST_CASE("FastcallTypeInferThroughLocal")
{
ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}, {FFlag::LuauCodegenFastcall3, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}};
CHECK_EQ(
"\n" + getCodegenAssembly(
@ -997,7 +994,7 @@ bb_bytecode_1:
TEST_CASE("FastcallTypeInferThroughUpvalue")
{
ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}, {FFlag::LuauCodegenFastcall3, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}};
CHECK_EQ(
"\n" + getCodegenAssembly(
@ -1127,7 +1124,7 @@ bb_bytecode_4:
TEST_CASE("ArgumentTypeRefinement")
{
ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}, {FFlag::LuauCodegenFastcall3, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}};
CHECK_EQ(
"\n" + getCodegenAssembly(
@ -1436,7 +1433,7 @@ bb_2:
TEST_CASE("UnaryTypeResolve")
{
ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}, {FFlag::LuauCodegenFastcall3, true}};
ScopedFastFlag sffs[]{{FFlag::LuauCompileFastcall3, true}};
CHECK_EQ(
"\n" + getCodegenHeader(R"(

View File

@ -316,6 +316,9 @@ TEST_CASE_FIXTURE(Fixture, "clone_free_tables")
TEST_CASE_FIXTURE(BuiltinsFixture, "clone_self_property")
{
// CLI-117082 ModuleTests.clone_self_property we don't infer self correctly, instead replacing it with unknown.
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
fileResolver.source["Module/A"] = R"(
--!nonstrict
local a = {}

View File

@ -415,7 +415,15 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "error_suppression")
CHECK(!isSubtype(any, unk));
}
CHECK(!isSubtype(err, str));
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK(isSubtype(err, str));
}
else
{
CHECK(!isSubtype(err, str));
}
CHECK(!isSubtype(str, err));
CHECK(!isSubtype(err, unk));
@ -701,6 +709,10 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function")
TEST_CASE_FIXTURE(Fixture, "higher_order_function_with_annotation")
{
// CLI-117088 - Inferring the type of a higher order function with an annotation sometimes doesn't fully constrain the type (there are free types
// left over).
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
check(R"(
function apply<a, b>(f: (a) -> b, x)
return f(x)

View File

@ -72,12 +72,12 @@ struct SubtypeFixture : Fixture
ScopePtr rootScope{new Scope(builtinTypes->emptyTypePack)};
ScopePtr moduleScope{new Scope(rootScope)};
Subtyping subtyping = mkSubtyping(rootScope);
Subtyping subtyping = mkSubtyping();
BuiltinTypeFunctions builtinTypeFunctions{};
Subtyping mkSubtyping(const ScopePtr& scope)
Subtyping mkSubtyping()
{
return Subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&iceReporter}, NotNull{scope.get()}};
return Subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&iceReporter}};
}
TypePackId pack(std::initializer_list<TypeId> tys)
@ -184,7 +184,7 @@ struct SubtypeFixture : Fixture
SubtypingResult isSubtype(TypeId subTy, TypeId superTy)
{
return subtyping.isSubtype(subTy, superTy);
return subtyping.isSubtype(subTy, superTy, NotNull{rootScope.get()});
}
TypeId helloType = arena.addType(SingletonType{StringSingleton{"hello"}});
@ -1210,7 +1210,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(...any) -> () <: <T>(T...) -> ()")
TypeId anysToNothing = arena.addType(FunctionType{builtinTypes->anyTypePack, builtinTypes->emptyTypePack});
TypeId genericTToAnys = arena.addType(FunctionType{genericAs, builtinTypes->emptyTypePack});
CHECK_MESSAGE(subtyping.isSubtype(anysToNothing, genericTToAnys).isSubtype, "(...any) -> () <: <T>(T...) -> ()");
CHECK_MESSAGE(isSubtype(anysToNothing, genericTToAnys).isSubtype, "(...any) -> () <: <T>(T...) -> ()");
}
// See https://github.com/luau-lang/luau/issues/767
@ -1220,7 +1220,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(...unknown) -> () <: <T>(T...) -> ()")
arena.addType(FunctionType{arena.addTypePack(VariadicTypePack{builtinTypes->unknownType}), builtinTypes->emptyTypePack});
TypeId genericTToAnys = arena.addType(FunctionType{genericAs, builtinTypes->emptyTypePack});
CHECK_MESSAGE(subtyping.isSubtype(unknownsToNothing, genericTToAnys).isSubtype, "(...unknown) -> () <: <T>(T...) -> ()");
CHECK_MESSAGE(isSubtype(unknownsToNothing, genericTToAnys).isSubtype, "(...unknown) -> () <: <T>(T...) -> ()");
}
TEST_CASE_FIXTURE(SubtypeFixture, "bill")
@ -1233,8 +1233,8 @@ TEST_CASE_FIXTURE(SubtypeFixture, "bill")
{{"a", builtinTypes->stringType}}, TableIndexer{builtinTypes->stringType, builtinTypes->numberType}, TypeLevel{}, nullptr, TableState::Sealed
});
CHECK(subtyping.isSubtype(a, b).isSubtype);
CHECK(subtyping.isSubtype(b, a).isSubtype);
CHECK(isSubtype(a, b).isSubtype);
CHECK(isSubtype(b, a).isSubtype);
}
// TEST_CASE_FIXTURE(SubtypeFixture, "({[string]: number, a: string}) -> () <: ({[string]: number, a: string}) -> ()")
@ -1256,7 +1256,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "fred")
TypeId a = makeTheType();
TypeId b = makeTheType();
CHECK_MESSAGE(subtyping.isSubtype(a, b).isSubtype, "({[string]: number, a: string}) -> () <: ({[string]: number, a: string}) -> ()");
CHECK_MESSAGE(isSubtype(a, b).isSubtype, "({[string]: number, a: string}) -> () <: ({[string]: number, a: string}) -> ()");
}
/*
@ -1273,17 +1273,17 @@ TEST_CASE_FIXTURE(SubtypeFixture, "unknown <: X")
TypeId genericX = arena.addType(GenericType(childScope.get(), "X"));
SubtypingResult usingGlobalScope = subtyping.isSubtype(builtinTypes->unknownType, genericX);
SubtypingResult usingGlobalScope = isSubtype(builtinTypes->unknownType, genericX);
CHECK_MESSAGE(!usingGlobalScope.isSubtype, "Expected " << builtinTypes->unknownType << " </: " << genericX);
Subtyping childSubtyping{mkSubtyping(childScope)};
Subtyping childSubtyping{mkSubtyping()};
SubtypingResult usingChildScope = childSubtyping.isSubtype(builtinTypes->unknownType, genericX);
SubtypingResult usingChildScope = childSubtyping.isSubtype(builtinTypes->unknownType, genericX, NotNull{childScope.get()});
CHECK_MESSAGE(usingChildScope.isSubtype, "Expected " << builtinTypes->unknownType << " <: " << genericX);
Subtyping grandChildSubtyping{mkSubtyping(grandChildScope)};
Subtyping grandChildSubtyping{mkSubtyping()};
SubtypingResult usingGrandChildScope = grandChildSubtyping.isSubtype(builtinTypes->unknownType, genericX);
SubtypingResult usingGrandChildScope = grandChildSubtyping.isSubtype(builtinTypes->unknownType, genericX, NotNull{grandChildScope.get()});
CHECK_MESSAGE(usingGrandChildScope.isSubtype, "Expected " << builtinTypes->unknownType << " <: " << genericX);
}

View File

@ -490,7 +490,6 @@ TEST_CASE_FIXTURE(Fixture, "another_other_higher_order_function")
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
else
{
@ -778,11 +777,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "higher_order_function_4")
end
)");
// This function currently has a bug in the new solver reporting `{T} | {T}` is not a table.
if (FFlag::DebugLuauDeferredConstraintResolution)
LUAU_REQUIRE_ERRORS(result);
else
LUAU_REQUIRE_NO_ERRORS(result);
LUAU_REQUIRE_NO_ERRORS(result);
/*
* mergesort takes two arguments: an array of some type T and a function that takes two Ts.
@ -1678,8 +1673,14 @@ end
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(toString(result.errors[0]), R"(Type function instance add<a, number> depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)");
CHECK_EQ(toString(result.errors[1]), R"(Type function instance add<a, number> depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)");
CHECK_EQ(
toString(result.errors[0]),
R"(Type function instance add<a, number> depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)"
);
CHECK_EQ(
toString(result.errors[1]),
R"(Type function instance add<a, number> depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)"
);
}
else
{
@ -1717,7 +1718,8 @@ TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_th
TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_the_right_time3")
{
// This test regresses in the new solver, but is sort of nonsensical insofar as `foo` is known to be `nil`, so it's "right" to not be able to call it.
// This test regresses in the new solver, but is sort of nonsensical insofar as `foo` is known to be `nil`, so it's "right" to not be able to call
// it.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
@ -1749,7 +1751,10 @@ end
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), R"(Type function instance add<a, number> depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)");
CHECK_EQ(
toString(result.errors[0]),
R"(Type function instance add<a, number> depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)"
);
}
else
{
@ -2477,6 +2482,9 @@ a = function(a, b) return a + b end
TEST_CASE_FIXTURE(BuiltinsFixture, "simple_unannotated_mutual_recursion")
{
// CLI-117118 - TypeInferFunctions.simple_unannotated_mutual_recursion relies on unstable assertions to pass.
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
function even(n)
if n == 0 then
@ -2500,11 +2508,13 @@ end
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(5, result);
// CLI-117117 Constraint solving is incomplete inTypeInferFunctions.simple_unannotated_mutual_recursion
CHECK(get<ConstraintSolvingIncompleteError>(result.errors[0]));
CHECK(
toString(result.errors[1]) ==
"Type pack '*blocked-tp-1*' could not be converted into 'boolean'; type *blocked-tp-1*.tail() (*blocked-tp-1*) is not a subtype of boolean (boolean)"
);
// This check is unstable between different machines and different runs of DCR because it depends on string equality between
// blocked type numbers, which is not guaranteed.
bool r = toString(result.errors[1]) == "Type pack '*blocked-tp-1*' could not be converted into 'boolean'; type *blocked-tp-1*.tail() "
"(*blocked-tp-1*) is not a subtype of boolean (boolean)";
CHECK(r);
CHECK(
toString(result.errors[2]) ==
"Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub"

View File

@ -142,6 +142,8 @@ TEST_CASE_FIXTURE(Fixture, "properties_can_be_polytypes")
TEST_CASE_FIXTURE(Fixture, "properties_can_be_instantiated_polytypes")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local t: { m: (number)->number } = { m = function(x:number) return x+1 end }
local function id<a>(x:a):a return x end
@ -256,8 +258,10 @@ TEST_CASE_FIXTURE(Fixture, "check_mutual_generic_functions_errors")
}
}
TEST_CASE_FIXTURE(Fixture, "generic_functions_in_types")
TEST_CASE_FIXTURE(Fixture, "generic_functions_in_types_old_solver")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
type T = { id: <a>(a) -> a }
local x: T = { id = function<a>(x:a):a return x end }
@ -267,8 +271,23 @@ TEST_CASE_FIXTURE(Fixture, "generic_functions_in_types")
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "generic_functions_in_types_new_solver")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
type T = { read id: <a>(a) -> a }
local x: T = { id = function<a>(x:a):a return x end }
local y: string = x.id("hi")
local z: number = x.id(37)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "generic_factories")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
type T<a> = { id: (a) -> a }
type Factory = { build: <a>() -> T<a> }
@ -290,6 +309,8 @@ TEST_CASE_FIXTURE(Fixture, "generic_factories")
TEST_CASE_FIXTURE(Fixture, "factories_of_generics")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
type T = { id: <a>(a) -> a }
type Factory = { build: () -> T }
@ -445,7 +466,14 @@ TEST_CASE_FIXTURE(Fixture, "dont_leak_generic_types")
local b: boolean = f(true)
)");
LUAU_REQUIRE_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_NO_ERRORS(result);
}
else
{
LUAU_REQUIRE_ERRORS(result);
}
}
TEST_CASE_FIXTURE(Fixture, "dont_leak_inferred_generic_types")
@ -461,7 +489,14 @@ TEST_CASE_FIXTURE(Fixture, "dont_leak_inferred_generic_types")
local y: number = id(37)
end
)");
LUAU_REQUIRE_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_NO_ERRORS(result);
}
else
{
LUAU_REQUIRE_ERRORS(result);
}
}
TEST_CASE_FIXTURE(Fixture, "dont_substitute_bound_types")
@ -737,17 +772,19 @@ return exports
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "instantiated_function_argument_names")
TEST_CASE_FIXTURE(Fixture, "instantiated_function_argument_names_old_solver")
{
CheckResult result = check(R"(
local function f<T, U...>(a: T, ...: U...) end
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
f(1, 2, 3)
CheckResult result = check(R"(
local function f<T, U...>(a: T, ...: U...) end
f(1, 2, 3)
)");
LUAU_REQUIRE_NO_ERRORS(result);
auto ty = findTypeAtPosition(Position(3, 0));
auto ty = findTypeAtPosition(Position(3, 8));
REQUIRE(ty);
ToStringOptions opts;
opts.functionTypeArguments = true;
@ -756,6 +793,8 @@ f(1, 2, 3)
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_generic_types")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
type C = () -> ()
type D = <T>() -> ()
@ -771,6 +810,8 @@ local d: D = c
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_generic_pack")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
type C = () -> ()
type D = <T...>() -> ()
@ -845,6 +886,8 @@ Type 'number' could not be converted into 'string' in an invariant context)";
TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification1")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!strict
type Dispatcher = {
@ -863,6 +906,8 @@ local TheDispatcher: Dispatcher = {
TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification2")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!strict
type Dispatcher = {
@ -881,6 +926,8 @@ local TheDispatcher: Dispatcher = {
TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification3")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!strict
type Dispatcher = {
@ -899,6 +946,8 @@ local TheDispatcher: Dispatcher = {
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_few")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
function test(a: number)
return 1
@ -916,6 +965,8 @@ wrapper(test)
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
function test2(a: number, b: string)
return 1
@ -1370,6 +1421,8 @@ TEST_CASE_FIXTURE(Fixture, "apply_type_function_nested_generics3")
TEST_CASE_FIXTURE(Fixture, "quantify_functions_even_if_they_have_an_explicit_generic")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
function foo<X>(f, x: X)
return f(x)
@ -1381,6 +1434,8 @@ TEST_CASE_FIXTURE(Fixture, "quantify_functions_even_if_they_have_an_explicit_gen
TEST_CASE_FIXTURE(Fixture, "do_not_always_instantiate_generic_intersection_types")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!strict
type Array<T> = { [number]: T }
@ -1415,6 +1470,7 @@ end
TEST_CASE_FIXTURE(BuiltinsFixture, "higher_rank_polymorphism_should_not_accept_instantiated_arguments")
{
ScopedFastFlag sffs[] = {
{FFlag::DebugLuauDeferredConstraintResolution, false},
{FFlag::LuauInstantiateInSubtyping, true},
};
@ -1481,6 +1537,8 @@ TEST_CASE_FIXTURE(Fixture, "missing_generic_type_parameter")
TEST_CASE_FIXTURE(BuiltinsFixture, "generic_type_functions_work_in_subtyping")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
if (!FFlag::DebugLuauDeferredConstraintResolution)
return;

View File

@ -554,17 +554,17 @@ TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
const std::string expected1 = R"(Type
'(nil) -> nil'
'((number?) -> number?) & ((string?) -> string?)'
could not be converted into
'((number?) -> number?) & ((string?) -> string?)'; type (nil) -> nil.arguments()[0] (nil) is not a supertype of ((number?) -> number?) & ((string?) -> string?)[0].arguments()[0][0] (number)
type (nil) -> nil.arguments()[0] (nil) is not a supertype of ((number?) -> number?) & ((string?) -> string?)[1].arguments()[0][0] (string))";
'(nil) -> nil'; type ((number?) -> number?) & ((string?) -> string?)[0].returns()[0][0] (number) is not a subtype of (nil) -> nil.returns()[0] (nil)
type ((number?) -> number?) & ((string?) -> string?)[1].returns()[0][0] (string) is not a subtype of (nil) -> nil.returns()[0] (nil))";
const std::string expected2 = R"(Type
'(number) -> number'
'((number?) -> number?) & ((string?) -> string?)'
could not be converted into
'((number?) -> number?) & ((string?) -> string?)'; type (number) -> number.arguments()[0] (number) is not a supertype of ((number?) -> number?) & ((string?) -> string?)[0].arguments()[0][1] (nil)
type (number) -> number.arguments()[0] (number) is not a supertype of ((number?) -> number?) & ((string?) -> string?)[1].arguments()[0][0] (string)
type (number) -> number.arguments()[0] (number) is not a supertype of ((number?) -> number?) & ((string?) -> string?)[1].arguments()[0][1] (nil)
type (number) -> number.returns()[0] (number) is not a subtype of ((number?) -> number?) & ((string?) -> string?)[1].returns()[0] (string?))";
'(number) -> number'; type ((number?) -> number?) & ((string?) -> string?)[0].returns()[0][1] (nil) is not a subtype of (number) -> number.returns()[0] (number)
type ((number?) -> number?) & ((string?) -> string?)[1].arguments()[0] (string?) is not a supertype of (number) -> number.arguments()[0] (number)
type ((number?) -> number?) & ((string?) -> string?)[1].returns()[0][0] (string) is not a subtype of (number) -> number.returns()[0] (number)
type ((number?) -> number?) & ((string?) -> string?)[1].returns()[0][1] (nil) is not a subtype of (number) -> number.returns()[0] (number))";
CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1]));
}
@ -609,13 +609,12 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::DebugLuauDeferredConstraintResolution)
? "Type "
"'{ p: number?, q: number?, r: number? } & { p: number?, q: string? }'"
" could not be converted into "
"'{ p: nil }'; none of the intersection parts are compatible"
:
R"(Type
const std::string expected =
(FFlag::DebugLuauDeferredConstraintResolution)
? R"(Type '{ p: number?, q: number?, r: number? } & { p: number?, q: string? }' could not be converted into '{ p: nil }'; type { p: number?, q: number?, r: number? } & { p: number?, q: string? }[0][read "p"][0] (number) is not exactly { p: nil }[read "p"] (nil)
type { p: number?, q: number?, r: number? } & { p: number?, q: string? }[1][read "p"][0] (number) is not exactly { p: nil }[read "p"] (nil))"
:
R"(Type
'{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}'
could not be converted into
'{| p: nil |}'; none of the intersection parts are compatible)";
@ -633,28 +632,18 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(
toString(result.errors[0]),
"Type '{| p: number?, q: string? |}' could not be converted into '{| p: string?, q: number? |}'\n"
"caused by:\n"
" Property 'p' is not compatible. Type 'number?' could not be converted into 'string?'\n"
"caused by:\n"
" Not all union options are compatible. Type 'number' could not be converted into 'string?'\n"
"caused by:\n"
" None of the union options are compatible. For example: Type 'number' could not be converted into 'string' in an invariant context"
);
CHECK_EQ(
toString(result.errors[1]),
"Type '{| p: number?, q: string? |}' could not be converted into '{| p: string?, q: number? |}'\n"
"caused by:\n"
" Property 'q' is not compatible. Type 'string?' could not be converted into 'number?'\n"
"caused by:\n"
" Not all union options are compatible. Type 'string' could not be converted into 'number?'\n"
"caused by:\n"
" None of the union options are compatible. For example: Type 'string' could not be converted into 'number' in an invariant context"
R"(Type
'{ p: number?, q: any } & { p: unknown, q: string? }'
could not be converted into
'{ p: string?, q: number? }'; type { p: number?, q: any } & { p: unknown, q: string? }[0][read "p"] (number?) is not exactly { p: string?, q: number? }[read "p"][0] (string)
type { p: number?, q: any } & { p: unknown, q: string? }[0][read "p"][0] (number) is not exactly { p: string?, q: number? }[read "p"] (string?)
type { p: number?, q: any } & { p: unknown, q: string? }[0][read "q"] (any) is not exactly { p: string?, q: number? }[read "q"] (number?)
type { p: number?, q: any } & { p: unknown, q: string? }[1][read "p"] (unknown) is not exactly { p: string?, q: number? }[read "p"] (string?)
type { p: number?, q: any } & { p: unknown, q: string? }[1][read "q"] (string?) is not exactly { p: string?, q: number? }[read "q"][0] (number)
type { p: number?, q: any } & { p: unknown, q: string? }[1][read "q"][0] (string) is not exactly { p: string?, q: number? }[read "q"] (number?))",
toString(result.errors[0])
);
}
else
@ -689,18 +678,42 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::DebugLuauDeferredConstraintResolution) ?
R"(Type
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(
R"(Type
'((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })'
could not be converted into
'(number?) -> { p: number, q: number, r: number }'; none of the intersection parts are compatible)"
:
R"(Type
'(nil) -> { p: number, q: number, r: number }'; type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[0].returns()[0][0] ({ p: number }) is not a subtype of (nil) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number })
type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[0].returns()[0][1] ({ q: number }) is not a subtype of (nil) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number })
type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].returns()[0][0] ({ p: number }) is not a subtype of (nil) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number })
type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].returns()[0][1] ({ r: number }) is not a subtype of (nil) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }))",
toString(result.errors[0])
);
CHECK_EQ(
R"(Type
'((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })'
could not be converted into
'(number?) -> { p: number, q: number, r: number }'; type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[0].returns()[0][0] ({ p: number }) is not a subtype of (number?) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number })
type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[0].returns()[0][1] ({ q: number }) is not a subtype of (number?) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number })
type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].arguments()[0] (string?) is not a supertype of (number?) -> { p: number, q: number, r: number }.arguments()[0][0] (number)
type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].returns()[0][0] ({ p: number }) is not a subtype of (number?) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number })
type ((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })[1].returns()[0][1] ({ r: number }) is not a subtype of (number?) -> { p: number, q: number, r: number }.returns()[0] ({ p: number, q: number, r: number }))",
toString(result.errors[1])
);
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(
R"(Type
'((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})'
could not be converted into
'(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
'(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible)",
toString(result.errors[0])
);
}
}
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
@ -713,13 +726,19 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
end
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(0, result);
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'((number?) -> a | number) & ((string?) -> a | string)'
could not be converted into
'(number?) -> a'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(expected, toString(result.errors[0]));
}
}
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
@ -733,12 +752,20 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_NO_ERRORS(result);
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'((a?) -> a | b) & ((c?) -> b | c)'
could not be converted into
'(a?) -> (a & c) | b'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(expected, toString(result.errors[0]));
}
}
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
@ -751,13 +778,35 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
end
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(
R"(Type
'((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))'
could not be converted into
'(nil, a...) -> (nil, b...)'; type ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))[0].returns()[0][0] (number) is not a subtype of (nil, a...) -> (nil, b...).returns()[0] (nil)
type ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))[1].returns()[0][0] (string) is not a subtype of (nil, a...) -> (nil, b...).returns()[0] (nil))",
toString(result.errors[0])
);
CHECK_EQ(
R"(Type
'((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))'
could not be converted into
'(nil, b...) -> (nil, a...)'; type ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))[0].returns()[0][0] (number) is not a subtype of (nil, b...) -> (nil, a...).returns()[0] (nil)
type ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))[1].returns()[0][0] (string) is not a subtype of (nil, b...) -> (nil, a...).returns()[0] (nil))",
toString(result.errors[1])
);
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))'
could not be converted into
'(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(expected, toString(result.errors[0]));
}
}
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
@ -858,12 +907,34 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
const std::string expected1 = R"(Type
'((never) -> string?) & ((number) -> number?)'
could not be converted into
'(never) -> nil'; type ((never) -> string?) & ((number) -> number?)[0].returns()[0][0] (number) is not a subtype of (never) -> nil.returns()[0] (nil)
type ((never) -> string?) & ((number) -> number?)[1].returns()[0][0] (string) is not a subtype of (never) -> nil.returns()[0] (nil))";
const std::string expected2 = R"(Type
'((never) -> string?) & ((number) -> number?)'
could not be converted into
'(number?) -> nil'; type ((never) -> string?) & ((number) -> number?)[0].arguments()[0] (number) is not a supertype of (number?) -> nil.arguments()[0][1] (nil)
type ((never) -> string?) & ((number) -> number?)[0].returns()[0][0] (number) is not a subtype of (number?) -> nil.returns()[0] (nil)
type ((never) -> string?) & ((number) -> number?)[1].arguments()[0] (never) is not a supertype of (number?) -> nil.arguments()[0][0] (number)
type ((never) -> string?) & ((number) -> number?)[1].arguments()[0] (never) is not a supertype of (number?) -> nil.arguments()[0][1] (nil)
type ((never) -> string?) & ((number) -> number?)[1].returns()[0][0] (string) is not a subtype of (number?) -> nil.returns()[0] (nil))";
CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'((never) -> string?) & ((number) -> number?)'
could not be converted into
'(number?) -> nil'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(expected, toString(result.errors[0]));
}
}
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_variadics")
@ -999,6 +1070,10 @@ could not be converted into
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables")
{
// CLI-117121 - Intersection of types are not compatible with the equivalent alias
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CheckResult result = check(R"(
@ -1165,10 +1240,9 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_intersection_types")
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
LUAU_REQUIRE_ERROR_COUNT(3, result);
// TODO? We do not simplify types from explicit annotations.
CHECK_EQ("({| x: number |} & {| x: string |}) -> {| x: number |} & {| x: string |}", toString(requireType("f")));
CHECK_EQ("(never) -> { x: number } & { x: string }", toString(requireType("f")));
}
TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_intersection_types_2")

View File

@ -50,6 +50,9 @@ TEST_CASE_FIXTURE(NegationFixture, "string_is_not_a_subtype_of_negated_string")
TEST_CASE_FIXTURE(Fixture, "cofinite_strings_can_be_compared_for_equality")
{
// CLI-117082 Cofinite strings cannot be compared for equality because normalization produces a large type with cycles
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
function f(e)
if e == 'strictEqual' then

View File

@ -1500,6 +1500,9 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "narrow_from_subclasses_of_instance_or
TEST_CASE_FIXTURE(RefinementClassFixture, "x_as_any_if_x_is_instance_elseif_x_is_table")
{
// CLI-117136 - this code doesn't finish constraint solving and has blocked types in the output
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
--!nonstrict
@ -1588,6 +1591,9 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "isa_type_refinement_must_be_known_ahe
TEST_CASE_FIXTURE(RefinementClassFixture, "x_is_not_instance_or_else_not_part")
{
// CLI-117135 - RefinementTests.x_is_not_instance_or_else_not_part not correctly applying refinements to a function parameter
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
local function f(x: Part | Folder | string)
if typeof(x) ~= "Instance" or not x:IsA("Part") then
@ -1807,6 +1813,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_clone_it")
TEST_CASE_FIXTURE(RefinementClassFixture, "refine_a_param_that_got_resolved_during_constraint_solving_stage")
{
// CLI-117134 - Applying a refinement causes an optional value access error.
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
type Id<T> = T

View File

@ -15,9 +15,10 @@
using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
LUAU_FASTFLAG(LuauAcceptIndexingTableUnionsIntersections)
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
@ -4794,4 +4795,41 @@ end
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "indexing_branching_table")
{
ScopedFastFlag sff{FFlag::LuauAcceptIndexingTableUnionsIntersections, true};
CheckResult result = check(R"(
local test = if true then { "meow", "woof" } else { 4, 81 }
local test2 = test[1]
)");
LUAU_REQUIRE_NO_ERRORS(result);
// unfortunate type duplication in the union
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("number | string | string" == toString(requireType("test2")));
else
CHECK("number | string" == toString(requireType("test2")));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "indexing_branching_table2")
{
ScopedFastFlag sff{FFlag::LuauAcceptIndexingTableUnionsIntersections, true};
CheckResult result = check(R"(
local test = if true then {} else {}
local test2 = test[1]
)");
LUAU_REQUIRE_NO_ERRORS(result);
// unfortunate type duplication in the union
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("unknown | unknown" == toString(requireType("test2")));
else
CHECK("any" == toString(requireType("test2")));
}
TEST_SUITE_END();

View File

@ -459,7 +459,9 @@ TEST_CASE_FIXTURE(TypeStateFixture, "typestates_preserve_error_suppression")
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)
// unconditional return
// CLI-117098 Type states with error suppressing properties doesn't infer the correct type for properties.
if (!FFlag::DebugLuauDeferredConstraintResolution || FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(

View File

@ -8,7 +8,8 @@
using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauAcceptIndexingTableUnionsIntersections)
TEST_SUITE_BEGIN("UnionTypes");
@ -636,7 +637,12 @@ TEST_CASE_FIXTURE(Fixture, "indexing_into_a_cyclic_union_doesnt_crash")
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
// this is a cyclic union of number arrays, so it _is_ a table, even if it's a nonsense type.
// no need to generate a NotATable error here.
if (FFlag::LuauAcceptIndexingTableUnionsIntersections)
LUAU_REQUIRE_NO_ERRORS(result);
else
LUAU_REQUIRE_ERROR_COUNT(1, result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect")

View File

@ -193,12 +193,20 @@ TEST_CASE_FIXTURE(Fixture, "call_never")
TEST_CASE_FIXTURE(Fixture, "assign_to_local_which_is_never")
{
// CLI-117119 - What do we do about assigning to never?
CheckResult result = check(R"(
local t: never
t = 3
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
}
else
{
LUAU_REQUIRE_NO_ERRORS(result);
}
}
TEST_CASE_FIXTURE(Fixture, "assign_to_global_which_is_never")
@ -257,6 +265,9 @@ TEST_CASE_FIXTURE(Fixture, "pick_never_from_variadic_type_pack")
TEST_CASE_FIXTURE(Fixture, "index_on_union_of_tables_for_properties_that_is_never")
{
// CLI-117116 - We are erroneously warning when passing a valid table literal where we expect a union of tables.
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
type Disjoint = {foo: never, bar: unknown, tag: "ok"} | {foo: never, baz: unknown, tag: "err"}
@ -274,6 +285,9 @@ TEST_CASE_FIXTURE(Fixture, "index_on_union_of_tables_for_properties_that_is_neve
TEST_CASE_FIXTURE(Fixture, "index_on_union_of_tables_for_properties_that_is_sorta_never")
{
// CLI-117116 - We are erroneously warning when passing a valid table literal where we expect a union of tables.
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
type Disjoint = {foo: string, bar: unknown, tag: "ok"} | {foo: never, baz: unknown, tag: "err"}

View File

@ -15,15 +15,28 @@ TEST_SUITE_BEGIN("VisitType");
TEST_CASE_FIXTURE(Fixture, "throw_when_limit_is_exceeded")
{
ScopedFastInt sfi{FInt::LuauVisitRecursionLimit, 3};
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CheckResult result = check(R"(
local t : {a: {b: {c: {d: {e: boolean}}}}}
)");
ScopedFastInt sfi{FInt::LuauVisitRecursionLimit, 3};
TypeId tType = requireType("t");
CheckResult result = check(R"(
local t : {a: {b: {c: {d: {e: boolean}}}}}
)");
CHECK_THROWS_AS(toString(tType), RecursionLimitException);
}
else
{
ScopedFastInt sfi{FInt::LuauVisitRecursionLimit, 3};
TypeId tType = requireType("t");
CheckResult result = check(R"(
local t : {a: {b: {c: {d: {e: boolean}}}}}
)");
CHECK_THROWS_AS(toString(tType), RecursionLimitException);
TypeId tType = requireType("t");
CHECK_THROWS_AS(toString(tType), RecursionLimitException);
}
}
TEST_CASE_FIXTURE(Fixture, "dont_throw_when_limit_is_high_enough")

View File

@ -164,4 +164,23 @@ do
assert(larget[vector(-0, 0, 0)] == 42)
end
local function numvectemporary()
local proptab = {}
proptab.vec3compsum = function(vec: vector)
local num = vec.X + vec.Y
local tmp = vec / num
local num2 = num * 2
return tmp, num2
end
local a, b = proptab.vec3compsum(vector(2, 6, 0))
assert(a.X == 0.25)
assert(a.Y == 0.75)
assert(b == 16)
end
numvectemporary()
return 'OK'

View File

@ -51,7 +51,7 @@ static bool skipFastFlag(const char* flagName)
if (strncmp(flagName, "Debug", 5) == 0)
return true;
if (strcmp(flagName, "StudioReportLuauAny") == 0)
if (strcmp(flagName, "StudioReportLuauAny2") == 0)
return true;
return false;

View File

@ -1,45 +0,0 @@
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg
AutocompleteTest.autocomplete_string_singletons
AutocompleteTest.suggest_table_keys
AutocompleteTest.type_correct_suggestion_for_overloads
AutocompleteTest.type_correct_suggestion_in_table
GenericsTests.do_not_always_instantiate_generic_intersection_types
GenericsTests.error_detailed_function_mismatch_generic_pack
GenericsTests.error_detailed_function_mismatch_generic_types
GenericsTests.factories_of_generics
GenericsTests.generic_argument_count_too_few
GenericsTests.generic_argument_count_too_many
GenericsTests.generic_factories
GenericsTests.generic_functions_in_types
GenericsTests.generic_type_functions_work_in_subtyping
GenericsTests.generic_type_pack_unification1
GenericsTests.generic_type_pack_unification2
GenericsTests.generic_type_pack_unification3
GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments
GenericsTests.instantiated_function_argument_names
GenericsTests.properties_can_be_instantiated_polytypes
GenericsTests.quantify_functions_even_if_they_have_an_explicit_generic
IntersectionTypes.intersect_metatables
IntersectionTypes.intersect_saturate_overloaded_functions
IntersectionTypes.intersection_of_tables
IntersectionTypes.intersection_of_tables_with_top_properties
IntersectionTypes.less_greedy_unification_with_intersection_types
IntersectionTypes.overloaded_functions_mentioning_generic
IntersectionTypes.overloaded_functions_mentioning_generic_packs
IntersectionTypes.overloaded_functions_mentioning_generics
IntersectionTypes.overloaded_functions_returning_intersections
IntersectionTypes.overloadeded_functions_with_never_arguments
ModuleTests.clone_self_property
Negations.cofinite_strings_can_be_compared_for_equality
Normalize.higher_order_function_with_annotation
RefinementTest.refine_a_param_that_got_resolved_during_constraint_solving_stage
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
RefinementTest.x_is_not_instance_or_else_not_part
TypeInferFunctions.simple_unannotated_mutual_recursion
TypeInferUnknownNever.assign_to_local_which_is_never
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never
TypePackTests.fuzz_typepack_iter_follow_2
TypeStatesTest.typestates_preserve_error_suppression_properties
VisitType.throw_when_limit_is_exceeded