Sync to upstream/release/561 (#820)

* Fix a potential debugger crash by adding checks for invalid stack
index values

---------

Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
Andy Friesen 2023-01-27 14:28:31 -08:00 committed by GitHub
parent 4a2e8013c7
commit f763f4c948
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 2334 additions and 762 deletions

View file

@ -89,6 +89,7 @@ struct NameConstraint
{
TypeId namedType;
std::string name;
bool synthetic = false;
};
// target ~ inst target

View file

@ -80,6 +80,8 @@ struct ConstraintGraphBuilder
// A mapping of AST node to TypePackId.
DenseHashMap<const AstExpr*, TypePackId> astTypePacks{nullptr};
DenseHashMap<const AstExpr*, TypeId> astExpectedTypes{nullptr};
// If the node was applied as a function, this is the unspecialized type of
// that expression.
DenseHashMap<const void*, TypeId> astOriginalCallTypes{nullptr};
@ -88,6 +90,8 @@ struct ConstraintGraphBuilder
// overload that was selected.
DenseHashMap<const void*, TypeId> astOverloadResolvedTypes{nullptr};
// Types resolved from type annotations. Analogous to astTypes.
DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr};
@ -207,6 +211,7 @@ struct ConstraintGraphBuilder
Inference check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
Inference check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType);
Inference check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert);
Inference check(const ScopePtr& scope, AstExprInterpString* interpString);
Inference check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType);
std::tuple<TypeId, TypeId, ConnectiveId> checkBinary(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);

View file

@ -253,7 +253,8 @@ struct NormalizedType
TypeId threads;
// The (meta)table part of the type.
// Each element of this set is a (meta)table type.
// Each element of this set is a (meta)table type, or the top `table` type.
// An empty set denotes never.
TypeIds tables;
// The function part of the type.

View file

@ -117,6 +117,7 @@ struct PrimitiveType
String,
Thread,
Function,
Table,
};
Type type;
@ -651,6 +652,7 @@ public:
const TypeId threadType;
const TypeId functionType;
const TypeId classType;
const TypeId tableType;
const TypeId trueType;
const TypeId falseType;
const TypeId anyType;

View file

@ -9,7 +9,6 @@
#include "Luau/Type.h"
LUAU_FASTINT(LuauVisitRecursionLimit)
LUAU_FASTFLAG(LuauCompleteVisitor);
namespace Luau
{
@ -322,8 +321,6 @@ struct GenericTypeVisitor
if (visit(ty, *ntv))
traverse(ntv->ty);
}
else if (!FFlag::LuauCompleteVisitor)
return visit_detail::unsee(seen, ty);
else
LUAU_ASSERT(!"GenericTypeVisitor::traverse(TypeId) is not exhaustive!");

View file

@ -15,9 +15,7 @@
#include <algorithm>
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauBuiltInMetatableNoBadSynthetic, false)
LUAU_FASTFLAG(LuauReportShadowedTypeAlias)
/** FIXME: Many of these type definitions are not quite completely accurate.
*
@ -252,11 +250,8 @@ void registerBuiltinTypes(Frontend& frontend)
frontend.getGlobalScope()->addBuiltinTypeBinding("string", TypeFun{{}, frontend.builtinTypes->stringType});
frontend.getGlobalScope()->addBuiltinTypeBinding("boolean", TypeFun{{}, frontend.builtinTypes->booleanType});
frontend.getGlobalScope()->addBuiltinTypeBinding("thread", TypeFun{{}, frontend.builtinTypes->threadType});
if (FFlag::LuauUnknownAndNeverType)
{
frontend.getGlobalScope()->addBuiltinTypeBinding("unknown", TypeFun{{}, frontend.builtinTypes->unknownType});
frontend.getGlobalScope()->addBuiltinTypeBinding("never", TypeFun{{}, frontend.builtinTypes->neverType});
}
frontend.getGlobalScope()->addBuiltinTypeBinding("unknown", TypeFun{{}, frontend.builtinTypes->unknownType});
frontend.getGlobalScope()->addBuiltinTypeBinding("never", TypeFun{{}, frontend.builtinTypes->neverType});
}
void registerBuiltinGlobals(TypeChecker& typeChecker)
@ -315,7 +310,7 @@ void registerBuiltinGlobals(TypeChecker& typeChecker)
FunctionType{
{genericMT},
{},
arena.addTypePack(TypePack{{FFlag::LuauUnknownAndNeverType ? tabTy : tableMetaMT, genericMT}}),
arena.addTypePack(TypePack{{tabTy, genericMT}}),
arena.addTypePack(TypePack{{tableMetaMT}})
}
), "@luau"
@ -357,8 +352,7 @@ void registerBuiltinGlobals(Frontend& frontend)
LUAU_ASSERT(!frontend.globalTypes.types.isFrozen());
LUAU_ASSERT(!frontend.globalTypes.typePacks.isFrozen());
if (FFlag::LuauReportShadowedTypeAlias)
registerBuiltinTypes(frontend);
registerBuiltinTypes(frontend);
TypeArena& arena = frontend.globalTypes;
NotNull<BuiltinTypes> builtinTypes = frontend.builtinTypes;
@ -409,7 +403,7 @@ void registerBuiltinGlobals(Frontend& frontend)
FunctionType{
{genericMT},
{},
arena.addTypePack(TypePack{{FFlag::LuauUnknownAndNeverType ? tabTy : tableMetaMT, genericMT}}),
arena.addTypePack(TypePack{{tabTy, genericMT}}),
arena.addTypePack(TypePack{{tableMetaMT}})
}
), "@luau"
@ -537,11 +531,8 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
{
auto [paramPack, _predicates] = withPredicate;
if (FFlag::LuauUnknownAndNeverType)
{
if (size(paramPack) < 2 && finite(paramPack))
return std::nullopt;
}
if (size(paramPack) < 2 && finite(paramPack))
return std::nullopt;
TypeArena& arena = typechecker.currentModule->internalTypes;
@ -550,11 +541,8 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
TypeId target = follow(expectedArgs[0]);
TypeId mt = follow(expectedArgs[1]);
if (FFlag::LuauUnknownAndNeverType)
{
typechecker.tablify(target);
typechecker.tablify(mt);
}
typechecker.tablify(target);
typechecker.tablify(mt);
if (const auto& tab = get<TableType>(target))
{
@ -564,9 +552,6 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
}
else
{
if (!FFlag::LuauUnknownAndNeverType)
typechecker.tablify(mt);
const TableType* mtTtv = get<TableType>(mt);
MetatableType mtv{target, mt};
if ((tab->name || tab->syntheticName) && (mtTtv && (mtTtv->name || mtTtv->syntheticName)))
@ -583,12 +568,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
TypeId mtTy = arena.addType(mtv);
if (expr.args.size < 1)
{
if (FFlag::LuauUnknownAndNeverType)
return std::nullopt;
else
return WithPredicate<TypePackId>{};
}
return std::nullopt;
if (!expr.self)
{
@ -635,20 +615,10 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionAssert(
if (head.size() > 0)
{
auto [ty, ok] = typechecker.pickTypesFromSense(head[0], true, typechecker.builtinTypes->nilType);
if (FFlag::LuauUnknownAndNeverType)
{
if (get<NeverType>(*ty))
head = {*ty};
else
head[0] = *ty;
}
if (get<NeverType>(*ty))
head = {*ty};
else
{
if (!ty)
head = {typechecker.nilType};
else
head[0] = *ty;
}
head[0] = *ty;
}
return WithPredicate<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};

View file

@ -414,6 +414,10 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
std::vector<TypeId> varTypes;
varTypes.reserve(local->vars.size);
// Used to name the first value type, even if it's not placed in varTypes,
// for the purpose of synthetic name attribution.
std::optional<TypeId> firstValueType;
for (AstLocal* local : local->vars)
{
TypeId ty = nullptr;
@ -456,6 +460,9 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
else
varTypes[i] = exprType;
}
if (i == 0)
firstValueType = exprType;
}
else
{
@ -488,6 +495,22 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
}
}
if (local->vars.size == 1 && local->values.size == 1 && firstValueType)
{
AstLocal* var = local->vars.data[0];
AstExpr* value = local->values.data[0];
if (value->is<AstExprTable>())
addConstraint(scope, value->location, NameConstraint{*firstValueType, var->name.value, /*synthetic*/ true});
else if (const AstExprCall* call = value->as<AstExprCall>())
{
if (const AstExprGlobal* global = call->func->as<AstExprGlobal>(); global && global->name == "setmetatable")
{
addConstraint(scope, value->location, NameConstraint{*firstValueType, var->name.value, /*synthetic*/ true});
}
}
}
for (size_t i = 0; i < local->vars.size; ++i)
{
AstLocal* l = local->vars.data[i];
@ -1138,7 +1161,13 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
TypeId resultTy = arena->addType(mtv);
if (AstExprLocal* targetLocal = targetExpr->as<AstExprLocal>())
{
scope->bindings[targetLocal->local].typeId = resultTy;
auto def = dfg->getDef(targetLocal->local);
if (def)
scope->dcrRefinements[*def] = resultTy; // TODO: typestates: track this as an assignment
}
return InferencePack{arena->addTypePack({resultTy}), std::move(returnConnectives)};
}
@ -1248,6 +1277,8 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr, st
result = check(scope, ifElse, expectedType);
else if (auto typeAssert = expr->as<AstExprTypeAssertion>())
result = check(scope, typeAssert);
else if (auto interpString = expr->as<AstExprInterpString>())
result = check(scope, interpString);
else if (auto err = expr->as<AstExprError>())
{
// Open question: Should we traverse into this?
@ -1264,6 +1295,8 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr, st
LUAU_ASSERT(result.ty);
astTypes[expr] = result.ty;
if (expectedType)
astExpectedTypes[expr] = *expectedType;
return result;
}
@ -1509,6 +1542,14 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTypeAssert
return Inference{resolveType(scope, typeAssert->annotation, /* inTypeArguments */ false)};
}
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprInterpString* interpString)
{
for (AstExpr* expr : interpString->expressions)
check(scope, expr);
return Inference{builtinTypes->stringType};
}
std::tuple<TypeId, TypeId, ConnectiveId> ConstraintGraphBuilder::checkBinary(
const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType)
{
@ -1551,7 +1592,7 @@ std::tuple<TypeId, TypeId, ConnectiveId> ConstraintGraphBuilder::checkBinary(
else if (typeguard->type == "boolean")
discriminantTy = builtinTypes->threadType;
else if (typeguard->type == "table")
discriminantTy = builtinTypes->neverType; // TODO: replace with top table type
discriminantTy = builtinTypes->tableType;
else if (typeguard->type == "function")
discriminantTy = builtinTypes->functionType;
else if (typeguard->type == "userdata")

View file

@ -883,7 +883,12 @@ bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNull<const Constr
return true;
if (TableType* ttv = getMutable<TableType>(target))
ttv->name = c.name;
{
if (c.synthetic && !ttv->name)
ttv->syntheticName = c.name;
else
ttv->name = c.name;
}
else if (MetatableType* mtv = getMutable<MetatableType>(target))
mtv->syntheticName = c.name;
else if (get<IntersectionType>(target) || get<UnionType>(target))
@ -1594,7 +1599,7 @@ bool ConstraintSolver::tryDispatchIterableFunction(
const TypeId firstIndex = isNil(firstIndexTy) ? arena->freshType(constraint->scope) // FIXME: Surely this should be a union (free | nil)
: firstIndexTy;
// nextTy : (tableTy, indexTy?) -> (indexTy, valueTailTy...)
// nextTy : (tableTy, indexTy?) -> (indexTy?, valueTailTy...)
const TypePackId nextArgPack = arena->addTypePack({tableTy, arena->addType(UnionType{{firstIndex, builtinTypes->nilType}})});
const TypePackId valueTailTy = arena->addTypePack(FreeTypePack{constraint->scope});
const TypePackId nextRetPack = arena->addTypePack(TypePack{{firstIndex}, valueTailTy});
@ -1602,7 +1607,25 @@ bool ConstraintSolver::tryDispatchIterableFunction(
const TypeId expectedNextTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope, nextArgPack, nextRetPack});
unify(nextTy, expectedNextTy, constraint->scope);
pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{c.variables, nextRetPack});
auto it = begin(nextRetPack);
std::vector<TypeId> modifiedNextRetHead;
// The first value is never nil in the context of the loop, even if it's nil
// in the next function's return type, because the loop will not advance if
// it's nil.
if (it != end(nextRetPack))
{
TypeId firstRet = *it;
TypeId modifiedFirstRet = stripNil(builtinTypes, *arena, firstRet);
modifiedNextRetHead.push_back(modifiedFirstRet);
++it;
}
for (; it != end(nextRetPack); ++it)
modifiedNextRetHead.push_back(*it);
TypePackId modifiedNextRetPack = arena->addTypePack(std::move(modifiedNextRetHead), it.tail());
pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{c.variables, modifiedNextRetPack});
return true;
}
@ -1649,8 +1672,8 @@ std::optional<TypeId> ConstraintSolver::lookupTableProp(TypeId subjectType, cons
resultType = parts[0];
else if (parts.size() > 1)
resultType = arena->addType(UnionType{std::move(parts)});
else
LUAU_ASSERT(false); // parts.size() == 0
// otherwise, nothing: no matching property
}
else if (auto itv = get<IntersectionType>(subjectType))
{
@ -1662,8 +1685,8 @@ std::optional<TypeId> ConstraintSolver::lookupTableProp(TypeId subjectType, cons
resultType = parts[0];
else if (parts.size() > 1)
resultType = arena->addType(IntersectionType{std::move(parts)});
else
LUAU_ASSERT(false); // parts.size() == 0
// otherwise, nothing: no matching property
}
return resultType;

View file

@ -1,8 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauUnknownAndNeverType)
namespace Luau
{
@ -115,6 +113,7 @@ declare function typeof<T>(value: T): string
-- `assert` has a magic function attached that will give more detailed type information
declare function assert<T>(value: T, errorMessage: string?): T
declare function error<T>(message: T, level: number?): never
declare function tostring<T>(value: T): string
declare function tonumber<T>(value: T, radix: number?): number?
@ -199,14 +198,7 @@ declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
std::string getBuiltinDefinitionSource()
{
std::string result = kBuiltinDefinitionLuaSrc;
if (FFlag::LuauUnknownAndNeverType)
result += "declare function error<T>(message: T, level: number?): never\n";
else
result += "declare function error<T>(message: T, level: number?)\n";
return result;
}

View file

@ -922,10 +922,10 @@ ModulePtr Frontend::check(
for (TypeError& e : cs.errors)
result->errors.emplace_back(std::move(e));
result->scopes = std::move(cgb.scopes);
result->astTypes = std::move(cgb.astTypes);
result->astTypePacks = std::move(cgb.astTypePacks);
result->astExpectedTypes = std::move(cgb.astExpectedTypes);
result->astOriginalCallTypes = std::move(cgb.astOriginalCallTypes);
result->astOverloadResolvedTypes = std::move(cgb.astOverloadResolvedTypes);
result->astResolvedTypes = std::move(cgb.astResolvedTypes);

View file

@ -19,7 +19,7 @@ LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
LUAU_FASTFLAGVARIABLE(LuauNegatedClassTypes, false);
LUAU_FASTFLAGVARIABLE(LuauNegatedFunctionTypes, false);
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauNegatedTableTypes, false);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauUninhabitedSubAnything2)
@ -448,8 +448,20 @@ static bool areNormalizedFunctions(const NormalizedFunctionType& tys)
static bool areNormalizedTables(const TypeIds& tys)
{
for (TypeId ty : tys)
if (!get<TableType>(ty) && !get<MetatableType>(ty))
{
if (get<TableType>(ty) || get<MetatableType>(ty))
continue;
const PrimitiveType* pt = get<PrimitiveType>(ty);
if (!pt)
return false;
if (pt->type == PrimitiveType::Table && FFlag::LuauNegatedTableTypes)
continue;
return false;
}
return true;
}
@ -1216,7 +1228,25 @@ void Normalizer::unionTablesWithTable(TypeIds& heres, TypeId there)
void Normalizer::unionTables(TypeIds& heres, const TypeIds& theres)
{
for (TypeId there : theres)
unionTablesWithTable(heres, there);
{
if (FFlag::LuauNegatedTableTypes)
{
if (there == builtinTypes->tableType)
{
heres.clear();
heres.insert(there);
return;
}
else
{
unionTablesWithTable(heres, there);
}
}
else
{
unionTablesWithTable(heres, there);
}
}
}
// So why `ignoreSmallerTyvars`?
@ -1375,6 +1405,11 @@ bool Normalizer::unionNormalWithTy(NormalizedType& here, TypeId there, int ignor
LUAU_ASSERT(FFlag::LuauNegatedFunctionTypes);
here.functions.resetToTop();
}
else if (ptv->type == PrimitiveType::Table && FFlag::LuauNegatedTableTypes)
{
here.tables.clear();
here.tables.insert(there);
}
else
LUAU_ASSERT(!"Unreachable");
}
@ -1504,6 +1539,21 @@ std::optional<NormalizedType> Normalizer::negateNormal(const NormalizedType& her
return std::nullopt;
}
/*
* It is not possible to negate an arbitrary table type, because function
* types are not runtime-testable. Thus, we prohibit negation of anything
* other than `table` and `never`.
*/
if (FFlag::LuauNegatedTableTypes)
{
if (here.tables.empty())
result.tables.insert(builtinTypes->tableType);
else if (here.tables.size() == 1 && here.tables.front() == builtinTypes->tableType)
result.tables.clear();
else
return std::nullopt;
}
// TODO: negating tables
// TODO: negating tyvars?
@ -1571,6 +1621,10 @@ void Normalizer::subtractPrimitive(NormalizedType& here, TypeId ty)
case PrimitiveType::Function:
here.functions.resetToNever();
break;
case PrimitiveType::Table:
LUAU_ASSERT(FFlag::LuauNegatedTableTypes);
here.tables.clear();
break;
}
}
@ -1995,6 +2049,11 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
if (sharedState->counters.recursionLimit > 0 && sharedState->counters.recursionLimit < sharedState->counters.recursionCount)
return std::nullopt;
if (isPrim(here, PrimitiveType::Table))
return there;
else if (isPrim(there, PrimitiveType::Table))
return here;
TypeId htable = here;
TypeId hmtable = nullptr;
if (const MetatableType* hmtv = get<MetatableType>(here))
@ -2522,6 +2581,7 @@ bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there)
NormalizedStringType strings = std::move(here.strings);
NormalizedFunctionType functions = std::move(here.functions);
TypeId threads = here.threads;
TypeIds tables = std::move(here.tables);
clearNormal(here);
@ -2540,6 +2600,11 @@ bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there)
LUAU_ASSERT(FFlag::LuauNegatedFunctionTypes);
here.functions = std::move(functions);
}
else if (ptv->type == PrimitiveType::Table)
{
LUAU_ASSERT(FFlag::LuauNegatedTableTypes);
here.tables = std::move(tables);
}
else
LUAU_ASSERT(!"Unreachable");
}

View file

@ -12,7 +12,6 @@ LUAU_FASTFLAGVARIABLE(LuauSubstitutionFixMissingFields, false)
LUAU_FASTFLAG(LuauClonePublicInterfaceLess)
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAGVARIABLE(LuauClassTypeVarsInSubstitution, false)
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauSubstitutionReentrant, false)
namespace Luau
@ -184,7 +183,7 @@ TarjanResult Tarjan::loop()
if (currEdge == -1)
{
++childCount;
if (childLimit > 0 && (FFlag::LuauUnknownAndNeverType ? childLimit <= childCount : childLimit < childCount))
if (childLimit > 0 && childLimit <= childCount)
return TarjanResult::TooManyChildren;
stack.push_back(index);

View file

@ -14,7 +14,6 @@
#include <stdexcept>
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauFunctionReturnStringificationFixup, false)
/*
@ -444,6 +443,9 @@ struct TypeStringifier
case PrimitiveType::Function:
state.emit("function");
return;
case PrimitiveType::Table:
state.emit("table");
return;
default:
LUAU_ASSERT(!"Unknown primitive type");
throw InternalCompilerError("Unknown primitive type " + std::to_string(ptv.type));
@ -823,7 +825,7 @@ struct TypeStringifier
void operator()(TypeId, const ErrorType& tv)
{
state.result.error = true;
state.emit(FFlag::LuauUnknownAndNeverType ? "*error-type*" : "*unknown*");
state.emit("*error-type*");
}
void operator()(TypeId, const LazyType& ltv)
@ -962,7 +964,7 @@ struct TypePackStringifier
void operator()(TypePackId, const Unifiable::Error& error)
{
state.result.error = true;
state.emit(FFlag::LuauUnknownAndNeverType ? "*error-type*" : "*unknown*");
state.emit("*error-type*");
}
void operator()(TypePackId, const VariadicTypePack& pack)

View file

@ -8,8 +8,6 @@
#include <algorithm>
#include <stdexcept>
LUAU_FASTFLAG(LuauUnknownAndNeverType)
namespace Luau
{

View file

@ -24,7 +24,6 @@ LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAGVARIABLE(LuauMatchReturnsOptionalString, false);
@ -213,7 +212,7 @@ bool isOptional(TypeId ty)
ty = follow(ty);
if (get<AnyType>(ty) || (FFlag::LuauUnknownAndNeverType && get<UnknownType>(ty)))
if (get<AnyType>(ty) || get<UnknownType>(ty))
return true;
auto utv = get<UnionType>(ty);
@ -761,6 +760,7 @@ BuiltinTypes::BuiltinTypes()
, threadType(arena->addType(Type{PrimitiveType{PrimitiveType::Thread}, /*persistent*/ true}))
, functionType(arena->addType(Type{PrimitiveType{PrimitiveType::Function}, /*persistent*/ true}))
, classType(arena->addType(Type{ClassType{"class", {}, std::nullopt, std::nullopt, {}, {}, {}}, /*persistent*/ true}))
, tableType(arena->addType(Type{PrimitiveType{PrimitiveType::Table}, /*persistent*/ true}))
, trueType(arena->addType(Type{SingletonType{BooleanSingleton{true}}, /*persistent*/ true}))
, falseType(arena->addType(Type{SingletonType{BooleanSingleton{false}}, /*persistent*/ true}))
, anyType(arena->addType(Type{AnyType{}, /*persistent*/ true}))

View file

@ -372,7 +372,7 @@ struct TypeChecker2
break;
}
AstLocal* var = local->vars.data[i];
AstLocal* var = local->vars.data[j];
if (var->annotation)
{
TypeId varType = lookupAnnotation(var->annotation);
@ -755,6 +755,8 @@ struct TypeChecker2
return visit(e);
else if (auto e = expr->as<AstExprIfElse>())
return visit(e);
else if (auto e = expr->as<AstExprInterpString>())
return visit(e);
else if (auto e = expr->as<AstExprError>())
return visit(e);
else
@ -1358,6 +1360,12 @@ struct TypeChecker2
visit(expr->falseExpr, RValue);
}
void visit(AstExprInterpString* interpString)
{
for (AstExpr* expr : interpString->expressions)
visit(expr, RValue);
}
void visit(AstExprError* expr)
{
// TODO!

View file

@ -35,18 +35,10 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
LUAU_FASTFLAGVARIABLE(LuauUnknownAndNeverType, false)
LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false)
LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false)
LUAU_FASTFLAGVARIABLE(LuauScopelessModule, false)
LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false)
LUAU_FASTFLAGVARIABLE(LuauTryhardAnd, false)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAGVARIABLE(LuauCompleteVisitor, false)
LUAU_FASTFLAGVARIABLE(LuauReportShadowedTypeAlias, false)
LUAU_FASTFLAGVARIABLE(LuauBetterMessagingOnCountMismatch, false)
LUAU_FASTFLAGVARIABLE(LuauIntersectionTestForEquality, false)
LUAU_FASTFLAGVARIABLE(LuauImplicitElseRefinement, false)
LUAU_FASTFLAG(LuauNegatedClassTypes)
LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false)
LUAU_FASTFLAG(LuauUninhabitedSubAnything2)
@ -246,11 +238,8 @@ TypeChecker::TypeChecker(ModuleResolver* resolver, NotNull<BuiltinTypes> builtin
globalScope->addBuiltinTypeBinding("string", TypeFun{{}, stringType});
globalScope->addBuiltinTypeBinding("boolean", TypeFun{{}, booleanType});
globalScope->addBuiltinTypeBinding("thread", TypeFun{{}, threadType});
if (FFlag::LuauUnknownAndNeverType)
{
globalScope->addBuiltinTypeBinding("unknown", TypeFun{{}, unknownType});
globalScope->addBuiltinTypeBinding("never", TypeFun{{}, neverType});
}
globalScope->addBuiltinTypeBinding("unknown", TypeFun{{}, unknownType});
globalScope->addBuiltinTypeBinding("never", TypeFun{{}, neverType});
}
ModulePtr TypeChecker::check(const SourceModule& module, Mode mode, std::optional<ScopePtr> environmentScope)
@ -661,7 +650,7 @@ LUAU_NOINLINE void TypeChecker::checkBlockTypeAliases(const ScopePtr& scope, std
Name name = typealias->name.value;
if (FFlag::LuauReportShadowedTypeAlias && duplicateTypeAliases.contains({typealias->exported, name}))
if (duplicateTypeAliases.contains({typealias->exported, name}))
continue;
TypeId type = bindings[name].type;
@ -1066,17 +1055,14 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatLocal& local)
// If the expression list only contains one expression and it's a function call or is otherwise within parentheses, use FunctionResult.
// Otherwise, we'll want to use ExprListResult to make the error messaging more general.
CountMismatch::Context ctx = FFlag::LuauBetterMessagingOnCountMismatch ? CountMismatch::ExprListResult : CountMismatch::FunctionResult;
if (FFlag::LuauBetterMessagingOnCountMismatch)
CountMismatch::Context ctx = CountMismatch::ExprListResult;
if (local.values.size == 1)
{
if (local.values.size == 1)
{
AstExpr* e = local.values.data[0];
while (auto group = e->as<AstExprGroup>())
e = group->expr;
if (e->is<AstExprCall>())
ctx = CountMismatch::FunctionResult;
}
AstExpr* e = local.values.data[0];
while (auto group = e->as<AstExprGroup>())
e = group->expr;
if (e->is<AstExprCall>())
ctx = CountMismatch::FunctionResult;
}
Unifier state = mkUnifier(scope, local.location);
@ -1438,11 +1424,8 @@ void TypeChecker::check(const ScopePtr& scope, TypeId ty, const ScopePtr& funSco
checkFunctionBody(funScope, ty, *function.func);
if (FFlag::LuauUnknownAndNeverType)
{
InplaceDemoter demoter{funScope->level, &currentModule->internalTypes};
demoter.traverse(ty);
}
InplaceDemoter demoter{funScope->level, &currentModule->internalTypes};
demoter.traverse(ty);
if (ttv && ttv->state != TableState::Sealed)
ttv->props[name->index.value] = {follow(quantify(funScope, ty, name->indexLocation)), /* deprecated */ false, {}, name->indexLocation};
@ -1591,12 +1574,9 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
Location location = scope->typeAliasLocations[name];
reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}});
if (!FFlag::LuauReportShadowedTypeAlias)
bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)};
duplicateTypeAliases.insert({typealias.exported, name});
}
else if (FFlag::LuauReportShadowedTypeAlias)
else
{
if (globalScope->builtinTypeNames.contains(name))
{
@ -1623,25 +1603,6 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
scope->typeAliasNameLocations[name] = typealias.nameLocation;
}
}
else
{
ScopePtr aliasScope = childScope(scope, typealias.location);
aliasScope->level = scope->level.incr();
aliasScope->level.subLevel = subLevel;
auto [generics, genericPacks] =
createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks, /* useCache = */ true);
TypeId ty = freshType(aliasScope);
FreeType* ftv = getMutable<FreeType>(ty);
LUAU_ASSERT(ftv);
ftv->forwardedTypeAlias = true;
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
scope->typeAliasLocations[name] = typealias.location;
if (FFlag::SupportTypeAliasGoToDeclaration)
scope->typeAliasNameLocations[name] = typealias.nameLocation;
}
}
void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
@ -1840,7 +1801,7 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
else if (auto a = expr.as<AstExprUnary>())
result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprBinary>())
result = checkExpr(scope, *a, FFlag::LuauBinaryNeedsExpectedTypesToo ? expectedType : std::nullopt);
result = checkExpr(scope, *a, expectedType);
else if (auto a = expr.as<AstExprTypeAssertion>())
result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprError>())
@ -2084,7 +2045,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
}
std::vector<TypeId> result = reduceUnion(goodOptions);
if (FFlag::LuauUnknownAndNeverType && result.empty())
if (result.empty())
return neverType;
if (result.size() == 1)
@ -2432,13 +2393,8 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
operandType = stripFromNilAndReport(operandType, expr.location);
// # operator is guaranteed to return number
if ((FFlag::LuauNeverTypesAndOperatorsInference && get<AnyType>(operandType)) || get<ErrorType>(operandType) || get<NeverType>(operandType))
{
if (FFlag::LuauNeverTypesAndOperatorsInference)
return {numberType};
else
return {!FFlag::LuauUnknownAndNeverType ? errorRecoveryType(scope) : operandType};
}
if (get<AnyType>(operandType) || get<ErrorType>(operandType) || get<NeverType>(operandType))
return {numberType};
DenseHashSet<TypeId> seen{nullptr};
@ -2518,7 +2474,7 @@ TypeId TypeChecker::unionOfTypes(TypeId a, TypeId b, const ScopePtr& scope, cons
return a;
std::vector<TypeId> types = reduceUnion({a, b});
if (FFlag::LuauUnknownAndNeverType && types.empty())
if (types.empty())
return neverType;
if (types.size() == 1)
@ -2649,12 +2605,9 @@ TypeId TypeChecker::checkRelationalOperation(
case AstExprBinary::CompareGe:
case AstExprBinary::CompareLe:
{
if (FFlag::LuauNeverTypesAndOperatorsInference)
{
// If one of the operand is never, it doesn't make sense to unify these.
if (get<NeverType>(lhsType) || get<NeverType>(rhsType))
return booleanType;
}
// If one of the operand is never, it doesn't make sense to unify these.
if (get<NeverType>(lhsType) || get<NeverType>(rhsType))
return booleanType;
if (FFlag::LuauIntersectionTestForEquality && isEquality)
{
@ -2897,10 +2850,8 @@ TypeId TypeChecker::checkBinaryOperation(
// If we know nothing at all about the lhs type, we can usually say nothing about the result.
// The notable exception to this is the equality and inequality operators, which always produce a boolean.
const bool lhsIsAny = get<AnyType>(lhsType) || get<ErrorType>(lhsType) ||
(FFlag::LuauUnknownAndNeverType && FFlag::LuauNeverTypesAndOperatorsInference && get<NeverType>(lhsType));
const bool rhsIsAny = get<AnyType>(rhsType) || get<ErrorType>(rhsType) ||
(FFlag::LuauUnknownAndNeverType && FFlag::LuauNeverTypesAndOperatorsInference && get<NeverType>(rhsType));
const bool lhsIsAny = get<AnyType>(lhsType) || get<ErrorType>(lhsType) || get<NeverType>(lhsType);
const bool rhsIsAny = get<AnyType>(rhsType) || get<ErrorType>(rhsType) || get<NeverType>(rhsType);
if (lhsIsAny)
return lhsType;
@ -3102,7 +3053,7 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
return {trueType.type};
std::vector<TypeId> types = reduceUnion({trueType.type, falseType.type});
if (FFlag::LuauUnknownAndNeverType && types.empty())
if (types.empty())
return {neverType};
return {types.size() == 1 ? types[0] : addType(UnionType{std::move(types)})};
}
@ -3709,15 +3660,10 @@ void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstE
WithPredicate<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, const AstExpr& expr)
{
if (FFlag::LuauUnknownAndNeverType)
{
WithPredicate<TypePackId> result = checkExprPackHelper(scope, expr);
if (containsNever(result.type))
return {uninhabitableTypePack};
return result;
}
else
return checkExprPackHelper(scope, expr);
WithPredicate<TypePackId> result = checkExprPackHelper(scope, expr);
if (containsNever(result.type))
return {uninhabitableTypePack};
return result;
}
WithPredicate<TypePackId> TypeChecker::checkExprPackHelper(const ScopePtr& scope, const AstExpr& expr)
@ -3843,10 +3789,7 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
}
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, paramIter.tail()}});
if (FFlag::LuauReturnsFromCallsitesAreNotWidened)
state.tryUnify(tail, varPack);
else
state.tryUnify(varPack, tail);
state.tryUnify(tail, varPack);
return;
}
}
@ -4031,24 +3974,13 @@ WithPredicate<TypePackId> TypeChecker::checkExprPackHelper(const ScopePtr& scope
return {errorRecoveryTypePack(scope)};
TypePack* args = nullptr;
if (FFlag::LuauUnknownAndNeverType)
if (expr.self)
{
if (expr.self)
{
argPack = addTypePack(TypePack{{selfType}, argPack});
argListResult.type = argPack;
}
args = getMutable<TypePack>(argPack);
LUAU_ASSERT(args);
}
else
{
args = getMutable<TypePack>(argPack);
LUAU_ASSERT(args != nullptr);
if (expr.self)
args->head.insert(args->head.begin(), selfType);
argPack = addTypePack(TypePack{{selfType}, argPack});
argListResult.type = argPack;
}
args = getMutable<TypePack>(argPack);
LUAU_ASSERT(args);
std::vector<Location> argLocations;
argLocations.reserve(expr.args.size + 1);
@ -4107,7 +4039,7 @@ std::vector<std::optional<TypeId>> TypeChecker::getExpectedTypesForCall(const st
else
{
std::vector<TypeId> result = reduceUnion({*el, ty});
if (FFlag::LuauUnknownAndNeverType && result.empty())
if (result.empty())
el = neverType;
else
el = result.size() == 1 ? result[0] : addType(UnionType{std::move(result)});
@ -4451,7 +4383,7 @@ WithPredicate<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, cons
auto [typePack, exprPredicates] = checkExprPack(scope, *expr);
insert(exprPredicates);
if (FFlag::LuauUnknownAndNeverType && containsNever(typePack))
if (containsNever(typePack))
{
// f(), g() where f() returns (never, string) or (string, never) means this whole TypePackId is uninhabitable, so return (never,
// ...never)
@ -4474,7 +4406,7 @@ WithPredicate<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, cons
auto [type, exprPredicates] = checkExpr(scope, *expr, expectedType);
insert(exprPredicates);
if (FFlag::LuauUnknownAndNeverType && get<NeverType>(type))
if (get<NeverType>(type))
{
// f(), g() where f() returns (never, string) or (string, never) means this whole TypePackId is uninhabitable, so return (never,
// ...never)
@ -4509,7 +4441,7 @@ WithPredicate<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, cons
for (TxnLog& log : inverseLogs)
log.commit();
if (FFlag::LuauUnknownAndNeverType && uninhabitable)
if (uninhabitable)
return {uninhabitableTypePack};
return {pack, predicates};
}
@ -4997,16 +4929,8 @@ std::optional<TypeId> TypeChecker::filterMapImpl(TypeId type, TypeIdPredicate pr
std::pair<std::optional<TypeId>, bool> TypeChecker::filterMap(TypeId type, TypeIdPredicate predicate)
{
if (FFlag::LuauUnknownAndNeverType)
{
TypeId ty = filterMapImpl(type, predicate).value_or(neverType);
return {ty, !bool(get<NeverType>(ty))};
}
else
{
std::optional<TypeId> ty = filterMapImpl(type, predicate);
return {ty, bool(ty)};
}
TypeId ty = filterMapImpl(type, predicate).value_or(neverType);
return {ty, !bool(get<NeverType>(ty))};
}
std::pair<std::optional<TypeId>, bool> TypeChecker::pickTypesFromSense(TypeId type, bool sense, TypeId emptySetTy)
@ -5587,18 +5511,7 @@ void TypeChecker::refineLValue(const LValue& lvalue, RefinementMap& refis, const
if (!key)
{
auto [result, ok] = filterMap(*ty, predicate);
if (FFlag::LuauUnknownAndNeverType)
{
addRefinement(refis, *target, *result);
}
else
{
if (ok)
addRefinement(refis, *target, *result);
else
addRefinement(refis, *target, errorRecoveryType(scope));
}
addRefinement(refis, *target, *result);
return;
}
@ -5621,21 +5534,10 @@ void TypeChecker::refineLValue(const LValue& lvalue, RefinementMap& refis, const
return; // Do nothing. An error was already reported, as per usual.
auto [result, ok] = filterMap(*discriminantTy, predicate);
if (FFlag::LuauUnknownAndNeverType)
if (!get<NeverType>(*result))
{
if (!get<NeverType>(*result))
{
viableTargetOptions.insert(option);
viableChildOptions.insert(*result);
}
}
else
{
if (ok)
{
viableTargetOptions.insert(option);
viableChildOptions.insert(*result);
}
viableTargetOptions.insert(option);
viableChildOptions.insert(*result);
}
}
@ -5891,7 +5793,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
auto refine = [this, &lvalue = typeguardP.lvalue, &refis, &scope, sense](bool(f)(TypeId), std::optional<TypeId> mapsTo = std::nullopt) {
TypeIdPredicate predicate = [f, mapsTo, sense](TypeId ty) -> std::optional<TypeId> {
if (FFlag::LuauUnknownAndNeverType && sense && get<UnknownType>(ty))
if (sense && get<UnknownType>(ty))
return mapsTo.value_or(ty);
if (f(ty) == sense)
@ -5985,56 +5887,44 @@ void TypeChecker::resolve(const EqPredicate& eqP, RefinementMap& refis, const Sc
if (maybeSingleton(eqP.type))
{
if (FFlag::LuauImplicitElseRefinement)
bool optionIsSubtype = canUnify(option, eqP.type, scope, eqP.location).empty();
bool targetIsSubtype = canUnify(eqP.type, option, scope, eqP.location).empty();
// terminology refresher:
// - option is the type of the expression `x`, and
// - eqP.type is the type of the expression `"hello"`
//
// "hello" == x where
// x : "hello" | "world" -> x : "hello"
// x : number | string -> x : "hello"
// x : number -> x : never
//
// "hello" ~= x where
// x : "hello" | "world" -> x : "world"
// x : number | string -> x : number | string
// x : number -> x : number
// local variable works around an odd gcc 9.3 warning: <anonymous> may be used uninitialized
std::optional<TypeId> nope = std::nullopt;
if (sense)
{
bool optionIsSubtype = canUnify(option, eqP.type, scope, eqP.location).empty();
bool targetIsSubtype = canUnify(eqP.type, option, scope, eqP.location).empty();
// terminology refresher:
// - option is the type of the expression `x`, and
// - eqP.type is the type of the expression `"hello"`
//
// "hello" == x where
// x : "hello" | "world" -> x : "hello"
// x : number | string -> x : "hello"
// x : number -> x : never
//
// "hello" ~= x where
// x : "hello" | "world" -> x : "world"
// x : number | string -> x : number | string
// x : number -> x : number
// local variable works around an odd gcc 9.3 warning: <anonymous> may be used uninitialized
std::optional<TypeId> nope = std::nullopt;
if (sense)
{
if (optionIsSubtype && !targetIsSubtype)
return option;
else if (!optionIsSubtype && targetIsSubtype)
return follow(eqP.type);
else if (!optionIsSubtype && !targetIsSubtype)
return nope;
else if (optionIsSubtype && targetIsSubtype)
return follow(eqP.type);
}
else
{
bool isOptionSingleton = get<SingletonType>(option);
if (!isOptionSingleton)
return option;
else if (optionIsSubtype && targetIsSubtype)
return nope;
}
if (optionIsSubtype && !targetIsSubtype)
return option;
else if (!optionIsSubtype && targetIsSubtype)
return follow(eqP.type);
else if (!optionIsSubtype && !targetIsSubtype)
return nope;
else if (optionIsSubtype && targetIsSubtype)
return follow(eqP.type);
}
else
{
if (!sense || canUnify(eqP.type, option, scope, eqP.location).empty())
return sense ? eqP.type : option;
// local variable works around an odd gcc 9.3 warning: <anonymous> may be used uninitialized
std::optional<TypeId> res = std::nullopt;
return res;
bool isOptionSingleton = get<SingletonType>(option);
if (!isOptionSingleton)
return option;
else if (optionIsSubtype && targetIsSubtype)
return nope;
}
}
@ -6063,8 +5953,7 @@ std::vector<TypeId> TypeChecker::unTypePack(const ScopePtr& scope, TypePackId tp
// HACK: tryUnify would undo the changes to the expectedTypePack if the length mismatches, but
// we want to tie up free types to be error types, so we do this instead.
if (FFlag::LuauUnknownAndNeverType)
currentModule->errors.resize(oldErrorsSize);
currentModule->errors.resize(oldErrorsSize);
for (TypeId& tp : expectedPack->head)
tp = follow(tp);

View file

@ -56,12 +56,6 @@ struct TypeReducer
TypeId memoize(TypeId ty, TypeId reducedTy);
TypePackId memoize(TypePackId tp, TypePackId reducedTp);
// It's either cyclic with no memoized result, so we should terminate, or
// there is a memoized result but one that's being reduced top-down, so
// we need to return the root of that memoized result to tighten up things.
TypeId memoizedOr(TypeId ty) const;
TypePackId memoizedOr(TypePackId tp) const;
using BinaryFold = std::optional<TypeId> (TypeReducer::*)(TypeId, TypeId);
using UnaryFold = TypeId (TypeReducer::*)(TypeId);
@ -319,6 +313,24 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
}
else if (auto [f, p] = get2<FunctionType, PrimitiveType>(left, right); f && p)
return intersectionType(right, left); // () -> () & P ~ P & () -> ()
else if (auto [p, t] = get2<PrimitiveType, TableType>(left, right); p && t)
{
if (p->type == PrimitiveType::Table)
return right; // table & {} ~ {}
else
return builtinTypes->neverType; // string & {} ~ never
}
else if (auto [p, t] = get2<PrimitiveType, MetatableType>(left, right); p && t)
{
if (p->type == PrimitiveType::Table)
return right; // table & {} ~ {}
else
return builtinTypes->neverType; // string & {} ~ never
}
else if (auto [t, p] = get2<TableType, PrimitiveType>(left, right); t && p)
return intersectionType(right, left); // {} & P ~ P & {}
else if (auto [t, p] = get2<MetatableType, PrimitiveType>(left, right); t && p)
return intersectionType(right, left); // M & P ~ P & M
else if (auto [s1, s2] = get2<SingletonType, SingletonType>(left, right); s1 && s2)
{
if (*s1 == *s2)
@ -472,6 +484,20 @@ std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
else
return right; // ~Base & Unrelated ~ Unrelated
}
else if (auto [np, t] = get2<PrimitiveType, TableType>(nlTy, right); np && t)
{
if (np->type == PrimitiveType::Table)
return builtinTypes->neverType; // ~table & {} ~ never
else
return right; // ~string & {} ~ {}
}
else if (auto [np, t] = get2<PrimitiveType, MetatableType>(nlTy, right); np && t)
{
if (np->type == PrimitiveType::Table)
return builtinTypes->neverType; // ~table & {} ~ never
else
return right; // ~string & {} ~ {}
}
else
return std::nullopt; // TODO
}
@ -529,6 +555,24 @@ std::optional<TypeId> TypeReducer::unionType(TypeId left, TypeId right)
}
else if (auto [f, p] = get2<FunctionType, PrimitiveType>(left, right); f && p)
return unionType(right, left); // () -> () | P ~ P | () -> ()
else if (auto [p, t] = get2<PrimitiveType, TableType>(left, right); p && t)
{
if (p->type == PrimitiveType::Table)
return left; // table | {} ~ table
else
return std::nullopt; // P | {} ~ P | {}
}
else if (auto [p, t] = get2<PrimitiveType, MetatableType>(left, right); p && t)
{
if (p->type == PrimitiveType::Table)
return left; // table | {} ~ table
else
return std::nullopt; // P | {} ~ P | {}
}
else if (auto [t, p] = get2<TableType, PrimitiveType>(left, right); t && p)
return unionType(right, left); // {} | P ~ P | {}
else if (auto [t, p] = get2<MetatableType, PrimitiveType>(left, right); t && p)
return unionType(right, left); // M | P ~ P | M
else if (auto [s1, s2] = get2<SingletonType, SingletonType>(left, right); s1 && s2)
{
if (*s1 == *s2)
@ -642,6 +686,20 @@ std::optional<TypeId> TypeReducer::unionType(TypeId left, TypeId right)
else
return left; // ~Base | Unrelated ~ ~Base
}
else if (auto [np, t] = get2<PrimitiveType, TableType>(nlTy, right); np && t)
{
if (np->type == PrimitiveType::Table)
return std::nullopt; // ~table | {} ~ ~table | {}
else
return right; // ~P | {} ~ ~P | {}
}
else if (auto [np, t] = get2<PrimitiveType, MetatableType>(nlTy, right); np && t)
{
if (np->type == PrimitiveType::Table)
return std::nullopt; // ~table | {} ~ ~table | {}
else
return right; // ~P | M ~ ~P | M
}
else
return std::nullopt; // TODO
}
@ -850,26 +908,6 @@ TypePackId TypeReducer::memoize(TypePackId tp, TypePackId reducedTp)
return reducedTp;
}
TypeId TypeReducer::memoizedOr(TypeId ty) const
{
ty = follow(ty);
if (auto ctx = memoizedTypes->find(ty))
return ctx->type;
else
return ty;
};
TypePackId TypeReducer::memoizedOr(TypePackId tp) const
{
tp = follow(tp);
if (auto ctx = memoizedTypePacks->find(tp))
return ctx->type;
else
return tp;
};
struct MarkCycles : TypeVisitor
{
DenseHashSet<TypeId> cyclicTypes{nullptr};

View file

@ -15,10 +15,8 @@
#include <algorithm>
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
LUAU_FASTFLAG(LuauErrorRecoveryType);
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauScalarShapeSubtyping, false)
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
LUAU_FASTFLAG(LuauErrorRecoveryType)
LUAU_FASTFLAGVARIABLE(LuauUnifyAnyTxnLog, false)
LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
LUAU_FASTFLAGVARIABLE(LuauScalarShapeUnifyToMtOwner2, false)
@ -28,6 +26,7 @@ LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauNegatedFunctionTypes)
LUAU_FASTFLAG(LuauNegatedClassTypes)
LUAU_FASTFLAG(LuauNegatedTableTypes)
namespace Luau
{
@ -452,13 +451,10 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
}
else if (subFree)
{
if (FFlag::LuauUnknownAndNeverType)
{
// Normally, if the subtype is free, it should not be bound to any, unknown, or error types.
// But for bug compatibility, we'll only apply this rule to unknown. Doing this will silence cascading type errors.
if (log.get<UnknownType>(superTy))
return;
}
// Normally, if the subtype is free, it should not be bound to any, unknown, or error types.
// But for bug compatibility, we'll only apply this rule to unknown. Doing this will silence cascading type errors.
if (log.get<UnknownType>(superTy))
return;
// Unification can't change the level of a generic.
auto superGeneric = log.getMutable<GenericType>(superTy);
@ -569,6 +565,11 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
// Ok. Do nothing. forall functions F, F <: function
}
else if (FFlag::LuauNegatedTableTypes && isPrim(superTy, PrimitiveType::Table) && (get<TableType>(subTy) || get<MetatableType>(subTy)))
{
// Ok, do nothing: forall tables T, T <: table
}
else if (log.getMutable<FunctionType>(superTy) && log.getMutable<FunctionType>(subTy))
tryUnifyFunctions(subTy, superTy, isFunctionCall);
@ -576,11 +577,11 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
{
tryUnifyTables(subTy, superTy, isIntersection);
}
else if (FFlag::LuauScalarShapeSubtyping && log.get<TableType>(superTy) && (log.get<PrimitiveType>(subTy) || log.get<SingletonType>(subTy)))
else if (log.get<TableType>(superTy) && (log.get<PrimitiveType>(subTy) || log.get<SingletonType>(subTy)))
{
tryUnifyScalarShape(subTy, superTy, /*reversed*/ false);
}
else if (FFlag::LuauScalarShapeSubtyping && log.get<TableType>(subTy) && (log.get<PrimitiveType>(superTy) || log.get<SingletonType>(superTy)))
else if (log.get<TableType>(subTy) && (log.get<PrimitiveType>(superTy) || log.get<SingletonType>(superTy)))
{
tryUnifyScalarShape(subTy, superTy, /*reversed*/ true);
}
@ -1032,6 +1033,12 @@ void Unifier::tryUnifyNormalizedTypes(
bool found = false;
for (TypeId superTable : superNorm.tables)
{
if (FFlag::LuauNegatedTableTypes && isPrim(superTable, PrimitiveType::Table))
{
found = true;
break;
}
Unifier innerState = makeChildUnifier();
if (get<MetatableType>(superTable))
innerState.tryUnifyWithMetatable(subTable, superTable, /* reversed */ false);
@ -2031,8 +2038,6 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
void Unifier::tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed)
{
LUAU_ASSERT(FFlag::LuauScalarShapeSubtyping);
TypeId osubTy = subTy;
TypeId osuperTy = superTy;
@ -2490,22 +2495,14 @@ void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy)
return;
}
TypePackId anyTp;
if (FFlag::LuauUnknownAndNeverType)
anyTp = types->addTypePack(TypePackVar{VariadicTypePack{anyTy}});
else
{
const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{builtinTypes->anyType}});
anyTp = get<AnyType>(anyTy) ? anyTypePack : types->addTypePack(TypePackVar{Unifiable::Error{}});
}
TypePackId anyTp = types->addTypePack(TypePackVar{VariadicTypePack{anyTy}});
std::vector<TypeId> queue = {subTy};
sharedState.tempSeenTy.clear();
sharedState.tempSeenTp.clear();
Luau::tryUnifyWithAny(
queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types, FFlag::LuauUnknownAndNeverType ? anyTy : builtinTypes->anyType, anyTp);
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types, anyTy, anyTp);
}
void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp)

View file

@ -52,246 +52,12 @@ struct AstName
}
};
class AstVisitor
{
public:
virtual ~AstVisitor() {}
virtual bool visit(class AstNode*)
{
return true;
}
virtual bool visit(class AstExpr* node)
{
return visit((class AstNode*)node);
}
virtual bool visit(class AstExprGroup* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprConstantNil* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprConstantBool* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprConstantNumber* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprConstantString* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprLocal* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprGlobal* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprVarargs* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprCall* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprIndexName* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprIndexExpr* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprFunction* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprTable* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprUnary* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprBinary* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprTypeAssertion* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprIfElse* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprInterpString* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstExprError* node)
{
return visit((class AstExpr*)node);
}
virtual bool visit(class AstStat* node)
{
return visit((class AstNode*)node);
}
virtual bool visit(class AstStatBlock* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatIf* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatWhile* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatRepeat* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatBreak* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatContinue* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatReturn* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatExpr* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatLocal* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatFor* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatForIn* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatAssign* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatCompoundAssign* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatFunction* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatLocalFunction* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatTypeAlias* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatDeclareFunction* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatDeclareGlobal* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatDeclareClass* node)
{
return visit((class AstStat*)node);
}
virtual bool visit(class AstStatError* node)
{
return visit((class AstStat*)node);
}
// By default visiting type annotations is disabled; override this in your visitor if you need to!
virtual bool visit(class AstType* node)
{
return false;
}
virtual bool visit(class AstTypeReference* node)
{
return visit((class AstType*)node);
}
virtual bool visit(class AstTypeTable* node)
{
return visit((class AstType*)node);
}
virtual bool visit(class AstTypeFunction* node)
{
return visit((class AstType*)node);
}
virtual bool visit(class AstTypeTypeof* node)
{
return visit((class AstType*)node);
}
virtual bool visit(class AstTypeUnion* node)
{
return visit((class AstType*)node);
}
virtual bool visit(class AstTypeIntersection* node)
{
return visit((class AstType*)node);
}
virtual bool visit(class AstTypeSingletonBool* node)
{
return visit((class AstType*)node);
}
virtual bool visit(class AstTypeSingletonString* node)
{
return visit((class AstType*)node);
}
virtual bool visit(class AstTypeError* node)
{
return visit((class AstType*)node);
}
virtual bool visit(class AstTypePack* node)
{
return false;
}
virtual bool visit(class AstTypePackExplicit* node)
{
return visit((class AstTypePack*)node);
}
virtual bool visit(class AstTypePackVariadic* node)
{
return visit((class AstTypePack*)node);
}
virtual bool visit(class AstTypePackGeneric* node)
{
return visit((class AstTypePack*)node);
}
};
class AstType;
class AstVisitor;
class AstStat;
class AstStatBlock;
class AstExpr;
class AstTypePack;
struct AstLocal
{
@ -1277,6 +1043,245 @@ public:
AstName genericName;
};
class AstVisitor
{
public:
virtual ~AstVisitor() {}
virtual bool visit(class AstNode*)
{
return true;
}
virtual bool visit(class AstExpr* node)
{
return visit(static_cast<AstNode*>(node));
}
virtual bool visit(class AstExprGroup* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprConstantNil* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprConstantBool* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprConstantNumber* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprConstantString* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprLocal* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprGlobal* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprVarargs* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprCall* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprIndexName* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprIndexExpr* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprFunction* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprTable* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprUnary* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprBinary* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprTypeAssertion* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprIfElse* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprInterpString* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstExprError* node)
{
return visit(static_cast<AstExpr*>(node));
}
virtual bool visit(class AstStat* node)
{
return visit(static_cast<AstNode*>(node));
}
virtual bool visit(class AstStatBlock* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatIf* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatWhile* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatRepeat* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatBreak* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatContinue* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatReturn* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatExpr* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatLocal* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatFor* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatForIn* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatAssign* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatCompoundAssign* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatFunction* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatLocalFunction* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatTypeAlias* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatDeclareFunction* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatDeclareGlobal* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatDeclareClass* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatError* node)
{
return visit(static_cast<AstStat*>(node));
}
// By default visiting type annotations is disabled; override this in your visitor if you need to!
virtual bool visit(class AstType* node)
{
return false;
}
virtual bool visit(class AstTypeReference* node)
{
return visit(static_cast<AstType*>(node));
}
virtual bool visit(class AstTypeTable* node)
{
return visit(static_cast<AstType*>(node));
}
virtual bool visit(class AstTypeFunction* node)
{
return visit(static_cast<AstType*>(node));
}
virtual bool visit(class AstTypeTypeof* node)
{
return visit(static_cast<AstType*>(node));
}
virtual bool visit(class AstTypeUnion* node)
{
return visit(static_cast<AstType*>(node));
}
virtual bool visit(class AstTypeIntersection* node)
{
return visit(static_cast<AstType*>(node));
}
virtual bool visit(class AstTypeSingletonBool* node)
{
return visit(static_cast<AstType*>(node));
}
virtual bool visit(class AstTypeSingletonString* node)
{
return visit(static_cast<AstType*>(node));
}
virtual bool visit(class AstTypeError* node)
{
return visit(static_cast<AstType*>(node));
}
virtual bool visit(class AstTypePack* node)
{
return false;
}
virtual bool visit(class AstTypePackExplicit* node)
{
return visit(static_cast<AstTypePack*>(node));
}
virtual bool visit(class AstTypePackVariadic* node)
{
return visit(static_cast<AstTypePack*>(node));
}
virtual bool visit(class AstTypePackGeneric* node)
{
return visit(static_cast<AstTypePack*>(node));
}
};
AstName getIdentifier(AstExpr*);
Location getLocation(const AstTypeList& typeList);

View file

@ -6,8 +6,6 @@
#include <limits.h>
LUAU_FASTFLAG(LuauInterpolatedStringBaseSupport)
namespace Luau
{
@ -835,13 +833,7 @@ Lexeme Lexer::readNext()
return readQuotedString();
case '`':
if (FFlag::LuauInterpolatedStringBaseSupport)
return readInterpolatedStringBegin();
else
{
consume();
return Lexeme(Location(start, 1), '`');
}
return readInterpolatedStringBegin();
case '.':
consume();

View file

@ -17,8 +17,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauErrorDoubleHexPrefix, false)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false)
LUAU_FASTFLAGVARIABLE(LuauInterpolatedStringBaseSupport, false)
LUAU_FASTFLAGVARIABLE(LuauParserErrorsOnMissingDefaultTypePackArgument, false)
bool lua_telemetry_parsed_out_of_range_bin_integer = false;
@ -2174,11 +2172,11 @@ AstExpr* Parser::parseSimpleExpr()
return parseNumber();
}
else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString ||
(FFlag::LuauInterpolatedStringBaseSupport && lexer.current().type == Lexeme::InterpStringSimple))
lexer.current().type == Lexeme::InterpStringSimple)
{
return parseString();
}
else if (FFlag::LuauInterpolatedStringBaseSupport && lexer.current().type == Lexeme::InterpStringBegin)
else if (lexer.current().type == Lexeme::InterpStringBegin)
{
return parseInterpString();
}

View file

@ -48,8 +48,10 @@ enum class CompileFormat
Text,
Binary,
Remarks,
Codegen,
CodegenVerbose,
Codegen, // Prints annotated native code including IR and assembly
CodegenAsm, // Prints annotated native code assembly
CodegenIr, // Prints annotated native code IR
CodegenVerbose, // Prints annotated native code including IR, assembly and outlined code
CodegenNull,
Null
};
@ -716,7 +718,19 @@ static bool compileFile(const char* name, CompileFormat format, CompileStats& st
try
{
Luau::BytecodeBuilder bcb;
Luau::CodeGen::AssemblyOptions options = {format == CompileFormat::CodegenNull, format == CompileFormat::Codegen, annotateInstruction, &bcb};
Luau::CodeGen::AssemblyOptions options;
options.outputBinary = format == CompileFormat::CodegenNull;
if (!options.outputBinary)
{
options.includeAssembly = format != CompileFormat::CodegenIr;
options.includeIr = format != CompileFormat::CodegenAsm;
options.includeOutlinedCode = format == CompileFormat::CodegenVerbose;
}
options.annotator = annotateInstruction;
options.annotatorContext = &bcb;
if (format == CompileFormat::Text)
{
@ -729,7 +743,8 @@ static bool compileFile(const char* name, CompileFormat format, CompileStats& st
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Remarks);
bcb.setDumpSource(*source);
}
else if (format == CompileFormat::Codegen || format == CompileFormat::CodegenVerbose)
else if (format == CompileFormat::Codegen || format == CompileFormat::CodegenAsm || format == CompileFormat::CodegenIr ||
format == CompileFormat::CodegenVerbose)
{
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals |
Luau::BytecodeBuilder::Dump_Remarks);
@ -760,6 +775,8 @@ static bool compileFile(const char* name, CompileFormat format, CompileStats& st
fwrite(bcb.getBytecode().data(), 1, bcb.getBytecode().size(), stdout);
break;
case CompileFormat::Codegen:
case CompileFormat::CodegenAsm:
case CompileFormat::CodegenIr:
case CompileFormat::CodegenVerbose:
printf("%s", getCodegenAssembly(name, bcb.getBytecode(), options).c_str());
break;
@ -850,6 +867,14 @@ int replMain(int argc, char** argv)
{
compileFormat = CompileFormat::Codegen;
}
else if (strcmp(argv[1], "--compile=codegenasm") == 0)
{
compileFormat = CompileFormat::CodegenAsm;
}
else if (strcmp(argv[1], "--compile=codegenir") == 0)
{
compileFormat = CompileFormat::CodegenIr;
}
else if (strcmp(argv[1], "--compile=codegenverbose") == 0)
{
compileFormat = CompileFormat::CodegenVerbose;

View file

@ -22,7 +22,10 @@ using annotatorFn = void (*)(void* context, std::string& result, int fid, int in
struct AssemblyOptions
{
bool outputBinary = false;
bool skipOutlinedCode = false;
bool includeAssembly = false;
bool includeIr = false;
bool includeOutlinedCode = false;
// Optional annotator function can be provided to describe each instruction, it takes function id and sequential instruction id
annotatorFn annotator = nullptr;

View file

@ -13,6 +13,9 @@
#include "CodeGenX64.h"
#include "EmitCommonX64.h"
#include "EmitInstructionX64.h"
#include "IrAnalysis.h"
#include "IrBuilder.h"
#include "IrLoweringX64.h"
#include "NativeState.h"
#include "lapi.h"
@ -27,6 +30,8 @@
#endif
#endif
LUAU_FASTFLAGVARIABLE(DebugUseOldCodegen, false)
namespace Luau
{
namespace CodeGen
@ -241,7 +246,7 @@ static int emitInst(AssemblyBuilderX64& build, NativeState& data, ModuleHelpers&
skip = emitInstFastCall2K(build, pc, i, next);
break;
case LOP_FORNPREP:
emitInstForNPrep(build, pc, i, labelarr[i + 1 + LUAU_INSN_D(*pc)]);
emitInstForNPrep(build, pc, i, next, labelarr[i + 1 + LUAU_INSN_D(*pc)]);
break;
case LOP_FORNLOOP:
emitInstForNLoop(build, pc, i, labelarr[i + 1 + LUAU_INSN_D(*pc)], next);
@ -404,7 +409,7 @@ static NativeProto* assembleFunction(AssemblyBuilderX64& build, NativeState& dat
result->proto = proto;
if (build.logText)
if (options.includeAssembly || options.includeIr)
{
if (proto->debugname)
build.logAppend("; function %s()", getstr(proto->debugname));
@ -417,6 +422,38 @@ static NativeProto* assembleFunction(AssemblyBuilderX64& build, NativeState& dat
build.logAppend("\n");
}
if (!FFlag::DebugUseOldCodegen)
{
build.align(kFunctionAlignment, AlignmentDataX64::Ud2);
Label start = build.setLabel();
IrBuilder builder;
builder.buildFunctionIr(proto);
updateUseInfo(builder.function);
IrLoweringX64 lowering(build, helpers, data, proto, builder.function);
lowering.lower(options);
result->instTargets = new uintptr_t[proto->sizecode];
for (int i = 0; i < proto->sizecode; i++)
{
auto [irLocation, asmLocation] = builder.function.bcMapping[i];
result->instTargets[i] = irLocation == ~0u ? 0 : asmLocation - start.location;
}
result->location = start.location;
if (build.logText)
build.logAppend("\n");
return result;
}
std::vector<Label> instLabels;
instLabels.resize(proto->sizecode);
@ -457,7 +494,7 @@ static NativeProto* assembleFunction(AssemblyBuilderX64& build, NativeState& dat
size_t textSize = build.text.size();
uint32_t codeSize = build.getCodeSize();
if (options.annotator && !options.skipOutlinedCode)
if (options.annotator && options.includeOutlinedCode)
build.logAppend("; outlined instructions\n");
for (auto [pcpos, length] : instOutlines)
@ -474,7 +511,7 @@ static NativeProto* assembleFunction(AssemblyBuilderX64& build, NativeState& dat
build.setLabel(instLabels[i]);
if (options.annotator && !options.skipOutlinedCode)
if (options.annotator && options.includeOutlinedCode)
options.annotator(options.annotatorContext, build.text, proto->bytecodeid, i);
Label& next = nexti < proto->sizecode ? instLabels[nexti] : start; // Last instruction can't use 'next' label
@ -489,7 +526,7 @@ static NativeProto* assembleFunction(AssemblyBuilderX64& build, NativeState& dat
build.jmp(instLabels[i]);
}
if (options.annotator && !options.skipOutlinedCode)
if (options.annotator && options.includeOutlinedCode)
build.logAppend("; outlined code\n");
for (int i = 0, instid = 0; i < proto->sizecode; ++instid)
@ -506,7 +543,7 @@ static NativeProto* assembleFunction(AssemblyBuilderX64& build, NativeState& dat
continue;
}
if (options.annotator && !options.skipOutlinedCode)
if (options.annotator && options.includeOutlinedCode)
options.annotator(options.annotatorContext, build.text, proto->bytecodeid, instid);
build.setLabel(instFallbacks[i]);
@ -521,7 +558,7 @@ static NativeProto* assembleFunction(AssemblyBuilderX64& build, NativeState& dat
}
// Truncate assembly output if we don't care for outlined code part
if (options.skipOutlinedCode)
if (!options.includeOutlinedCode)
{
build.text.resize(textSize);
@ -730,7 +767,7 @@ std::string getAssembly(lua_State* L, int idx, AssemblyOptions options)
LUAU_ASSERT(lua_isLfunction(L, idx));
const TValue* func = luaA_toobject(L, idx);
AssemblyBuilderX64 build(/* logText= */ !options.outputBinary);
AssemblyBuilderX64 build(/* logText= */ options.includeAssembly);
NativeState data;
initFallbackTable(data);

View file

@ -191,9 +191,19 @@ static void callBarrierImpl(AssemblyBuilderX64& build, RegisterX64 tmp, Register
build.test(byte[tmp + offsetof(GCheader, marked)], bit2mask(WHITE0BIT, WHITE1BIT));
build.jcc(ConditionX64::Zero, skip);
LUAU_ASSERT(object != rArg3);
build.mov(rArg3, tmp);
build.mov(rArg2, object);
// TODO: even with re-ordering we have a chance of failure, we have a task to fix this in the future
if (object == rArg3)
{
LUAU_ASSERT(tmp != rArg2);
build.mov(rArg2, object);
build.mov(rArg3, tmp);
}
else
{
build.mov(rArg3, tmp);
build.mov(rArg2, object);
}
build.mov(rArg1, rState);
build.call(qword[rNativeContext + contextOffset]);
}

View file

@ -1077,11 +1077,11 @@ int emitInstFastCall(AssemblyBuilderX64& build, const Instruction* pc, int pcpos
return emitInstFastCallN(build, pc, /* customParams */ false, /* customParamCount */ 0, /* customArgs */ 0, pcpos, fallback);
}
void emitInstForNPrep(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopExit)
void emitInstForNPrep(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopStart, Label& loopExit)
{
int ra = LUAU_INSN_A(*pc);
Label tryConvert, exit;
Label tryConvert;
jumpIfTagIsNot(build, ra + 0, LUA_TNUMBER, tryConvert);
jumpIfTagIsNot(build, ra + 1, LUA_TNUMBER, tryConvert);
@ -1107,12 +1107,12 @@ void emitInstForNPrep(AssemblyBuilderX64& build, const Instruction* pc, int pcpo
// TODO: target branches can probably be arranged better, but we need tests for NaN behavior preservation
// false: idx <= limit
jumpOnNumberCmp(build, noreg, idx, limit, ConditionX64::LessEqual, exit);
jumpOnNumberCmp(build, noreg, idx, limit, ConditionX64::LessEqual, loopStart);
build.jmp(loopExit);
// true: limit <= idx
build.setLabel(reverse);
jumpOnNumberCmp(build, noreg, limit, idx, ConditionX64::LessEqual, exit);
jumpOnNumberCmp(build, noreg, limit, idx, ConditionX64::LessEqual, loopStart);
build.jmp(loopExit);
// TOOD: place at the end of the function
@ -1120,8 +1120,6 @@ void emitInstForNPrep(AssemblyBuilderX64& build, const Instruction* pc, int pcpo
emitSetSavedPc(build, pcpos + 1);
callPrepareForN(build, ra + 0, ra + 1, ra + 2);
build.jmp(retry);
build.setLabel(exit);
}
void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat, Label& loopExit)

View file

@ -60,7 +60,7 @@ int emitInstFastCall1(AssemblyBuilderX64& build, const Instruction* pc, int pcpo
int emitInstFastCall2(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& fallback);
int emitInstFastCall2K(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& fallback);
int emitInstFastCall(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& fallback);
void emitInstForNPrep(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopExit);
void emitInstForNPrep(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopStart, Label& loopExit);
void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat, Label& loopExit);
void emitinstForGLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat, Label& loopExit, Label& fallback);
void emitinstForGLoopFallback(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat);

View file

@ -0,0 +1,50 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "IrAnalysis.h"
#include "IrData.h"
#include "IrUtils.h"
#include <stddef.h>
namespace Luau
{
namespace CodeGen
{
static void recordUse(IrInst& inst, size_t index)
{
LUAU_ASSERT(inst.useCount < 0xffff);
inst.useCount++;
inst.lastUse = uint32_t(index);
}
void updateUseInfo(IrFunction& function)
{
std::vector<IrInst>& instructions = function.instructions;
for (IrInst& inst : instructions)
{
inst.useCount = 0;
inst.lastUse = 0;
}
for (size_t i = 0; i < instructions.size(); ++i)
{
IrInst& inst = instructions[i];
auto checkOp = [&instructions, i](IrOp op) {
if (op.kind == IrOpKind::Inst)
recordUse(instructions[op.index], i);
};
checkOp(inst.a);
checkOp(inst.b);
checkOp(inst.c);
checkOp(inst.d);
checkOp(inst.e);
}
}
} // namespace CodeGen
} // namespace Luau

14
CodeGen/src/IrAnalysis.h Normal file
View file

@ -0,0 +1,14 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
namespace Luau
{
namespace CodeGen
{
struct IrFunction;
void updateUseInfo(IrFunction& function);
} // namespace CodeGen
} // namespace Luau

View file

@ -318,9 +318,12 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
}
case LOP_FORNPREP:
{
IrOp loopStart = blockAtInst(i + getOpLength(LOP_FORNPREP));
IrOp loopExit = blockAtInst(i + 1 + LUAU_INSN_D(*pc));
inst(IrCmd::LOP_FORNPREP, constUint(i), loopExit);
inst(IrCmd::LOP_FORNPREP, constUint(i), loopStart, loopExit);
beginBlock(loopStart);
break;
}
case LOP_FORNLOOP:
@ -437,7 +440,11 @@ bool IrBuilder::isInternalBlock(IrOp block)
void IrBuilder::beginBlock(IrOp block)
{
function.blocks[block.index].start = uint32_t(function.instructions.size());
IrBlock& target = function.blocks[block.index];
LUAU_ASSERT(target.start == ~0u || target.start == uint32_t(function.instructions.size()));
target.start = uint32_t(function.instructions.size());
}
IrOp IrBuilder::constBool(bool value)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,89 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/AssemblyBuilderX64.h"
#include "IrData.h"
#include <array>
#include <initializer_list>
#include <vector>
struct Proto;
namespace Luau
{
namespace CodeGen
{
struct ModuleHelpers;
struct NativeState;
struct AssemblyOptions;
struct IrLoweringX64
{
// Some of these arguments are only required while we re-use old direct bytecode to x64 lowering
IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, NativeState& data, Proto* proto, IrFunction& function);
void lower(AssemblyOptions options);
void lowerInst(IrInst& inst, uint32_t index, IrBlock& next);
bool isFallthroughBlock(IrBlock target, IrBlock next);
void jumpOrFallthrough(IrBlock& target, IrBlock& next);
// Operand data lookup helpers
OperandX64 memRegDoubleOp(IrOp op) const;
OperandX64 memRegTagOp(IrOp op) const;
RegisterX64 regOp(IrOp op) const;
IrConst constOp(IrOp op) const;
uint8_t tagOp(IrOp op) const;
bool boolOp(IrOp op) const;
int intOp(IrOp op) const;
unsigned uintOp(IrOp op) const;
double doubleOp(IrOp op) const;
IrBlock& blockOp(IrOp op) const;
Label& labelOp(IrOp op) const;
// Unscoped register allocation
RegisterX64 allocGprReg(SizeX64 preferredSize);
RegisterX64 allocXmmReg();
RegisterX64 allocGprRegOrReuse(SizeX64 preferredSize, uint32_t index, std::initializer_list<IrOp> oprefs);
RegisterX64 allocXmmRegOrReuse(uint32_t index, std::initializer_list<IrOp> oprefs);
void freeReg(RegisterX64 reg);
void freeLastUseReg(IrInst& target, uint32_t index);
void freeLastUseRegs(const IrInst& inst, uint32_t index);
ConditionX64 getX64Condition(IrCondition cond) const;
struct ScopedReg
{
ScopedReg(IrLoweringX64& owner, SizeX64 size);
~ScopedReg();
ScopedReg(const ScopedReg&) = delete;
ScopedReg& operator=(const ScopedReg&) = delete;
void free();
IrLoweringX64& owner;
RegisterX64 reg;
};
AssemblyBuilderX64& build;
ModuleHelpers& helpers;
NativeState& data;
Proto* proto = nullptr; // Temporarily required to provide 'Instruction* pc' to old emitInst* methods
IrFunction& function;
std::array<bool, 16> freeGprMap;
std::array<bool, 16> freeXmmMap;
};
} // namespace CodeGen
} // namespace Luau

View file

@ -236,9 +236,9 @@ public:
std::swap(capacity, newtable.capacity);
}
void rehash_if_full()
void rehash_if_full(const Key& key)
{
if (count >= capacity * 3 / 4)
if (count >= capacity * 3 / 4 && !find(key))
{
rehash();
}
@ -489,7 +489,7 @@ public:
const Key& insert(const Key& key)
{
impl.rehash_if_full();
impl.rehash_if_full(key);
return *impl.insert_unsafe(key);
}
@ -559,7 +559,7 @@ public:
// Note: this reference is invalidated by any insert operation (i.e. operator[])
Value& operator[](const Key& key)
{
impl.rehash_if_full();
impl.rehash_if_full(key);
return impl.insert_unsafe(key)->second;
}

View file

@ -11,7 +11,6 @@ inline bool isFlagExperimental(const char* flag)
// Flags in this list are disabled by default in various command-line tools. They may have behavior that is not fully final,
// or critical bugs that are found after the code has been submitted.
static const char* kList[] = {
"LuauInterpolatedStringBaseSupport",
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
"LuauTryhardAnd", // waiting for a fix in graphql-lua -> apollo-client-lia -> lua-apps
// makes sure we always have at least one entry

View file

@ -25,7 +25,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAG(LuauInterpolatedStringBaseSupport)
LUAU_FASTFLAGVARIABLE(LuauMultiAssignmentConflictFix, false)
LUAU_FASTFLAGVARIABLE(LuauSelfAssignmentSkip, false)
LUAU_FASTFLAGVARIABLE(LuauCompileInterpStringLimit, false)
@ -2090,7 +2089,7 @@ struct Compiler
{
compileExprIfElse(expr, target, targetTemp);
}
else if (AstExprInterpString* interpString = node->as<AstExprInterpString>(); FFlag::LuauInterpolatedStringBaseSupport && interpString)
else if (AstExprInterpString* interpString = node->as<AstExprInterpString>())
{
compileExprInterpString(interpString, target, targetTemp);
}

View file

@ -82,8 +82,10 @@ target_sources(Luau.CodeGen PRIVATE
CodeGen/src/EmitCommonX64.cpp
CodeGen/src/EmitInstructionX64.cpp
CodeGen/src/Fallbacks.cpp
CodeGen/src/IrAnalysis.cpp
CodeGen/src/IrBuilder.cpp
CodeGen/src/IrDump.cpp
CodeGen/src/IrLoweringX64.cpp
CodeGen/src/IrTranslation.cpp
CodeGen/src/NativeState.cpp
CodeGen/src/UnwindBuilderDwarf2.cpp
@ -98,9 +100,11 @@ target_sources(Luau.CodeGen PRIVATE
CodeGen/src/EmitInstructionX64.h
CodeGen/src/Fallbacks.h
CodeGen/src/FallbacksProlog.h
CodeGen/src/IrAnalysis.h
CodeGen/src/IrBuilder.h
CodeGen/src/IrDump.h
CodeGen/src/IrData.h
CodeGen/src/IrLoweringX64.h
CodeGen/src/IrTranslation.h
CodeGen/src/IrUtils.h
CodeGen/src/NativeState.h
@ -330,6 +334,7 @@ if(TARGET Luau.UnitTest)
tests/ConstraintSolver.test.cpp
tests/CostModel.test.cpp
tests/DataFlowGraph.test.cpp
tests/DenseHash.test.cpp
tests/Error.test.cpp
tests/Frontend.test.cpp
tests/JsonEmitter.test.cpp

View file

@ -12,6 +12,8 @@
#include <string.h>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauCheckGetInfoIndex, false)
static const char* getfuncname(Closure* f);
static int currentpc(lua_State* L, CallInfo* ci)
@ -174,9 +176,18 @@ int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar)
CallInfo* ci = NULL;
if (level < 0)
{
StkId func = L->top + level;
api_check(L, ttisfunction(func));
f = clvalue(func);
if (FFlag::LuauCheckGetInfoIndex)
{
const TValue* func = luaA_toobject(L, level);
api_check(L, ttisfunction(func));
f = clvalue(func);
}
else
{
StkId func = L->top + level;
api_check(L, ttisfunction(func));
f = clvalue(func);
}
}
else if (unsigned(level) < unsigned(L->ci - L->base_ci))
{

View file

@ -185,8 +185,6 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprIfThen")
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprInterpString")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
AstStat* statement = expectParseStatement("local a = `var = {x}`");
std::string_view expected =

View file

@ -2814,8 +2814,6 @@ a = if temp then even else abc@3
TEST_CASE_FIXTURE(ACFixture, "autocomplete_interpolated_string_constant")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
check(R"(f(`@1`))");
auto ac = autocomplete('1');
CHECK(ac.entryMap.empty());
@ -2839,8 +2837,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_interpolated_string_constant")
TEST_CASE_FIXTURE(ACFixture, "autocomplete_interpolated_string_expression")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
check(R"(f(`expression = {@1}`))");
auto ac = autocomplete('1');
CHECK(ac.entryMap.count("table"));
@ -2849,8 +2845,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_interpolated_string_expression")
TEST_CASE_FIXTURE(ACFixture, "autocomplete_interpolated_string_expression_with_comments")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
check(R"(f(`expression = {--[[ bla bla bla ]]@1`))");
auto ac = autocomplete('1');
@ -2866,8 +2860,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_interpolated_string_expression_with_c
TEST_CASE_FIXTURE(ACFixture, "autocomplete_interpolated_string_as_singleton")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
check(R"(
--!strict
local function f(a: "cat" | "dog") end

View file

@ -1277,15 +1277,11 @@ RETURN R1 1
TEST_CASE("InterpStringWithNoExpressions")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
CHECK_EQ(compileFunction0(R"(return "hello")"), compileFunction0("return `hello`"));
}
TEST_CASE("InterpStringZeroCost")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
CHECK_EQ("\n" + compileFunction0(R"(local _ = `hello, {"world"}!`)"),
R"(
LOADK R1 K0 ['hello, %*!']
@ -1299,8 +1295,6 @@ RETURN R0 0
TEST_CASE("InterpStringRegisterCleanup")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
CHECK_EQ("\n" + compileFunction0(R"(
local a, b, c = nil, "um", "uh oh"
a = `foo{"bar"}`
@ -1325,7 +1319,6 @@ RETURN R0 0
TEST_CASE("InterpStringRegisterLimit")
{
ScopedFastFlag luauInterpolatedStringBaseSupport{"LuauInterpolatedStringBaseSupport", true};
ScopedFastFlag luauCompileInterpStringLimit{"LuauCompileInterpStringLimit", true};
CHECK_THROWS_AS(compileFunction0(("local a = `" + rep("{1}", 254) + "`").c_str()), std::exception);

View file

@ -304,8 +304,6 @@ TEST_CASE("Strings")
TEST_CASE("StringInterp")
{
ScopedFastFlag sffInterpStrings{"LuauInterpolatedStringBaseSupport", true};
runConformance("stringinterp.lua");
}

View file

@ -227,8 +227,6 @@ end
TEST_CASE("InterpString")
{
ScopedFastFlag sff("LuauInterpolatedStringBaseSupport", true);
uint64_t model = modelFunction(R"(
function test(a)
return `hello, {a}!`

79
tests/DenseHash.test.cpp Normal file
View file

@ -0,0 +1,79 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/DenseHash.h"
#include "doctest.h"
/** So... why are we picking a very specific number to fill the DenseHash(Map|Set)?
*
* That's because that's the count that happens to trigger a specific bug.
*
* DHT determines if it is full by checking whether the count is greater than or equal to 75% of
* its capacity. Once this condition triggers, it rehashes everything by doubling its buffer.
*
* If DHT gets rehashed, the iterator gets invalidated, but it's very important that we support
* the use case of overwriting/merging DHTs while iterating.
*/
TEST_SUITE_BEGIN("DenseHashTests");
TEST_CASE("overwriting_an_existing_field_when_full_shouldnt_rehash")
{
// See the note at the top on why these numbers were chosen.
Luau::DenseHashMap<int, int> m{-1};
for (int i = 0; i < 12; ++i)
m[i] = i;
REQUIRE(m.size() == 12);
for (auto [k, a] : m)
m[k] = a + 1;
for (size_t i = 0; i < m.size(); ++i)
{
int* a = m.find(int(i));
REQUIRE(a);
CHECK(i + 1 == *a);
}
}
TEST_CASE("merging_another_map_and_resolve_conflicts_that_also_just_so_happens_to_rehash_while_iterating")
{
// See the note at the top on why these numbers were chosen.
Luau::DenseHashMap<int, int> m1{-1};
for (int i = 0; i < 12; ++i)
m1[i] = i;
Luau::DenseHashMap<int, int> m2{-1};
for (int i = 8; i < 24; ++i)
m2[i] = i;
REQUIRE(m1.size() == 12);
REQUIRE(m2.size() == 16);
for (auto [k, a] : m1)
{
if (int* b = m2.find(k))
m1[k] = a + *b;
}
for (auto [k, a] : m2)
{
if (!m1.find(k))
m1[k] = a;
}
REQUIRE(m1.size() == 24);
for (size_t i = 0; i < m1.size(); ++i)
{
int* a = m1.find(int(i));
REQUIRE(a);
if (i < 8 || i >= 12)
CHECK(i == *a);
else
CHECK(i + i == *a);
}
}
TEST_SUITE_END();

View file

@ -21,8 +21,6 @@
static const char* mainModuleName = "MainModule";
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAG(LuauReportShadowedTypeAlias)
extern std::optional<unsigned> randomSeed; // tests/main.cpp
@ -585,6 +583,7 @@ void registerHiddenTypes(Frontend* frontend)
globalScope->exportedTypeBindings["fun"] = TypeFun{{}, frontend->builtinTypes->functionType};
globalScope->exportedTypeBindings["cls"] = TypeFun{{}, frontend->builtinTypes->classType};
globalScope->exportedTypeBindings["err"] = TypeFun{{}, frontend->builtinTypes->errorType};
globalScope->exportedTypeBindings["tbl"] = TypeFun{{}, frontend->builtinTypes->tableType};
}
void createSomeClasses(Frontend* frontend)

View file

@ -94,7 +94,6 @@ struct Fixture
TypeId requireTypeAlias(const std::string& name);
ScopedFastFlag sff_DebugLuauFreezeArena;
ScopedFastFlag sff_UnknownNever{"LuauUnknownAndNeverType", true};
TestFileResolver fileResolver;
TestConfigResolver configResolver;

View file

@ -140,8 +140,6 @@ TEST_CASE("lookahead")
TEST_CASE("string_interpolation_basic")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
const std::string testInput = R"(`foo {"bar"}`)";
Luau::Allocator alloc;
AstNameTable table(alloc);
@ -159,8 +157,6 @@ TEST_CASE("string_interpolation_basic")
TEST_CASE("string_interpolation_double_brace")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
const std::string testInput = R"(`foo{{bad}}bar`)";
Luau::Allocator alloc;
AstNameTable table(alloc);
@ -179,8 +175,6 @@ TEST_CASE("string_interpolation_double_brace")
TEST_CASE("string_interpolation_double_but_unmatched_brace")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
const std::string testInput = R"(`{{oops}`, 1)";
Luau::Allocator alloc;
AstNameTable table(alloc);
@ -195,8 +189,6 @@ TEST_CASE("string_interpolation_double_but_unmatched_brace")
TEST_CASE("string_interpolation_unmatched_brace")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
const std::string testInput = R"({
`hello {"world"}
} -- this might be incorrectly parsed as a string)";
@ -213,8 +205,6 @@ TEST_CASE("string_interpolation_unmatched_brace")
TEST_CASE("string_interpolation_with_unicode_escape")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
const std::string testInput = R"(`\u{1F41B}`)";
Luau::Allocator alloc;
AstNameTable table(alloc);

View file

@ -1686,8 +1686,6 @@ TEST_CASE_FIXTURE(Fixture, "WrongCommentOptimize")
TEST_CASE_FIXTURE(Fixture, "TestStringInterpolation")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
LintResult result = lint(R"(
--!nocheck
local _ = `unknown {foo}`

View file

@ -492,9 +492,12 @@ TEST_CASE_FIXTURE(NormalizeFixture, "union_function_and_top_function")
TEST_CASE_FIXTURE(NormalizeFixture, "negated_function_is_anything_except_a_function")
{
ScopedFastFlag{"LuauNegatedClassTypes", true};
ScopedFastFlag sffs[] = {
{"LuauNegatedTableTypes", true},
{"LuauNegatedClassTypes", true},
};
CHECK("(boolean | class | number | string | thread)?" == toString(normal(R"(
CHECK("(boolean | class | number | string | table | thread)?" == toString(normal(R"(
Not<fun>
)")));
}
@ -506,9 +509,13 @@ TEST_CASE_FIXTURE(NormalizeFixture, "specific_functions_cannot_be_negated")
TEST_CASE_FIXTURE(NormalizeFixture, "bare_negated_boolean")
{
ScopedFastFlag{"LuauNegatedClassTypes", true};
ScopedFastFlag sffs[] = {
{"LuauNegatedTableTypes", true},
{"LuauNegatedClassTypes", true},
};
// TODO: We don't yet have a way to say number | string | thread | nil | Class | Table | Function
CHECK("(class | function | number | string | thread)?" == toString(normal(R"(
CHECK("(class | function | number | string | table | thread)?" == toString(normal(R"(
Not<boolean>
)")));
}
@ -603,15 +610,18 @@ TEST_CASE_FIXTURE(NormalizeFixture, "narrow_union_of_classes_with_intersection")
TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_classes")
{
ScopedFastFlag sff{"LuauNegatedClassTypes", true};
ScopedFastFlag sffs[] = {
{"LuauNegatedTableTypes", true},
{"LuauNegatedClassTypes", true},
};
createSomeClasses(&frontend);
CHECK("(Parent & ~Child) | Unrelated" == toString(normal("(Parent & Not<Child>) | Unrelated")));
CHECK("((class & ~Child) | boolean | function | number | string | thread)?" == toString(normal("Not<Child>")));
CHECK("((class & ~Child) | boolean | function | number | string | table | thread)?" == toString(normal("Not<Child>")));
CHECK("Child" == toString(normal("Not<Parent> & Child")));
CHECK("((class & ~Parent) | Child | boolean | function | number | string | thread)?" == toString(normal("Not<Parent> | Child")));
CHECK("(boolean | function | number | string | thread)?" == toString(normal("Not<cls>")));
CHECK("(Parent | Unrelated | boolean | function | number | string | thread)?" ==
CHECK("((class & ~Parent) | Child | boolean | function | number | string | table | thread)?" == toString(normal("Not<Parent> | Child")));
CHECK("(boolean | function | number | string | table | thread)?" == toString(normal("Not<cls>")));
CHECK("(Parent | Unrelated | boolean | function | number | string | table | thread)?" ==
toString(normal("Not<cls & Not<Parent> & Not<Child> & Not<Unrelated>>")));
}
@ -631,4 +641,22 @@ TEST_CASE_FIXTURE(NormalizeFixture, "classes_and_never")
CHECK("never" == toString(normal("Parent & never")));
}
TEST_CASE_FIXTURE(NormalizeFixture, "top_table_type")
{
ScopedFastFlag sff{"LuauNegatedTableTypes", true};
CHECK("table" == toString(normal("{} | tbl")));
CHECK("{| |}" == toString(normal("{} & tbl")));
CHECK("never" == toString(normal("number & tbl")));
}
TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_tables")
{
ScopedFastFlag sff{"LuauNegatedTableTypes", true};
CHECK(nullptr == toNormalizedType("Not<{}>"));
CHECK("(boolean | class | function | number | string | thread)?" == toString(normal("Not<tbl>")));
CHECK("table" == toString(normal("Not<Not<tbl>>")));
}
TEST_SUITE_END();

View file

@ -906,8 +906,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_compound_assignment_error_multiple")
TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_double_brace_begin")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
try
{
parse(R"(
@ -923,8 +921,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_double_brace_begin")
TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_double_brace_mid")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
try
{
parse(R"(
@ -940,8 +936,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_double_brace_mid")
TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_without_end_brace")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
auto columnOfEndBraceError = [this](const char* code) {
try
{
@ -966,8 +960,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_without_end_brace")
TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_without_end_brace_in_table")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
try
{
parse(R"(
@ -986,8 +978,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_without_end_brace_in_table
TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_mid_without_end_brace_in_table")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
try
{
parse(R"(
@ -1006,8 +996,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_mid_without_end_brace_in_t
TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_as_type_fail")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
try
{
parse(R"(
@ -1028,8 +1016,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_as_type_fail")
TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_call_without_parens")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
try
{
parse(R"(

View file

@ -417,7 +417,7 @@ n1 [label="BoundType 1"];
n1 -> n2;
n2 [label="TableType 2"];
n2 -> n3 [label="boundTo"];
n3 [label="TableType 3"];
n3 [label="TableType a"];
n3 -> n4 [label="x"];
n4 [label="number"];
})",

View file

@ -681,8 +681,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_for_in_multiple_types")
TEST_CASE_FIXTURE(Fixture, "transpile_string_interp")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
std::string code = R"( local _ = `hello {name}` )";
CHECK_EQ(code, transpile(code, {}, true).code);
@ -690,8 +688,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp")
TEST_CASE_FIXTURE(Fixture, "transpile_string_literal_escape")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
std::string code = R"( local _ = ` bracket = \{, backtick = \` = {'ok'} ` )";
CHECK_EQ(code, transpile(code, {}, true).code);

View file

@ -201,11 +201,11 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases")
const char* expectedError;
if (FFlag::LuauTypeMismatchInvarianceInError)
expectedError = "Type '{ v: string }' could not be converted into 'T<number>'\n"
expectedError = "Type 'bad' could not be converted into 'T<number>'\n"
"caused by:\n"
" Property 'v' is not compatible. Type 'string' could not be converted into 'number' in an invariant context";
else
expectedError = "Type '{ v: string }' could not be converted into 'T<number>'\n"
expectedError = "Type 'bad' could not be converted into 'T<number>'\n"
"caused by:\n"
" Property 'v' is not compatible. Type 'string' could not be converted into 'number'";
@ -228,13 +228,13 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
const char* expectedError;
if (FFlag::LuauTypeMismatchInvarianceInError)
expectedError = "Type '{ t: { v: string } }' could not be converted into 'U<number>'\n"
expectedError = "Type 'bad' could not be converted into 'U<number>'\n"
"caused by:\n"
" Property 't' is not compatible. Type '{ v: string }' could not be converted into 'T<number>'\n"
"caused by:\n"
" Property 'v' is not compatible. Type 'string' could not be converted into 'number' in an invariant context";
else
expectedError = "Type '{ t: { v: string } }' could not be converted into 'U<number>'\n"
expectedError = "Type 'bad' could not be converted into 'U<number>'\n"
"caused by:\n"
" Property 't' is not compatible. Type '{ v: string }' could not be converted into 'T<number>'\n"
"caused by:\n"
@ -890,8 +890,6 @@ TEST_CASE_FIXTURE(Fixture, "recursive_types_restriction_not_ok")
TEST_CASE_FIXTURE(Fixture, "report_shadowed_aliases")
{
ScopedFastFlag sff{"LuauReportShadowedTypeAlias", true};
// We allow a previous type alias to depend on a future type alias. That exact feature enables a confusing example, like the following snippet,
// which has the type alias FakeString point to the type alias `string` that which points to `number`.
CheckResult result = check(R"(

View file

@ -865,8 +865,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "calling_function_with_anytypepack_doesnt_lea
TEST_CASE_FIXTURE(Fixture, "too_many_return_values")
{
ScopedFastFlag sff{"LuauBetterMessagingOnCountMismatch", true};
CheckResult result = check(R"(
--!strict
@ -888,8 +886,6 @@ TEST_CASE_FIXTURE(Fixture, "too_many_return_values")
TEST_CASE_FIXTURE(Fixture, "too_many_return_values_in_parentheses")
{
ScopedFastFlag sff{"LuauBetterMessagingOnCountMismatch", true};
CheckResult result = check(R"(
--!strict
@ -911,8 +907,6 @@ TEST_CASE_FIXTURE(Fixture, "too_many_return_values_in_parentheses")
TEST_CASE_FIXTURE(Fixture, "too_many_return_values_no_function")
{
ScopedFastFlag sff{"LuauBetterMessagingOnCountMismatch", true};
CheckResult result = check(R"(
--!strict

View file

@ -519,7 +519,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus")
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK(toString(result.errors[0]) == "Type '{ value: number }' could not be converted into 'number'");
CHECK(toString(result.errors[0]) == "Type 'bar' could not be converted into 'number'");
}
else
{
@ -978,8 +978,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "equality_operations_succeed_if_any_union_bra
TEST_CASE_FIXTURE(BuiltinsFixture, "expected_types_through_binary_and")
{
ScopedFastFlag sff{"LuauBinaryNeedsExpectedTypesToo", true};
CheckResult result = check(R"(
local x: "a" | "b" | boolean = math.random() > 0.5 and "a"
)");
@ -989,8 +987,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "expected_types_through_binary_and")
TEST_CASE_FIXTURE(BuiltinsFixture, "expected_types_through_binary_or")
{
ScopedFastFlag sff{"LuauBinaryNeedsExpectedTypesToo", true};
CheckResult result = check(R"(
local x: "a" | "b" | boolean = math.random() > 0.5 or "b"
)");

View file

@ -342,8 +342,6 @@ TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_variadic_pack")
// Belongs in TypeInfer.builtins.test.cpp.
TEST_CASE_FIXTURE(BuiltinsFixture, "pcall_returns_at_least_two_value_but_function_returns_nothing")
{
ScopedFastFlag sff{"LuauBetterMessagingOnCountMismatch", true};
CheckResult result = check(R"(
local function f(): () end
local ok, res = pcall(f)

View file

@ -1014,7 +1014,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "merge_should_be_fully_agnostic_of_hashmap_or
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("(never | string) & (string | {| x: string |}) & string", toString(requireTypeAtPosition({6, 28})));
CHECK_EQ("(string | table) & (string | {| x: string |}) & string", toString(requireTypeAtPosition({6, 28})));
}
else
{
@ -1241,8 +1241,6 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_tag")
TEST_CASE_FIXTURE(Fixture, "discriminate_tag_with_implicit_else")
{
ScopedFastFlag sff{"LuauImplicitElseRefinement", true};
CheckResult result = check(R"(
type Cat = {tag: "Cat", name: string, catfood: string}
type Dog = {tag: "Dog", name: string, dogfood: string}
@ -1561,7 +1559,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_instance_without
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("Folder & Instance", toString(requireTypeAtPosition({3, 28})));
CHECK_EQ("Instance & ~Folder & never", toString(requireTypeAtPosition({5, 28})));
CHECK_EQ("Instance & ~Folder & table", toString(requireTypeAtPosition({5, 28})));
}
else
{
@ -1728,8 +1726,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "what_nonsensical_condition")
TEST_CASE_FIXTURE(Fixture, "else_with_no_explicit_expression_should_also_refine_the_tagged_union")
{
ScopedFastFlag sff{"LuauImplicitElseRefinement", true};
CheckResult result = check(R"(
type Ok<T> = { tag: "ok", value: T }
type Err<E> = { tag: "err", err: E }

View file

@ -341,9 +341,9 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias")
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expectedError = "Type '{ result: string, success: false }' could not be converted into 'Err<number> | Ok<string>'\n"
const std::string expectedError = "Type 'a' could not be converted into 'Err<number> | Ok<string>'\n"
"caused by:\n"
" None of the union options are compatible. For example: Table type '{ result: string, success: false }'"
" None of the union options are compatible. For example: Table type 'a'"
" not compatible with type 'Err<number>' because the former is missing field 'error'";
CHECK(toString(result.errors[0]) == expectedError);
@ -500,8 +500,6 @@ TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_union_of_string_singleton")
TEST_CASE_FIXTURE(Fixture, "no_widening_from_callsites")
{
ScopedFastFlag sff{"LuauReturnsFromCallsitesAreNotWidened", true};
CheckResult result = check(R"(
type Direction = "North" | "East" | "West" | "South"

View file

@ -3214,8 +3214,6 @@ local b = a.x
TEST_CASE_FIXTURE(Fixture, "scalar_is_a_subtype_of_a_compatible_polymorphic_shape_type")
{
ScopedFastFlag sff{"LuauScalarShapeSubtyping", true};
CheckResult result = check(R"(
local function f(s)
return s:lower()
@ -3231,8 +3229,6 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_a_subtype_of_a_compatible_polymorphic_shap
TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type")
{
ScopedFastFlag sff{"LuauScalarShapeSubtyping", true};
CheckResult result = check(R"(
local function f(s)
return s:absolutely_no_scalar_has_this_method()
@ -3263,7 +3259,6 @@ caused by:
TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compatible")
{
ScopedFastFlag sff{"LuauScalarShapeSubtyping", true};
ScopedFastFlag luauScalarShapeUnifyToMtOwner{"LuauScalarShapeUnifyToMtOwner2", true}; // Changes argument from table type to primitive
CheckResult result = check(R"(
@ -3279,8 +3274,6 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compati
TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible")
{
ScopedFastFlag sff{"LuauScalarShapeSubtyping", true};
CheckResult result = check(R"(
local function f(s): string
local foo = s:absolutely_no_scalar_has_this_method()
@ -3298,7 +3291,6 @@ caused by:
TEST_CASE_FIXTURE(BuiltinsFixture, "a_free_shape_can_turn_into_a_scalar_directly")
{
ScopedFastFlag luauScalarShapeSubtyping{"LuauScalarShapeSubtyping", true};
ScopedFastFlag luauScalarShapeUnifyToMtOwner{"LuauScalarShapeUnifyToMtOwner2", true};
CheckResult result = check(R"(
@ -3385,7 +3377,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_has_a_side_effect")
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK(toString(requireType("foo")) == "{ @metatable { __add: <a, b>(a, b) -> number }, { } }");
CHECK(toString(requireType("foo")) == "{ @metatable mt, foo }");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "tables_should_be_fully_populated")

View file

@ -816,8 +816,6 @@ end
TEST_CASE_FIXTURE(Fixture, "tc_interpolated_string_basic")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
CheckResult result = check(R"(
local foo: string = `hello {"world"}`
)");
@ -827,8 +825,6 @@ TEST_CASE_FIXTURE(Fixture, "tc_interpolated_string_basic")
TEST_CASE_FIXTURE(Fixture, "tc_interpolated_string_with_invalid_expression")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
CheckResult result = check(R"(
local function f(x: number) end
@ -840,8 +836,6 @@ TEST_CASE_FIXTURE(Fixture, "tc_interpolated_string_with_invalid_expression")
TEST_CASE_FIXTURE(Fixture, "tc_interpolated_string_constant_type")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
CheckResult result = check(R"(
local foo: "hello" = `hello`
)");
@ -1153,6 +1147,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "it_is_ok_to_have_inconsistent_number_of_retu
TEST_CASE_FIXTURE(Fixture, "fuzz_free_table_type_change_during_index_check")
{
ScopedFastFlag sff{"LuauScalarShapeUnifyToMtOwner2", true};
CheckResult result = check(R"(
local _ = nil
while _["" >= _] do

View file

@ -268,8 +268,6 @@ TEST_CASE_FIXTURE(Fixture, "unary_minus_of_never")
TEST_CASE_FIXTURE(Fixture, "length_of_never")
{
ScopedFastFlag sff{"LuauNeverTypesAndOperatorsInference", true};
CheckResult result = check(R"(
local x = #({} :: never)
)");
@ -282,8 +280,6 @@ TEST_CASE_FIXTURE(Fixture, "length_of_never")
TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_in_any_ordering_operators")
{
ScopedFastFlag sff[]{
{"LuauUnknownAndNeverType", true},
{"LuauNeverTypesAndOperatorsInference", true},
{"LuauTryhardAnd", true},
};
@ -300,11 +296,6 @@ TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_i
TEST_CASE_FIXTURE(Fixture, "math_operators_and_never")
{
ScopedFastFlag sff[]{
{"LuauUnknownAndNeverType", true},
{"LuauNeverTypesAndOperatorsInference", true},
};
CheckResult result = check(R"(
local function mul(x: nil, y)
return x ~= nil and x * y -- infers boolean | never, which is normalized into boolean

View file

@ -521,6 +521,30 @@ TEST_CASE_FIXTURE(ReductionFixture, "intersections_without_negations")
TypeId ty = reductionof("(Parent & Child) | (Parent & AnotherChild) | (Parent & Unrelated)");
CHECK("AnotherChild | Child" == toString(ty));
}
SUBCASE("top_table_and_table")
{
TypeId ty = reductionof("tbl & {}");
CHECK("{| |}" == toString(ty));
}
SUBCASE("top_table_and_non_table")
{
TypeId ty = reductionof("tbl & \"foo\"");
CHECK("never" == toString(ty));
}
SUBCASE("top_table_and_metatable")
{
BuiltinsFixture fixture;
registerHiddenTypes(&fixture.frontend);
fixture.check(R"(
type Ty = tbl & typeof(setmetatable({}, {}))
)");
TypeId ty = reductionof(fixture.requireTypeAlias("Ty"));
CHECK("{ @metatable { }, { } }" == toString(ty));
}
} // intersections_without_negations
TEST_CASE_FIXTURE(ReductionFixture, "intersections_with_negations")
@ -686,6 +710,24 @@ TEST_CASE_FIXTURE(ReductionFixture, "intersections_with_negations")
TypeId ty = reductionof("{ x: { p: string } } & { x: { p: Not<number> } }");
CHECK("{| x: {| p: string |} |}" == toStringFull(ty));
}
SUBCASE("not_top_table_and_table")
{
TypeId ty = reductionof("Not<tbl> & {}");
CHECK("never" == toString(ty));
}
SUBCASE("not_top_table_and_metatable")
{
BuiltinsFixture fixture;
registerHiddenTypes(&fixture.frontend);
fixture.check(R"(
type Ty = Not<tbl> & typeof(setmetatable({}, {}))
)");
TypeId ty = reductionof(fixture.requireTypeAlias("Ty"));
CHECK("never" == toString(ty));
}
} // intersections_with_negations
TEST_CASE_FIXTURE(ReductionFixture, "unions_without_negations")
@ -911,6 +953,30 @@ TEST_CASE_FIXTURE(ReductionFixture, "unions_without_negations")
TypeId ty = reductionof("string | err");
CHECK("*error-type* | string" == toStringFull(ty));
}
SUBCASE("top_table_or_table")
{
TypeId ty = reductionof("tbl | {}");
CHECK("table" == toString(ty));
}
SUBCASE("top_table_or_metatable")
{
BuiltinsFixture fixture;
registerHiddenTypes(&fixture.frontend);
fixture.check(R"(
type Ty = tbl | typeof(setmetatable({}, {}))
)");
TypeId ty = reductionof(fixture.requireTypeAlias("Ty"));
CHECK("table" == toString(ty));
}
SUBCASE("top_table_or_non_table")
{
TypeId ty = reductionof("tbl | number");
CHECK("number | table" == toString(ty));
}
} // unions_without_negations
TEST_CASE_FIXTURE(ReductionFixture, "unions_with_negations")
@ -1124,6 +1190,24 @@ TEST_CASE_FIXTURE(ReductionFixture, "unions_with_negations")
TypeId ty = reductionof("string | Not<err>");
CHECK("string | ~*error-type*" == toStringFull(ty));
}
SUBCASE("not_top_table_or_table")
{
TypeId ty = reductionof("Not<tbl> | {}");
CHECK("{| |} | ~table" == toString(ty));
}
SUBCASE("not_top_table_or_metatable")
{
BuiltinsFixture fixture;
registerHiddenTypes(&fixture.frontend);
fixture.check(R"(
type Ty = Not<tbl> | typeof(setmetatable({}, {}))
)");
TypeId ty = reductionof(fixture.requireTypeAlias("Ty"));
CHECK("{ @metatable { }, { } } | ~table" == toString(ty));
}
} // unions_with_negations
TEST_CASE_FIXTURE(ReductionFixture, "tables")

View file

@ -11,14 +11,7 @@ AstQuery::getDocumentationSymbolAtPosition.overloaded_class_method
AstQuery::getDocumentationSymbolAtPosition.overloaded_fn
AstQuery::getDocumentationSymbolAtPosition.table_overloaded_function_prop
AutocompleteTest.autocomplete_first_function_arg_expected_type
AutocompleteTest.autocomplete_interpolated_string_as_singleton
AutocompleteTest.autocomplete_interpolated_string_constant
AutocompleteTest.autocomplete_interpolated_string_expression
AutocompleteTest.autocomplete_interpolated_string_expression_with_comments
AutocompleteTest.autocomplete_oop_implicit_self
AutocompleteTest.autocomplete_string_singleton_escape
AutocompleteTest.autocomplete_string_singletons
AutocompleteTest.autocompleteProp_index_function_metamethod_is_variadic
AutocompleteTest.do_compatible_self_calls
AutocompleteTest.do_wrong_compatible_self_calls
AutocompleteTest.keyword_methods
@ -34,12 +27,7 @@ AutocompleteTest.type_correct_expected_argument_type_suggestion_optional
AutocompleteTest.type_correct_expected_argument_type_suggestion_self
AutocompleteTest.type_correct_expected_return_type_pack_suggestion
AutocompleteTest.type_correct_expected_return_type_suggestion
AutocompleteTest.type_correct_function_no_parenthesis
AutocompleteTest.type_correct_function_return_types
AutocompleteTest.type_correct_keywords
AutocompleteTest.type_correct_suggestion_for_overloads
AutocompleteTest.type_correct_suggestion_in_argument
AutocompleteTest.type_correct_suggestion_in_table
BuiltinTests.aliased_string_format
BuiltinTests.assert_removes_falsy_types
BuiltinTests.assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type
@ -49,20 +37,13 @@ BuiltinTests.coroutine_wrap_anything_goes
BuiltinTests.debug_info_is_crazy
BuiltinTests.debug_traceback_is_crazy
BuiltinTests.dont_add_definitions_to_persistent_types
BuiltinTests.find_capture_types
BuiltinTests.find_capture_types2
BuiltinTests.find_capture_types3
BuiltinTests.gmatch_definition
BuiltinTests.ipairs_iterator_should_infer_types_and_type_check
BuiltinTests.match_capture_types
BuiltinTests.match_capture_types2
BuiltinTests.math_max_checks_for_numbers
BuiltinTests.next_iterator_should_infer_types_and_type_check
BuiltinTests.pairs_iterator_should_infer_types_and_type_check
BuiltinTests.see_thru_select
BuiltinTests.select_slightly_out_of_range
BuiltinTests.select_way_out_of_range
BuiltinTests.select_with_decimal_argument_is_rounded_down
BuiltinTests.set_metatable_needs_arguments
BuiltinTests.setmetatable_should_not_mutate_persisted_types
BuiltinTests.sort_with_bad_predicate
@ -78,19 +59,16 @@ BuiltinTests.table_pack_reduce
BuiltinTests.table_pack_variadic
DefinitionTests.class_definition_overload_metamethods
DefinitionTests.class_definition_string_props
DefinitionTests.declaring_generic_functions
DefinitionTests.definition_file_classes
DefinitionTests.definitions_symbols_are_generated_for_recursively_referenced_types
DefinitionTests.single_class_type_identity_in_global_types
FrontendTest.environments
FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded
FrontendTest.nocheck_cycle_used_by_checked
FrontendTest.reexport_cyclic_type
FrontendTest.trace_requires_in_nonstrict_mode
GenericsTests.apply_type_function_nested_generics1
GenericsTests.apply_type_function_nested_generics2
GenericsTests.better_mismatch_error_messages
GenericsTests.check_generic_typepack_function
GenericsTests.check_mutual_generic_functions
GenericsTests.correctly_instantiate_polymorphic_member_functions
GenericsTests.do_not_infer_generic_functions
@ -176,7 +154,6 @@ RefinementTest.x_is_not_instance_or_else_not_part
RuntimeLimits.typescript_port_of_Result_type
TableTests.a_free_shape_can_turn_into_a_scalar_directly
TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
TableTests.access_index_metamethod_that_returns_variadic
TableTests.accidentally_checked_prop_in_opposite_branch
TableTests.builtin_table_names
TableTests.call_method
@ -196,14 +173,12 @@ TableTests.expected_indexer_from_table_union
TableTests.expected_indexer_value_type_extra
TableTests.expected_indexer_value_type_extra_2
TableTests.explicitly_typed_table
TableTests.explicitly_typed_table_error
TableTests.explicitly_typed_table_with_indexer
TableTests.found_like_key_in_table_function_call
TableTests.found_like_key_in_table_property_access
TableTests.found_multiple_like_keys
TableTests.function_calls_produces_sealed_table_given_unsealed_table
TableTests.generic_table_instantiation_potential_regression
TableTests.getmetatable_returns_pointer_to_metatable
TableTests.give_up_after_one_metatable_index_look_up
TableTests.indexer_on_sealed_table_must_unify_with_free_table
TableTests.indexing_from_a_table_should_prefer_properties_when_possible
@ -217,8 +192,6 @@ TableTests.invariant_table_properties_means_instantiating_tables_in_assignment_i
TableTests.invariant_table_properties_means_instantiating_tables_in_call_is_unsound
TableTests.leaking_bad_metatable_errors
TableTests.less_exponential_blowup_please
TableTests.meta_add_inferred
TableTests.metatable_mismatch_should_fail
TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred
TableTests.mixed_tables_with_implicit_numbered_keys
TableTests.nil_assign_doesnt_hit_indexer
@ -228,7 +201,6 @@ TableTests.only_ascribe_synthetic_names_at_module_scope
TableTests.oop_indexer_works
TableTests.oop_polymorphic
TableTests.open_table_unification_2
TableTests.property_lookup_through_tabletypevar_metatable
TableTests.quantify_even_that_table_was_never_exported_at_all
TableTests.quantify_metatables_of_metatables_of_table
TableTests.quantifying_a_bound_var_works
@ -248,7 +220,6 @@ TableTests.table_param_row_polymorphism_3
TableTests.table_simple_call
TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors
TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors
TableTests.tables_get_names_from_their_locals
TableTests.tc_member_function
TableTests.tc_member_function_2
TableTests.unification_of_unions_in_a_self_referential_type
@ -288,7 +259,6 @@ TypeAliases.mutually_recursive_types_restriction_not_ok_2
TypeAliases.mutually_recursive_types_swapsies_not_ok
TypeAliases.recursive_types_restriction_not_ok
TypeAliases.report_shadowed_aliases
TypeAliases.stringify_optional_parameterized_alias
TypeAliases.stringify_type_alias_of_recursive_template_table_type
TypeAliases.type_alias_local_mutation
TypeAliases.type_alias_local_rename
@ -308,12 +278,9 @@ TypeInfer.no_stack_overflow_from_isoptional
TypeInfer.no_stack_overflow_from_isoptional2
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
TypeInfer.tc_if_else_expressions_expected_type_3
TypeInfer.tc_interpolated_string_basic
TypeInfer.tc_interpolated_string_with_invalid_expression
TypeInfer.type_infer_recursion_limit_no_ice
TypeInfer.type_infer_recursion_limit_normalizer
TypeInferAnyError.for_in_loop_iterator_is_any2
TypeInferAnyError.metatable_of_any_can_be_a_table
TypeInferClasses.can_read_prop_of_base_class_using_string
TypeInferClasses.class_type_mismatch_with_name_conflict
TypeInferClasses.classes_without_overloaded_operators_cannot_be_added
@ -355,7 +322,6 @@ TypeInferFunctions.too_many_arguments_error_location
TypeInferFunctions.too_many_return_values_in_parentheses
TypeInferFunctions.too_many_return_values_no_function
TypeInferFunctions.vararg_function_is_quantified
TypeInferLoops.for_in_loop
TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values
TypeInferLoops.for_in_loop_with_next
TypeInferLoops.for_in_with_generic_next
@ -393,6 +359,7 @@ TypeInferOperators.infer_any_in_all_modes_when_lhs_is_unknown
TypeInferOperators.operator_eq_completely_incompatible
TypeInferOperators.or_joins_types_with_no_superfluous_union
TypeInferOperators.produce_the_correct_error_message_when_comparing_a_table_with_a_metatable_with_one_that_does_not
TypeInferOperators.refine_and_or
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs
TypeInferOperators.UnknownGlobalCompoundAssign
@ -435,8 +402,6 @@ TypePackTests.unify_variadic_tails_in_arguments
TypePackTests.unify_variadic_tails_in_arguments_free
TypePackTests.variadic_packs
TypeReductionTests.negations
TypeSingletons.error_detailed_tagged_union_mismatch_bool
TypeSingletons.error_detailed_tagged_union_mismatch_string
TypeSingletons.function_call_with_singletons
TypeSingletons.function_call_with_singletons_mismatch
TypeSingletons.indexing_on_string_singletons
@ -451,8 +416,6 @@ TypeSingletons.taking_the_length_of_union_of_string_singleton
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
TypeSingletons.widening_happens_almost_everywhere
TypeSingletons.widening_happens_almost_everywhere_except_for_tables
UnionTypes.error_detailed_optional
UnionTypes.error_detailed_union_all
UnionTypes.index_on_a_union_type_with_missing_property
UnionTypes.index_on_a_union_type_with_one_optional_property
UnionTypes.index_on_a_union_type_with_one_property_of_type_any