mirror of
https://github.com/luau-lang/luau.git
synced 2025-05-04 10:33:46 +01:00
Sync to upstream/release/672
This commit is contained in:
parent
2b7a89db49
commit
3adf25898a
58 changed files with 1389 additions and 445 deletions
|
@ -74,10 +74,6 @@ struct FreeType
|
|||
// This one got promoted to explicit
|
||||
explicit FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound, Polarity polarity = Polarity::Unknown);
|
||||
explicit FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId upperBound);
|
||||
// Old constructors
|
||||
explicit FreeType(TypeLevel level);
|
||||
explicit FreeType(Scope* scope);
|
||||
FreeType(Scope* scope, TypeLevel level);
|
||||
|
||||
int index;
|
||||
TypeLevel level;
|
||||
|
|
|
@ -37,10 +37,6 @@ struct TypeArena
|
|||
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope);
|
||||
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLevel level);
|
||||
|
||||
TypeId freshType_DEPRECATED(TypeLevel level);
|
||||
TypeId freshType_DEPRECATED(Scope* scope);
|
||||
TypeId freshType_DEPRECATED(Scope* scope, TypeLevel level);
|
||||
|
||||
TypePackId freshTypePack(Scope* scope, Polarity polarity = Polarity::Unknown);
|
||||
|
||||
TypePackId addTypePack(std::initializer_list<TypeId> types);
|
||||
|
|
|
@ -185,6 +185,8 @@ TypePackIterator begin(TypePackId tp);
|
|||
TypePackIterator begin(TypePackId tp, const TxnLog* log);
|
||||
TypePackIterator end(TypePackId tp);
|
||||
|
||||
TypePackId getTail(TypePackId tp);
|
||||
|
||||
using SeenSet = std::set<std::pair<const void*, const void*>>;
|
||||
|
||||
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs);
|
||||
|
|
|
@ -475,26 +475,26 @@ struct AstJsonEncoder : public AstVisitor
|
|||
writeRaw("}");
|
||||
}
|
||||
|
||||
void write(const AstGenericType& genericType)
|
||||
void write(class AstGenericType* genericType)
|
||||
{
|
||||
writeRaw("{");
|
||||
bool c = pushComma();
|
||||
writeType("AstGenericType");
|
||||
write("name", genericType.name);
|
||||
if (genericType.defaultValue)
|
||||
write("luauType", genericType.defaultValue);
|
||||
write("name", genericType->name);
|
||||
if (genericType->defaultValue)
|
||||
write("luauType", genericType->defaultValue);
|
||||
popComma(c);
|
||||
writeRaw("}");
|
||||
}
|
||||
|
||||
void write(const AstGenericTypePack& genericTypePack)
|
||||
void write(class AstGenericTypePack* genericTypePack)
|
||||
{
|
||||
writeRaw("{");
|
||||
bool c = pushComma();
|
||||
writeType("AstGenericTypePack");
|
||||
write("name", genericTypePack.name);
|
||||
if (genericTypePack.defaultValue)
|
||||
write("luauType", genericTypePack.defaultValue);
|
||||
write("name", genericTypePack->name);
|
||||
if (genericTypePack->defaultValue)
|
||||
write("luauType", genericTypePack->defaultValue);
|
||||
popComma(c);
|
||||
writeRaw("}");
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
|
||||
|
@ -1550,8 +1549,7 @@ bool MagicClone::infer(const MagicFunctionCallContext& context)
|
|||
static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCallContext& context)
|
||||
{
|
||||
TypeArena* arena = context.solver->arena;
|
||||
if (FFlag::LuauFollowTableFreeze)
|
||||
inputType = follow(inputType);
|
||||
inputType = follow(inputType);
|
||||
if (auto mt = get<MetatableType>(inputType))
|
||||
{
|
||||
std::optional<TypeId> frozenTable = freezeTable(mt->table, context);
|
||||
|
|
|
@ -51,16 +51,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor
|
|||
|
||||
bool visit(TypeId, const TypeFunctionInstanceType&) override
|
||||
{
|
||||
// We do not consider reference counted types that are inside a type
|
||||
// function to be part of the reachable reference counted types.
|
||||
// Otherwise, code can be constructed in just the right way such
|
||||
// that two type functions both claim to mutate a free type, which
|
||||
// prevents either type function from trying to generalize it, so
|
||||
// we potentially get stuck.
|
||||
//
|
||||
// The default behavior here is `true` for "visit the child types"
|
||||
// of this type, hence:
|
||||
return false;
|
||||
return FFlag::DebugLuauGreedyGeneralization;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -130,8 +121,10 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
|
|||
}
|
||||
else if (auto hic = get<HasIndexerConstraint>(*this))
|
||||
{
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
rci.traverse(hic->subjectType);
|
||||
rci.traverse(hic->resultType);
|
||||
// `HasIndexerConstraint` should not mutate `subjectType` or `indexType`.
|
||||
// `HasIndexerConstraint` should not mutate `indexType`.
|
||||
}
|
||||
else if (auto apc = get<AssignPropConstraint>(*this))
|
||||
{
|
||||
|
@ -150,6 +143,10 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
|
|||
rci.traverse(ty);
|
||||
// `UnpackConstraint` should not mutate `sourcePack`.
|
||||
}
|
||||
else if (auto rpc = get<ReduceConstraint>(*this); FFlag::DebugLuauGreedyGeneralization && rpc)
|
||||
{
|
||||
rci.traverse(rpc->ty);
|
||||
}
|
||||
else if (auto rpc = get<ReducePackConstraint>(*this))
|
||||
{
|
||||
rci.traverse(rpc->tp);
|
||||
|
|
|
@ -39,7 +39,6 @@ LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
|
|||
|
||||
LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions)
|
||||
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations)
|
||||
|
||||
|
@ -52,6 +51,7 @@ LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
|
|||
|
||||
LUAU_FASTFLAGVARIABLE(LuauNoTypeFunctionsNamedTypeOf)
|
||||
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
|
||||
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -1409,7 +1409,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
|
|||
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
|
||||
sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->name->location};
|
||||
|
||||
bool sigFullyDefined = !hasFreeType(sig.signature);
|
||||
bool sigFullyDefined = FFlag::DebugLuauGreedyGeneralization ? false : !hasFreeType(sig.signature);
|
||||
if (sigFullyDefined)
|
||||
emplaceType<BoundType>(asMutable(functionType), sig.signature);
|
||||
|
||||
|
@ -1471,7 +1471,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
|
|||
|
||||
Checkpoint start = checkpoint(this);
|
||||
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
|
||||
bool sigFullyDefined = !hasFreeType(sig.signature);
|
||||
bool sigFullyDefined = FFlag::DebugLuauGreedyGeneralization ? false : !hasFreeType(sig.signature);
|
||||
|
||||
DefId def = dfg->getDef(function->name);
|
||||
|
||||
|
@ -2389,9 +2389,12 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
|||
this,
|
||||
[checkConstraint, callConstraint](const ConstraintPtr& constraint)
|
||||
{
|
||||
constraint->dependencies.emplace_back(checkConstraint);
|
||||
if (!(FFlag::DebugLuauGreedyGeneralization && get<PrimitiveTypeConstraint>(*constraint)))
|
||||
{
|
||||
constraint->dependencies.emplace_back(checkConstraint);
|
||||
|
||||
callConstraint->dependencies.emplace_back(constraint.get());
|
||||
callConstraint->dependencies.emplace_back(constraint.get());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -2496,8 +2499,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin
|
|||
}
|
||||
else
|
||||
{
|
||||
FreeType ft =
|
||||
FFlag::LuauFreeTypesMustHaveBounds ? FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType} : FreeType{scope.get()};
|
||||
FreeType ft = FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType};
|
||||
ft.lowerBound = arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}});
|
||||
ft.upperBound = builtinTypes->stringType;
|
||||
freeTy = arena->addType(ft);
|
||||
|
@ -2524,8 +2526,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool*
|
|||
}
|
||||
else
|
||||
{
|
||||
FreeType ft =
|
||||
FFlag::LuauFreeTypesMustHaveBounds ? FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType} : FreeType{scope.get()};
|
||||
FreeType ft = FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType};
|
||||
ft.lowerBound = singletonType;
|
||||
ft.upperBound = builtinTypes->booleanType;
|
||||
freeTy = arena->addType(ft);
|
||||
|
@ -3076,7 +3077,7 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprLocal* local
|
|||
if (ty)
|
||||
{
|
||||
TypeIds* localDomain = localTypes.find(*ty);
|
||||
if (localDomain)
|
||||
if (localDomain && !(FFlag::LuauDoNotAddUpvalueTypesToLocalType && local->upvalue))
|
||||
localDomain->insert(rhsType);
|
||||
}
|
||||
else
|
||||
|
@ -3107,8 +3108,10 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprLocal* local
|
|||
if (annotatedTy)
|
||||
addConstraint(scope, local->location, SubtypeConstraint{rhsType, *annotatedTy});
|
||||
|
||||
if (TypeIds* localDomain = localTypes.find(*ty))
|
||||
localDomain->insert(rhsType);
|
||||
// This is vestigial.
|
||||
if (!FFlag::LuauDoNotAddUpvalueTypesToLocalType)
|
||||
if (TypeIds* localDomain = localTypes.find(*ty))
|
||||
localDomain->insert(rhsType);
|
||||
}
|
||||
|
||||
void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprGlobal* global, TypeId rhsType)
|
||||
|
@ -3410,13 +3413,22 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
|
|||
bodyScope->varargPack = std::nullopt;
|
||||
}
|
||||
|
||||
LUAU_ASSERT(nullptr != varargPack);
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
genericTypes = argTypes;
|
||||
// Some of the types in argTypes will eventually be generics, and some
|
||||
// will not. The ones that are not generic will be pruned when
|
||||
// GeneralizationConstraint dispatches.
|
||||
genericTypes.insert(genericTypes.begin(), argTypes.begin(), argTypes.end());
|
||||
varargPack = follow(varargPack);
|
||||
returnType = follow(returnType);
|
||||
if (varargPack == returnType)
|
||||
genericTypePacks = {varargPack};
|
||||
else
|
||||
genericTypePacks = {varargPack, returnType};
|
||||
}
|
||||
|
||||
LUAU_ASSERT(nullptr != varargPack);
|
||||
|
||||
// If there is both an annotation and an expected type, the annotation wins.
|
||||
// Type checking will sort out any discrepancies later.
|
||||
if (FFlag::LuauStoreReturnTypesAsPackOnAst && fn->returnAnnotation)
|
||||
|
|
|
@ -681,7 +681,6 @@ void ConstraintSolver::initFreeTypeTracking()
|
|||
}
|
||||
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
|
||||
|
||||
|
||||
for (NotNull<const Constraint> dep : c->dependencies)
|
||||
{
|
||||
block(dep, c);
|
||||
|
@ -2082,24 +2081,61 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
|
|||
if (auto lhsFree = getMutable<FreeType>(lhsType))
|
||||
{
|
||||
auto lhsFreeUpperBound = follow(lhsFree->upperBound);
|
||||
if (get<TableType>(lhsFreeUpperBound) || get<MetatableType>(lhsFreeUpperBound))
|
||||
lhsType = lhsFreeUpperBound;
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
const auto [blocked, maybeTy, isIndex] = lookupTableProp(constraint, lhsType, propName, ValueContext::LValue);
|
||||
if (!blocked.empty())
|
||||
{
|
||||
for (TypeId t : blocked)
|
||||
block(t, constraint);
|
||||
return false;
|
||||
}
|
||||
else if (maybeTy)
|
||||
{
|
||||
bind(constraint, c.propType, isIndex ? arena->addType(UnionType{{*maybeTy, builtinTypes->nilType}}) : *maybeTy);
|
||||
unify(constraint, rhsType, *maybeTy);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope});
|
||||
|
||||
trackInteriorFreeType(constraint->scope, newUpperBound);
|
||||
|
||||
TableType* upperTable = getMutable<TableType>(newUpperBound);
|
||||
LUAU_ASSERT(upperTable);
|
||||
|
||||
upperTable->props[c.propName] = rhsType;
|
||||
|
||||
// Food for thought: Could we block if simplification encounters a blocked type?
|
||||
lhsFree->upperBound = simplifyIntersection(constraint->scope, constraint->location, lhsFreeUpperBound, newUpperBound);
|
||||
|
||||
bind(constraint, c.propType, rhsType);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope});
|
||||
if (get<TableType>(lhsFreeUpperBound) || get<MetatableType>(lhsFreeUpperBound))
|
||||
lhsType = lhsFreeUpperBound;
|
||||
else
|
||||
{
|
||||
TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope});
|
||||
|
||||
trackInteriorFreeType(constraint->scope, newUpperBound);
|
||||
trackInteriorFreeType(constraint->scope, newUpperBound);
|
||||
|
||||
TableType* upperTable = getMutable<TableType>(newUpperBound);
|
||||
LUAU_ASSERT(upperTable);
|
||||
TableType* upperTable = getMutable<TableType>(newUpperBound);
|
||||
LUAU_ASSERT(upperTable);
|
||||
|
||||
upperTable->props[c.propName] = rhsType;
|
||||
upperTable->props[c.propName] = rhsType;
|
||||
|
||||
// Food for thought: Could we block if simplification encounters a blocked type?
|
||||
lhsFree->upperBound = simplifyIntersection(constraint->scope, constraint->location, lhsFreeUpperBound, newUpperBound);
|
||||
// Food for thought: Could we block if simplification encounters a blocked type?
|
||||
lhsFree->upperBound = simplifyIntersection(constraint->scope, constraint->location, lhsFreeUpperBound, newUpperBound);
|
||||
|
||||
bind(constraint, c.propType, rhsType);
|
||||
return true;
|
||||
bind(constraint, c.propType, rhsType);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2873,8 +2909,21 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
|
|||
{
|
||||
const TypeId upperBound = follow(ft->upperBound);
|
||||
|
||||
if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
|
||||
return lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen);
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
|
||||
{
|
||||
TablePropLookupResult res = lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen);
|
||||
// If the upper bound is a table that already has the property, we don't need to extend its bounds.
|
||||
if (res.propType || get<PrimitiveType>(upperBound))
|
||||
return res;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
|
||||
return lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen);
|
||||
}
|
||||
|
||||
// TODO: The upper bound could be an intersection that contains suitable tables or extern types.
|
||||
|
||||
|
|
|
@ -17,7 +17,8 @@ LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument)
|
|||
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
|
||||
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDfgIfBlocksShouldRespectControlFlow)
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -501,12 +502,26 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
|
|||
}
|
||||
|
||||
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
|
||||
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
|
||||
join(scope, scope, elseScope);
|
||||
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
|
||||
join(scope, thenScope, scope);
|
||||
else if ((thencf | elsecf) == ControlFlow::None)
|
||||
join(scope, thenScope, elseScope);
|
||||
if (FFlag::LuauDfgIfBlocksShouldRespectControlFlow)
|
||||
{
|
||||
// If the control flow from the `if` or `else` block is non-linear,
|
||||
// then we should assume that the _other_ branch is the one taken.
|
||||
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
|
||||
scope->inherit(elseScope);
|
||||
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
|
||||
scope->inherit(thenScope);
|
||||
else if ((thencf | elsecf) == ControlFlow::None)
|
||||
join(scope, thenScope, elseScope);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
|
||||
join(scope, scope, elseScope);
|
||||
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
|
||||
join(scope, thenScope, scope);
|
||||
else if ((thencf | elsecf) == ControlFlow::None)
|
||||
join(scope, thenScope, elseScope);
|
||||
}
|
||||
|
||||
if (thencf == elsecf)
|
||||
return thencf;
|
||||
|
@ -1164,7 +1179,7 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef)
|
|||
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
|
||||
|
||||
// In order to avoid alias tracking, we need to clip the reference to the parent def.
|
||||
if (scope->canUpdateDefinition(l->local))
|
||||
if (scope->canUpdateDefinition(l->local) && !(FFlag::LuauDoNotAddUpvalueTypesToLocalType && l->upvalue))
|
||||
{
|
||||
DefId updated = defArena->freshCell(l->local, l->location, containsSubscriptedDefinition(incomingDef));
|
||||
scope->bindings[l->local] = updated;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauDeclareExternType)
|
||||
LUAU_FASTFLAG(LuauTypeFunOptional)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -339,11 +340,11 @@ std::string getBuiltinDefinitionSource()
|
|||
}
|
||||
|
||||
// TODO: split into separate tagged unions when the new solver can appropriately handle that.
|
||||
static const std::string kBuiltinDefinitionTypesSrc = R"BUILTIN_SRC(
|
||||
static const std::string kBuiltinDefinitionTypeMethodSrc = R"BUILTIN_SRC(
|
||||
|
||||
export type type = {
|
||||
tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" |
|
||||
"singleton" | "negation" | "union" | "intesection" | "table" | "function" | "class" | "generic",
|
||||
"singleton" | "negation" | "union" | "intersection" | "table" | "function" | "class" | "generic",
|
||||
|
||||
is: (self: type, arg: string) -> boolean,
|
||||
|
||||
|
@ -390,6 +391,10 @@ export type type = {
|
|||
ispack: (self: type) -> boolean,
|
||||
}
|
||||
|
||||
)BUILTIN_SRC";
|
||||
|
||||
static const std::string kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC(
|
||||
|
||||
declare types: {
|
||||
unknown: type,
|
||||
never: type,
|
||||
|
@ -409,12 +414,44 @@ declare types: {
|
|||
newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type,
|
||||
copy: @checked (arg: type) -> type,
|
||||
}
|
||||
|
||||
)BUILTIN_SRC";
|
||||
|
||||
static const std::string kBuiltinDefinitionTypesLibWithOptionalSrc = R"BUILTIN_SRC(
|
||||
|
||||
declare types: {
|
||||
unknown: type,
|
||||
never: type,
|
||||
any: type,
|
||||
boolean: type,
|
||||
number: type,
|
||||
string: type,
|
||||
thread: type,
|
||||
buffer: type,
|
||||
|
||||
singleton: @checked (arg: string | boolean | nil) -> type,
|
||||
optional: @checked (arg: type) -> type,
|
||||
generic: @checked (name: string, ispack: boolean?) -> type,
|
||||
negationof: @checked (arg: type) -> type,
|
||||
unionof: @checked (...type) -> type,
|
||||
intersectionof: @checked (...type) -> type,
|
||||
newtable: @checked (props: {[type]: type} | {[type]: { read: type, write: type } } | nil, indexer: { index: type, readresult: type, writeresult: type }?, metatable: type?) -> type,
|
||||
newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type,
|
||||
copy: @checked (arg: type) -> type,
|
||||
}
|
||||
)BUILTIN_SRC";
|
||||
|
||||
|
||||
std::string getTypeFunctionDefinitionSource()
|
||||
{
|
||||
return kBuiltinDefinitionTypesSrc;
|
||||
|
||||
std::string result = kBuiltinDefinitionTypeMethodSrc;
|
||||
|
||||
if (FFlag::LuauTypeFunOptional)
|
||||
result += kBuiltinDefinitionTypesLibWithOptionalSrc;
|
||||
else
|
||||
result += kBuiltinDefinitionTypesLibSrc;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -28,8 +28,6 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit);
|
|||
LUAU_FASTINT(LuauTypeInferIterationLimit);
|
||||
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLogFragmentsFromAutocomplete)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes)
|
||||
|
@ -737,7 +735,7 @@ struct MixedModeIncrementalTCDefFinder : public AstVisitor
|
|||
// requires that we find the local/global `m` and place it in the environment.
|
||||
// The default behaviour here is to return false, and have individual visitors override
|
||||
// the specific behaviour they need.
|
||||
return FFlag::LuauMixedModeDefFinderTraversesTypeOf;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstStatTypeAlias* alias) override
|
||||
|
@ -1227,31 +1225,6 @@ ModulePtr cloneModule(CloneState& cloneState, const ModulePtr& source, std::uniq
|
|||
return incremental;
|
||||
}
|
||||
|
||||
ModulePtr copyModule(const ModulePtr& result, std::unique_ptr<Allocator> alloc)
|
||||
{
|
||||
ModulePtr incrementalModule = std::make_shared<Module>();
|
||||
incrementalModule->name = result->name;
|
||||
incrementalModule->humanReadableName = "Incremental$" + result->humanReadableName;
|
||||
incrementalModule->internalTypes.owningModule = incrementalModule.get();
|
||||
incrementalModule->interfaceTypes.owningModule = incrementalModule.get();
|
||||
incrementalModule->allocator = std::move(alloc);
|
||||
// Don't need to keep this alive (it's already on the source module)
|
||||
copyModuleVec(incrementalModule->scopes, result->scopes);
|
||||
copyModuleMap(incrementalModule->astTypes, result->astTypes);
|
||||
copyModuleMap(incrementalModule->astTypePacks, result->astTypePacks);
|
||||
copyModuleMap(incrementalModule->astExpectedTypes, result->astExpectedTypes);
|
||||
// Don't need to clone astOriginalCallTypes
|
||||
copyModuleMap(incrementalModule->astOverloadResolvedTypes, result->astOverloadResolvedTypes);
|
||||
// Don't need to clone astForInNextTypes
|
||||
copyModuleMap(incrementalModule->astForInNextTypes, result->astForInNextTypes);
|
||||
// Don't need to clone astResolvedTypes
|
||||
// Don't need to clone astResolvedTypePacks
|
||||
// Don't need to clone upperBoundContributors
|
||||
copyModuleMap(incrementalModule->astScopes, result->astScopes);
|
||||
// Don't need to clone declared Globals;
|
||||
return incrementalModule;
|
||||
}
|
||||
|
||||
void mixedModeCompatibility(
|
||||
const ScopePtr& bottomScopeStale,
|
||||
const ScopePtr& myFakeScope,
|
||||
|
@ -1315,10 +1288,8 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
|
|||
ModulePtr incrementalModule = nullptr;
|
||||
if (FFlag::LuauAllFreeTypesHaveScopes)
|
||||
incrementalModule = cloneModule(cloneState, stale, std::move(astAllocator), freshChildOfNearestScope.get());
|
||||
else if (FFlag::LuauCloneIncrementalModule)
|
||||
incrementalModule = cloneModule_DEPRECATED(cloneState, stale, std::move(astAllocator));
|
||||
else
|
||||
incrementalModule = copyModule(stale, std::move(astAllocator));
|
||||
incrementalModule = cloneModule_DEPRECATED(cloneState, stale, std::move(astAllocator));
|
||||
|
||||
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneModuleEnd);
|
||||
incrementalModule->checkedInNewSolver = true;
|
||||
|
@ -1372,44 +1343,27 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
|
|||
};
|
||||
|
||||
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart);
|
||||
if (FFlag::LuauCloneIncrementalModule)
|
||||
{
|
||||
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
|
||||
cg.rootScope = freshChildOfNearestScope.get();
|
||||
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
|
||||
cg.rootScope = freshChildOfNearestScope.get();
|
||||
|
||||
if (FFlag::LuauAllFreeTypesHaveScopes)
|
||||
cloneAndSquashScopes(
|
||||
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
|
||||
);
|
||||
else
|
||||
cloneAndSquashScopes_DEPRECATED(
|
||||
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
|
||||
);
|
||||
|
||||
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeEnd);
|
||||
cg.visitFragmentRoot(freshChildOfNearestScope, root);
|
||||
|
||||
if (FFlag::LuauPersistConstraintGenerationScopes)
|
||||
{
|
||||
for (auto p : cg.scopes)
|
||||
incrementalModule->scopes.emplace_back(std::move(p));
|
||||
}
|
||||
}
|
||||
if (FFlag::LuauAllFreeTypesHaveScopes)
|
||||
cloneAndSquashScopes(
|
||||
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
|
||||
);
|
||||
else
|
||||
cloneAndSquashScopes_DEPRECATED(
|
||||
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
|
||||
);
|
||||
|
||||
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeEnd);
|
||||
cg.visitFragmentRoot(freshChildOfNearestScope, root);
|
||||
|
||||
if (FFlag::LuauPersistConstraintGenerationScopes)
|
||||
{
|
||||
// Any additions to the scope must occur in a fresh scope
|
||||
cg.rootScope = stale->getModuleScope().get();
|
||||
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
|
||||
mixedModeCompatibility(closestScope, freshChildOfNearestScope, stale, NotNull{&dfg}, root);
|
||||
// closest Scope -> children = { ...., freshChildOfNearestScope}
|
||||
// We need to trim nearestChild from the scope hierarchy
|
||||
closestScope->children.emplace_back(freshChildOfNearestScope.get());
|
||||
cg.visitFragmentRoot(freshChildOfNearestScope, root);
|
||||
// Trim nearestChild from the closestScope
|
||||
Scope* back = closestScope->children.back().get();
|
||||
LUAU_ASSERT(back == freshChildOfNearestScope.get());
|
||||
closestScope->children.pop_back();
|
||||
for (auto p : cg.scopes)
|
||||
incrementalModule->scopes.emplace_back(std::move(p));
|
||||
}
|
||||
|
||||
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverStart);
|
||||
|
||||
if (FFlag::LuauAllFreeTypesHaveScopes)
|
||||
|
|
|
@ -1403,7 +1403,10 @@ std::optional<TypeId> generalize(
|
|||
}
|
||||
|
||||
for (TypeId unsealedTableTy : fts.unsealedTables)
|
||||
sealTable(scope, unsealedTableTy);
|
||||
{
|
||||
if (!generalizationTarget || unsealedTableTy == *generalizationTarget)
|
||||
sealTable(scope, unsealedTableTy);
|
||||
}
|
||||
|
||||
for (const auto& [freePackId, params] : fts.typePacks)
|
||||
{
|
||||
|
@ -1463,29 +1466,93 @@ std::optional<TypeId> generalize(
|
|||
|
||||
struct GenericCounter : TypeVisitor
|
||||
{
|
||||
struct CounterState
|
||||
{
|
||||
size_t count = 0;
|
||||
Polarity polarity = Polarity::None;
|
||||
};
|
||||
|
||||
NotNull<DenseHashSet<TypeId>> cachedTypes;
|
||||
DenseHashMap<TypeId, size_t> generics{nullptr};
|
||||
DenseHashMap<TypePackId, size_t> genericPacks{nullptr};
|
||||
DenseHashMap<TypeId, CounterState> generics{nullptr};
|
||||
DenseHashMap<TypePackId, CounterState> genericPacks{nullptr};
|
||||
|
||||
Polarity polarity = Polarity::Positive;
|
||||
|
||||
explicit GenericCounter(NotNull<DenseHashSet<TypeId>> cachedTypes)
|
||||
: cachedTypes(cachedTypes)
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const FunctionType& ft) override
|
||||
{
|
||||
if (ty->persistent)
|
||||
return false;
|
||||
|
||||
polarity = invert(polarity);
|
||||
traverse(ft.argTypes);
|
||||
polarity = invert(polarity);
|
||||
traverse(ft.retTypes);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const TableType& tt) override
|
||||
{
|
||||
if (ty->persistent)
|
||||
return false;
|
||||
|
||||
const Polarity previous = polarity;
|
||||
|
||||
for (const auto& [_name, prop] : tt.props)
|
||||
{
|
||||
if (prop.isReadOnly())
|
||||
traverse(*prop.readTy);
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(prop.isShared());
|
||||
|
||||
polarity = Polarity::Mixed;
|
||||
traverse(prop.type());
|
||||
polarity = previous;
|
||||
}
|
||||
}
|
||||
|
||||
if (tt.indexer)
|
||||
{
|
||||
polarity = Polarity::Mixed;
|
||||
traverse(tt.indexer->indexType);
|
||||
traverse(tt.indexer->indexResultType);
|
||||
polarity = previous;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const ExternType&) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const GenericType&) override
|
||||
{
|
||||
size_t* count = generics.find(ty);
|
||||
if (count)
|
||||
++*count;
|
||||
auto state = generics.find(ty);
|
||||
if (state)
|
||||
{
|
||||
++state->count;
|
||||
state->polarity |= polarity;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp, const GenericTypePack&) override
|
||||
{
|
||||
size_t* count = genericPacks.find(tp);
|
||||
if (count)
|
||||
++*count;
|
||||
auto state = genericPacks.find(tp);
|
||||
if (state)
|
||||
{
|
||||
++state->count;
|
||||
state->polarity |= polarity;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -1521,21 +1588,30 @@ void pruneUnnecessaryGenerics(
|
|||
generic = follow(generic);
|
||||
auto g = get<GenericType>(generic);
|
||||
if (g && !g->explicitName)
|
||||
counter.generics[generic] = 0;
|
||||
counter.generics[generic] = {};
|
||||
}
|
||||
for (TypePackId genericPack : functionTy->genericPacks)
|
||||
|
||||
// It is sometimes the case that a pack in the generic list will become a
|
||||
// pack that (transitively) has a generic tail. If it does, we need to add
|
||||
// that generic tail to the generic pack list.
|
||||
for (size_t i = 0; i < functionTy->genericPacks.size(); ++i)
|
||||
{
|
||||
genericPack = follow(genericPack);
|
||||
auto g = get<GenericTypePack>(genericPack);
|
||||
if (g && !g->explicitName)
|
||||
counter.genericPacks[genericPack] = 0;
|
||||
TypePackId genericPack = follow(functionTy->genericPacks[i]);
|
||||
|
||||
TypePackId tail = getTail(genericPack);
|
||||
|
||||
if (tail != genericPack)
|
||||
functionTy->genericPacks.push_back(tail);
|
||||
|
||||
if (auto g = get<GenericTypePack>(tail); g && !g->explicitName)
|
||||
counter.genericPacks[genericPack] = {};
|
||||
}
|
||||
|
||||
counter.traverse(ty);
|
||||
|
||||
for (const auto& [generic, count] : counter.generics)
|
||||
for (const auto& [generic, state] : counter.generics)
|
||||
{
|
||||
if (count == 1)
|
||||
if (state.count == 1 && state.polarity != Polarity::Mixed)
|
||||
emplaceType<BoundType>(asMutable(generic), builtinTypes->unknownType);
|
||||
}
|
||||
|
||||
|
@ -1557,9 +1633,9 @@ void pruneUnnecessaryGenerics(
|
|||
|
||||
functionTy->generics.erase(it, functionTy->generics.end());
|
||||
|
||||
for (const auto& [genericPack, count] : counter.genericPacks)
|
||||
for (const auto& [genericPack, state] : counter.genericPacks)
|
||||
{
|
||||
if (count == 1)
|
||||
if (state.count == 1)
|
||||
emplaceTypePack<BoundTypePack>(asMutable(genericPack), builtinTypes->unknownTypePack);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -164,7 +163,7 @@ TypeId ReplaceGenerics::clean(TypeId ty)
|
|||
}
|
||||
else
|
||||
{
|
||||
return FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtinTypes, scope, level) : addType(FreeType{scope, level});
|
||||
return arena->freshType(builtinTypes, scope, level);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,10 +22,9 @@
|
|||
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes2)
|
||||
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
|
||||
|
||||
namespace Luau
|
||||
|
@ -218,7 +217,7 @@ struct NonStrictTypeChecker
|
|||
return *fst;
|
||||
else if (auto ftp = get<FreeTypePack>(pack))
|
||||
{
|
||||
TypeId result = FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtinTypes, ftp->scope) : arena->addType(FreeType{ftp->scope});
|
||||
TypeId result = arena->freshType(builtinTypes, ftp->scope);
|
||||
TypePackId freeTail = arena->addTypePack(FreeTypePack{ftp->scope});
|
||||
|
||||
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
|
||||
|
@ -341,11 +340,8 @@ struct NonStrictTypeChecker
|
|||
{
|
||||
ctx.remove(dfg->getDef(local));
|
||||
|
||||
if (FFlag::LuauNewNonStrictVisitTypes)
|
||||
{
|
||||
if (local->annotation)
|
||||
visit(local->annotation);
|
||||
}
|
||||
if (FFlag::LuauNewNonStrictVisitTypes2)
|
||||
visit(local->annotation);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -431,9 +427,8 @@ struct NonStrictTypeChecker
|
|||
|
||||
NonStrictContext visit(AstStatFor* forStatement)
|
||||
{
|
||||
if (FFlag::LuauNewNonStrictVisitTypes)
|
||||
if (forStatement->var->annotation)
|
||||
visit(forStatement->var->annotation);
|
||||
if (FFlag::LuauNewNonStrictVisitTypes2)
|
||||
visit(forStatement->var->annotation);
|
||||
|
||||
if (FFlag::LuauNonStrictVisitorImprovements)
|
||||
{
|
||||
|
@ -454,13 +449,10 @@ struct NonStrictTypeChecker
|
|||
|
||||
NonStrictContext visit(AstStatForIn* forInStatement)
|
||||
{
|
||||
if (FFlag::LuauNewNonStrictVisitTypes)
|
||||
if (FFlag::LuauNewNonStrictVisitTypes2)
|
||||
{
|
||||
for (auto var : forInStatement->vars)
|
||||
{
|
||||
if (var->annotation)
|
||||
visit(var->annotation);
|
||||
}
|
||||
visit(var->annotation);
|
||||
}
|
||||
|
||||
if (FFlag::LuauNonStrictVisitorImprovements)
|
||||
|
@ -511,7 +503,7 @@ struct NonStrictTypeChecker
|
|||
|
||||
NonStrictContext visit(AstStatTypeAlias* typeAlias)
|
||||
{
|
||||
if (FFlag::LuauNewNonStrictVisitTypes)
|
||||
if (FFlag::LuauNewNonStrictVisitTypes2)
|
||||
{
|
||||
visitGenerics(typeAlias->generics, typeAlias->genericPacks);
|
||||
visit(typeAlias->type);
|
||||
|
@ -527,7 +519,7 @@ struct NonStrictTypeChecker
|
|||
|
||||
NonStrictContext visit(AstStatDeclareFunction* declFn)
|
||||
{
|
||||
if (FFlag::LuauNewNonStrictVisitTypes)
|
||||
if (FFlag::LuauNewNonStrictVisitTypes2)
|
||||
{
|
||||
visitGenerics(declFn->generics, declFn->genericPacks);
|
||||
visit(declFn->params);
|
||||
|
@ -539,7 +531,7 @@ struct NonStrictTypeChecker
|
|||
|
||||
NonStrictContext visit(AstStatDeclareGlobal* declGlobal)
|
||||
{
|
||||
if (FFlag::LuauNewNonStrictVisitTypes)
|
||||
if (FFlag::LuauNewNonStrictVisitTypes2)
|
||||
visit(declGlobal->type);
|
||||
|
||||
return {};
|
||||
|
@ -547,7 +539,7 @@ struct NonStrictTypeChecker
|
|||
|
||||
NonStrictContext visit(AstStatDeclareExternType* declClass)
|
||||
{
|
||||
if (FFlag::LuauNewNonStrictVisitTypes)
|
||||
if (FFlag::LuauNewNonStrictVisitTypes2)
|
||||
{
|
||||
if (declClass->indexer)
|
||||
{
|
||||
|
@ -823,22 +815,16 @@ struct NonStrictTypeChecker
|
|||
}
|
||||
remainder.remove(dfg->getDef(local));
|
||||
|
||||
if (FFlag::LuauNewNonStrictVisitTypes)
|
||||
{
|
||||
if (local->annotation)
|
||||
visit(local->annotation);
|
||||
}
|
||||
if (FFlag::LuauNewNonStrictVisitTypes2)
|
||||
visit(local->annotation);
|
||||
}
|
||||
|
||||
if (FFlag::LuauNewNonStrictVisitTypes)
|
||||
if (FFlag::LuauNewNonStrictVisitTypes2)
|
||||
{
|
||||
visitGenerics(exprFn->generics, exprFn->genericPacks);
|
||||
|
||||
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
|
||||
{
|
||||
if (exprFn->returnAnnotation)
|
||||
visit(exprFn->returnAnnotation);
|
||||
}
|
||||
visit(exprFn->returnAnnotation);
|
||||
else
|
||||
{
|
||||
if (exprFn->returnAnnotation_DEPRECATED)
|
||||
|
@ -889,7 +875,7 @@ struct NonStrictTypeChecker
|
|||
|
||||
NonStrictContext visit(AstExprTypeAssertion* typeAssertion)
|
||||
{
|
||||
if (FFlag::LuauNewNonStrictVisitTypes)
|
||||
if (FFlag::LuauNewNonStrictVisitTypes2)
|
||||
visit(typeAssertion->annotation);
|
||||
|
||||
if (FFlag::LuauNonStrictVisitorImprovements)
|
||||
|
@ -930,7 +916,11 @@ struct NonStrictTypeChecker
|
|||
|
||||
void visit(AstType* ty)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes);
|
||||
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes2);
|
||||
|
||||
// If this node is `nullptr`, early exit.
|
||||
if (!ty)
|
||||
return;
|
||||
|
||||
if (auto t = ty->as<AstTypeReference>())
|
||||
return visit(t);
|
||||
|
@ -1139,7 +1129,11 @@ struct NonStrictTypeChecker
|
|||
|
||||
void visit(AstTypePack* pack)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes);
|
||||
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes2);
|
||||
|
||||
// If there is no pack node, early exit.
|
||||
if (!pack)
|
||||
return;
|
||||
|
||||
if (auto p = pack->as<AstTypePackExplicit>())
|
||||
return visit(p);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/Set.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypePairHash.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
|
@ -17,6 +18,7 @@ LUAU_FASTFLAG(LuauSolverV2)
|
|||
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSimplificationRecheckAssumption)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOptimizeFalsyAndTruthyIntersect)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -316,12 +318,14 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
|||
{
|
||||
if (get<AnyType>(right))
|
||||
return Relation::Subset;
|
||||
else if (get<UnknownType>(right))
|
||||
|
||||
if (get<UnknownType>(right))
|
||||
return Relation::Coincident;
|
||||
else if (get<ErrorType>(right))
|
||||
|
||||
if (get<ErrorType>(right))
|
||||
return Relation::Disjoint;
|
||||
else
|
||||
return Relation::Superset;
|
||||
|
||||
return Relation::Superset;
|
||||
}
|
||||
|
||||
if (get<UnknownType>(right))
|
||||
|
@ -331,8 +335,8 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
|||
{
|
||||
if (get<AnyType>(right))
|
||||
return Relation::Coincident;
|
||||
else
|
||||
return Relation::Superset;
|
||||
|
||||
return Relation::Superset;
|
||||
}
|
||||
|
||||
if (get<AnyType>(right))
|
||||
|
@ -364,26 +368,33 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
|||
if (isTypeVariable(left) || isTypeVariable(right))
|
||||
return Relation::Intersects;
|
||||
|
||||
if (FFlag::LuauSimplificationTableExternType)
|
||||
{
|
||||
// if either type is a type function, we cannot know if they'll be related.
|
||||
if (get<TypeFunctionInstanceType>(left) || get<TypeFunctionInstanceType>(right))
|
||||
return Relation::Intersects;
|
||||
}
|
||||
|
||||
if (get<ErrorType>(left))
|
||||
{
|
||||
if (get<ErrorType>(right))
|
||||
return Relation::Coincident;
|
||||
else if (get<AnyType>(right))
|
||||
return Relation::Subset;
|
||||
else
|
||||
return Relation::Disjoint;
|
||||
|
||||
return Relation::Disjoint;
|
||||
}
|
||||
if (get<ErrorType>(right))
|
||||
else if (get<ErrorType>(right))
|
||||
return flip(relate(right, left, seen));
|
||||
|
||||
if (get<NeverType>(left))
|
||||
{
|
||||
if (get<NeverType>(right))
|
||||
return Relation::Coincident;
|
||||
else
|
||||
return Relation::Subset;
|
||||
|
||||
return Relation::Subset;
|
||||
}
|
||||
if (get<NeverType>(right))
|
||||
else if (get<NeverType>(right))
|
||||
return flip(relate(right, left, seen));
|
||||
|
||||
if (auto ut = get<IntersectionType>(left))
|
||||
|
@ -447,33 +458,34 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
|||
{
|
||||
if (lp->type == rp->type)
|
||||
return Relation::Coincident;
|
||||
else
|
||||
return Relation::Disjoint;
|
||||
|
||||
return Relation::Disjoint;
|
||||
}
|
||||
|
||||
if (auto rs = get<SingletonType>(right))
|
||||
{
|
||||
if (lp->type == PrimitiveType::String && rs->variant.get_if<StringSingleton>())
|
||||
return Relation::Superset;
|
||||
else if (lp->type == PrimitiveType::Boolean && rs->variant.get_if<BooleanSingleton>())
|
||||
|
||||
if (lp->type == PrimitiveType::Boolean && rs->variant.get_if<BooleanSingleton>())
|
||||
return Relation::Superset;
|
||||
else
|
||||
return Relation::Disjoint;
|
||||
|
||||
return Relation::Disjoint;
|
||||
}
|
||||
|
||||
if (lp->type == PrimitiveType::Function)
|
||||
{
|
||||
if (get<FunctionType>(right))
|
||||
return Relation::Superset;
|
||||
else
|
||||
return Relation::Disjoint;
|
||||
|
||||
return Relation::Disjoint;
|
||||
}
|
||||
if (lp->type == PrimitiveType::Table)
|
||||
{
|
||||
if (get<TableType>(right))
|
||||
return Relation::Superset;
|
||||
else
|
||||
return Relation::Disjoint;
|
||||
|
||||
return Relation::Disjoint;
|
||||
}
|
||||
|
||||
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ExternType>(right))
|
||||
|
@ -487,12 +499,13 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
|||
|
||||
if (get<PrimitiveType>(right))
|
||||
return flip(relate(right, left, seen));
|
||||
|
||||
if (auto rs = get<SingletonType>(right))
|
||||
{
|
||||
if (ls->variant == rs->variant)
|
||||
return Relation::Coincident;
|
||||
else
|
||||
return Relation::Disjoint;
|
||||
|
||||
return Relation::Disjoint;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -502,11 +515,11 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
|||
{
|
||||
if (rp->type == PrimitiveType::Function)
|
||||
return Relation::Subset;
|
||||
else
|
||||
return Relation::Disjoint;
|
||||
|
||||
return Relation::Disjoint;
|
||||
}
|
||||
else
|
||||
return Relation::Intersects;
|
||||
|
||||
return Relation::Intersects;
|
||||
}
|
||||
|
||||
if (auto lt = get<TableType>(left))
|
||||
|
@ -515,10 +528,11 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
|||
{
|
||||
if (rp->type == PrimitiveType::Table)
|
||||
return Relation::Subset;
|
||||
else
|
||||
return Relation::Disjoint;
|
||||
|
||||
return Relation::Disjoint;
|
||||
}
|
||||
else if (auto rt = get<TableType>(right))
|
||||
|
||||
if (auto rt = get<TableType>(right))
|
||||
{
|
||||
// TODO PROBABLY indexers and metatables.
|
||||
if (1 == rt->props.size())
|
||||
|
@ -538,14 +552,42 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
|||
*/
|
||||
if (lt->props.size() > 1 && r == Relation::Superset)
|
||||
return Relation::Intersects;
|
||||
else
|
||||
return r;
|
||||
|
||||
return r;
|
||||
}
|
||||
else if (1 == lt->props.size())
|
||||
|
||||
if (1 == lt->props.size())
|
||||
return flip(relate(right, left, seen));
|
||||
else
|
||||
return Relation::Intersects;
|
||||
|
||||
return Relation::Intersects;
|
||||
}
|
||||
|
||||
if (FFlag::LuauSimplificationTableExternType)
|
||||
{
|
||||
if (auto re = get<ExternType>(right))
|
||||
{
|
||||
Relation overall = Relation::Coincident;
|
||||
|
||||
for (auto& [name, prop] : lt->props)
|
||||
{
|
||||
if (auto propInExternType = re->props.find(name); propInExternType != re->props.end())
|
||||
{
|
||||
Relation propRel = relate(prop.type(), propInExternType->second.type());
|
||||
|
||||
if (propRel == Relation::Disjoint)
|
||||
return Relation::Disjoint;
|
||||
|
||||
if (propRel == Relation::Coincident)
|
||||
continue;
|
||||
|
||||
overall = Relation::Intersects;
|
||||
}
|
||||
}
|
||||
|
||||
return overall;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO metatables
|
||||
|
||||
return Relation::Disjoint;
|
||||
|
@ -557,10 +599,11 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
|||
{
|
||||
if (isSubclass(ct, rct))
|
||||
return Relation::Subset;
|
||||
else if (isSubclass(rct, ct))
|
||||
|
||||
if (isSubclass(rct, ct))
|
||||
return Relation::Superset;
|
||||
else
|
||||
return Relation::Disjoint;
|
||||
|
||||
return Relation::Disjoint;
|
||||
}
|
||||
|
||||
return Relation::Disjoint;
|
||||
|
|
|
@ -7,13 +7,11 @@
|
|||
#include "Luau/Normalize.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
#include "Luau/TypeFunction.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypePath.h"
|
||||
|
@ -33,7 +31,7 @@ struct VarianceFlipper
|
|||
Subtyping::Variance* variance;
|
||||
Subtyping::Variance oldValue;
|
||||
|
||||
VarianceFlipper(Subtyping::Variance* v)
|
||||
explicit VarianceFlipper(Subtyping::Variance* v)
|
||||
: variance(v)
|
||||
, oldValue(*v)
|
||||
{
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauStoreCSTData2)
|
||||
LUAU_FASTFLAG(LuauAstTypeGroup3)
|
||||
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
|
||||
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
|
||||
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
|
||||
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
|
||||
|
@ -706,8 +705,6 @@ struct Printer_DEPRECATED
|
|||
writer.keyword("do");
|
||||
for (const auto& s : block->body)
|
||||
visualize(*s);
|
||||
if (!FFlag::LuauFixDoBlockEndLocation)
|
||||
writer.advance(block->location.end);
|
||||
writeEnd(program.location);
|
||||
}
|
||||
else if (const auto& a = program.as<AstStatIf>())
|
||||
|
@ -2323,8 +2320,6 @@ struct Printer
|
|||
{
|
||||
const auto cstNode = lookupCstNode<CstExprFunction>(&func);
|
||||
|
||||
// TODO(CLI-139347): need to handle return type (incl. parentheses of return type)
|
||||
|
||||
if (func.generics.size > 0 || func.genericPacks.size > 0)
|
||||
{
|
||||
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
|
||||
|
@ -2395,12 +2390,18 @@ struct Printer
|
|||
if (cstNode)
|
||||
advance(cstNode->returnSpecifierPosition);
|
||||
writer.symbol(":");
|
||||
writer.space();
|
||||
|
||||
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
|
||||
{
|
||||
if (!cstNode)
|
||||
writer.space();
|
||||
visualizeTypePackAnnotation(*func.returnAnnotation, false, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.space();
|
||||
visualizeTypeList(*func.returnAnnotation_DEPRECATED, false);
|
||||
}
|
||||
}
|
||||
|
||||
visualizeBlock(*func.body);
|
||||
|
|
|
@ -506,31 +506,6 @@ FreeType::FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId uppe
|
|||
{
|
||||
}
|
||||
|
||||
// Old constructors
|
||||
FreeType::FreeType(TypeLevel level)
|
||||
: index(Unifiable::freshIndex())
|
||||
, level(level)
|
||||
, scope(nullptr)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
|
||||
}
|
||||
|
||||
FreeType::FreeType(Scope* scope)
|
||||
: index(Unifiable::freshIndex())
|
||||
, level{}
|
||||
, scope(scope)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
|
||||
}
|
||||
|
||||
FreeType::FreeType(Scope* scope, TypeLevel level)
|
||||
: index(Unifiable::freshIndex())
|
||||
, level(level)
|
||||
, scope(scope)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
|
||||
}
|
||||
|
||||
GenericType::GenericType()
|
||||
: index(Unifiable::freshIndex())
|
||||
, name("g" + std::to_string(index))
|
||||
|
|
|
@ -50,33 +50,6 @@ TypeId TypeArena::freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLe
|
|||
return allocated;
|
||||
}
|
||||
|
||||
TypeId TypeArena::freshType_DEPRECATED(TypeLevel level)
|
||||
{
|
||||
TypeId allocated = types.allocate(FreeType{level});
|
||||
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
TypeId TypeArena::freshType_DEPRECATED(Scope* scope)
|
||||
{
|
||||
TypeId allocated = types.allocate(FreeType{scope});
|
||||
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
TypeId TypeArena::freshType_DEPRECATED(Scope* scope, TypeLevel level)
|
||||
{
|
||||
TypeId allocated = types.allocate(FreeType{scope, level});
|
||||
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
TypePackId TypeArena::freshTypePack(Scope* scope, Polarity polarity)
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(FreeTypePack{scope, polarity});
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
|
||||
|
@ -2114,10 +2113,7 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
|
|||
}
|
||||
else
|
||||
{
|
||||
expectedRets = module->internalTypes.addTypePack(
|
||||
{FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, scope, TypeLevel{})
|
||||
: module->internalTypes.freshType_DEPRECATED(scope, TypeLevel{})}
|
||||
);
|
||||
expectedRets = module->internalTypes.addTypePack({module->internalTypes.freshType(builtinTypes, scope, TypeLevel{})});
|
||||
}
|
||||
|
||||
TypeId expectedTy = module->internalTypes.addType(FunctionType(expectedArgs, expectedRets));
|
||||
|
@ -2380,8 +2376,7 @@ TypeId TypeChecker2::flattenPack(TypePackId pack)
|
|||
return *fst;
|
||||
else if (auto ftp = get<FreeTypePack>(pack))
|
||||
{
|
||||
TypeId result = FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, ftp->scope)
|
||||
: module->internalTypes.addType(FreeType{ftp->scope});
|
||||
TypeId result = module->internalTypes.freshType(builtinTypes, ftp->scope);
|
||||
TypePackId freeTail = module->internalTypes.addTypePack(FreeTypePack{ftp->scope});
|
||||
|
||||
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
|
||||
|
|
|
@ -1839,10 +1839,20 @@ TypeFunctionReductionResult<TypeId> orTypeFunction(
|
|||
return {rhsTy, Reduction::MaybeOk, {}, {}};
|
||||
|
||||
// check to see if both operand types are resolved enough, and wait to reduce if not
|
||||
if (isPending(lhsTy, ctx->solver))
|
||||
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
|
||||
else if (isPending(rhsTy, ctx->solver))
|
||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy))
|
||||
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
|
||||
else if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(rhsTy))
|
||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isPending(lhsTy, ctx->solver))
|
||||
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
|
||||
else if (isPending(rhsTy, ctx->solver))
|
||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||
}
|
||||
|
||||
// Or evalutes to the LHS type if the LHS is truthy, and the RHS type if LHS is falsy.
|
||||
SimplifyResult filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->truthyType);
|
||||
|
@ -1876,10 +1886,20 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
|
|||
if (lhsTy == instance || rhsTy == instance)
|
||||
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
|
||||
|
||||
if (isPending(lhsTy, ctx->solver))
|
||||
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
|
||||
else if (isPending(rhsTy, ctx->solver))
|
||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy))
|
||||
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
|
||||
else if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(rhsTy))
|
||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isPending(lhsTy, ctx->solver))
|
||||
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
|
||||
else if (isPending(rhsTy, ctx->solver))
|
||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||
}
|
||||
|
||||
// Algebra Reduction Rules for comparison type functions
|
||||
// Note that comparing to never tells you nothing about the other operand
|
||||
|
@ -2240,8 +2260,12 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
|||
for (size_t i = 1; i < typeParams.size(); i++)
|
||||
discriminantTypes.push_back(follow(typeParams.at(i)));
|
||||
|
||||
const bool targetIsPending = FFlag::DebugLuauGreedyGeneralization
|
||||
? is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(targetTy)
|
||||
: isPending(targetTy, ctx->solver);
|
||||
|
||||
// check to see if both operand types are resolved enough, and wait to reduce if not
|
||||
if (isPending(targetTy, ctx->solver))
|
||||
if (targetIsPending)
|
||||
return {std::nullopt, Reduction::MaybeOk, {targetTy}, {}};
|
||||
else
|
||||
{
|
||||
|
@ -2326,8 +2350,32 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
|||
if (is<TableType>(target) || isSimpleDiscriminant(discriminant))
|
||||
{
|
||||
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
|
||||
if (!result.blockedTypes.empty())
|
||||
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
// Simplification considers free and generic types to be
|
||||
// 'blocking', but that's not suitable for refine<>.
|
||||
//
|
||||
// If we are only blocked on those types, we consider
|
||||
// the simplification a success and reduce.
|
||||
if (std::all_of(
|
||||
begin(result.blockedTypes),
|
||||
end(result.blockedTypes),
|
||||
[](auto&& v)
|
||||
{
|
||||
return is<FreeType, GenericType>(follow(v));
|
||||
}
|
||||
))
|
||||
{
|
||||
return {result.result, {}};
|
||||
}
|
||||
else
|
||||
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!result.blockedTypes.empty())
|
||||
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
|
||||
}
|
||||
return {result.result, {}};
|
||||
}
|
||||
}
|
||||
|
@ -3527,7 +3575,7 @@ BuiltinTypeFunctions::BuiltinTypeFunctions()
|
|||
, ltFunc{"lt", ltTypeFunction}
|
||||
, leFunc{"le", leTypeFunction}
|
||||
, eqFunc{"eq", eqTypeFunction}
|
||||
, refineFunc{"refine", refineTypeFunction}
|
||||
, refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ FFlag::DebugLuauGreedyGeneralization}
|
||||
, singletonFunc{"singleton", singletonTypeFunction}
|
||||
, unionFunc{"union", unionTypeFunction}
|
||||
, intersectFunc{"intersect", intersectTypeFunction}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeFunOptional)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -315,6 +316,38 @@ static int getSingletonValue(lua_State* L)
|
|||
luaL_error(L, "type.value: can't call `value` method on `%s` type", getTag(L, self).c_str());
|
||||
}
|
||||
|
||||
// Luau: `types.optional(ty: type) -> type`
|
||||
// Returns the type instance representing an optional version of `ty`.
|
||||
// If `ty` is a union, this adds `nil` to the components of the union.
|
||||
// Otherwise, makes a union of the two things.
|
||||
static int createOptional(lua_State* L)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauTypeFunOptional);
|
||||
|
||||
int argumentCount = lua_gettop(L);
|
||||
if (argumentCount != 1)
|
||||
luaL_error(L, "types.optional: expected 1 argument, but got %d", argumentCount);
|
||||
|
||||
TypeFunctionTypeId argument = getTypeUserData(L, 1);
|
||||
|
||||
std::vector<TypeFunctionTypeId> components;
|
||||
|
||||
if (auto unionTy = get<TypeFunctionUnionType>(argument))
|
||||
{
|
||||
components.reserve(unionTy->components.size() + 1);
|
||||
|
||||
components.insert(components.begin(), unionTy->components.begin(), unionTy->components.end());
|
||||
}
|
||||
else
|
||||
components.emplace_back(argument);
|
||||
|
||||
components.emplace_back(allocateTypeFunctionType(L, TypeFunctionPrimitiveType(TypeFunctionPrimitiveType::NilType)));
|
||||
|
||||
allocTypeUserData(L, TypeFunctionUnionType{components});
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Luau: `types.unionof(...: type) -> type`
|
||||
// Returns the type instance representing union
|
||||
static int createUnion(lua_State* L)
|
||||
|
@ -1524,6 +1557,7 @@ void registerTypesLibrary(lua_State* L)
|
|||
{"copy", deepCopy},
|
||||
{"generic", createGeneric},
|
||||
|
||||
{(FFlag::LuauTypeFunOptional) ? "optional" : nullptr, (FFlag::LuauTypeFunOptional) ? createOptional : nullptr},
|
||||
{nullptr, nullptr}
|
||||
};
|
||||
|
||||
|
|
|
@ -32,7 +32,6 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
|
|||
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
|
||||
|
||||
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
|
||||
|
@ -799,8 +798,7 @@ struct Demoter : Substitution
|
|||
{
|
||||
auto ftv = get<FreeType>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
return FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtins, demotedLevel(ftv->level))
|
||||
: addType(FreeType{demotedLevel(ftv->level)});
|
||||
return arena->freshType(builtins, demotedLevel(ftv->level));
|
||||
}
|
||||
|
||||
TypePackId clean(TypePackId tp) override
|
||||
|
@ -5410,8 +5408,7 @@ TypeId TypeChecker::freshType(const ScopePtr& scope)
|
|||
|
||||
TypeId TypeChecker::freshType(TypeLevel level)
|
||||
{
|
||||
return FFlag::LuauFreeTypesMustHaveBounds ? currentModule->internalTypes.freshType(builtinTypes, level)
|
||||
: currentModule->internalTypes.addType(Type(FreeType(level)));
|
||||
return currentModule->internalTypes.freshType(builtinTypes, level);
|
||||
}
|
||||
|
||||
TypeId TypeChecker::singletonType(bool value)
|
||||
|
|
|
@ -208,6 +208,26 @@ TypePackIterator end(TypePackId tp)
|
|||
return TypePackIterator{};
|
||||
}
|
||||
|
||||
TypePackId getTail(TypePackId tp)
|
||||
{
|
||||
DenseHashSet<TypePackId> seen{nullptr};
|
||||
while (tp)
|
||||
{
|
||||
tp = follow(tp);
|
||||
|
||||
if (seen.contains(tp))
|
||||
break;
|
||||
seen.insert(tp);
|
||||
|
||||
if (auto pack = get<TypePack>(tp); pack && pack->tail)
|
||||
tp = *pack->tail;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
return follow(tp);
|
||||
}
|
||||
|
||||
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs)
|
||||
{
|
||||
TypePackId lhsId = const_cast<TypePackId>(&lhs);
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
|
||||
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
|
||||
|
||||
|
@ -328,7 +327,7 @@ TypePack extendTypePack(
|
|||
trackInteriorFreeType(ftp->scope, t);
|
||||
}
|
||||
else
|
||||
t = FFlag::LuauFreeTypesMustHaveBounds ? arena.freshType(builtinTypes, ftp->scope) : arena.freshType_DEPRECATED(ftp->scope);
|
||||
t = arena.freshType(builtinTypes, ftp->scope);
|
||||
}
|
||||
|
||||
newPack.head.push_back(t);
|
||||
|
|
|
@ -22,7 +22,6 @@ LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping)
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnifierRecursionOnRestart)
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -1559,7 +1558,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||
if (FFlag::LuauSolverV2)
|
||||
return freshType(NotNull{types}, builtinTypes, scope);
|
||||
else
|
||||
return FFlag::LuauFreeTypesMustHaveBounds ? types->freshType(builtinTypes, scope, level) : types->freshType_DEPRECATED(scope, level);
|
||||
return types->freshType(builtinTypes, scope, level);
|
||||
};
|
||||
|
||||
const TypePackId emptyTp = types->addTypePack(TypePack{{}, std::nullopt});
|
||||
|
|
|
@ -20,7 +20,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
|||
LUAU_FASTFLAGVARIABLE(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStoreCSTData2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixDoBlockEndLocation)
|
||||
LUAU_FASTFLAGVARIABLE(LuauParseOptionalAsNode2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDeclareExternType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer)
|
||||
|
@ -555,7 +554,7 @@ AstStat* Parser::parseDo()
|
|||
|
||||
Location endLocation = lexer.current().location;
|
||||
body->hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo);
|
||||
if (FFlag::LuauFixDoBlockEndLocation && body->hasEnd)
|
||||
if (body->hasEnd)
|
||||
body->location.end = endLocation.end;
|
||||
|
||||
if (FFlag::LuauStoreCSTData2 && options.storeCstData)
|
||||
|
|
|
@ -87,6 +87,11 @@ private:
|
|||
[[nodiscard]] Error navigateToAlias(const std::string& alias, const std::string& value);
|
||||
[[nodiscard]] Error navigateToAndPopulateConfig(const std::string& desiredAlias);
|
||||
|
||||
[[nodiscard]] Error resetToRequirer();
|
||||
[[nodiscard]] Error jumpToAlias(const std::string& aliasPath);
|
||||
[[nodiscard]] Error navigateToParent(std::optional<std::string> previousComponent);
|
||||
[[nodiscard]] Error navigateToChild(const std::string& component);
|
||||
|
||||
NavigationContext& navigationContext;
|
||||
ErrorHandler& errorHandler;
|
||||
Luau::Config config;
|
||||
|
|
|
@ -10,24 +10,11 @@
|
|||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
static constexpr char kRequireErrorAmbiguous[] = "require path could not be resolved to a unique file";
|
||||
static constexpr char kRequireErrorGeneric[] = "error requiring module";
|
||||
|
||||
namespace Luau::Require
|
||||
{
|
||||
|
||||
using Error = std::optional<std::string>;
|
||||
|
||||
static Error toError(NavigationContext::NavigateResult result)
|
||||
{
|
||||
if (result == NavigationContext::NavigateResult::Success)
|
||||
return std::nullopt;
|
||||
if (result == NavigationContext::NavigateResult::Ambiguous)
|
||||
return kRequireErrorAmbiguous;
|
||||
else
|
||||
return kRequireErrorGeneric;
|
||||
}
|
||||
|
||||
static std::string extractAlias(std::string_view path)
|
||||
{
|
||||
// To ignore the '@' alias prefix when processing the alias
|
||||
|
@ -53,7 +40,7 @@ Navigator::Status Navigator::navigate(std::string path)
|
|||
{
|
||||
std::replace(path.begin(), path.end(), '\\', '/');
|
||||
|
||||
if (Error error = toError(navigationContext.reset(navigationContext.getRequirerIdentifier())))
|
||||
if (Error error = resetToRequirer())
|
||||
{
|
||||
errorHandler.reportError(*error);
|
||||
return Status::ErrorReported;
|
||||
|
@ -98,7 +85,7 @@ Error Navigator::navigateImpl(std::string_view path)
|
|||
|
||||
// If the alias is "@self", we reset to the requirer's context and
|
||||
// navigate directly from there.
|
||||
if (Error error = toError(navigationContext.reset(navigationContext.getRequirerIdentifier())))
|
||||
if (Error error = resetToRequirer())
|
||||
return error;
|
||||
if (Error error = navigateThroughPath(path))
|
||||
return error;
|
||||
|
@ -114,7 +101,7 @@ Error Navigator::navigateImpl(std::string_view path)
|
|||
|
||||
if (pathType == PathType::RelativeToCurrent || pathType == PathType::RelativeToParent)
|
||||
{
|
||||
if (Error error = toError(navigationContext.toParent()))
|
||||
if (Error error = navigateToParent(std::nullopt))
|
||||
return error;
|
||||
if (Error error = navigateThroughPath(path))
|
||||
return error;
|
||||
|
@ -133,6 +120,7 @@ Error Navigator::navigateThroughPath(std::string_view path)
|
|||
components = splitPath(components.second);
|
||||
}
|
||||
|
||||
std::optional<std::string> previousComponent;
|
||||
while (!(components.first.empty() && components.second.empty()))
|
||||
{
|
||||
if (components.first == "." || components.first.empty())
|
||||
|
@ -142,14 +130,15 @@ Error Navigator::navigateThroughPath(std::string_view path)
|
|||
}
|
||||
else if (components.first == "..")
|
||||
{
|
||||
if (Error error = toError(navigationContext.toParent()))
|
||||
if (Error error = navigateToParent(previousComponent))
|
||||
return error;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Error error = toError(navigationContext.toChild(std::string{components.first})))
|
||||
if (Error error = navigateToChild(std::string{components.first}))
|
||||
return error;
|
||||
}
|
||||
previousComponent = components.first;
|
||||
components = splitPath(components.second);
|
||||
}
|
||||
|
||||
|
@ -167,11 +156,11 @@ Error Navigator::navigateToAlias(const std::string& alias, const std::string& va
|
|||
}
|
||||
else if (pathType == PathType::Aliased)
|
||||
{
|
||||
return "@" + alias + " cannot point to other aliases";
|
||||
return "alias \"@" + alias + "\" cannot point to an aliased path (\"" + value + "\")";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Error error = toError(navigationContext.jumpToAlias(value)))
|
||||
if (Error error = jumpToAlias(value))
|
||||
return error;
|
||||
}
|
||||
|
||||
|
@ -189,7 +178,7 @@ Error Navigator::navigateToAndPopulateConfig(const std::string& desiredAlias)
|
|||
{
|
||||
std::optional<std::string> configContents = navigationContext.getConfig();
|
||||
if (!configContents)
|
||||
return "could not get configuration file contents";
|
||||
return "could not get configuration file contents to resolve alias \"" + desiredAlias + "\"";
|
||||
|
||||
Luau::ConfigOptions opts;
|
||||
Luau::ConfigOptions::AliasOptions aliasOpts;
|
||||
|
@ -205,4 +194,56 @@ Error Navigator::navigateToAndPopulateConfig(const std::string& desiredAlias)
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
Error Navigator::resetToRequirer()
|
||||
{
|
||||
NavigationContext::NavigateResult result = navigationContext.reset(navigationContext.getRequirerIdentifier());
|
||||
if (result == NavigationContext::NavigateResult::Success)
|
||||
return std::nullopt;
|
||||
|
||||
std::string errorMessage = "could not reset to requiring context";
|
||||
if (result == NavigationContext::NavigateResult::Ambiguous)
|
||||
errorMessage += " (ambiguous)";
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
Error Navigator::jumpToAlias(const std::string& aliasPath)
|
||||
{
|
||||
NavigationContext::NavigateResult result = navigationContext.jumpToAlias(aliasPath);
|
||||
if (result == NavigationContext::NavigateResult::Success)
|
||||
return std::nullopt;
|
||||
|
||||
std::string errorMessage = "could not jump to alias \"" + aliasPath + "\"";
|
||||
if (result == NavigationContext::NavigateResult::Ambiguous)
|
||||
errorMessage += " (ambiguous)";
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
Error Navigator::navigateToParent(std::optional<std::string> previousComponent)
|
||||
{
|
||||
NavigationContext::NavigateResult result = navigationContext.toParent();
|
||||
if (result == NavigationContext::NavigateResult::Success)
|
||||
return std::nullopt;
|
||||
|
||||
std::string errorMessage;
|
||||
if (previousComponent)
|
||||
errorMessage = "could not get parent of component \"" + *previousComponent + "\"";
|
||||
else
|
||||
errorMessage = "could not get parent of requiring context";
|
||||
if (result == NavigationContext::NavigateResult::Ambiguous)
|
||||
errorMessage += " (ambiguous)";
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
Error Navigator::navigateToChild(const std::string& component)
|
||||
{
|
||||
NavigationContext::NavigateResult result = navigationContext.toChild(component);
|
||||
if (result == NavigationContext::NavigateResult::Success)
|
||||
return std::nullopt;
|
||||
|
||||
std::string errorMessage = "could not resolve child component \"" + component + "\"";
|
||||
if (result == NavigationContext::NavigateResult::Ambiguous)
|
||||
errorMessage += " (ambiguous)";
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
} // namespace Luau::Require
|
||||
|
|
|
@ -106,7 +106,9 @@ struct luarequire_Configuration
|
|||
luarequire_WriteResult (*get_config)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out);
|
||||
|
||||
// Executes the module and places the result on the stack. Returns the
|
||||
// number of results placed on the stack.
|
||||
// number of results placed on the stack. Returning -1 directs the requiring
|
||||
// thread to yield. In this case, this thread should be resumed with the
|
||||
// module result pushed onto its stack.
|
||||
int (*load)(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* contents);
|
||||
};
|
||||
|
||||
|
@ -130,3 +132,10 @@ LUALIB_API int luarequire_pushproxyrequire(lua_State* L, luarequire_Configuratio
|
|||
// result will always be immediately returned when the given path is required.
|
||||
// Expects the path and table to be passed as arguments on the stack.
|
||||
LUALIB_API int luarequire_registermodule(lua_State* L);
|
||||
|
||||
// Clears the entry associated with the given cache key from the require cache.
|
||||
// Expects the cache key to be passed as an argument on the stack.
|
||||
LUALIB_API int luarequire_clearcacheentry(lua_State* L);
|
||||
|
||||
// Clears all entries from the require cache.
|
||||
LUALIB_API int luarequire_clearcache(lua_State* L);
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#include "lua.h"
|
||||
#include "lualib.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
static constexpr size_t initalFileBufferSize = 1024;
|
||||
static constexpr size_t initalIdentifierBufferSize = 64;
|
||||
|
||||
|
@ -111,14 +113,16 @@ std::optional<std::string> RuntimeNavigationContext::getStringFromCWriter(
|
|||
}
|
||||
|
||||
|
||||
RuntimeErrorHandler::RuntimeErrorHandler(lua_State* L)
|
||||
RuntimeErrorHandler::RuntimeErrorHandler(lua_State* L, std::string requiredPath)
|
||||
: L(L)
|
||||
, errorPrefix("error requiring module \"" + std::move(requiredPath) + "\": ")
|
||||
{
|
||||
}
|
||||
|
||||
void RuntimeErrorHandler::reportError(std::string message)
|
||||
{
|
||||
luaL_errorL(L, "%s", message.c_str());
|
||||
std::string fullError = errorPrefix + std::move(message);
|
||||
luaL_errorL(L, "%s", fullError.c_str());
|
||||
}
|
||||
|
||||
} // namespace Luau::Require
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include "Luau/RequireNavigator.h"
|
||||
#include "Luau/Require.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
struct lua_State;
|
||||
struct luarequire_Configuration;
|
||||
|
||||
|
@ -48,11 +50,12 @@ private:
|
|||
class RuntimeErrorHandler : public ErrorHandler
|
||||
{
|
||||
public:
|
||||
RuntimeErrorHandler(lua_State* L);
|
||||
RuntimeErrorHandler(lua_State* L, std::string requiredPath);
|
||||
void reportError(std::string message) override;
|
||||
|
||||
private:
|
||||
lua_State* L;
|
||||
std::string errorPrefix;
|
||||
};
|
||||
|
||||
} // namespace Luau::Require
|
||||
|
|
|
@ -53,7 +53,7 @@ static int pushrequireclosureinternal(
|
|||
lua_pushlightuserdata(L, ctx);
|
||||
|
||||
// require-like closure captures config and ctx as upvalues
|
||||
lua_pushcclosure(L, requirelikefunc, debugname, 2);
|
||||
lua_pushcclosurek(L, requirelikefunc, debugname, 2, Luau::Require::lua_requirecont);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -77,3 +77,13 @@ int luarequire_registermodule(lua_State* L)
|
|||
{
|
||||
return Luau::Require::registerModuleImpl(L);
|
||||
}
|
||||
|
||||
int luarequire_clearcacheentry(lua_State* L)
|
||||
{
|
||||
return Luau::Require::clearCacheEntry(L);
|
||||
}
|
||||
|
||||
int luarequire_clearcache(lua_State* L)
|
||||
{
|
||||
return Luau::Require::clearCache(L);
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State*
|
|||
luaL_error(L, "require is not supported in this context");
|
||||
|
||||
RuntimeNavigationContext navigationContext{lrc, L, ctx, requirerChunkname};
|
||||
RuntimeErrorHandler errorHandler{L}; // Errors reported directly to lua_State.
|
||||
RuntimeErrorHandler errorHandler{L, path}; // Errors reported directly to lua_State.
|
||||
|
||||
Navigator navigator(navigationContext, errorHandler);
|
||||
|
||||
|
@ -61,7 +61,7 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State*
|
|||
|
||||
if (!navigationContext.isModulePresent())
|
||||
{
|
||||
luaL_errorL(L, "no module present at resolved path");
|
||||
errorHandler.reportError("no module present at resolved path");
|
||||
return ResolvedRequire{ResolvedRequire::Status::ErrorReported};
|
||||
}
|
||||
|
||||
|
@ -118,24 +118,13 @@ static int checkRegisteredModules(lua_State* L, const char* path)
|
|||
return 1;
|
||||
}
|
||||
|
||||
int lua_requireinternal(lua_State* L, const char* requirerChunkname)
|
||||
int lua_requirecont(lua_State* L, int status)
|
||||
{
|
||||
luarequire_Configuration* lrc = static_cast<luarequire_Configuration*>(lua_touserdata(L, lua_upvalueindex(1)));
|
||||
if (!lrc)
|
||||
luaL_error(L, "unable to find require configuration");
|
||||
// Number of stack arguments present before this continuation is called.
|
||||
const int numStackArgs = 2;
|
||||
const int numResults = lua_gettop(L) - numStackArgs;
|
||||
const char* cacheKey = luaL_checkstring(L, 2);
|
||||
|
||||
void* ctx = lua_tolightuserdata(L, lua_upvalueindex(2));
|
||||
|
||||
const char* path = luaL_checkstring(L, 1);
|
||||
|
||||
if (checkRegisteredModules(L, path) == 1)
|
||||
return 1;
|
||||
|
||||
ResolvedRequire resolvedRequire = resolveRequire(lrc, L, ctx, requirerChunkname, path);
|
||||
if (resolvedRequire.status == ResolvedRequire::Status::Cached)
|
||||
return 1;
|
||||
|
||||
int numResults = lrc->load(L, ctx, path, resolvedRequire.chunkname.c_str(), resolvedRequire.contents.c_str());
|
||||
if (numResults > 1)
|
||||
luaL_error(L, "module must return a single value");
|
||||
|
||||
|
@ -151,7 +140,7 @@ int lua_requireinternal(lua_State* L, const char* requirerChunkname)
|
|||
lua_pushvalue(L, -2);
|
||||
// (-3) result, (-2) cache table, (-1) result
|
||||
|
||||
lua_setfield(L, -2, resolvedRequire.cacheKey.c_str());
|
||||
lua_setfield(L, -2, cacheKey);
|
||||
// (-2) result, (-1) cache table
|
||||
|
||||
lua_pop(L, 1);
|
||||
|
@ -161,6 +150,43 @@ int lua_requireinternal(lua_State* L, const char* requirerChunkname)
|
|||
return numResults;
|
||||
}
|
||||
|
||||
int lua_requireinternal(lua_State* L, const char* requirerChunkname)
|
||||
{
|
||||
// If modifying the state of the stack, please update numStackArgs in the
|
||||
// lua_requirecont continuation function.
|
||||
|
||||
luarequire_Configuration* lrc = static_cast<luarequire_Configuration*>(lua_touserdata(L, lua_upvalueindex(1)));
|
||||
if (!lrc)
|
||||
luaL_error(L, "unable to find require configuration");
|
||||
|
||||
void* ctx = lua_tolightuserdata(L, lua_upvalueindex(2));
|
||||
|
||||
// (1) path
|
||||
const char* path = luaL_checkstring(L, 1);
|
||||
|
||||
if (checkRegisteredModules(L, path) == 1)
|
||||
return 1;
|
||||
|
||||
ResolvedRequire resolvedRequire = resolveRequire(lrc, L, ctx, requirerChunkname, path);
|
||||
if (resolvedRequire.status == ResolvedRequire::Status::Cached)
|
||||
return 1;
|
||||
|
||||
// (1) path, (2) cacheKey
|
||||
lua_pushstring(L, resolvedRequire.cacheKey.c_str());
|
||||
|
||||
int numArgsBeforeLoad = lua_gettop(L);
|
||||
int numResults = lrc->load(L, ctx, path, resolvedRequire.chunkname.c_str(), resolvedRequire.contents.c_str());
|
||||
if (numResults == -1)
|
||||
{
|
||||
if (lua_gettop(L) != numArgsBeforeLoad)
|
||||
luaL_error(L, "stack cannot be modified when require yields");
|
||||
|
||||
return lua_yield(L, 0);
|
||||
}
|
||||
|
||||
return lua_requirecont(L, LUA_OK);
|
||||
}
|
||||
|
||||
int lua_proxyrequire(lua_State* L)
|
||||
{
|
||||
const char* requirerChunkname = luaL_checkstring(L, 2);
|
||||
|
@ -170,7 +196,14 @@ int lua_proxyrequire(lua_State* L)
|
|||
int lua_require(lua_State* L)
|
||||
{
|
||||
lua_Debug ar;
|
||||
lua_getinfo(L, 1, "s", &ar);
|
||||
int level = 1;
|
||||
|
||||
do
|
||||
{
|
||||
if (!lua_getinfo(L, level++, "s", &ar))
|
||||
luaL_error(L, "require is not supported in this context");
|
||||
} while (ar.what[0] == 'C');
|
||||
|
||||
return lua_requireinternal(L, ar.source);
|
||||
}
|
||||
|
||||
|
@ -199,4 +232,21 @@ int registerModuleImpl(lua_State* L)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int clearCacheEntry(lua_State* L)
|
||||
{
|
||||
const char* cacheKey = luaL_checkstring(L, 1);
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, requiredCacheTableKey, 1);
|
||||
lua_pushnil(L);
|
||||
lua_setfield(L, -2, cacheKey);
|
||||
lua_pop(L, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int clearCache(lua_State* L)
|
||||
{
|
||||
lua_newtable(L);
|
||||
lua_setfield(L, LUA_REGISTRYINDEX, requiredCacheTableKey);
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace Luau::Require
|
||||
|
|
|
@ -8,7 +8,11 @@ namespace Luau::Require
|
|||
|
||||
int lua_require(lua_State* L);
|
||||
int lua_proxyrequire(lua_State* L);
|
||||
int lua_requirecont(lua_State* L, int status);
|
||||
|
||||
int registerModuleImpl(lua_State* L);
|
||||
|
||||
int clearCacheEntry(lua_State* L);
|
||||
int clearCache(lua_State* L);
|
||||
|
||||
} // namespace Luau::Require
|
||||
|
|
|
@ -599,4 +599,63 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypePackExplicit")
|
|||
CHECK(toJson(root->body.data[1]) == expected);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericType")
|
||||
{
|
||||
AstStatBlock* root = expectParse(R"(
|
||||
a = function<b, c>()
|
||||
end
|
||||
)");
|
||||
|
||||
CHECK(1 == root->body.size);
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatAssign","location":"1,8 - 2,11","vars":[{"type":"AstExprGlobal","location":"1,8 - 1,9","global":"a"}],"values":[{"type":"AstExprFunction","location":"1,12 - 2,11","attributes":[],"generics":[{"type":"AstGenericType","name":"b"},{"type":"AstGenericType","name":"c"}],"genericPacks":[],"args":[],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"1,28 - 2,8","hasEnd":true,"body":[]},"functionDepth":1,"debugname":""}]})";
|
||||
|
||||
CHECK(toJson(root->body.data[0]) == expected);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericTypeWithDefault")
|
||||
{
|
||||
AstStatBlock* root = expectParse(R"(
|
||||
type Foo<X = string> = X
|
||||
)");
|
||||
|
||||
CHECK(1 == root->body.size);
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatTypeAlias","location":"1,8 - 1,32","name":"Foo","generics":[{"type":"AstGenericType","name":"X","luauType":{"type":"AstTypeReference","location":"1,21 - 1,27","name":"string","nameLocation":"1,21 - 1,27","parameters":[]}}],"genericPacks":[],"value":{"type":"AstTypeReference","location":"1,31 - 1,32","name":"X","nameLocation":"1,31 - 1,32","parameters":[]},"exported":false})";
|
||||
|
||||
CHECK(toJson(root->body.data[0]) == expected);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericTypePack")
|
||||
{
|
||||
AstStatBlock* root = expectParse(R"(
|
||||
a = function<b..., c...>()
|
||||
end
|
||||
)");
|
||||
|
||||
CHECK(1 == root->body.size);
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatAssign","location":"1,8 - 2,11","vars":[{"type":"AstExprGlobal","location":"1,8 - 1,9","global":"a"}],"values":[{"type":"AstExprFunction","location":"1,12 - 2,11","attributes":[],"generics":[],"genericPacks":[{"type":"AstGenericTypePack","name":"b"},{"type":"AstGenericTypePack","name":"c"}],"args":[],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"1,34 - 2,8","hasEnd":true,"body":[]},"functionDepth":1,"debugname":""}]})";
|
||||
|
||||
CHECK(toJson(root->body.data[0]) == expected);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericTypePackWithDefault")
|
||||
{
|
||||
AstStatBlock* root = expectParse(R"(
|
||||
type Foo<X... = ...string> = any
|
||||
)");
|
||||
|
||||
CHECK(1 == root->body.size);
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatTypeAlias","location":"1,8 - 1,40","name":"Foo","generics":[],"genericPacks":[{"type":"AstGenericTypePack","name":"X","luauType":{"type":"AstTypePackVariadic","location":"1,24 - 1,33","variadicType":{"type":"AstTypeReference","location":"1,27 - 1,33","name":"string","nameLocation":"1,27 - 1,33","parameters":[]}}}],"value":{"type":"AstTypeReference","location":"1,37 - 1,40","name":"any","nameLocation":"1,37 - 1,40","parameters":[]},"exported":false})";
|
||||
|
||||
CHECK(toJson(root->body.data[0]) == expected);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
|
||||
|
||||
struct DataFlowGraphFixture
|
||||
{
|
||||
|
@ -421,6 +422,8 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "property_lookup_on_a_phi_node_3")
|
|||
|
||||
TEST_CASE_FIXTURE(DataFlowGraphFixture, "function_captures_are_phi_nodes_of_all_versions")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true};
|
||||
|
||||
dfg(R"(
|
||||
local x = 5
|
||||
|
||||
|
@ -439,15 +442,14 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "function_captures_are_phi_nodes_of_all_
|
|||
DefId x4 = getDef<AstExprLocal, 3>(); // x = "five"
|
||||
|
||||
CHECK(x1 != x2);
|
||||
CHECK(x2 != x3);
|
||||
CHECK(x2 == x3);
|
||||
CHECK(x3 != x4);
|
||||
|
||||
const Phi* phi = get<Phi>(x2);
|
||||
REQUIRE(phi);
|
||||
REQUIRE(phi->operands.size() == 3);
|
||||
REQUIRE(phi->operands.size() == 2);
|
||||
CHECK(phi->operands.at(0) == x1);
|
||||
CHECK(phi->operands.at(1) == x3);
|
||||
CHECK(phi->operands.at(2) == x4);
|
||||
CHECK(phi->operands.at(1) == x4);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DataFlowGraphFixture, "function_captures_are_phi_nodes_of_all_versions_properties")
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||
LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests)
|
||||
|
||||
LUAU_FASTFLAG(LuauTypeFunOptional)
|
||||
|
||||
#define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::LuauSolverV2, FFlag::DebugLuauForceAllNewSolverTests};
|
||||
|
||||
#define DOES_NOT_PASS_NEW_SOLVER_GUARD() DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(__LINE__)
|
||||
|
@ -182,6 +184,9 @@ private:
|
|||
struct BuiltinsFixture : Fixture
|
||||
{
|
||||
explicit BuiltinsFixture(bool prepareAutocomplete = false);
|
||||
|
||||
// For the purpose of our tests, we're always the latest version of type functions.
|
||||
ScopedFastFlag sff_optionalInTypeFunctionLib{FFlag::LuauTypeFunOptional, true};
|
||||
};
|
||||
|
||||
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& segments);
|
||||
|
|
|
@ -24,10 +24,6 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTINT(LuauParseErrorLimit)
|
||||
LUAU_FASTFLAG(LuauCloneIncrementalModule)
|
||||
|
||||
LUAU_FASTFLAG(LuauMixedModeDefFinderTraversesTypeOf)
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
|
||||
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking)
|
||||
LUAU_FASTFLAG(LuauAutocompleteUsesModuleForTypeCompatibility)
|
||||
|
@ -72,8 +68,6 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
|||
{
|
||||
static_assert(std::is_base_of_v<Fixture, BaseType>, "BaseType must be a descendant of Fixture");
|
||||
|
||||
ScopedFastFlag luauFreeTypesMustHaveBounds{FFlag::LuauFreeTypesMustHaveBounds, true};
|
||||
ScopedFastFlag luauCloneIncrementalModule{FFlag::LuauCloneIncrementalModule, true};
|
||||
ScopedFastFlag luauAllFreeTypesHaveScopes{FFlag::LuauAllFreeTypesHaveScopes, true};
|
||||
ScopedFastFlag luauClonedTableAndFunctionTypesMustHaveScopes{FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes, true};
|
||||
ScopedFastFlag luauDisableNewSolverAssertsInMixedMode{FFlag::LuauDisableNewSolverAssertsInMixedMode, true};
|
||||
|
@ -1562,8 +1556,6 @@ return module)";
|
|||
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
|
||||
ScopedFastFlag sff2{FFlag::LuauCloneIncrementalModule, true};
|
||||
ScopedFastFlag sff3{FFlag::LuauFreeTypesMustHaveBounds, true};
|
||||
checkAndExamine(source, "module", "{ }");
|
||||
fragmentACAndCheck(updated1, Position{1, 17}, "module", "{ }", "{ a: (%error-id%: unknown) -> () }");
|
||||
fragmentACAndCheck(updated2, Position{1, 18}, "module", "{ }", "{ ab: (%error-id%: unknown) -> () }");
|
||||
|
@ -2782,7 +2774,6 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "fragment_ac_must_travers
|
|||
// This test ensures that we traverse typeof expressions for defs that are being referred to in the fragment
|
||||
// In this case, we want to ensure we populate the incremental environment with the reference to `m`
|
||||
// Without this, we would ice as we will refer to the local `m` before it's declaration
|
||||
ScopedFastFlag sff{FFlag::LuauMixedModeDefFinderTraversesTypeOf, true};
|
||||
const std::string source = R"(
|
||||
--!strict
|
||||
local m = {}
|
||||
|
@ -2860,7 +2851,6 @@ type V = {h : number, i : U?}
|
|||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "generalization_crash_when_old_solver_freetypes_have_no_bounds_set")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauFreeTypesMustHaveBounds, true};
|
||||
const std::string source = R"(
|
||||
local UserInputService = game:GetService("UserInputService");
|
||||
|
||||
|
@ -2892,7 +2882,6 @@ end)
|
|||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_ensures_memory_isolation")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauCloneIncrementalModule, true};
|
||||
ToStringOptions opt;
|
||||
opt.exhaustive = true;
|
||||
opt.exhaustive = true;
|
||||
|
@ -2962,7 +2951,6 @@ return module)";
|
|||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_shouldnt_crash_on_cross_module_mutation")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauCloneIncrementalModule, true};
|
||||
const std::string source = R"(local module = {}
|
||||
function module.
|
||||
return module
|
||||
|
|
|
@ -17,7 +17,7 @@ LUAU_FASTFLAG(LuauSolverV2);
|
|||
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes)
|
||||
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -1015,7 +1015,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "environments")
|
|||
LUAU_REQUIRE_NO_ERRORS(resultA);
|
||||
|
||||
CheckResult resultB = frontend.check("B");
|
||||
if (FFlag::LuauSolverV2 && !FFlag::LuauNewNonStrictVisitTypes)
|
||||
if (FFlag::LuauSolverV2 && !FFlag::LuauNewNonStrictVisitTypes2)
|
||||
LUAU_REQUIRE_NO_ERRORS(resultB);
|
||||
else
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, resultB);
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals)
|
||||
LUAU_FASTFLAG(LuauNonStrictVisitorImprovements)
|
||||
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes)
|
||||
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -668,7 +668,7 @@ TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_non_strict")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "unknown_types_in_non_strict")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauNewNonStrictVisitTypes, true};
|
||||
ScopedFastFlag sff{FFlag::LuauNewNonStrictVisitTypes2, true};
|
||||
|
||||
CheckResult result = check(Mode::Nonstrict, R"(
|
||||
--!nonstrict
|
||||
|
@ -683,7 +683,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "unknown_types_in_non_strict")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "unknown_types_in_non_strict_2")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauNewNonStrictVisitTypes, true};
|
||||
ScopedFastFlag sff{FFlag::LuauNewNonStrictVisitTypes2, true};
|
||||
|
||||
CheckResult result = check(Mode::Nonstrict, R"(
|
||||
--!nonstrict
|
||||
|
@ -707,4 +707,13 @@ end
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "incomplete_function_annotation")
|
||||
{
|
||||
CheckResult result = check(Mode::Nonstrict, R"(
|
||||
local x: () ->
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -18,7 +18,6 @@ LUAU_FASTINT(LuauTypeLengthLimit)
|
|||
LUAU_FASTINT(LuauParseErrorLimit)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauAstTypeGroup3)
|
||||
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
|
||||
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
|
||||
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
|
||||
LUAU_FASTFLAG(LuauParseStringIndexer)
|
||||
|
@ -2869,8 +2868,6 @@ TEST_CASE_FIXTURE(Fixture, "inner_and_outer_scope_of_functions_have_correct_end_
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "do_block_end_location_is_after_end_token")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauFixDoBlockEndLocation, true};
|
||||
|
||||
AstStatBlock* stat = parse(R"(
|
||||
do
|
||||
local x = 1
|
||||
|
|
|
@ -332,6 +332,13 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireSimpleRelativePath")
|
|||
assertOutputContainsAll({"true", "result from dependency"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireSimpleRelativePathWithinPcall")
|
||||
{
|
||||
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/dependency";
|
||||
runCode(L, "return pcall(require, \"" + path + "\")");
|
||||
assertOutputContainsAll({"true", "result from dependency"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireRelativeToRequiringFile")
|
||||
{
|
||||
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module";
|
||||
|
@ -372,7 +379,9 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithFileAmbiguity")
|
|||
std::string ambiguousPath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/ambiguous_file_requirer";
|
||||
|
||||
runProtectedRequire(ambiguousPath);
|
||||
assertOutputContainsAll({"false", "require path could not be resolved to a unique file"});
|
||||
assertOutputContainsAll(
|
||||
{"false", "error requiring module \"./ambiguous/file/dependency\": could not resolve child component \"dependency\" (ambiguous)"}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithDirectoryAmbiguity")
|
||||
|
@ -380,7 +389,9 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithDirectoryAmbiguity")
|
|||
std::string ambiguousPath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/ambiguous_directory_requirer";
|
||||
|
||||
runProtectedRequire(ambiguousPath);
|
||||
assertOutputContainsAll({"false", "require path could not be resolved to a unique file"});
|
||||
assertOutputContainsAll(
|
||||
{"false", "error requiring module \"./ambiguous/directory/dependency\": could not resolve child component \"dependency\" (ambiguous)"}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireLuau")
|
||||
|
@ -466,6 +477,61 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCachedResult")
|
|||
assertOutputContainsAll({"true"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckClearCacheEntry")
|
||||
{
|
||||
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module";
|
||||
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/module";
|
||||
std::string cacheKey = absolutePath + ".luau";
|
||||
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
lua_getfield(L, -1, cacheKey.c_str());
|
||||
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
|
||||
|
||||
runProtectedRequire(relativePath);
|
||||
|
||||
assertOutputContainsAll({"true", "result from dependency", "required into module"});
|
||||
|
||||
// Check cache for the absolute path as a cache key
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
lua_getfield(L, -1, cacheKey.c_str());
|
||||
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
|
||||
|
||||
lua_pushcfunction(L, luarequire_clearcacheentry, nullptr);
|
||||
lua_pushstring(L, cacheKey.c_str());
|
||||
lua_call(L, 1, 0);
|
||||
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
lua_getfield(L, -1, cacheKey.c_str());
|
||||
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache was not cleared");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckClearCache")
|
||||
{
|
||||
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module";
|
||||
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/module";
|
||||
std::string cacheKey = absolutePath + ".luau";
|
||||
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
lua_getfield(L, -1, cacheKey.c_str());
|
||||
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
|
||||
|
||||
runProtectedRequire(relativePath);
|
||||
|
||||
assertOutputContainsAll({"true", "result from dependency", "required into module"});
|
||||
|
||||
// Check cache for the absolute path as a cache key
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
lua_getfield(L, -1, cacheKey.c_str());
|
||||
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
|
||||
|
||||
lua_pushcfunction(L, luarequire_clearcache, nullptr);
|
||||
lua_call(L, 0, 0);
|
||||
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
lua_getfield(L, -1, cacheKey.c_str());
|
||||
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache was not cleared");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "RegisterRuntimeModule")
|
||||
{
|
||||
lua_pushcfunction(L, luarequire_registermodule, nullptr);
|
||||
|
|
|
@ -1249,8 +1249,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "bill")
|
|||
CHECK(isSubtype(b, a).isSubtype);
|
||||
}
|
||||
|
||||
// TEST_CASE_FIXTURE(SubtypeFixture, "({[string]: number, a: string}) -> () <: ({[string]: number, a: string}) -> ()")
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "fred")
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "({[string]: number, a: string}) -> () <: ({[string]: number, a: string}) -> ()")
|
||||
{
|
||||
auto makeTheType = [&]()
|
||||
{
|
||||
|
|
|
@ -341,7 +341,10 @@ TEST_CASE("function_spaces_around_tokens")
|
|||
|
||||
TEST_CASE("function_with_types_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
{FFlag::LuauStoreReturnTypesAsPackOnAst, true}
|
||||
};
|
||||
std::string code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
|
@ -392,9 +395,8 @@ TEST_CASE("function_with_types_spaces_around_tokens")
|
|||
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any ): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
// TODO(CLI-139347): re-enable test once return type positions are supported
|
||||
// code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any) :string end )";
|
||||
// CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any) :string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
|
|
@ -18,6 +18,7 @@ LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
|
|||
LUAU_FASTFLAG(LuauIndexTypeFunctionFunctionMetamethods)
|
||||
LUAU_FASTFLAG(LuauMetatableTypeFunctions)
|
||||
LUAU_FASTFLAG(LuauMetatablesHaveLength)
|
||||
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
|
||||
LUAU_FASTFLAG(LuauIndexAnyIsAny)
|
||||
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
|
||||
LUAU_FASTFLAG(LuauHasPropProperBlock)
|
||||
|
@ -1674,4 +1675,70 @@ print(test.a)
|
|||
CHECK("Type 'add<string, string>' does not have key 'a'" == toString(result.errors[1]));
|
||||
}
|
||||
|
||||
struct TFFixture
|
||||
{
|
||||
TypeArena arena_;
|
||||
NotNull<TypeArena> arena{&arena_};
|
||||
|
||||
BuiltinTypes builtinTypes_;
|
||||
NotNull<BuiltinTypes> builtinTypes{&builtinTypes_};
|
||||
|
||||
ScopePtr globalScope = std::make_shared<Scope>(builtinTypes->anyTypePack);
|
||||
|
||||
InternalErrorReporter ice;
|
||||
UnifierSharedState unifierState{&ice};
|
||||
SimplifierPtr simplifier = EqSatSimplification::newSimplifier(arena, builtinTypes);
|
||||
Normalizer normalizer{arena, builtinTypes, NotNull{&unifierState}};
|
||||
TypeCheckLimits limits;
|
||||
TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}};
|
||||
|
||||
const ScopedFastFlag sff[1] = {
|
||||
{FFlag::DebugLuauGreedyGeneralization, true},
|
||||
};
|
||||
|
||||
BuiltinTypeFunctions builtinTypeFunctions;
|
||||
|
||||
TypeFunctionContext tfc{
|
||||
arena,
|
||||
builtinTypes,
|
||||
NotNull{globalScope.get()},
|
||||
NotNull{simplifier.get()},
|
||||
NotNull{&normalizer},
|
||||
NotNull{&runtime},
|
||||
NotNull{&ice},
|
||||
NotNull{&limits}
|
||||
};
|
||||
};
|
||||
|
||||
TEST_CASE_FIXTURE(TFFixture, "refine<G, ~(false?)>")
|
||||
{
|
||||
TypeId g = arena->addType(GenericType{globalScope.get(), Polarity::Negative});
|
||||
|
||||
TypeId refineTy = arena->addType(TypeFunctionInstanceType{
|
||||
builtinTypeFunctions.refineFunc, {g, builtinTypes->truthyType}
|
||||
});
|
||||
|
||||
FunctionGraphReductionResult res = reduceTypeFunctions(refineTy, Location{}, tfc);
|
||||
|
||||
CHECK(res.reducedTypes.size() == 1);
|
||||
|
||||
CHECK(res.errors.size() == 0);
|
||||
CHECK(res.irreducibleTypes.size() == 0);
|
||||
CHECK(res.blockedTypes.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TFFixture, "or<'a, 'b>")
|
||||
{
|
||||
TypeId aType = arena->freshType(builtinTypes, globalScope.get());
|
||||
TypeId bType = arena->freshType(builtinTypes, globalScope.get());
|
||||
|
||||
TypeId orType = arena->addType(TypeFunctionInstanceType{
|
||||
builtinTypeFunctions.orFunc, {aType, bType}
|
||||
});
|
||||
|
||||
FunctionGraphReductionResult res = reduceTypeFunctions(orType, Location{}, tfc);
|
||||
|
||||
CHECK(res.reducedTypes.size() == 1);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -15,7 +15,6 @@ LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
|||
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
|
||||
LUAU_FASTFLAG(LuauNoTypeFunctionsNamedTypeOf)
|
||||
|
||||
|
||||
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_nil_serialization_works")
|
||||
|
@ -370,6 +369,43 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_serialization_works")
|
|||
CHECK(toString(tpm->givenTp) == "boolean | number | string");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optional_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function numberhuh()
|
||||
return types.optional(types.number)
|
||||
end
|
||||
-- forcing an error here to check the exact type of the union
|
||||
local function ok(idx: numberhuh<>): never return idx end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
||||
REQUIRE(tpm);
|
||||
CHECK(toString(tpm->givenTp) == "number?");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optional_works_on_unions")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function foobar()
|
||||
local ty = types.unionof(types.string, types.number, types.boolean)
|
||||
return types.optional(ty)
|
||||
end
|
||||
-- forcing an error here to check the exact type of the union
|
||||
local function ok(idx: foobar<>): never return idx end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
||||
REQUIRE(tpm);
|
||||
CHECK(toString(tpm->givenTp) == "(boolean | number | string)?");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
|
|
|
@ -14,7 +14,7 @@ LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization)
|
|||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
|
||||
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes)
|
||||
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeAliases");
|
||||
|
||||
|
@ -1197,7 +1197,7 @@ TEST_CASE_FIXTURE(Fixture, "bound_type_in_alias_segfault")
|
|||
export type FieldConfigMap<TSource, TContext> = Map<string, FieldConfig<TSource, TContext>>
|
||||
)");
|
||||
|
||||
if (FFlag::LuauNewNonStrictVisitTypes)
|
||||
if (FFlag::LuauNewNonStrictVisitTypes2)
|
||||
LUAU_CHECK_ERROR_COUNT(2, result);
|
||||
else
|
||||
LUAU_CHECK_NO_ERRORS(result);
|
||||
|
|
|
@ -12,6 +12,7 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauTableCloneClonesType3)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
|
||||
LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments)
|
||||
|
||||
TEST_SUITE_BEGIN("BuiltinTests");
|
||||
|
@ -472,7 +473,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_reduce")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::DebugLuauGreedyGeneralization)
|
||||
CHECK("{ [number]: string | string | string, n: number }" == toString(requireType("t")));
|
||||
else if (FFlag::LuauSolverV2)
|
||||
CHECK_EQ("{ [number]: string, n: number }", toString(requireType("t")));
|
||||
else
|
||||
CHECK_EQ("{| [number]: string, n: number |}", toString(requireType("t")));
|
||||
|
|
|
@ -24,11 +24,13 @@ LUAU_FASTINT(LuauTarjanChildLimit)
|
|||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauUngeneralizedTypesForRecursiveFunctions)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
|
||||
LUAU_FASTFLAG(LuauReduceUnionFollowUnionType)
|
||||
LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments)
|
||||
LUAU_FASTFLAG(LuauHasPropProperBlock)
|
||||
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
|
||||
LUAU_FASTFLAG(LuauFormatUseLastPosition)
|
||||
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferFunctions");
|
||||
|
||||
|
@ -90,7 +92,7 @@ TEST_CASE_FIXTURE(Fixture, "check_function_bodies")
|
|||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
const TypePackMismatch* tm = get<TypePackMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
REQUIRE_MESSAGE(tm, "Expected TypeMismatch but got " << result.errors[0]);
|
||||
CHECK(toString(tm->wantedTp) == "number");
|
||||
CHECK(toString(tm->givenTp) == "boolean");
|
||||
}
|
||||
|
@ -3032,7 +3034,11 @@ TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types")
|
|||
auto tm2 = get<TypePackMismatch>(result.errors[1]);
|
||||
REQUIRE(tm2);
|
||||
CHECK(toString(tm2->wantedTp) == "string");
|
||||
CHECK(toString(tm2->givenTp) == "~(false?)");
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
CHECK(toString(tm2->givenTp) == "unknown & ~(false?)");
|
||||
else
|
||||
CHECK(toString(tm2->givenTp) == "~(false?)");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -3213,12 +3219,13 @@ TEST_CASE_FIXTURE(Fixture, "recursive_function_calls_should_not_use_the_generali
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzz_unwind_mutually_recursive_union_type_func")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauReduceUnionFollowUnionType, true}};
|
||||
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauDoNotAddUpvalueTypesToLocalType, true}};
|
||||
|
||||
// This block ends up minting a type like:
|
||||
// Previously, this block minted a type like:
|
||||
//
|
||||
// t2 where t1 = union<t2, t1> | union<t2, t1> | union<t2, t1> ; t2 = union<t2, t1>
|
||||
//
|
||||
// ... due to how upvalues contributed to the locally inferred types.
|
||||
CheckResult result = check(R"(
|
||||
local _ = ...
|
||||
function _()
|
||||
|
@ -3226,12 +3233,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_unwind_mutually_recursive_union_type_func")
|
|||
end
|
||||
_[function(...) repeat until _(_[l100]) _ = _ end] += _
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
auto err0 = get<UnknownSymbol>(result.errors[0]);
|
||||
CHECK(err0);
|
||||
CHECK_EQ(err0->name, "l100");
|
||||
auto err1 = get<NotATable>(result.errors[1]);
|
||||
CHECK(err1);
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_pack")
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferOperators");
|
||||
|
||||
|
@ -27,8 +28,18 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types")
|
|||
local x:string|number = s
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(toString(*requireType("s")), "number | string");
|
||||
CHECK_EQ(toString(*requireType("x")), "number | string");
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
// FIXME: Regression
|
||||
CHECK("(string & ~(false?)) | number" == toString(*requireType("s")));
|
||||
CHECK("number | string" == toString(*requireType("x")));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(*requireType("s")), "number | string");
|
||||
CHECK_EQ(toString(*requireType("x")), "number | string");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras")
|
||||
|
@ -39,8 +50,18 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras")
|
|||
local y = x or "s"
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(toString(*requireType("s")), "number | string");
|
||||
CHECK_EQ(toString(*requireType("y")), "number | string");
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
// FIXME: Regression.
|
||||
CHECK("(string & ~(false?)) | number" == toString(*requireType("s")));
|
||||
CHECK("number | string | string" == toString(*requireType("y")));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(*requireType("s")), "number | string");
|
||||
CHECK_EQ(toString(*requireType("y")), "number | string");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union")
|
||||
|
@ -50,7 +71,14 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union")
|
|||
local x:string = s
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(*requireType("s"), *builtinTypes->stringType);
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
// FIXME: Regression
|
||||
CHECK("(string & ~(false?)) | string" == toString(requireType("s")));
|
||||
}
|
||||
else
|
||||
CHECK_EQ(*requireType("s"), *builtinTypes->stringType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "and_does_not_always_add_boolean")
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
|
||||
#include "Fixture.h"
|
||||
|
@ -11,10 +12,12 @@ LUAU_FASTFLAG(LuauSolverV2)
|
|||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauIntersectNotNil)
|
||||
LUAU_FASTFLAG(LuauSkipNoRefineDuringRefinement)
|
||||
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
|
||||
LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable)
|
||||
LUAU_FASTFLAG(LuauSimplyRefineNotNil)
|
||||
LUAU_FASTFLAG(LuauWeakNilRefinementType)
|
||||
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
|
||||
LUAU_FASTFLAG(LuauSimplificationTableExternType)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -113,10 +116,18 @@ struct RefinementExternTypeFixture : BuiltinsFixture
|
|||
{"Position", Property{vec3}},
|
||||
};
|
||||
|
||||
TypeId optionalPart = arena.addType(UnionType{{part, builtinTypes->nilType}});
|
||||
TypeId weldConstraint = frontend.globals.globalTypes.addType(ExternType{"WeldConstraint", {}, inst, std::nullopt, {}, nullptr, "Test", {}});
|
||||
getMutable<ExternType>(weldConstraint)->props = {
|
||||
{"Part0", Property{optionalPart}},
|
||||
{"Part1", Property{optionalPart}},
|
||||
};
|
||||
|
||||
frontend.globals.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vec3};
|
||||
frontend.globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, inst};
|
||||
frontend.globals.globalScope->exportedTypeBindings["Folder"] = TypeFun{{}, folder};
|
||||
frontend.globals.globalScope->exportedTypeBindings["Part"] = TypeFun{{}, part};
|
||||
frontend.globals.globalScope->exportedTypeBindings["WeldConstraint"] = TypeFun{{}, weldConstraint};
|
||||
|
||||
for (const auto& [name, ty] : frontend.globals.globalScope->exportedTypeBindings)
|
||||
persist(ty.type);
|
||||
|
@ -752,11 +763,22 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil"
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil"
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil"
|
||||
CHECK("string & unknown & unknown & ~nil" == toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil"
|
||||
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil"
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil"
|
||||
CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil"
|
||||
CHECK("string & unknown & unknown & ~nil" == toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil"
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil"
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil"
|
||||
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil"
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil"
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_not_to_be_string")
|
||||
|
@ -1578,9 +1600,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "refine_param_of_type_folder_or_p
|
|||
|
||||
TEST_CASE_FIXTURE(RefinementExternTypeFixture, "isa_type_refinement_must_be_known_ahead_of_time")
|
||||
{
|
||||
// CLI-115087 - The new solver does not consistently combine tables with
|
||||
// class types when they appear in the upper bounds of a free type.
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
ScopedFastFlag sff{FFlag::LuauSimplificationTableExternType, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x): Instance
|
||||
|
@ -1596,8 +1616,41 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "isa_type_refinement_must_be_know
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28})));
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
CHECK_EQ("t1 where t1 = Instance & { read IsA: (t1, string) -> (unknown, ...unknown) }", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("t1 where t1 = Instance & { read IsA: (t1, string) -> (unknown, ...unknown) }", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementExternTypeFixture, "asserting_optional_properties_should_not_refine_extern_types_to_never")
|
||||
{
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local weld: WeldConstraint = nil :: any
|
||||
assert(weld.Part1)
|
||||
print(weld) -- hover type incorrectly becomes `never`
|
||||
assert(weld.Part1.Name == "RootPart")
|
||||
local part1 = assert(weld.Part1)
|
||||
local pos = part1.Position
|
||||
)");
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
// CLI-142467: this is a major regression that we need to address.
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({3, 15})));
|
||||
CHECK_EQ("any", toString(requireTypeAtPosition({6, 29})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("WeldConstraint", toString(requireTypeAtPosition({3, 15})));
|
||||
CHECK_EQ("Vector3", toString(requireTypeAtPosition({6, 29})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementExternTypeFixture, "x_is_not_instance_or_else_not_part")
|
||||
|
@ -1605,6 +1658,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "x_is_not_instance_or_else_not_pa
|
|||
// CLI-117135 - RefinementTests.x_is_not_instance_or_else_not_part not correctly applying refinements to a function parameter
|
||||
if (FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: Part | Folder | string)
|
||||
if typeof(x) ~= "Instance" or not x:IsA("Part") then
|
||||
|
@ -1827,6 +1881,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "refine_a_param_that_got_resolved
|
|||
// CLI-117134 - Applying a refinement causes an optional value access error.
|
||||
if (FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Id<T> = T
|
||||
|
||||
|
@ -2447,7 +2502,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi
|
|||
end
|
||||
)"));
|
||||
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24})));
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
CHECK_EQ("nil & string & unknown", toString(requireTypeAtPosition({4, 24})));
|
||||
else
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "nonnil_refinement_on_generic")
|
||||
|
|
|
@ -22,7 +22,6 @@ LUAU_FASTFLAG(LuauSolverV2)
|
|||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
|
||||
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
|
||||
LUAU_FASTFLAG(LuauFollowTableFreeze)
|
||||
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
|
||||
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
@ -4723,10 +4722,10 @@ TEST_CASE_FIXTURE(Fixture, "refined_thing_can_be_an_array")
|
|||
end
|
||||
)");
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && !FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(get<NotATable>(result.errors[0]));
|
||||
LUAU_CHECK_ERROR_COUNT(1, result);
|
||||
LUAU_CHECK_ERROR(result, NotATable);
|
||||
CHECK_EQ("(unknown, *error-type*) -> *error-type*", toString(requireType("foo")));
|
||||
}
|
||||
else
|
||||
|
@ -5335,7 +5334,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_musnt_assert")
|
|||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauFollowTableFreeze, true},
|
||||
};
|
||||
|
||||
auto result = check(R"(
|
||||
|
|
|
@ -2023,7 +2023,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_assert_table_freeze_constraint_solving"
|
|||
LUAU_REQUIRE_NO_ERROR(results, ConstraintSolvingIncompleteError);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "konnichiwa" * doctest::timeout(0.25))
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_unification_aborts_eventually" * doctest::timeout(0.25))
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, false},
|
||||
|
|
|
@ -13,6 +13,7 @@ LUAU_FASTFLAG(LuauSolverV2)
|
|||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
|
||||
|
||||
TEST_SUITE_BEGIN("TypePackTests");
|
||||
|
||||
|
@ -96,7 +97,10 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function")
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("<a, b..., c...>((b...) -> (c...), (a) -> (b...), a) -> (c...)", toString(requireType("apply")));
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
CHECK_EQ("<a, b..., c...>((c...) -> (b...), (a) -> (c...), a) -> (b...)", toString(requireType("apply")));
|
||||
else
|
||||
CHECK_EQ("<a, b..., c...>((b...) -> (c...), (a) -> (b...), a) -> (c...)", toString(requireType("apply")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "return_type_should_be_empty_if_nothing_is_returned")
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget)
|
||||
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
|
||||
LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -263,6 +265,8 @@ TEST_CASE_FIXTURE(TypeStateFixture, "local_assigned_in_only_one_branch_that_fall
|
|||
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "then_branch_assigns_and_else_branch_also_assigns_but_is_met_with_return")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x = nil
|
||||
if math.random() > 0.5 then
|
||||
|
@ -275,11 +279,13 @@ TEST_CASE_FIXTURE(TypeStateFixture, "then_branch_assigns_and_else_branch_also_as
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK("number?" == toString(requireType("y")));
|
||||
CHECK("number" == toString(requireType("y")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "then_branch_assigns_but_is_met_with_return_and_else_branch_assigns")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x = nil
|
||||
if math.random() > 0.5 then
|
||||
|
@ -292,7 +298,7 @@ TEST_CASE_FIXTURE(TypeStateFixture, "then_branch_assigns_but_is_met_with_return_
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK("string?" == toString(requireType("y")));
|
||||
CHECK("string" == toString(requireType("y")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "invalidate_type_refinements_upon_assignments")
|
||||
|
@ -338,8 +344,9 @@ TEST_CASE_FIXTURE(TypeStateFixture, "local_t_is_assigned_a_fresh_table_with_x_as
|
|||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_are_unions_of_all_assignments")
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_do_not_mutate_upvalue_type")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true};
|
||||
CheckResult result = check(R"(
|
||||
local x = nil
|
||||
|
||||
|
@ -352,12 +359,16 @@ TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_are_unions_of_all_assignmen
|
|||
f()
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK("(number | string)?" == toString(requireTypeAtPosition({4, 18})));
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
auto err = get<TypeMismatch>(result.errors[0]);
|
||||
CHECK_EQ("number?", toString(err->wantedType));
|
||||
CHECK_EQ("string", toString(err->givenType));
|
||||
CHECK("number?" == toString(requireTypeAtPosition({4, 18})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_are_unions_of_all_assignments_2")
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_do_not_mutate_upvalue_type_2")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true};
|
||||
CheckResult result = check(R"(
|
||||
local t = {x = nil}
|
||||
|
||||
|
@ -370,9 +381,13 @@ TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_are_unions_of_all_assignmen
|
|||
f()
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK("{ x: nil } | { x: number } | { x: string }" == toString(requireTypeAtPosition({4, 18}), {true}));
|
||||
CHECK("(number | string)?" == toString(requireTypeAtPosition({4, 20})));
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
auto err = get<TypeMismatch>(result.errors[0]);
|
||||
CHECK_EQ("t | { x: number }", toString(err->wantedType));
|
||||
CHECK_EQ("{ x: string }", toString(err->givenType));
|
||||
|
||||
CHECK("{ x: nil } | { x: number }" == toString(requireTypeAtPosition({4, 18}), {true}));
|
||||
CHECK("number?" == toString(requireTypeAtPosition({4, 20})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "prototyped_recursive_functions")
|
||||
|
@ -555,4 +570,280 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_normalized_type_variables_are_bad" *
|
|||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1547_simple")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
local rand = 0
|
||||
|
||||
function a()
|
||||
rand = (rand % 4) + 1;
|
||||
end
|
||||
)"));
|
||||
|
||||
auto randTy = getType("rand");
|
||||
REQUIRE(randTy);
|
||||
CHECK_EQ("number", toString(*randTy));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1547")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
local rand = 0
|
||||
|
||||
function a()
|
||||
rand = (rand % 4) + 1;
|
||||
end
|
||||
|
||||
function b()
|
||||
rand = math.max(rand - 1, 0);
|
||||
end
|
||||
)"));
|
||||
|
||||
auto randTy = getType("rand");
|
||||
REQUIRE(randTy);
|
||||
CHECK_EQ("number", toString(*randTy));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "modify_captured_table_field")
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
local state = { x = 0 }
|
||||
function incr()
|
||||
state.x = state.x + 1
|
||||
end
|
||||
)"));
|
||||
|
||||
auto randTy = getType("state");
|
||||
REQUIRE(randTy);
|
||||
CHECK_EQ("{ x: number }", toString(*randTy, {true}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "oss_1561")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true};
|
||||
|
||||
loadDefinition(R"(
|
||||
declare class Vector3
|
||||
X: number
|
||||
Y: number
|
||||
Z: number
|
||||
end
|
||||
|
||||
declare Vector3: {
|
||||
new: (number?, number?, number?) -> Vector3
|
||||
}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
local targetVelocity: Vector3 = Vector3.new()
|
||||
function set2D(X: number, Y: number)
|
||||
targetVelocity = Vector3.new(X, Y, targetVelocity.Z)
|
||||
end
|
||||
)"));
|
||||
|
||||
CHECK_EQ("(number, number) -> ()", toString(requireType("set2D")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "oss_1575")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
local flag = true
|
||||
local function Flip()
|
||||
flag = not flag
|
||||
end
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "capture_upvalue_in_returned_function")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
function def()
|
||||
local i : number = 0
|
||||
local function Counter()
|
||||
i = i + 1
|
||||
return i
|
||||
end
|
||||
return Counter
|
||||
end
|
||||
)"));
|
||||
CHECK_EQ("() -> () -> number", toString(requireType("def")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "throw_in_else_branch")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local x
|
||||
local coinflip : () -> boolean = (nil :: any)
|
||||
|
||||
if coinflip () then
|
||||
x = "I win."
|
||||
else
|
||||
error("You lose.")
|
||||
end
|
||||
|
||||
print(x)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({11, 14})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "throw_in_if_branch")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local x
|
||||
local coinflip : () -> boolean = (nil :: any)
|
||||
|
||||
if coinflip () then
|
||||
error("You lose.")
|
||||
else
|
||||
x = "I win."
|
||||
end
|
||||
|
||||
print(x)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({11, 14})));
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
type Payload = { payload: number }
|
||||
|
||||
local function decode(s: string): Payload?
|
||||
return (nil :: any)
|
||||
end
|
||||
|
||||
local function decodeEx(s: string): Payload
|
||||
local p = decode(s)
|
||||
if not p then
|
||||
error("failed to decode payload!!!")
|
||||
end
|
||||
return p
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring_in_loop")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
|
||||
local x = nil
|
||||
|
||||
while math.random() > 0.5 do
|
||||
x = 42
|
||||
return
|
||||
end
|
||||
|
||||
print(x)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// CLI-142447: This should probably be `nil` given that the `while` loop
|
||||
// unconditionally returns, but `number?` is sound, if incomplete.
|
||||
CHECK_EQ("number?", toString(requireTypeAtPosition({10, 14})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_refinement_in_loop")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local function onEachString(t: { string | number })
|
||||
for _, v in t do
|
||||
if type(v) ~= "string" then
|
||||
continue
|
||||
end
|
||||
print(v)
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("number | string", toString(requireTypeAtPosition({4, 24})));
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({7, 22})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "throw_in_if_branch_and_do_nothing_in_else")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local x
|
||||
local coinflip : () -> boolean = (nil :: any)
|
||||
|
||||
if coinflip () then
|
||||
error("You lose.")
|
||||
else
|
||||
end
|
||||
|
||||
print(x)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({10, 14})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "assign_in_an_if_branch_without_else")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local x
|
||||
local coinflip : () -> boolean = (nil :: any)
|
||||
|
||||
if coinflip () then
|
||||
x = "I win."
|
||||
end
|
||||
|
||||
print(x)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("string?", toString(requireTypeAtPosition({9, 14})));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
|
@ -1 +0,0 @@
|
|||
return {"result from global_library"}
|
|
@ -1 +0,0 @@
|
|||
return {"result from library"}
|
Loading…
Add table
Reference in a new issue