mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-13 05:20:38 +00:00
Merge branch 'upstream' into merge
This commit is contained in:
commit
2b36613293
44 changed files with 1535 additions and 582 deletions
|
@ -66,6 +66,14 @@ struct ConstraintGraphBuilder
|
|||
// The root scope of the module we're generating constraints for.
|
||||
// This is null when the CGB is initially constructed.
|
||||
Scope* rootScope;
|
||||
|
||||
// Constraints that go straight to the solver.
|
||||
std::vector<ConstraintPtr> constraints;
|
||||
|
||||
// Constraints that do not go to the solver right away. Other constraints
|
||||
// will enqueue them during solving.
|
||||
std::vector<ConstraintPtr> unqueuedConstraints;
|
||||
|
||||
// A mapping of AST node to TypeId.
|
||||
DenseHashMap<const AstExpr*, TypeId> astTypes{nullptr};
|
||||
// A mapping of AST node to TypePackId.
|
||||
|
@ -252,16 +260,8 @@ struct ConstraintGraphBuilder
|
|||
void prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program);
|
||||
};
|
||||
|
||||
/**
|
||||
* Collects a vector of borrowed constraints from the scope and all its child
|
||||
* scopes. It is important to only call this function when you're done adding
|
||||
* constraints to the scope or its descendants, lest the borrowed pointers
|
||||
* become invalid due to a container reallocation.
|
||||
* @param rootScope the root scope of the scope graph to collect constraints
|
||||
* from.
|
||||
* @return a list of pointers to constraints contained within the scope graph.
|
||||
* None of these pointers should be null.
|
||||
/** Borrow a vector of pointers from a vector of owning pointers to constraints.
|
||||
*/
|
||||
std::vector<NotNull<Constraint>> collectConstraints(NotNull<Scope> rootScope);
|
||||
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints);
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -76,8 +76,8 @@ struct ConstraintSolver
|
|||
|
||||
DcrLogger* logger;
|
||||
|
||||
explicit ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, ModuleName moduleName, NotNull<ModuleResolver> moduleResolver,
|
||||
std::vector<RequireCycle> requireCycles, DcrLogger* logger);
|
||||
explicit ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, std::vector<NotNull<Constraint>> constraints,
|
||||
ModuleName moduleName, NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger);
|
||||
|
||||
// Randomize the order in which to dispatch constraints
|
||||
void randomize(unsigned seed);
|
||||
|
|
|
@ -38,11 +38,6 @@ struct Scope
|
|||
std::unordered_map<Symbol, Binding> bindings;
|
||||
TypePackId returnType;
|
||||
std::optional<TypePackId> varargPack;
|
||||
// All constraints belonging to this scope.
|
||||
std::vector<ConstraintPtr> constraints;
|
||||
// Constraints belonging to this scope that are queued manually by other
|
||||
// constraints.
|
||||
std::vector<ConstraintPtr> unqueuedConstraints;
|
||||
|
||||
TypeLevel level;
|
||||
|
||||
|
|
|
@ -34,7 +34,6 @@ struct ToStringOptions
|
|||
size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypeVars
|
||||
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);
|
||||
ToStringNameMap nameMap;
|
||||
std::optional<ToStringNameMap> DEPRECATED_nameMap;
|
||||
std::shared_ptr<Scope> scope; // If present, module names will be added and types that are not available in scope will be marked as 'invalid'
|
||||
std::vector<std::string> namedFunctionOverrideArgNames; // If present, named function argument names will be overridden
|
||||
};
|
||||
|
@ -42,7 +41,6 @@ struct ToStringOptions
|
|||
struct ToStringResult
|
||||
{
|
||||
std::string name;
|
||||
ToStringNameMap DEPRECATED_nameMap;
|
||||
|
||||
bool invalid = false;
|
||||
bool error = false;
|
||||
|
|
|
@ -280,14 +280,14 @@ private:
|
|||
TypeId singletonType(bool value);
|
||||
TypeId singletonType(std::string value);
|
||||
|
||||
TypeIdPredicate mkTruthyPredicate(bool sense);
|
||||
TypeIdPredicate mkTruthyPredicate(bool sense, TypeId emptySetTy);
|
||||
|
||||
// TODO: Return TypeId only.
|
||||
std::optional<TypeId> filterMapImpl(TypeId type, TypeIdPredicate predicate);
|
||||
std::pair<std::optional<TypeId>, bool> filterMap(TypeId type, TypeIdPredicate predicate);
|
||||
|
||||
public:
|
||||
std::pair<std::optional<TypeId>, bool> pickTypesFromSense(TypeId type, bool sense);
|
||||
std::pair<std::optional<TypeId>, bool> pickTypesFromSense(TypeId type, bool sense, TypeId emptySetTy);
|
||||
|
||||
private:
|
||||
TypeId unionOfTypes(TypeId a, TypeId b, const ScopePtr& scope, const Location& location, bool unifyFreeTypes = true);
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false)
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBuiltInMetatableNoBadSynthetic, false)
|
||||
LUAU_FASTFLAG(LuauOptionalNextKey)
|
||||
LUAU_FASTFLAG(LuauReportShadowedTypeAlias)
|
||||
LUAU_FASTFLAG(LuauNewLibraryTypeNames)
|
||||
|
||||
/** FIXME: Many of these type definitions are not quite completely accurate.
|
||||
*
|
||||
|
@ -276,18 +278,38 @@ void registerBuiltinGlobals(TypeChecker& typeChecker)
|
|||
|
||||
addGlobalBinding(typeChecker, "string", it->second.type, "@luau");
|
||||
|
||||
// next<K, V>(t: Table<K, V>, i: K?) -> (K, V)
|
||||
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(typeChecker, arena, genericK)}});
|
||||
addGlobalBinding(typeChecker, "next",
|
||||
arena.addType(FunctionTypeVar{{genericK, genericV}, {}, nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})}), "@luau");
|
||||
if (FFlag::LuauOptionalNextKey)
|
||||
{
|
||||
// next<K, V>(t: Table<K, V>, i: K?) -> (K?, V)
|
||||
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(typeChecker, arena, genericK)}});
|
||||
TypePackId nextRetsTypePack = arena.addTypePack(TypePack{{makeOption(typeChecker, arena, genericK), genericV}});
|
||||
addGlobalBinding(typeChecker, "next", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, nextArgsTypePack, nextRetsTypePack}), "@luau");
|
||||
|
||||
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
|
||||
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
|
||||
|
||||
TypeId pairsNext = arena.addType(FunctionTypeVar{nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})});
|
||||
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, nilType}});
|
||||
TypeId pairsNext = arena.addType(FunctionTypeVar{nextArgsTypePack, nextRetsTypePack});
|
||||
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, nilType}});
|
||||
|
||||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
|
||||
addGlobalBinding(typeChecker, "pairs", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
|
||||
addGlobalBinding(
|
||||
typeChecker, "pairs", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
}
|
||||
else
|
||||
{
|
||||
// next<K, V>(t: Table<K, V>, i: K?) -> (K, V)
|
||||
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(typeChecker, arena, genericK)}});
|
||||
addGlobalBinding(typeChecker, "next",
|
||||
arena.addType(FunctionTypeVar{{genericK, genericV}, {}, nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})}), "@luau");
|
||||
|
||||
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
|
||||
|
||||
TypeId pairsNext = arena.addType(FunctionTypeVar{nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})});
|
||||
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, nilType}});
|
||||
|
||||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
|
||||
addGlobalBinding(
|
||||
typeChecker, "pairs", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
}
|
||||
|
||||
TypeId genericMT = arena.addType(GenericTypeVar{"MT"});
|
||||
|
||||
|
@ -319,7 +341,12 @@ void registerBuiltinGlobals(TypeChecker& typeChecker)
|
|||
if (TableTypeVar* ttv = getMutable<TableTypeVar>(pair.second.typeId))
|
||||
{
|
||||
if (!ttv->name)
|
||||
ttv->name = toString(pair.first);
|
||||
{
|
||||
if (FFlag::LuauNewLibraryTypeNames)
|
||||
ttv->name = "typeof(" + toString(pair.first) + ")";
|
||||
else
|
||||
ttv->name = toString(pair.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -370,18 +397,38 @@ void registerBuiltinGlobals(Frontend& frontend)
|
|||
|
||||
addGlobalBinding(frontend, "string", it->second.type, "@luau");
|
||||
|
||||
// next<K, V>(t: Table<K, V>, i: K?) -> (K, V)
|
||||
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(frontend, arena, genericK)}});
|
||||
addGlobalBinding(frontend, "next",
|
||||
arena.addType(FunctionTypeVar{{genericK, genericV}, {}, nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})}), "@luau");
|
||||
if (FFlag::LuauOptionalNextKey)
|
||||
{
|
||||
// next<K, V>(t: Table<K, V>, i: K?) -> (K?, V)
|
||||
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(frontend, arena, genericK)}});
|
||||
TypePackId nextRetsTypePack = arena.addTypePack(TypePack{{makeOption(frontend, arena, genericK), genericV}});
|
||||
addGlobalBinding(frontend, "next", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, nextArgsTypePack, nextRetsTypePack}), "@luau");
|
||||
|
||||
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
|
||||
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
|
||||
|
||||
TypeId pairsNext = arena.addType(FunctionTypeVar{nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})});
|
||||
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, frontend.singletonTypes->nilType}});
|
||||
TypeId pairsNext = arena.addType(FunctionTypeVar{nextArgsTypePack, nextRetsTypePack});
|
||||
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, frontend.singletonTypes->nilType}});
|
||||
|
||||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
|
||||
addGlobalBinding(frontend, "pairs", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K?, V), Table<K, V>, nil)
|
||||
addGlobalBinding(
|
||||
frontend, "pairs", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
}
|
||||
else
|
||||
{
|
||||
// next<K, V>(t: Table<K, V>, i: K?) -> (K, V)
|
||||
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(frontend, arena, genericK)}});
|
||||
addGlobalBinding(frontend, "next",
|
||||
arena.addType(FunctionTypeVar{{genericK, genericV}, {}, nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})}), "@luau");
|
||||
|
||||
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
|
||||
|
||||
TypeId pairsNext = arena.addType(FunctionTypeVar{nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})});
|
||||
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, frontend.singletonTypes->nilType}});
|
||||
|
||||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
|
||||
addGlobalBinding(
|
||||
frontend, "pairs", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
}
|
||||
|
||||
TypeId genericMT = arena.addType(GenericTypeVar{"MT"});
|
||||
|
||||
|
@ -413,7 +460,12 @@ void registerBuiltinGlobals(Frontend& frontend)
|
|||
if (TableTypeVar* ttv = getMutable<TableTypeVar>(pair.second.typeId))
|
||||
{
|
||||
if (!ttv->name)
|
||||
ttv->name = toString(pair.first);
|
||||
{
|
||||
if (FFlag::LuauNewLibraryTypeNames)
|
||||
ttv->name = "typeof(" + toString(pair.first) + ")";
|
||||
else
|
||||
ttv->name = toString(pair.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -623,7 +675,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionAssert(
|
|||
|
||||
if (head.size() > 0)
|
||||
{
|
||||
auto [ty, ok] = typechecker.pickTypesFromSense(head[0], true);
|
||||
auto [ty, ok] = typechecker.pickTypesFromSense(head[0], true, typechecker.singletonTypes->nilType);
|
||||
if (FFlag::LuauUnknownAndNeverType)
|
||||
{
|
||||
if (get<NeverTypeVar>(*ty))
|
||||
|
|
|
@ -52,6 +52,70 @@ static bool matchSetmetatable(const AstExprCall& call)
|
|||
return true;
|
||||
}
|
||||
|
||||
struct TypeGuard
|
||||
{
|
||||
bool isTypeof;
|
||||
AstExpr* target;
|
||||
std::string type;
|
||||
};
|
||||
|
||||
static std::optional<TypeGuard> matchTypeGuard(const AstExprBinary* binary)
|
||||
{
|
||||
if (binary->op != AstExprBinary::CompareEq && binary->op != AstExprBinary::CompareNe)
|
||||
return std::nullopt;
|
||||
|
||||
AstExpr* left = binary->left;
|
||||
AstExpr* right = binary->right;
|
||||
if (right->is<AstExprCall>())
|
||||
std::swap(left, right);
|
||||
|
||||
if (!right->is<AstExprConstantString>())
|
||||
return std::nullopt;
|
||||
|
||||
AstExprCall* call = left->as<AstExprCall>();
|
||||
AstExprConstantString* string = right->as<AstExprConstantString>();
|
||||
if (!call || !string)
|
||||
return std::nullopt;
|
||||
|
||||
AstExprGlobal* callee = call->func->as<AstExprGlobal>();
|
||||
if (!callee)
|
||||
return std::nullopt;
|
||||
|
||||
if (callee->name != "type" && callee->name != "typeof")
|
||||
return std::nullopt;
|
||||
|
||||
if (call->args.size != 1)
|
||||
return std::nullopt;
|
||||
|
||||
return TypeGuard{
|
||||
/*isTypeof*/ callee->name == "typeof",
|
||||
/*target*/ call->args.data[0],
|
||||
/*type*/ std::string(string->value.data, string->value.size),
|
||||
};
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct Checkpoint
|
||||
{
|
||||
size_t offset;
|
||||
};
|
||||
|
||||
Checkpoint checkpoint(const ConstraintGraphBuilder* cgb)
|
||||
{
|
||||
return Checkpoint{cgb->constraints.size()};
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void forEachConstraint(const Checkpoint& start, const Checkpoint& end, const ConstraintGraphBuilder* cgb, F f)
|
||||
{
|
||||
for (size_t i = start.offset; i < end.offset; ++i)
|
||||
f(cgb->constraints[i]);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ConstraintGraphBuilder::ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena,
|
||||
NotNull<ModuleResolver> moduleResolver, NotNull<SingletonTypes> singletonTypes, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope,
|
||||
DcrLogger* logger, NotNull<DataFlowGraph> dfg)
|
||||
|
@ -99,12 +163,12 @@ ScopePtr ConstraintGraphBuilder::childScope(AstNode* node, const ScopePtr& paren
|
|||
|
||||
NotNull<Constraint> ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, const Location& location, ConstraintV cv)
|
||||
{
|
||||
return NotNull{scope->constraints.emplace_back(new Constraint{NotNull{scope.get()}, location, std::move(cv)}).get()};
|
||||
return NotNull{constraints.emplace_back(new Constraint{NotNull{scope.get()}, location, std::move(cv)}).get()};
|
||||
}
|
||||
|
||||
NotNull<Constraint> ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, std::unique_ptr<Constraint> c)
|
||||
{
|
||||
return NotNull{scope->constraints.emplace_back(std::move(c)).get()};
|
||||
return NotNull{constraints.emplace_back(std::move(c)).get()};
|
||||
}
|
||||
|
||||
static void unionRefinements(const std::unordered_map<DefId, TypeId>& lhs, const std::unordered_map<DefId, TypeId>& rhs,
|
||||
|
@ -476,6 +540,9 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* forIn)
|
|||
TypeId ty = freshType(loopScope);
|
||||
loopScope->bindings[var] = Binding{ty, var->location};
|
||||
variableTypes.push_back(ty);
|
||||
|
||||
if (auto def = dfg->getDef(var))
|
||||
loopScope->dcrRefinements[*def] = ty;
|
||||
}
|
||||
|
||||
// It is always ok to provide too few variables, so we give this pack a free tail.
|
||||
|
@ -506,20 +573,6 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatRepeat* repeat)
|
|||
check(repeatScope, repeat->condition);
|
||||
}
|
||||
|
||||
void addConstraints(Constraint* constraint, NotNull<Scope> scope)
|
||||
{
|
||||
scope->constraints.reserve(scope->constraints.size() + scope->constraints.size());
|
||||
|
||||
for (const auto& c : scope->constraints)
|
||||
constraint->dependencies.push_back(NotNull{c.get()});
|
||||
|
||||
for (const auto& c : scope->unqueuedConstraints)
|
||||
constraint->dependencies.push_back(NotNull{c.get()});
|
||||
|
||||
for (NotNull<Scope> childScope : scope->children)
|
||||
addConstraints(constraint, childScope);
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFunction* function)
|
||||
{
|
||||
// Local
|
||||
|
@ -537,12 +590,17 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFunction*
|
|||
FunctionSignature sig = checkFunctionSignature(scope, function->func);
|
||||
sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->func->location};
|
||||
|
||||
auto start = checkpoint(this);
|
||||
checkFunctionBody(sig.bodyScope, function->func);
|
||||
auto end = checkpoint(this);
|
||||
|
||||
NotNull<Scope> constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()};
|
||||
std::unique_ptr<Constraint> c =
|
||||
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature});
|
||||
addConstraints(c.get(), NotNull(sig.bodyScope.get()));
|
||||
|
||||
forEachConstraint(start, end, this, [&c](const ConstraintPtr& constraint) {
|
||||
c->dependencies.push_back(NotNull{constraint.get()});
|
||||
});
|
||||
|
||||
addConstraint(scope, std::move(c));
|
||||
}
|
||||
|
@ -610,12 +668,17 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
|
|||
|
||||
LUAU_ASSERT(functionType != nullptr);
|
||||
|
||||
auto start = checkpoint(this);
|
||||
checkFunctionBody(sig.bodyScope, function->func);
|
||||
auto end = checkpoint(this);
|
||||
|
||||
NotNull<Scope> constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()};
|
||||
std::unique_ptr<Constraint> c =
|
||||
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature});
|
||||
addConstraints(c.get(), NotNull(sig.bodyScope.get()));
|
||||
|
||||
forEachConstraint(start, end, this, [&c](const ConstraintPtr& constraint) {
|
||||
c->dependencies.push_back(NotNull{constraint.get()});
|
||||
});
|
||||
|
||||
addConstraint(scope, std::move(c));
|
||||
}
|
||||
|
@ -947,8 +1010,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr*
|
|||
InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCall* call, const std::vector<TypeId>& expectedTypes)
|
||||
{
|
||||
TypeId fnType = check(scope, call->func).ty;
|
||||
const size_t constraintIndex = scope->constraints.size();
|
||||
const size_t scopeIndex = scopes.size();
|
||||
auto startCheckpoint = checkpoint(this);
|
||||
|
||||
std::vector<TypeId> args;
|
||||
|
||||
|
@ -977,8 +1039,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
|||
}
|
||||
else
|
||||
{
|
||||
const size_t constraintEndIndex = scope->constraints.size();
|
||||
const size_t scopeEndIndex = scopes.size();
|
||||
auto endCheckpoint = checkpoint(this);
|
||||
|
||||
astOriginalCallTypes[call->func] = fnType;
|
||||
|
||||
|
@ -989,29 +1050,22 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
|||
FunctionTypeVar ftv(TypeLevel{}, scope.get(), argPack, rets);
|
||||
TypeId inferredFnType = arena->addType(ftv);
|
||||
|
||||
scope->unqueuedConstraints.push_back(
|
||||
unqueuedConstraints.push_back(
|
||||
std::make_unique<Constraint>(NotNull{scope.get()}, call->func->location, InstantiationConstraint{instantiatedType, fnType}));
|
||||
NotNull<const Constraint> ic(scope->unqueuedConstraints.back().get());
|
||||
NotNull<const Constraint> ic(unqueuedConstraints.back().get());
|
||||
|
||||
scope->unqueuedConstraints.push_back(
|
||||
unqueuedConstraints.push_back(
|
||||
std::make_unique<Constraint>(NotNull{scope.get()}, call->func->location, SubtypeConstraint{inferredFnType, instantiatedType}));
|
||||
NotNull<Constraint> sc(scope->unqueuedConstraints.back().get());
|
||||
NotNull<Constraint> sc(unqueuedConstraints.back().get());
|
||||
|
||||
// We force constraints produced by checking function arguments to wait
|
||||
// until after we have resolved the constraint on the function itself.
|
||||
// This ensures, for instance, that we start inferring the contents of
|
||||
// lambdas under the assumption that their arguments and return types
|
||||
// will be compatible with the enclosing function call.
|
||||
for (size_t ci = constraintIndex; ci < constraintEndIndex; ++ci)
|
||||
scope->constraints[ci]->dependencies.push_back(sc);
|
||||
|
||||
for (size_t si = scopeIndex; si < scopeEndIndex; ++si)
|
||||
{
|
||||
for (auto& c : scopes[si].second->constraints)
|
||||
{
|
||||
c->dependencies.push_back(sc);
|
||||
}
|
||||
}
|
||||
forEachConstraint(startCheckpoint, endCheckpoint, this, [sc](const ConstraintPtr& constraint) {
|
||||
constraint->dependencies.push_back(sc);
|
||||
});
|
||||
|
||||
addConstraint(scope, call->func->location,
|
||||
FunctionCallConstraint{
|
||||
|
@ -1283,6 +1337,54 @@ std::tuple<TypeId, TypeId, ConnectiveId> ConstraintGraphBuilder::checkBinary(
|
|||
|
||||
return {leftType, rightType, connectiveArena.disjunction(leftConnective, rightConnective)};
|
||||
}
|
||||
else if (auto typeguard = matchTypeGuard(binary))
|
||||
{
|
||||
TypeId leftType = check(scope, binary->left).ty;
|
||||
TypeId rightType = check(scope, binary->right).ty;
|
||||
|
||||
std::optional<DefId> def = dfg->getDef(typeguard->target);
|
||||
if (!def)
|
||||
return {leftType, rightType, nullptr};
|
||||
|
||||
TypeId discriminantTy = singletonTypes->neverType;
|
||||
if (typeguard->type == "nil")
|
||||
discriminantTy = singletonTypes->nilType;
|
||||
else if (typeguard->type == "string")
|
||||
discriminantTy = singletonTypes->stringType;
|
||||
else if (typeguard->type == "number")
|
||||
discriminantTy = singletonTypes->numberType;
|
||||
else if (typeguard->type == "boolean")
|
||||
discriminantTy = singletonTypes->threadType;
|
||||
else if (typeguard->type == "table")
|
||||
discriminantTy = singletonTypes->neverType; // TODO: replace with top table type
|
||||
else if (typeguard->type == "function")
|
||||
discriminantTy = singletonTypes->functionType;
|
||||
else if (typeguard->type == "userdata")
|
||||
{
|
||||
// For now, we don't really care about being accurate with userdata if the typeguard was using typeof
|
||||
discriminantTy = singletonTypes->neverType; // TODO: replace with top class type
|
||||
}
|
||||
else if (!typeguard->isTypeof && typeguard->type == "vector")
|
||||
discriminantTy = singletonTypes->neverType; // TODO: figure out a way to deal with this quirky type
|
||||
else if (!typeguard->isTypeof)
|
||||
discriminantTy = singletonTypes->neverType;
|
||||
else if (auto typeFun = globalScope->lookupType(typeguard->type); typeFun && typeFun->typeParams.empty() && typeFun->typePackParams.empty())
|
||||
{
|
||||
TypeId ty = follow(typeFun->type);
|
||||
|
||||
// We're only interested in the root class of any classes.
|
||||
if (auto ctv = get<ClassTypeVar>(ty); !ctv || !ctv->parent)
|
||||
discriminantTy = ty;
|
||||
}
|
||||
|
||||
ConnectiveId proposition = connectiveArena.proposition(*def, discriminantTy);
|
||||
if (binary->op == AstExprBinary::CompareEq)
|
||||
return {leftType, rightType, proposition};
|
||||
else if (binary->op == AstExprBinary::CompareNe)
|
||||
return {leftType, rightType, connectiveArena.negation(proposition)};
|
||||
else
|
||||
ice->ice("matchTypeGuard should only return a Some under `==` or `~=`!");
|
||||
}
|
||||
else if (binary->op == AstExprBinary::CompareEq || binary->op == AstExprBinary::CompareNe)
|
||||
{
|
||||
TypeId leftType = check(scope, binary->left, expectedType, true).ty;
|
||||
|
@ -2066,19 +2168,14 @@ void ConstraintGraphBuilder::prepopulateGlobalScope(const ScopePtr& globalScope,
|
|||
program->visit(&gp);
|
||||
}
|
||||
|
||||
void collectConstraints(std::vector<NotNull<Constraint>>& result, NotNull<Scope> scope)
|
||||
{
|
||||
for (const auto& c : scope->constraints)
|
||||
result.push_back(NotNull{c.get()});
|
||||
|
||||
for (NotNull<Scope> child : scope->children)
|
||||
collectConstraints(result, child);
|
||||
}
|
||||
|
||||
std::vector<NotNull<Constraint>> collectConstraints(NotNull<Scope> rootScope)
|
||||
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints)
|
||||
{
|
||||
std::vector<NotNull<Constraint>> result;
|
||||
collectConstraints(result, rootScope);
|
||||
result.reserve(constraints.size());
|
||||
|
||||
for (const auto& c : constraints)
|
||||
result.emplace_back(c.get());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false);
|
||||
LUAU_FASTFLAG(LuauFixNameMaps)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -27,34 +26,14 @@ namespace Luau
|
|||
{
|
||||
for (const auto& [k, v] : scope->bindings)
|
||||
{
|
||||
if (FFlag::LuauFixNameMaps)
|
||||
{
|
||||
auto d = toString(v.typeId, opts);
|
||||
printf("\t%s : %s\n", k.c_str(), d.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto d = toStringDetailed(v.typeId, opts);
|
||||
opts.DEPRECATED_nameMap = d.DEPRECATED_nameMap;
|
||||
printf("\t%s : %s\n", k.c_str(), d.name.c_str());
|
||||
}
|
||||
auto d = toString(v.typeId, opts);
|
||||
printf("\t%s : %s\n", k.c_str(), d.c_str());
|
||||
}
|
||||
|
||||
for (NotNull<Scope> child : scope->children)
|
||||
dumpBindings(child, opts);
|
||||
}
|
||||
|
||||
static void dumpConstraints(NotNull<Scope> scope, ToStringOptions& opts)
|
||||
{
|
||||
for (const ConstraintPtr& c : scope->constraints)
|
||||
{
|
||||
printf("\t%s\n", toString(*c, opts).c_str());
|
||||
}
|
||||
|
||||
for (NotNull<Scope> child : scope->children)
|
||||
dumpConstraints(child, opts);
|
||||
}
|
||||
|
||||
static std::pair<std::vector<TypeId>, std::vector<TypePackId>> saturateArguments(TypeArena* arena, NotNull<SingletonTypes> singletonTypes,
|
||||
const TypeFun& fn, const std::vector<TypeId>& rawTypeArguments, const std::vector<TypePackId>& rawPackArguments)
|
||||
{
|
||||
|
@ -219,12 +198,6 @@ size_t HashInstantiationSignature::operator()(const InstantiationSignature& sign
|
|||
return hash;
|
||||
}
|
||||
|
||||
void dump(NotNull<Scope> rootScope, ToStringOptions& opts)
|
||||
{
|
||||
printf("constraints:\n");
|
||||
dumpConstraints(rootScope, opts);
|
||||
}
|
||||
|
||||
void dump(ConstraintSolver* cs, ToStringOptions& opts)
|
||||
{
|
||||
printf("constraints:\n");
|
||||
|
@ -248,17 +221,17 @@ void dump(ConstraintSolver* cs, ToStringOptions& opts)
|
|||
if (auto fcc = get<FunctionCallConstraint>(*c))
|
||||
{
|
||||
for (NotNull<const Constraint> inner : fcc->innerConstraints)
|
||||
printf("\t\t\t%s\n", toString(*inner, opts).c_str());
|
||||
printf("\t ->\t\t%s\n", toString(*inner, opts).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConstraintSolver::ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, ModuleName moduleName,
|
||||
NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger)
|
||||
ConstraintSolver::ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, std::vector<NotNull<Constraint>> constraints,
|
||||
ModuleName moduleName, NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger)
|
||||
: arena(normalizer->arena)
|
||||
, singletonTypes(normalizer->singletonTypes)
|
||||
, normalizer(normalizer)
|
||||
, constraints(collectConstraints(rootScope))
|
||||
, constraints(std::move(constraints))
|
||||
, rootScope(rootScope)
|
||||
, currentModuleName(std::move(moduleName))
|
||||
, moduleResolver(moduleResolver)
|
||||
|
@ -267,7 +240,7 @@ ConstraintSolver::ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope
|
|||
{
|
||||
opts.exhaustive = true;
|
||||
|
||||
for (NotNull<Constraint> c : constraints)
|
||||
for (NotNull<Constraint> c : this->constraints)
|
||||
{
|
||||
unsolvedConstraints.push_back(c);
|
||||
|
||||
|
@ -310,6 +283,8 @@ void ConstraintSolver::run()
|
|||
{
|
||||
printf("Starting solver\n");
|
||||
dump(this, opts);
|
||||
printf("Bindings:\n");
|
||||
dumpBindings(rootScope, opts);
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauLogSolverToJson)
|
||||
|
@ -633,13 +608,6 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
return true;
|
||||
}
|
||||
|
||||
// For or expressions, the LHS will never have nil as a possible output.
|
||||
// Consider:
|
||||
// local foo = nil or 2
|
||||
// `foo` will always be 2.
|
||||
if (c.op == AstExprBinary::Op::Or)
|
||||
leftType = stripNil(singletonTypes, *arena, leftType);
|
||||
|
||||
// Metatables go first, even if there is primitive behavior.
|
||||
if (auto it = kBinaryOpMetamethods.find(c.op); it != kBinaryOpMetamethods.end())
|
||||
{
|
||||
|
@ -769,15 +737,47 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
// And evalutes to a boolean if the LHS is falsey, and the RHS type if LHS is
|
||||
// truthy.
|
||||
case AstExprBinary::Op::And:
|
||||
asMutable(resultType)->ty.emplace<BoundTypeVar>(unionOfTypes(rightType, singletonTypes->booleanType, constraint->scope, false));
|
||||
{
|
||||
TypeId leftFilteredTy = arena->addType(IntersectionTypeVar{{singletonTypes->falsyType, leftType}});
|
||||
|
||||
// TODO: normaliztion here should be replaced by a more limited 'simplification'
|
||||
const NormalizedType* normalized = normalizer->normalize(arena->addType(UnionTypeVar{{leftFilteredTy, rightType}}));
|
||||
|
||||
if (!normalized)
|
||||
{
|
||||
reportError(CodeTooComplex{}, constraint->location);
|
||||
asMutable(resultType)->ty.emplace<BoundTypeVar>(errorRecoveryType());
|
||||
}
|
||||
else
|
||||
{
|
||||
asMutable(resultType)->ty.emplace<BoundTypeVar>(normalizer->typeFromNormal(*normalized));
|
||||
}
|
||||
|
||||
unblock(resultType);
|
||||
return true;
|
||||
}
|
||||
// Or evaluates to the LHS type if the LHS is truthy, and the RHS type if
|
||||
// LHS is falsey.
|
||||
case AstExprBinary::Op::Or:
|
||||
asMutable(resultType)->ty.emplace<BoundTypeVar>(unionOfTypes(rightType, leftType, constraint->scope, true));
|
||||
{
|
||||
TypeId rightFilteredTy = arena->addType(IntersectionTypeVar{{singletonTypes->truthyType, leftType}});
|
||||
|
||||
// TODO: normaliztion here should be replaced by a more limited 'simplification'
|
||||
const NormalizedType* normalized = normalizer->normalize(arena->addType(UnionTypeVar{{rightFilteredTy, rightType}}));
|
||||
|
||||
if (!normalized)
|
||||
{
|
||||
reportError(CodeTooComplex{}, constraint->location);
|
||||
asMutable(resultType)->ty.emplace<BoundTypeVar>(errorRecoveryType());
|
||||
}
|
||||
else
|
||||
{
|
||||
asMutable(resultType)->ty.emplace<BoundTypeVar>(normalizer->typeFromNormal(*normalized));
|
||||
}
|
||||
|
||||
unblock(resultType);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
iceReporter.ice("Unhandled AstExprBinary::Op for binary operation", constraint->location);
|
||||
break;
|
||||
|
@ -1148,6 +1148,17 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
// Alter the inner constraints.
|
||||
LUAU_ASSERT(c.innerConstraints.size() == 2);
|
||||
|
||||
// Anything that is blocked on this constraint must also be blocked on our inner constraints
|
||||
auto blockedIt = blocked.find(constraint.get());
|
||||
if (blockedIt != blocked.end())
|
||||
{
|
||||
for (const auto& ic : c.innerConstraints)
|
||||
{
|
||||
for (const auto& blockedConstraint : blockedIt->second)
|
||||
block(ic, blockedConstraint);
|
||||
}
|
||||
}
|
||||
|
||||
asMutable(*c.innerConstraints.at(0)).c = InstantiationConstraint{instantiatedType, *callMm};
|
||||
asMutable(*c.innerConstraints.at(1)).c = SubtypeConstraint{inferredFnType, instantiatedType};
|
||||
|
||||
|
@ -1180,6 +1191,17 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
}
|
||||
else
|
||||
{
|
||||
// Anything that is blocked on this constraint must also be blocked on our inner constraints
|
||||
auto blockedIt = blocked.find(constraint.get());
|
||||
if (blockedIt != blocked.end())
|
||||
{
|
||||
for (const auto& ic : c.innerConstraints)
|
||||
{
|
||||
for (const auto& blockedConstraint : blockedIt->second)
|
||||
block(ic, blockedConstraint);
|
||||
}
|
||||
}
|
||||
|
||||
unsolvedConstraints.insert(end(unsolvedConstraints), begin(c.innerConstraints), end(c.innerConstraints));
|
||||
asMutable(c.result)->ty.emplace<FreeTypePack>(constraint->scope);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAG(LuauOptionalNextKey)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -126,7 +127,7 @@ declare function rawlen<K, V>(obj: {[K]: V} | string): number
|
|||
|
||||
declare function setfenv<T..., R...>(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)?
|
||||
|
||||
declare function ipairs<V>(tab: {V}): (({V}, number) -> (number, V), {V}, number)
|
||||
-- TODO: place ipairs definition here with removal of FFlagLuauOptionalNextKey
|
||||
|
||||
declare function pcall<A..., R...>(f: (A...) -> R..., ...: A...): (boolean, R...)
|
||||
|
||||
|
@ -207,6 +208,11 @@ std::string getBuiltinDefinitionSource()
|
|||
else
|
||||
result += "declare function error<T>(message: T, level: number?)\n";
|
||||
|
||||
if (FFlag::LuauOptionalNextKey)
|
||||
result += "declare function ipairs<V>(tab: {V}): (({V}, number) -> (number?, V), {V}, number)\n";
|
||||
else
|
||||
result += "declare function ipairs<V>(tab: {V}): (({V}, number) -> (number, V), {V}, number)\n";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -955,7 +955,8 @@ ModulePtr Frontend::check(
|
|||
cgb.visit(sourceModule.root);
|
||||
result->errors = std::move(cgb.errors);
|
||||
|
||||
ConstraintSolver cs{NotNull{&normalizer}, NotNull(cgb.rootScope), sourceModule.name, NotNull(&moduleResolver), requireCycles, logger.get()};
|
||||
ConstraintSolver cs{NotNull{&normalizer}, NotNull(cgb.rootScope), borrowConstraints(cgb.constraints), sourceModule.name, NotNull(&moduleResolver),
|
||||
requireCycles, logger.get()};
|
||||
|
||||
if (options.randomizeConstraintResolutionSeed)
|
||||
cs.randomize(*options.randomizeConstraintResolutionSeed);
|
||||
|
|
|
@ -14,9 +14,7 @@
|
|||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauAnyifyModuleReturnGenerics)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAGVARIABLE(LuauForceExportSurfacesToBeNormal, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauClonePublicInterfaceLess, false);
|
||||
LUAU_FASTFLAG(LuauSubstitutionReentrant);
|
||||
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution);
|
||||
|
@ -222,18 +220,6 @@ void Module::clonePublicInterface(NotNull<SingletonTypes> singletonTypes, Intern
|
|||
}
|
||||
}
|
||||
|
||||
if (!FFlag::LuauAnyifyModuleReturnGenerics)
|
||||
{
|
||||
for (TypeId ty : returnType)
|
||||
{
|
||||
if (get<GenericTypeVar>(follow(ty)))
|
||||
{
|
||||
auto t = asMutable(ty);
|
||||
t->ty = AnyTypeVar{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& [name, ty] : declaredGlobals)
|
||||
{
|
||||
if (FFlag::LuauClonePublicInterfaceLess)
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauLvaluelessPath)
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixNameMaps, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFunctionReturnStringificationFixup, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnseeArrayTtv, false)
|
||||
|
||||
|
@ -130,28 +129,15 @@ struct StringifierState
|
|||
|
||||
bool exhaustive;
|
||||
|
||||
StringifierState(ToStringOptions& opts, ToStringResult& result, const std::optional<ToStringNameMap>& DEPRECATED_nameMap)
|
||||
StringifierState(ToStringOptions& opts, ToStringResult& result)
|
||||
: opts(opts)
|
||||
, result(result)
|
||||
, exhaustive(opts.exhaustive)
|
||||
{
|
||||
if (!FFlag::LuauFixNameMaps && DEPRECATED_nameMap)
|
||||
result.DEPRECATED_nameMap = *DEPRECATED_nameMap;
|
||||
|
||||
if (!FFlag::LuauFixNameMaps)
|
||||
{
|
||||
for (const auto& [_, v] : result.DEPRECATED_nameMap.typeVars)
|
||||
usedNames.insert(v);
|
||||
for (const auto& [_, v] : result.DEPRECATED_nameMap.typePacks)
|
||||
usedNames.insert(v);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& [_, v] : opts.nameMap.typeVars)
|
||||
usedNames.insert(v);
|
||||
for (const auto& [_, v] : opts.nameMap.typePacks)
|
||||
usedNames.insert(v);
|
||||
}
|
||||
for (const auto& [_, v] : opts.nameMap.typeVars)
|
||||
usedNames.insert(v);
|
||||
for (const auto& [_, v] : opts.nameMap.typePacks)
|
||||
usedNames.insert(v);
|
||||
}
|
||||
|
||||
bool hasSeen(const void* tv)
|
||||
|
@ -174,8 +160,8 @@ struct StringifierState
|
|||
|
||||
std::string getName(TypeId ty)
|
||||
{
|
||||
const size_t s = FFlag::LuauFixNameMaps ? opts.nameMap.typeVars.size() : result.DEPRECATED_nameMap.typeVars.size();
|
||||
std::string& n = FFlag::LuauFixNameMaps ? opts.nameMap.typeVars[ty] : result.DEPRECATED_nameMap.typeVars[ty];
|
||||
const size_t s = opts.nameMap.typeVars.size();
|
||||
std::string& n = opts.nameMap.typeVars[ty];
|
||||
if (!n.empty())
|
||||
return n;
|
||||
|
||||
|
@ -197,8 +183,8 @@ struct StringifierState
|
|||
|
||||
std::string getName(TypePackId ty)
|
||||
{
|
||||
const size_t s = FFlag::LuauFixNameMaps ? opts.nameMap.typePacks.size() : result.DEPRECATED_nameMap.typePacks.size();
|
||||
std::string& n = FFlag::LuauFixNameMaps ? opts.nameMap.typePacks[ty] : result.DEPRECATED_nameMap.typePacks[ty];
|
||||
const size_t s = opts.nameMap.typePacks.size();
|
||||
std::string& n = opts.nameMap.typePacks[ty];
|
||||
if (!n.empty())
|
||||
return n;
|
||||
|
||||
|
@ -405,10 +391,7 @@ struct TypeVarStringifier
|
|||
if (gtv.explicitName)
|
||||
{
|
||||
state.usedNames.insert(gtv.name);
|
||||
if (FFlag::LuauFixNameMaps)
|
||||
state.opts.nameMap.typeVars[ty] = gtv.name;
|
||||
else
|
||||
state.result.DEPRECATED_nameMap.typeVars[ty] = gtv.name;
|
||||
state.opts.nameMap.typeVars[ty] = gtv.name;
|
||||
state.emit(gtv.name);
|
||||
}
|
||||
else
|
||||
|
@ -1002,10 +985,7 @@ struct TypePackStringifier
|
|||
if (pack.explicitName)
|
||||
{
|
||||
state.usedNames.insert(pack.name);
|
||||
if (FFlag::LuauFixNameMaps)
|
||||
state.opts.nameMap.typePacks[tp] = pack.name;
|
||||
else
|
||||
state.result.DEPRECATED_nameMap.typePacks[tp] = pack.name;
|
||||
state.opts.nameMap.typePacks[tp] = pack.name;
|
||||
state.emit(pack.name);
|
||||
}
|
||||
else
|
||||
|
@ -1108,8 +1088,7 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts)
|
|||
|
||||
ToStringResult result;
|
||||
|
||||
StringifierState state =
|
||||
FFlag::LuauFixNameMaps ? StringifierState{opts, result, opts.nameMap} : StringifierState{opts, result, opts.DEPRECATED_nameMap};
|
||||
StringifierState state{opts, result};
|
||||
|
||||
std::set<TypeId> cycles;
|
||||
std::set<TypePackId> cycleTPs;
|
||||
|
@ -1216,8 +1195,7 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts)
|
|||
* 4. Print out the root of the type using the same algorithm as step 3.
|
||||
*/
|
||||
ToStringResult result;
|
||||
StringifierState state =
|
||||
FFlag::LuauFixNameMaps ? StringifierState{opts, result, opts.nameMap} : StringifierState{opts, result, opts.DEPRECATED_nameMap};
|
||||
StringifierState state{opts, result};
|
||||
|
||||
std::set<TypeId> cycles;
|
||||
std::set<TypePackId> cycleTPs;
|
||||
|
@ -1303,8 +1281,7 @@ std::string toString(const TypePackVar& tp, ToStringOptions& opts)
|
|||
std::string toStringNamedFunction(const std::string& funcName, const FunctionTypeVar& ftv, ToStringOptions& opts)
|
||||
{
|
||||
ToStringResult result;
|
||||
StringifierState state =
|
||||
FFlag::LuauFixNameMaps ? StringifierState{opts, result, opts.nameMap} : StringifierState{opts, result, opts.DEPRECATED_nameMap};
|
||||
StringifierState state{opts, result};
|
||||
TypeVarStringifier tvs{state};
|
||||
|
||||
state.emit(funcName);
|
||||
|
@ -1436,91 +1413,84 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
|||
auto go = [&opts](auto&& c) -> std::string {
|
||||
using T = std::decay_t<decltype(c)>;
|
||||
|
||||
// TODO: Inline and delete this function when clipping FFlag::LuauFixNameMaps
|
||||
auto tos = [](auto&& a, ToStringOptions& opts) {
|
||||
if (FFlag::LuauFixNameMaps)
|
||||
return toString(a, opts);
|
||||
else
|
||||
{
|
||||
ToStringResult tsr = toStringDetailed(a, opts);
|
||||
opts.DEPRECATED_nameMap = std::move(tsr.DEPRECATED_nameMap);
|
||||
return tsr.name;
|
||||
}
|
||||
auto tos = [&opts](auto&& a)
|
||||
{
|
||||
return toString(a, opts);
|
||||
};
|
||||
|
||||
if constexpr (std::is_same_v<T, SubtypeConstraint>)
|
||||
{
|
||||
std::string subStr = tos(c.subType, opts);
|
||||
std::string superStr = tos(c.superType, opts);
|
||||
std::string subStr = tos(c.subType);
|
||||
std::string superStr = tos(c.superType);
|
||||
return subStr + " <: " + superStr;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, PackSubtypeConstraint>)
|
||||
{
|
||||
std::string subStr = tos(c.subPack, opts);
|
||||
std::string superStr = tos(c.superPack, opts);
|
||||
std::string subStr = tos(c.subPack);
|
||||
std::string superStr = tos(c.superPack);
|
||||
return subStr + " <: " + superStr;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, GeneralizationConstraint>)
|
||||
{
|
||||
std::string subStr = tos(c.generalizedType, opts);
|
||||
std::string superStr = tos(c.sourceType, opts);
|
||||
std::string subStr = tos(c.generalizedType);
|
||||
std::string superStr = tos(c.sourceType);
|
||||
return subStr + " ~ gen " + superStr;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, InstantiationConstraint>)
|
||||
{
|
||||
std::string subStr = tos(c.subType, opts);
|
||||
std::string superStr = tos(c.superType, opts);
|
||||
std::string subStr = tos(c.subType);
|
||||
std::string superStr = tos(c.superType);
|
||||
return subStr + " ~ inst " + superStr;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, UnaryConstraint>)
|
||||
{
|
||||
std::string resultStr = tos(c.resultType, opts);
|
||||
std::string operandStr = tos(c.operandType, opts);
|
||||
std::string resultStr = tos(c.resultType);
|
||||
std::string operandStr = tos(c.operandType);
|
||||
|
||||
return resultStr + " ~ Unary<" + toString(c.op) + ", " + operandStr + ">";
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, BinaryConstraint>)
|
||||
{
|
||||
std::string resultStr = tos(c.resultType, opts);
|
||||
std::string leftStr = tos(c.leftType, opts);
|
||||
std::string rightStr = tos(c.rightType, opts);
|
||||
std::string resultStr = tos(c.resultType);
|
||||
std::string leftStr = tos(c.leftType);
|
||||
std::string rightStr = tos(c.rightType);
|
||||
|
||||
return resultStr + " ~ Binary<" + toString(c.op) + ", " + leftStr + ", " + rightStr + ">";
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, IterableConstraint>)
|
||||
{
|
||||
std::string iteratorStr = tos(c.iterator, opts);
|
||||
std::string variableStr = tos(c.variables, opts);
|
||||
std::string iteratorStr = tos(c.iterator);
|
||||
std::string variableStr = tos(c.variables);
|
||||
|
||||
return variableStr + " ~ Iterate<" + iteratorStr + ">";
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, NameConstraint>)
|
||||
{
|
||||
std::string namedStr = tos(c.namedType, opts);
|
||||
std::string namedStr = tos(c.namedType);
|
||||
return "@name(" + namedStr + ") = " + c.name;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, TypeAliasExpansionConstraint>)
|
||||
{
|
||||
std::string targetStr = tos(c.target, opts);
|
||||
std::string targetStr = tos(c.target);
|
||||
return "expand " + targetStr;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, FunctionCallConstraint>)
|
||||
{
|
||||
return "call " + tos(c.fn, opts) + " with { result = " + tos(c.result, opts) + " }";
|
||||
return "call " + tos(c.fn) + " with { result = " + tos(c.result) + " }";
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, PrimitiveTypeConstraint>)
|
||||
{
|
||||
return tos(c.resultType, opts) + " ~ prim " + tos(c.expectedType, opts) + ", " + tos(c.singletonType, opts) + ", " +
|
||||
tos(c.multitonType, opts);
|
||||
return tos(c.resultType) + " ~ prim " + tos(c.expectedType) + ", " + tos(c.singletonType) + ", " +
|
||||
tos(c.multitonType);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, HasPropConstraint>)
|
||||
{
|
||||
return tos(c.resultType, opts) + " ~ hasProp " + tos(c.subjectType, opts) + ", \"" + c.prop + "\"";
|
||||
return tos(c.resultType) + " ~ hasProp " + tos(c.subjectType) + ", \"" + c.prop + "\"";
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, SingletonOrTopTypeConstraint>)
|
||||
{
|
||||
std::string result = tos(c.resultType, opts);
|
||||
std::string discriminant = tos(c.discriminantType, opts);
|
||||
std::string result = tos(c.resultType);
|
||||
std::string discriminant = tos(c.discriminantType);
|
||||
|
||||
return result + " ~ if isSingleton D then ~D else unknown where D = " + discriminant;
|
||||
}
|
||||
|
|
|
@ -610,7 +610,7 @@ struct TypeChecker2
|
|||
visit(rhs);
|
||||
TypeId rhsType = lookupType(rhs);
|
||||
|
||||
if (!isSubtype(rhsType, lhsType, stack.back(), singletonTypes, ice))
|
||||
if (!isSubtype(rhsType, lhsType, stack.back()))
|
||||
{
|
||||
reportError(TypeMismatch{lhsType, rhsType}, rhs->location);
|
||||
}
|
||||
|
@ -761,7 +761,7 @@ struct TypeChecker2
|
|||
TypeId actualType = lookupType(number);
|
||||
TypeId numberType = singletonTypes->numberType;
|
||||
|
||||
if (!isSubtype(numberType, actualType, stack.back(), singletonTypes, ice))
|
||||
if (!isSubtype(numberType, actualType, stack.back()))
|
||||
{
|
||||
reportError(TypeMismatch{actualType, numberType}, number->location);
|
||||
}
|
||||
|
@ -772,7 +772,7 @@ struct TypeChecker2
|
|||
TypeId actualType = lookupType(string);
|
||||
TypeId stringType = singletonTypes->stringType;
|
||||
|
||||
if (!isSubtype(actualType, stringType, stack.back(), singletonTypes, ice))
|
||||
if (!isSubtype(actualType, stringType, stack.back()))
|
||||
{
|
||||
reportError(TypeMismatch{actualType, stringType}, string->location);
|
||||
}
|
||||
|
@ -861,7 +861,7 @@ struct TypeChecker2
|
|||
FunctionTypeVar ftv{argsTp, expectedRetType};
|
||||
TypeId expectedType = arena.addType(ftv);
|
||||
|
||||
if (!isSubtype(testFunctionType, expectedType, stack.back(), singletonTypes, ice))
|
||||
if (!isSubtype(testFunctionType, expectedType, stack.back()))
|
||||
{
|
||||
CloneState cloneState;
|
||||
expectedType = clone(expectedType, module->internalTypes, cloneState);
|
||||
|
@ -880,7 +880,7 @@ struct TypeChecker2
|
|||
getIndexTypeFromType(module->getModuleScope(), leftType, indexName->index.value, indexName->location, /* addErrors */ true);
|
||||
if (ty)
|
||||
{
|
||||
if (!isSubtype(resultType, *ty, stack.back(), singletonTypes, ice))
|
||||
if (!isSubtype(resultType, *ty, stack.back()))
|
||||
{
|
||||
reportError(TypeMismatch{resultType, *ty}, indexName->location);
|
||||
}
|
||||
|
@ -913,7 +913,7 @@ struct TypeChecker2
|
|||
TypeId inferredArgTy = *argIt;
|
||||
TypeId annotatedArgTy = lookupAnnotation(arg->annotation);
|
||||
|
||||
if (!isSubtype(annotatedArgTy, inferredArgTy, stack.back(), singletonTypes, ice))
|
||||
if (!isSubtype(annotatedArgTy, inferredArgTy, stack.back()))
|
||||
{
|
||||
reportError(TypeMismatch{annotatedArgTy, inferredArgTy}, arg->location);
|
||||
}
|
||||
|
@ -954,7 +954,7 @@ struct TypeChecker2
|
|||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(follow(*mm)))
|
||||
{
|
||||
TypePackId expectedArgs = module->internalTypes.addTypePack({operandType});
|
||||
reportErrors(tryUnify(scope, expr->location, ftv->argTypes, expectedArgs));
|
||||
reportErrors(tryUnify(scope, expr->location, expectedArgs, ftv->argTypes));
|
||||
|
||||
if (std::optional<TypeId> ret = first(ftv->retTypes))
|
||||
{
|
||||
|
@ -1096,7 +1096,7 @@ struct TypeChecker2
|
|||
expr->op == AstExprBinary::CompareGt || expr->op == AstExprBinary::Op::CompareLe || expr->op == AstExprBinary::Op::CompareLt)
|
||||
{
|
||||
TypePackId expectedRets = module->internalTypes.addTypePack({singletonTypes->booleanType});
|
||||
if (!isSubtype(ftv->retTypes, expectedRets, scope, singletonTypes, ice))
|
||||
if (!isSubtype(ftv->retTypes, expectedRets, scope))
|
||||
{
|
||||
reportError(GenericError{format("Metamethod '%s' must return type 'boolean'", it->second)}, expr->location);
|
||||
}
|
||||
|
@ -1207,10 +1207,10 @@ struct TypeChecker2
|
|||
TypeId computedType = lookupType(expr->expr);
|
||||
|
||||
// Note: As an optimization, we try 'number <: number | string' first, as that is the more likely case.
|
||||
if (isSubtype(annotationType, computedType, stack.back(), singletonTypes, ice))
|
||||
if (isSubtype(annotationType, computedType, stack.back()))
|
||||
return;
|
||||
|
||||
if (isSubtype(computedType, annotationType, stack.back(), singletonTypes, ice))
|
||||
if (isSubtype(computedType, annotationType, stack.back()))
|
||||
return;
|
||||
|
||||
reportError(TypesAreUnrelated{computedType, annotationType}, expr->location);
|
||||
|
@ -1505,12 +1505,27 @@ struct TypeChecker2
|
|||
}
|
||||
}
|
||||
|
||||
template<typename TID>
|
||||
bool isSubtype(TID subTy, TID superTy, NotNull<Scope> scope)
|
||||
{
|
||||
UnifierSharedState sharedState{&ice};
|
||||
TypeArena arena;
|
||||
Normalizer normalizer{&arena, singletonTypes, NotNull{&sharedState}};
|
||||
Unifier u{NotNull{&normalizer}, Mode::Strict, scope, Location{}, Covariant};
|
||||
u.useScopes = true;
|
||||
|
||||
u.tryUnify(subTy, superTy);
|
||||
const bool ok = u.errors.empty() && u.log.empty();
|
||||
return ok;
|
||||
}
|
||||
|
||||
template<typename TID>
|
||||
ErrorVec tryUnify(NotNull<Scope> scope, const Location& location, TID subTy, TID superTy)
|
||||
{
|
||||
UnifierSharedState sharedState{&ice};
|
||||
Normalizer normalizer{&module->internalTypes, singletonTypes, NotNull{&sharedState}};
|
||||
Unifier u{NotNull{&normalizer}, Mode::Strict, scope, location, Covariant};
|
||||
u.useScopes = true;
|
||||
u.tryUnify(subTy, superTy);
|
||||
|
||||
return std::move(u.errors);
|
||||
|
|
|
@ -35,18 +35,20 @@ LUAU_FASTFLAG(LuauTypeNormalization2)
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAnyifyModuleReturnGenerics, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLvaluelessPath, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNilIterator, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnknownAndNeverType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixVarargExprHeadType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTryhardAnd, false)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompleteVisitor, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOptionalNextKey, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReportShadowedTypeAlias, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterMessagingOnCountMismatch, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauArgMismatchReportFunctionLocation, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauImplicitElseRefinement, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -331,8 +333,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
|||
else
|
||||
moduleScope->returnType = anyify(moduleScope, moduleScope->returnType, Location{});
|
||||
|
||||
if (FFlag::LuauAnyifyModuleReturnGenerics)
|
||||
moduleScope->returnType = anyifyModuleReturnTypePackGenerics(moduleScope->returnType);
|
||||
moduleScope->returnType = anyifyModuleReturnTypePackGenerics(moduleScope->returnType);
|
||||
|
||||
for (auto& [_, typeFun] : moduleScope->exportedTypeBindings)
|
||||
typeFun.type = anyify(moduleScope, typeFun.type, Location{});
|
||||
|
@ -1209,10 +1210,10 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
|
|||
AstExpr* firstValue = forin.values.data[0];
|
||||
|
||||
// next is a function that takes Table<K, V> and an optional index of type K
|
||||
// next<K, V>(t: Table<K, V>, index: K | nil) -> (K, V)
|
||||
// next<K, V>(t: Table<K, V>, index: K | nil) -> (K?, V)
|
||||
// however, pairs and ipairs are quite messy, but they both share the same types
|
||||
// pairs returns 'next, t, nil', thus the type would be
|
||||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K | nil) -> (K, V), Table<K, V>, K | nil)
|
||||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K | nil) -> (K?, V), Table<K, V>, K | nil)
|
||||
// ipairs returns 'next, t, 0', thus ipairs will also share the same type as pairs, except K = number
|
||||
//
|
||||
// we can also define our own custom iterators by by returning a wrapped coroutine that calls coroutine.yield
|
||||
|
@ -1255,6 +1256,9 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
|
|||
iterTy = instantiate(scope, checkExpr(scope, *firstValue).type, firstValue->location);
|
||||
}
|
||||
|
||||
if (FFlag::LuauNilIterator)
|
||||
iterTy = stripFromNilAndReport(iterTy, firstValue->location);
|
||||
|
||||
if (std::optional<TypeId> iterMM = findMetatableEntry(iterTy, "__iter", firstValue->location, /* addErrors= */ true))
|
||||
{
|
||||
// if __iter metamethod is present, it will be called and the results are going to be called as if they are functions
|
||||
|
@ -1338,21 +1342,61 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
|
|||
reportErrors(state.errors);
|
||||
}
|
||||
|
||||
TypePackId varPack = addTypePack(TypePackVar{TypePack{varTypes, freshTypePack(scope)}});
|
||||
|
||||
if (forin.values.size >= 2)
|
||||
if (FFlag::LuauOptionalNextKey)
|
||||
{
|
||||
AstArray<AstExpr*> arguments{forin.values.data + 1, forin.values.size - 1};
|
||||
TypePackId retPack = iterFunc->retTypes;
|
||||
|
||||
Position start = firstValue->location.begin;
|
||||
Position end = values[forin.values.size - 1]->location.end;
|
||||
AstExprCall exprCall{Location(start, end), firstValue, arguments, /* self= */ false, Location()};
|
||||
if (forin.values.size >= 2)
|
||||
{
|
||||
AstArray<AstExpr*> arguments{forin.values.data + 1, forin.values.size - 1};
|
||||
|
||||
Position start = firstValue->location.begin;
|
||||
Position end = values[forin.values.size - 1]->location.end;
|
||||
AstExprCall exprCall{Location(start, end), firstValue, arguments, /* self= */ false, Location()};
|
||||
|
||||
retPack = checkExprPack(scope, exprCall).type;
|
||||
}
|
||||
|
||||
// We need to remove 'nil' from the set of options of the first return value
|
||||
// Because for loop stops when it gets 'nil', this result is never actually assigned to the first variable
|
||||
if (std::optional<TypeId> fty = first(retPack); fty && !varTypes.empty())
|
||||
{
|
||||
TypeId keyTy = follow(*fty);
|
||||
|
||||
if (get<UnionTypeVar>(keyTy))
|
||||
{
|
||||
if (std::optional<TypeId> ty = tryStripUnionFromNil(keyTy))
|
||||
keyTy = *ty;
|
||||
}
|
||||
|
||||
unify(keyTy, varTypes.front(), scope, forin.location);
|
||||
|
||||
// We have already handled the first variable type, make it match in the pack check
|
||||
varTypes.front() = *fty;
|
||||
}
|
||||
|
||||
TypePackId varPack = addTypePack(TypePackVar{TypePack{varTypes, freshTypePack(scope)}});
|
||||
|
||||
TypePackId retPack = checkExprPack(scope, exprCall).type;
|
||||
unify(retPack, varPack, scope, forin.location);
|
||||
}
|
||||
else
|
||||
unify(iterFunc->retTypes, varPack, scope, forin.location);
|
||||
{
|
||||
TypePackId varPack = addTypePack(TypePackVar{TypePack{varTypes, freshTypePack(scope)}});
|
||||
|
||||
if (forin.values.size >= 2)
|
||||
{
|
||||
AstArray<AstExpr*> arguments{forin.values.data + 1, forin.values.size - 1};
|
||||
|
||||
Position start = firstValue->location.begin;
|
||||
Position end = values[forin.values.size - 1]->location.end;
|
||||
AstExprCall exprCall{Location(start, end), firstValue, arguments, /* self= */ false, Location()};
|
||||
|
||||
TypePackId retPack = checkExprPack(scope, exprCall).type;
|
||||
unify(retPack, varPack, scope, forin.location);
|
||||
}
|
||||
else
|
||||
unify(iterFunc->retTypes, varPack, scope, forin.location);
|
||||
}
|
||||
|
||||
check(loopScope, *forin.body);
|
||||
}
|
||||
|
@ -1855,18 +1899,10 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
|
|||
|
||||
if (get<TypePack>(varargPack))
|
||||
{
|
||||
if (FFlag::LuauFixVarargExprHeadType)
|
||||
{
|
||||
if (std::optional<TypeId> ty = first(varargPack))
|
||||
return {*ty};
|
||||
if (std::optional<TypeId> ty = first(varargPack))
|
||||
return {*ty};
|
||||
|
||||
return {nilType};
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> types = flatten(varargPack).first;
|
||||
return {!types.empty() ? types[0] : nilType};
|
||||
}
|
||||
return {nilType};
|
||||
}
|
||||
else if (get<FreeTypePack>(varargPack))
|
||||
{
|
||||
|
@ -2717,12 +2753,54 @@ TypeId TypeChecker::checkRelationalOperation(
|
|||
|
||||
case AstExprBinary::And:
|
||||
if (lhsIsAny)
|
||||
{
|
||||
return lhsType;
|
||||
return unionOfTypes(rhsType, booleanType, scope, expr.location, false);
|
||||
}
|
||||
else if (FFlag::LuauTryhardAnd)
|
||||
{
|
||||
// If lhs is free, we can't tell which 'falsy' components it has, if any
|
||||
if (get<FreeTypeVar>(lhsType))
|
||||
return unionOfTypes(addType(UnionTypeVar{{nilType, singletonType(false)}}), rhsType, scope, expr.location, false);
|
||||
|
||||
auto [oty, notNever] = pickTypesFromSense(lhsType, false, neverType); // Filter out falsy types
|
||||
|
||||
if (notNever)
|
||||
{
|
||||
LUAU_ASSERT(oty);
|
||||
return unionOfTypes(*oty, rhsType, scope, expr.location, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
return rhsType;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return unionOfTypes(rhsType, booleanType, scope, expr.location, false);
|
||||
}
|
||||
case AstExprBinary::Or:
|
||||
if (lhsIsAny)
|
||||
{
|
||||
return lhsType;
|
||||
return unionOfTypes(lhsType, rhsType, scope, expr.location);
|
||||
}
|
||||
else if (FFlag::LuauTryhardAnd)
|
||||
{
|
||||
auto [oty, notNever] = pickTypesFromSense(lhsType, true, neverType); // Filter out truthy types
|
||||
|
||||
if (notNever)
|
||||
{
|
||||
LUAU_ASSERT(oty);
|
||||
return unionOfTypes(*oty, rhsType, scope, expr.location);
|
||||
}
|
||||
else
|
||||
{
|
||||
return rhsType;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return unionOfTypes(lhsType, rhsType, scope, expr.location);
|
||||
}
|
||||
default:
|
||||
LUAU_ASSERT(0);
|
||||
ice(format("checkRelationalOperation called with incorrect binary expression '%s'", toString(expr.op).c_str()), expr.location);
|
||||
|
@ -4840,9 +4918,9 @@ TypePackId TypeChecker::errorRecoveryTypePack(TypePackId guess)
|
|||
return singletonTypes->errorRecoveryTypePack(guess);
|
||||
}
|
||||
|
||||
TypeIdPredicate TypeChecker::mkTruthyPredicate(bool sense)
|
||||
TypeIdPredicate TypeChecker::mkTruthyPredicate(bool sense, TypeId emptySetTy)
|
||||
{
|
||||
return [this, sense](TypeId ty) -> std::optional<TypeId> {
|
||||
return [this, sense, emptySetTy](TypeId ty) -> std::optional<TypeId> {
|
||||
// any/error/free gets a special pass unconditionally because they can't be decided.
|
||||
if (get<AnyTypeVar>(ty) || get<ErrorTypeVar>(ty) || get<FreeTypeVar>(ty))
|
||||
return ty;
|
||||
|
@ -4860,7 +4938,7 @@ TypeIdPredicate TypeChecker::mkTruthyPredicate(bool sense)
|
|||
return sense ? std::nullopt : std::optional<TypeId>(ty);
|
||||
|
||||
// at this point, anything else is kept if sense is true, or replaced by nil
|
||||
return sense ? ty : nilType;
|
||||
return sense ? ty : emptySetTy;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -4886,9 +4964,9 @@ std::pair<std::optional<TypeId>, bool> TypeChecker::filterMap(TypeId type, TypeI
|
|||
}
|
||||
}
|
||||
|
||||
std::pair<std::optional<TypeId>, bool> TypeChecker::pickTypesFromSense(TypeId type, bool sense)
|
||||
std::pair<std::optional<TypeId>, bool> TypeChecker::pickTypesFromSense(TypeId type, bool sense, TypeId emptySetTy)
|
||||
{
|
||||
return filterMap(type, mkTruthyPredicate(sense));
|
||||
return filterMap(type, mkTruthyPredicate(sense, emptySetTy));
|
||||
}
|
||||
|
||||
TypeId TypeChecker::addTV(TypeVar&& tv)
|
||||
|
@ -5657,7 +5735,7 @@ void TypeChecker::resolve(const TruthyPredicate& truthyP, RefinementMap& refis,
|
|||
if (ty && fromOr)
|
||||
return addRefinement(refis, truthyP.lvalue, *ty);
|
||||
|
||||
refineLValue(truthyP.lvalue, refis, scope, mkTruthyPredicate(sense));
|
||||
refineLValue(truthyP.lvalue, refis, scope, mkTruthyPredicate(sense, nilType));
|
||||
}
|
||||
|
||||
void TypeChecker::resolve(const AndPredicate& andP, RefinementMap& refis, const ScopePtr& scope, bool sense)
|
||||
|
@ -5850,13 +5928,57 @@ void TypeChecker::resolve(const EqPredicate& eqP, RefinementMap& refis, const Sc
|
|||
|
||||
if (maybeSingleton(eqP.type))
|
||||
{
|
||||
// Normally we'd write option <: eqP.type, but singletons are always the subtype, so we flip this.
|
||||
if (!sense || canUnify(eqP.type, option, scope, eqP.location).empty())
|
||||
return sense ? eqP.type : option;
|
||||
if (FFlag::LuauImplicitElseRefinement)
|
||||
{
|
||||
bool optionIsSubtype = canUnify(option, eqP.type, scope, eqP.location).empty();
|
||||
bool targetIsSubtype = canUnify(eqP.type, option, scope, eqP.location).empty();
|
||||
|
||||
// local variable works around an odd gcc 9.3 warning: <anonymous> may be used uninitialized
|
||||
std::optional<TypeId> res = std::nullopt;
|
||||
return res;
|
||||
// terminology refresher:
|
||||
// - option is the type of the expression `x`, and
|
||||
// - eqP.type is the type of the expression `"hello"`
|
||||
//
|
||||
// "hello" == x where
|
||||
// x : "hello" | "world" -> x : "hello"
|
||||
// x : number | string -> x : "hello"
|
||||
// x : number -> x : never
|
||||
//
|
||||
// "hello" ~= x where
|
||||
// x : "hello" | "world" -> x : "world"
|
||||
// x : number | string -> x : number | string
|
||||
// x : number -> x : number
|
||||
|
||||
// local variable works around an odd gcc 9.3 warning: <anonymous> may be used uninitialized
|
||||
std::optional<TypeId> nope = std::nullopt;
|
||||
|
||||
if (sense)
|
||||
{
|
||||
if (optionIsSubtype && !targetIsSubtype)
|
||||
return option;
|
||||
else if (!optionIsSubtype && targetIsSubtype)
|
||||
return eqP.type;
|
||||
else if (!optionIsSubtype && !targetIsSubtype)
|
||||
return nope;
|
||||
else if (optionIsSubtype && targetIsSubtype)
|
||||
return eqP.type;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isOptionSingleton = get<SingletonTypeVar>(option);
|
||||
if (!isOptionSingleton)
|
||||
return option;
|
||||
else if (optionIsSubtype && targetIsSubtype)
|
||||
return nope;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!sense || canUnify(eqP.type, option, scope, eqP.location).empty())
|
||||
return sense ? eqP.type : option;
|
||||
|
||||
// local variable works around an odd gcc 9.3 warning: <anonymous> may be used uninitialized
|
||||
std::optional<TypeId> res = std::nullopt;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return option;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/ConstraintSolver.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
|
@ -26,6 +27,7 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
|||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNoMoreGlobalSingletonTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewLibraryTypeNames, false)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
|
||||
namespace Luau
|
||||
|
@ -33,15 +35,19 @@ namespace Luau
|
|||
|
||||
std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
||||
static bool dcrMagicFunctionFormat(MagicFunctionCallContext context);
|
||||
|
||||
static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
||||
static bool dcrMagicFunctionGmatch(MagicFunctionCallContext context);
|
||||
|
||||
static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
||||
static bool dcrMagicFunctionMatch(MagicFunctionCallContext context);
|
||||
|
||||
static std::optional<WithPredicate<TypePackId>> magicFunctionFind(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
||||
static bool dcrMagicFunctionFind(MagicFunctionCallContext context);
|
||||
|
||||
TypeId follow(TypeId t)
|
||||
{
|
||||
|
@ -800,6 +806,7 @@ TypeId SingletonTypes::makeStringMetatable()
|
|||
FunctionTypeVar formatFTV{arena->addTypePack(TypePack{{stringType}, anyTypePack}), oneStringPack};
|
||||
formatFTV.magicFunction = &magicFunctionFormat;
|
||||
const TypeId formatFn = arena->addType(formatFTV);
|
||||
attachDcrMagicFunction(formatFn, dcrMagicFunctionFormat);
|
||||
|
||||
const TypePackId emptyPack = arena->addTypePack({});
|
||||
const TypePackId stringVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{stringType}});
|
||||
|
@ -814,14 +821,17 @@ TypeId SingletonTypes::makeStringMetatable()
|
|||
const TypeId gmatchFunc =
|
||||
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionTypeVar{emptyPack, stringVariadicList})});
|
||||
attachMagicFunction(gmatchFunc, magicFunctionGmatch);
|
||||
attachDcrMagicFunction(gmatchFunc, dcrMagicFunctionGmatch);
|
||||
|
||||
const TypeId matchFunc = arena->addType(
|
||||
FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})});
|
||||
attachMagicFunction(matchFunc, magicFunctionMatch);
|
||||
attachDcrMagicFunction(matchFunc, dcrMagicFunctionMatch);
|
||||
|
||||
const TypeId findFunc = arena->addType(FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
|
||||
arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList})});
|
||||
attachMagicFunction(findFunc, magicFunctionFind);
|
||||
attachDcrMagicFunction(findFunc, dcrMagicFunctionFind);
|
||||
|
||||
TableTypeVar::Props stringLib = {
|
||||
{"byte", {arena->addType(FunctionTypeVar{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList})}},
|
||||
|
@ -855,7 +865,7 @@ TypeId SingletonTypes::makeStringMetatable()
|
|||
TypeId tableType = arena->addType(TableTypeVar{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed});
|
||||
|
||||
if (TableTypeVar* ttv = getMutable<TableTypeVar>(tableType))
|
||||
ttv->name = "string";
|
||||
ttv->name = FFlag::LuauNewLibraryTypeNames ? "typeof(string)" : "string";
|
||||
|
||||
return arena->addType(TableTypeVar{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
|
||||
}
|
||||
|
@ -1072,7 +1082,7 @@ IntersectionTypeVarIterator end(const IntersectionTypeVar* itv)
|
|||
return IntersectionTypeVarIterator{};
|
||||
}
|
||||
|
||||
static std::vector<TypeId> parseFormatString(TypeChecker& typechecker, const char* data, size_t size)
|
||||
static std::vector<TypeId> parseFormatString(NotNull<SingletonTypes> singletonTypes, const char* data, size_t size)
|
||||
{
|
||||
const char* options = "cdiouxXeEfgGqs*";
|
||||
|
||||
|
@ -1095,13 +1105,13 @@ static std::vector<TypeId> parseFormatString(TypeChecker& typechecker, const cha
|
|||
break;
|
||||
|
||||
if (data[i] == 'q' || data[i] == 's')
|
||||
result.push_back(typechecker.stringType);
|
||||
result.push_back(singletonTypes->stringType);
|
||||
else if (data[i] == '*')
|
||||
result.push_back(typechecker.unknownType);
|
||||
result.push_back(singletonTypes->unknownType);
|
||||
else if (strchr(options, data[i]))
|
||||
result.push_back(typechecker.numberType);
|
||||
result.push_back(singletonTypes->numberType);
|
||||
else
|
||||
result.push_back(typechecker.errorRecoveryType(typechecker.anyType));
|
||||
result.push_back(singletonTypes->errorRecoveryType(singletonTypes->anyType));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1130,7 +1140,7 @@ std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
|
|||
if (!fmt)
|
||||
return std::nullopt;
|
||||
|
||||
std::vector<TypeId> expected = parseFormatString(typechecker, fmt->value.data, fmt->value.size);
|
||||
std::vector<TypeId> expected = parseFormatString(typechecker.singletonTypes, fmt->value.data, fmt->value.size);
|
||||
const auto& [params, tail] = flatten(paramPack);
|
||||
|
||||
size_t paramOffset = 1;
|
||||
|
@ -1154,7 +1164,50 @@ std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
|
|||
return WithPredicate<TypePackId>{arena.addTypePack({typechecker.stringType})};
|
||||
}
|
||||
|
||||
static std::vector<TypeId> parsePatternString(TypeChecker& typechecker, const char* data, size_t size)
|
||||
static bool dcrMagicFunctionFormat(MagicFunctionCallContext context)
|
||||
{
|
||||
TypeArena* arena = context.solver->arena;
|
||||
|
||||
AstExprConstantString* fmt = nullptr;
|
||||
if (auto index = context.callSite->func->as<AstExprIndexName>(); index && context.callSite->self)
|
||||
{
|
||||
if (auto group = index->expr->as<AstExprGroup>())
|
||||
fmt = group->expr->as<AstExprConstantString>();
|
||||
else
|
||||
fmt = index->expr->as<AstExprConstantString>();
|
||||
}
|
||||
|
||||
if (!context.callSite->self && context.callSite->args.size > 0)
|
||||
fmt = context.callSite->args.data[0]->as<AstExprConstantString>();
|
||||
|
||||
if (!fmt)
|
||||
return false;
|
||||
|
||||
std::vector<TypeId> expected = parseFormatString(context.solver->singletonTypes, fmt->value.data, fmt->value.size);
|
||||
const auto& [params, tail] = flatten(context.arguments);
|
||||
|
||||
size_t paramOffset = 1;
|
||||
|
||||
// unify the prefix one argument at a time
|
||||
for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i)
|
||||
{
|
||||
context.solver->unify(params[i + paramOffset], expected[i], context.solver->rootScope);
|
||||
}
|
||||
|
||||
// if we know the argument count or if we have too many arguments for sure, we can issue an error
|
||||
size_t numActualParams = params.size();
|
||||
size_t numExpectedParams = expected.size() + 1; // + 1 for the format string
|
||||
|
||||
if (numExpectedParams != numActualParams && (!tail || numExpectedParams < numActualParams))
|
||||
context.solver->reportError(TypeError{context.callSite->location, CountMismatch{numExpectedParams, std::nullopt, numActualParams}});
|
||||
|
||||
TypePackId resultPack = arena->addTypePack({context.solver->singletonTypes->stringType});
|
||||
asMutable(context.result)->ty.emplace<BoundTypePack>(resultPack);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::vector<TypeId> parsePatternString(NotNull<SingletonTypes> singletonTypes, const char* data, size_t size)
|
||||
{
|
||||
std::vector<TypeId> result;
|
||||
int depth = 0;
|
||||
|
@ -1186,12 +1239,12 @@ static std::vector<TypeId> parsePatternString(TypeChecker& typechecker, const ch
|
|||
if (i + 1 < size && data[i + 1] == ')')
|
||||
{
|
||||
i++;
|
||||
result.push_back(typechecker.numberType);
|
||||
result.push_back(singletonTypes->numberType);
|
||||
continue;
|
||||
}
|
||||
|
||||
++depth;
|
||||
result.push_back(typechecker.stringType);
|
||||
result.push_back(singletonTypes->stringType);
|
||||
}
|
||||
else if (data[i] == ')')
|
||||
{
|
||||
|
@ -1209,7 +1262,7 @@ static std::vector<TypeId> parsePatternString(TypeChecker& typechecker, const ch
|
|||
return std::vector<TypeId>();
|
||||
|
||||
if (result.empty())
|
||||
result.push_back(typechecker.stringType);
|
||||
result.push_back(singletonTypes->stringType);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -1233,7 +1286,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
|
|||
if (!pattern)
|
||||
return std::nullopt;
|
||||
|
||||
std::vector<TypeId> returnTypes = parsePatternString(typechecker, pattern->value.data, pattern->value.size);
|
||||
std::vector<TypeId> returnTypes = parsePatternString(typechecker.singletonTypes, pattern->value.data, pattern->value.size);
|
||||
|
||||
if (returnTypes.empty())
|
||||
return std::nullopt;
|
||||
|
@ -1246,6 +1299,39 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
|
|||
return WithPredicate<TypePackId>{arena.addTypePack({iteratorType})};
|
||||
}
|
||||
|
||||
static bool dcrMagicFunctionGmatch(MagicFunctionCallContext context)
|
||||
{
|
||||
const auto& [params, tail] = flatten(context.arguments);
|
||||
|
||||
if (params.size() != 2)
|
||||
return false;
|
||||
|
||||
TypeArena* arena = context.solver->arena;
|
||||
|
||||
AstExprConstantString* pattern = nullptr;
|
||||
size_t index = context.callSite->self ? 0 : 1;
|
||||
if (context.callSite->args.size > index)
|
||||
pattern = context.callSite->args.data[index]->as<AstExprConstantString>();
|
||||
|
||||
if (!pattern)
|
||||
return false;
|
||||
|
||||
std::vector<TypeId> returnTypes = parsePatternString(context.solver->singletonTypes, pattern->value.data, pattern->value.size);
|
||||
|
||||
if (returnTypes.empty())
|
||||
return false;
|
||||
|
||||
context.solver->unify(params[0], context.solver->singletonTypes->stringType, context.solver->rootScope);
|
||||
|
||||
const TypePackId emptyPack = arena->addTypePack({});
|
||||
const TypePackId returnList = arena->addTypePack(returnTypes);
|
||||
const TypeId iteratorType = arena->addType(FunctionTypeVar{emptyPack, returnList});
|
||||
const TypePackId resTypePack = arena->addTypePack({iteratorType});
|
||||
asMutable(context.result)->ty.emplace<BoundTypePack>(resTypePack);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
||||
{
|
||||
|
@ -1265,7 +1351,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
|
|||
if (!pattern)
|
||||
return std::nullopt;
|
||||
|
||||
std::vector<TypeId> returnTypes = parsePatternString(typechecker, pattern->value.data, pattern->value.size);
|
||||
std::vector<TypeId> returnTypes = parsePatternString(typechecker.singletonTypes, pattern->value.data, pattern->value.size);
|
||||
|
||||
if (returnTypes.empty())
|
||||
return std::nullopt;
|
||||
|
@ -1282,6 +1368,42 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
|
|||
return WithPredicate<TypePackId>{returnList};
|
||||
}
|
||||
|
||||
static bool dcrMagicFunctionMatch(MagicFunctionCallContext context)
|
||||
{
|
||||
const auto& [params, tail] = flatten(context.arguments);
|
||||
|
||||
if (params.size() < 2 || params.size() > 3)
|
||||
return false;
|
||||
|
||||
TypeArena* arena = context.solver->arena;
|
||||
|
||||
AstExprConstantString* pattern = nullptr;
|
||||
size_t patternIndex = context.callSite->self ? 0 : 1;
|
||||
if (context.callSite->args.size > patternIndex)
|
||||
pattern = context.callSite->args.data[patternIndex]->as<AstExprConstantString>();
|
||||
|
||||
if (!pattern)
|
||||
return false;
|
||||
|
||||
std::vector<TypeId> returnTypes = parsePatternString(context.solver->singletonTypes, pattern->value.data, pattern->value.size);
|
||||
|
||||
if (returnTypes.empty())
|
||||
return false;
|
||||
|
||||
context.solver->unify(params[0], context.solver->singletonTypes->stringType, context.solver->rootScope);
|
||||
|
||||
const TypeId optionalNumber = arena->addType(UnionTypeVar{{context.solver->singletonTypes->nilType, context.solver->singletonTypes->numberType}});
|
||||
|
||||
size_t initIndex = context.callSite->self ? 1 : 2;
|
||||
if (params.size() == 3 && context.callSite->args.size > initIndex)
|
||||
context.solver->unify(params[2], optionalNumber, context.solver->rootScope);
|
||||
|
||||
const TypePackId returnList = arena->addTypePack(returnTypes);
|
||||
asMutable(context.result)->ty.emplace<BoundTypePack>(returnList);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::optional<WithPredicate<TypePackId>> magicFunctionFind(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
||||
{
|
||||
|
@ -1312,7 +1434,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionFind(
|
|||
std::vector<TypeId> returnTypes;
|
||||
if (!plain)
|
||||
{
|
||||
returnTypes = parsePatternString(typechecker, pattern->value.data, pattern->value.size);
|
||||
returnTypes = parsePatternString(typechecker.singletonTypes, pattern->value.data, pattern->value.size);
|
||||
|
||||
if (returnTypes.empty())
|
||||
return std::nullopt;
|
||||
|
@ -1336,6 +1458,60 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionFind(
|
|||
return WithPredicate<TypePackId>{returnList};
|
||||
}
|
||||
|
||||
static bool dcrMagicFunctionFind(MagicFunctionCallContext context)
|
||||
{
|
||||
const auto& [params, tail] = flatten(context.arguments);
|
||||
|
||||
if (params.size() < 2 || params.size() > 4)
|
||||
return false;
|
||||
|
||||
TypeArena* arena = context.solver->arena;
|
||||
NotNull<SingletonTypes> singletonTypes = context.solver->singletonTypes;
|
||||
|
||||
AstExprConstantString* pattern = nullptr;
|
||||
size_t patternIndex = context.callSite->self ? 0 : 1;
|
||||
if (context.callSite->args.size > patternIndex)
|
||||
pattern = context.callSite->args.data[patternIndex]->as<AstExprConstantString>();
|
||||
|
||||
if (!pattern)
|
||||
return false;
|
||||
|
||||
bool plain = false;
|
||||
size_t plainIndex = context.callSite->self ? 2 : 3;
|
||||
if (context.callSite->args.size > plainIndex)
|
||||
{
|
||||
AstExprConstantBool* p = context.callSite->args.data[plainIndex]->as<AstExprConstantBool>();
|
||||
plain = p && p->value;
|
||||
}
|
||||
|
||||
std::vector<TypeId> returnTypes;
|
||||
if (!plain)
|
||||
{
|
||||
returnTypes = parsePatternString(singletonTypes, pattern->value.data, pattern->value.size);
|
||||
|
||||
if (returnTypes.empty())
|
||||
return false;
|
||||
}
|
||||
|
||||
context.solver->unify(params[0], singletonTypes->stringType, context.solver->rootScope);
|
||||
|
||||
const TypeId optionalNumber = arena->addType(UnionTypeVar{{singletonTypes->nilType, singletonTypes->numberType}});
|
||||
const TypeId optionalBoolean = arena->addType(UnionTypeVar{{singletonTypes->nilType, singletonTypes->booleanType}});
|
||||
|
||||
size_t initIndex = context.callSite->self ? 1 : 2;
|
||||
if (params.size() >= 3 && context.callSite->args.size > initIndex)
|
||||
context.solver->unify(params[2], optionalNumber, context.solver->rootScope);
|
||||
|
||||
if (params.size() == 4 && context.callSite->args.size > plainIndex)
|
||||
context.solver->unify(params[3], optionalBoolean, context.solver->rootScope);
|
||||
|
||||
returnTypes.insert(returnTypes.begin(), {optionalNumber, optionalNumber});
|
||||
|
||||
const TypePackId returnList = arena->addTypePack(returnTypes);
|
||||
asMutable(context.result)->ty.emplace<BoundTypePack>(returnList);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate)
|
||||
{
|
||||
type = follow(type);
|
||||
|
|
|
@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauSubtypeNormalizer, false);
|
|||
LUAU_FASTFLAGVARIABLE(LuauScalarShapeSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOverloadedFunctionSubtypingPerf, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauScalarShapeUnifyToMtOwner, false)
|
||||
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauNegatedFunctionTypes)
|
||||
|
@ -1699,8 +1700,20 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||
// Recursive unification can change the txn log, and invalidate the old
|
||||
// table. If we detect that this has happened, we start over, with the updated
|
||||
// txn log.
|
||||
TableTypeVar* newSuperTable = log.getMutable<TableTypeVar>(superTy);
|
||||
TableTypeVar* newSubTable = log.getMutable<TableTypeVar>(subTy);
|
||||
TypeId superTyNew = FFlag::LuauScalarShapeUnifyToMtOwner ? log.follow(superTy) : superTy;
|
||||
TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner ? log.follow(subTy) : subTy;
|
||||
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner)
|
||||
{
|
||||
// If one of the types stopped being a table altogether, we need to restart from the top
|
||||
if ((superTy != superTyNew || subTy != subTyNew) && errors.empty())
|
||||
return tryUnify(subTy, superTy, false, isIntersection);
|
||||
}
|
||||
|
||||
// Otherwise, restart only the table unification
|
||||
TableTypeVar* newSuperTable = log.getMutable<TableTypeVar>(superTyNew);
|
||||
TableTypeVar* newSubTable = log.getMutable<TableTypeVar>(subTyNew);
|
||||
|
||||
if (superTable != newSuperTable || (subTable != newSubTable && subTable != instantiatedSubTable))
|
||||
{
|
||||
if (errors.empty())
|
||||
|
@ -1862,7 +1875,9 @@ void Unifier::tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed)
|
|||
if (reversed)
|
||||
std::swap(subTy, superTy);
|
||||
|
||||
if (auto ttv = log.get<TableTypeVar>(superTy); !ttv || ttv->state != TableState::Free)
|
||||
TableTypeVar* superTable = log.getMutable<TableTypeVar>(superTy);
|
||||
|
||||
if (!superTable || superTable->state != TableState::Free)
|
||||
return reportError(location, TypeMismatch{osuperTy, osubTy});
|
||||
|
||||
auto fail = [&](std::optional<TypeError> e) {
|
||||
|
@ -1887,6 +1902,20 @@ void Unifier::tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed)
|
|||
Unifier child = makeChildUnifier();
|
||||
child.tryUnify_(ty, superTy);
|
||||
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner)
|
||||
{
|
||||
// To perform subtype <: free table unification, we have tried to unify (subtype's metatable) <: free table
|
||||
// There is a chance that it was unified with the origial subtype, but then, (subtype's metatable) <: subtype could've failed
|
||||
// Here we check if we have a new supertype instead of the original free table and try original subtype <: new supertype check
|
||||
TypeId newSuperTy = child.log.follow(superTy);
|
||||
|
||||
if (superTy != newSuperTy && canUnify(subTy, newSuperTy).empty())
|
||||
{
|
||||
log.replace(superTy, BoundTypeVar{subTy});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto e = hasUnificationTooComplex(child.errors))
|
||||
reportError(*e);
|
||||
else if (!child.errors.empty())
|
||||
|
@ -1894,6 +1923,14 @@ void Unifier::tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed)
|
|||
|
||||
log.concat(std::move(child.log));
|
||||
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner)
|
||||
{
|
||||
// To perform subtype <: free table unification, we have tried to unify (subtype's metatable) <: free table
|
||||
// We return success because subtype <: free table which means that correct unification is to replace free table with the subtype
|
||||
if (child.errors.empty())
|
||||
log.replace(superTy, BoundTypeVar{subTy});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -27,6 +27,8 @@ LUAU_FASTFLAGVARIABLE(LuauInterpolatedStringBaseSupport, false)
|
|||
LUAU_FASTFLAGVARIABLE(LuauCommaParenWarnings, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableConstructorRecovery, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauParserErrorsOnMissingDefaultTypePackArgument, false)
|
||||
|
||||
bool lua_telemetry_parsed_out_of_range_bin_integer = false;
|
||||
bool lua_telemetry_parsed_out_of_range_hex_integer = false;
|
||||
bool lua_telemetry_parsed_double_prefix_hex_integer = false;
|
||||
|
@ -2503,7 +2505,7 @@ std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> Parser::parseG
|
|||
|
||||
namePacks.push_back({name, nameLocation, typePack});
|
||||
}
|
||||
else if (lexer.current().type == '(')
|
||||
else if (!FFlag::LuauParserErrorsOnMissingDefaultTypePackArgument && lexer.current().type == '(')
|
||||
{
|
||||
auto [type, typePack] = parseTypeOrPackAnnotation();
|
||||
|
||||
|
@ -2512,6 +2514,15 @@ std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> Parser::parseG
|
|||
|
||||
namePacks.push_back({name, nameLocation, typePack});
|
||||
}
|
||||
else if (FFlag::LuauParserErrorsOnMissingDefaultTypePackArgument)
|
||||
{
|
||||
auto [type, typePack] = parseTypeOrPackAnnotation();
|
||||
|
||||
if (type)
|
||||
report(type->location, "Expected type pack after '=', got type");
|
||||
|
||||
namePacks.push_back({name, nameLocation, typePack});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -17,7 +17,6 @@ enum class AddressKindA64 : uint8_t
|
|||
// reg + reg << shift
|
||||
// reg + sext(reg) << shift
|
||||
// reg + uext(reg) << shift
|
||||
// pc + offset
|
||||
};
|
||||
|
||||
struct AddressA64
|
||||
|
@ -28,8 +27,8 @@ struct AddressA64
|
|||
, offset(xzr)
|
||||
, data(off)
|
||||
{
|
||||
LUAU_ASSERT(base.kind == KindA64::x);
|
||||
LUAU_ASSERT(off >= 0 && off < 4096);
|
||||
LUAU_ASSERT(base.kind == KindA64::x || base == sp);
|
||||
LUAU_ASSERT(off >= -256 && off < 4096);
|
||||
}
|
||||
|
||||
AddressA64(RegisterA64 base, RegisterA64 offset)
|
||||
|
@ -48,5 +47,7 @@ struct AddressA64
|
|||
int data;
|
||||
};
|
||||
|
||||
using mem = AddressA64;
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
||||
|
|
|
@ -34,15 +34,18 @@ public:
|
|||
|
||||
// Comparisons
|
||||
// Note: some arithmetic instructions also have versions that update flags (ADDS etc) but we aren't using them atm
|
||||
// TODO: add cmp
|
||||
void cmp(RegisterA64 src1, RegisterA64 src2);
|
||||
void cmp(RegisterA64 src1, int src2);
|
||||
|
||||
// Binary
|
||||
// Bitwise
|
||||
// Note: shifted-register support and bitfield operations are omitted for simplicity
|
||||
// TODO: support immediate arguments (they have odd encoding and forbid many values)
|
||||
// TODO: support not variants for and/or/eor (required to support not...)
|
||||
void and_(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
|
||||
void orr(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
|
||||
void eor(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
|
||||
void mvn(RegisterA64 dst, RegisterA64 src);
|
||||
|
||||
// Shifts
|
||||
void lsl(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
|
||||
void lsr(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
|
||||
void asr(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
|
||||
|
@ -66,11 +69,19 @@ public:
|
|||
|
||||
// Control flow
|
||||
// Note: tbz/tbnz are currently not supported because they have 15-bit offsets and we don't support branch thunks
|
||||
void b(Label& label);
|
||||
void b(ConditionA64 cond, Label& label);
|
||||
void cbz(RegisterA64 src, Label& label);
|
||||
void cbnz(RegisterA64 src, Label& label);
|
||||
void br(RegisterA64 src);
|
||||
void blr(RegisterA64 src);
|
||||
void ret();
|
||||
|
||||
// Address of embedded data
|
||||
void adr(RegisterA64 dst, const void* ptr, size_t size);
|
||||
void adr(RegisterA64 dst, uint64_t value);
|
||||
void adr(RegisterA64 dst, double value);
|
||||
|
||||
// Run final checks
|
||||
bool finalize();
|
||||
|
||||
|
@ -97,17 +108,21 @@ private:
|
|||
// Instruction archetypes
|
||||
void place0(const char* name, uint32_t word);
|
||||
void placeSR3(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t op, int shift = 0);
|
||||
void placeSR2(const char* name, RegisterA64 dst, RegisterA64 src, uint8_t op);
|
||||
void placeSR2(const char* name, RegisterA64 dst, RegisterA64 src, uint8_t op, uint8_t op2 = 0);
|
||||
void placeR3(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t op, uint8_t op2);
|
||||
void placeR1(const char* name, RegisterA64 dst, RegisterA64 src, uint32_t op);
|
||||
void placeI12(const char* name, RegisterA64 dst, RegisterA64 src1, int src2, uint8_t op);
|
||||
void placeI16(const char* name, RegisterA64 dst, int src, uint8_t op, int shift = 0);
|
||||
void placeA(const char* name, RegisterA64 dst, AddressA64 src, uint8_t op, uint8_t size);
|
||||
void placeBC(const char* name, Label& label, uint8_t op, uint8_t cond);
|
||||
void placeBR(const char* name, Label& label, uint8_t op, RegisterA64 cond);
|
||||
void placeBCR(const char* name, Label& label, uint8_t op, RegisterA64 cond);
|
||||
void placeBR(const char* name, RegisterA64 src, uint32_t op);
|
||||
void placeADR(const char* name, RegisterA64 src, uint8_t op);
|
||||
|
||||
void place(uint32_t word);
|
||||
void placeLabel(Label& label);
|
||||
|
||||
void patchLabel(Label& label);
|
||||
void patchImm19(uint32_t location, int value);
|
||||
|
||||
void commit();
|
||||
LUAU_NOINLINE void extend();
|
||||
|
@ -123,6 +138,7 @@ private:
|
|||
LUAU_NOINLINE void log(const char* opcode, RegisterA64 dst, int src, int shift = 0);
|
||||
LUAU_NOINLINE void log(const char* opcode, RegisterA64 dst, AddressA64 src);
|
||||
LUAU_NOINLINE void log(const char* opcode, RegisterA64 src, Label label);
|
||||
LUAU_NOINLINE void log(const char* opcode, RegisterA64 src);
|
||||
LUAU_NOINLINE void log(const char* opcode, Label label);
|
||||
LUAU_NOINLINE void log(Label label);
|
||||
LUAU_NOINLINE void log(RegisterA64 reg);
|
||||
|
@ -133,6 +149,7 @@ private:
|
|||
std::vector<uint32_t> labelLocations;
|
||||
|
||||
bool finalized = false;
|
||||
bool overflowed = false;
|
||||
|
||||
size_t dataPos = 0;
|
||||
|
||||
|
|
|
@ -37,7 +37,10 @@ AssemblyBuilderA64::~AssemblyBuilderA64()
|
|||
|
||||
void AssemblyBuilderA64::mov(RegisterA64 dst, RegisterA64 src)
|
||||
{
|
||||
placeSR2("mov", dst, src, 0b01'01010);
|
||||
if (dst == sp || src == sp)
|
||||
placeR1("mov", dst, src, 0b00'100010'0'000000000000);
|
||||
else
|
||||
placeSR2("mov", dst, src, 0b01'01010);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::mov(RegisterA64 dst, uint16_t src, int shift)
|
||||
|
@ -75,6 +78,20 @@ void AssemblyBuilderA64::neg(RegisterA64 dst, RegisterA64 src)
|
|||
placeSR2("neg", dst, src, 0b10'01011);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::cmp(RegisterA64 src1, RegisterA64 src2)
|
||||
{
|
||||
RegisterA64 dst = src1.kind == KindA64::x ? xzr : wzr;
|
||||
|
||||
placeSR3("cmp", dst, src1, src2, 0b11'01011);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::cmp(RegisterA64 src1, int src2)
|
||||
{
|
||||
RegisterA64 dst = src1.kind == KindA64::x ? xzr : wzr;
|
||||
|
||||
placeI12("cmp", dst, src1, src2, 0b11'10001);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::and_(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2)
|
||||
{
|
||||
placeSR3("and", dst, src1, src2, 0b00'01010);
|
||||
|
@ -90,6 +107,11 @@ void AssemblyBuilderA64::eor(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2
|
|||
placeSR3("eor", dst, src1, src2, 0b10'01010);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::mvn(RegisterA64 dst, RegisterA64 src)
|
||||
{
|
||||
placeSR2("mvn", dst, src, 0b01'01010, 0b1);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::lsl(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2)
|
||||
{
|
||||
placeR3("lsl", dst, src1, src2, 0b11010110, 0b0010'00);
|
||||
|
@ -183,6 +205,12 @@ void AssemblyBuilderA64::strh(RegisterA64 src, AddressA64 dst)
|
|||
placeA("strh", src, dst, 0b11100000, 0b01);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::b(Label& label)
|
||||
{
|
||||
// Note: we aren't using 'b' form since it has a 26-bit immediate which requires custom fixup logic
|
||||
placeBC("b", label, 0b0101010'0, codeForCondition[int(ConditionA64::Always)]);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::b(ConditionA64 cond, Label& label)
|
||||
{
|
||||
placeBC(textForCondition[int(cond)], label, 0b0101010'0, codeForCondition[int(cond)]);
|
||||
|
@ -190,12 +218,22 @@ void AssemblyBuilderA64::b(ConditionA64 cond, Label& label)
|
|||
|
||||
void AssemblyBuilderA64::cbz(RegisterA64 src, Label& label)
|
||||
{
|
||||
placeBR("cbz", label, 0b011010'0, src);
|
||||
placeBCR("cbz", label, 0b011010'0, src);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::cbnz(RegisterA64 src, Label& label)
|
||||
{
|
||||
placeBR("cbnz", label, 0b011010'1, src);
|
||||
placeBCR("cbnz", label, 0b011010'1, src);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::br(RegisterA64 src)
|
||||
{
|
||||
placeBR("br", src, 0b1101011'0'0'00'11111'0000'0'0);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::blr(RegisterA64 src)
|
||||
{
|
||||
placeBR("blr", src, 0b1101011'0'0'01'11111'0000'0'0);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::ret()
|
||||
|
@ -203,10 +241,41 @@ void AssemblyBuilderA64::ret()
|
|||
place0("ret", 0b1101011'0'0'10'11111'0000'0'0'11110'00000);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::adr(RegisterA64 dst, const void* ptr, size_t size)
|
||||
{
|
||||
size_t pos = allocateData(size, 4);
|
||||
uint32_t location = getCodeSize();
|
||||
|
||||
memcpy(&data[pos], ptr, size);
|
||||
placeADR("adr", dst, 0b10000);
|
||||
|
||||
patchImm19(location, -int(location) - int((data.size() - pos) / 4));
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::adr(RegisterA64 dst, uint64_t value)
|
||||
{
|
||||
size_t pos = allocateData(8, 8);
|
||||
uint32_t location = getCodeSize();
|
||||
|
||||
writeu64(&data[pos], value);
|
||||
placeADR("adr", dst, 0b10000);
|
||||
|
||||
patchImm19(location, -int(location) - int((data.size() - pos) / 4));
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::adr(RegisterA64 dst, double value)
|
||||
{
|
||||
size_t pos = allocateData(8, 8);
|
||||
uint32_t location = getCodeSize();
|
||||
|
||||
writef64(&data[pos], value);
|
||||
placeADR("adr", dst, 0b10000);
|
||||
|
||||
patchImm19(location, -int(location) - int((data.size() - pos) / 4));
|
||||
}
|
||||
|
||||
bool AssemblyBuilderA64::finalize()
|
||||
{
|
||||
bool success = true;
|
||||
|
||||
code.resize(codePos - code.data());
|
||||
|
||||
// Resolve jump targets
|
||||
|
@ -214,15 +283,9 @@ bool AssemblyBuilderA64::finalize()
|
|||
{
|
||||
// If this assertion fires, a label was used in jmp without calling setLabel
|
||||
LUAU_ASSERT(labelLocations[fixup.id - 1] != ~0u);
|
||||
|
||||
int value = int(labelLocations[fixup.id - 1]) - int(fixup.location);
|
||||
|
||||
// imm19 encoding word offset, at bit offset 5
|
||||
// note that 18 bits of word offsets = 20 bits of byte offsets = +-1MB
|
||||
if (value > -(1 << 18) && value < (1 << 18))
|
||||
code[fixup.location] |= (value & ((1 << 19) - 1)) << 5;
|
||||
else
|
||||
success = false; // overflow
|
||||
patchImm19(fixup.location, value);
|
||||
}
|
||||
|
||||
size_t dataSize = data.size() - dataPos;
|
||||
|
@ -235,7 +298,7 @@ bool AssemblyBuilderA64::finalize()
|
|||
|
||||
finalized = true;
|
||||
|
||||
return success;
|
||||
return !overflowed;
|
||||
}
|
||||
|
||||
Label AssemblyBuilderA64::setLabel()
|
||||
|
@ -303,7 +366,7 @@ void AssemblyBuilderA64::placeSR3(const char* name, RegisterA64 dst, RegisterA64
|
|||
commit();
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::placeSR2(const char* name, RegisterA64 dst, RegisterA64 src, uint8_t op)
|
||||
void AssemblyBuilderA64::placeSR2(const char* name, RegisterA64 dst, RegisterA64 src, uint8_t op, uint8_t op2)
|
||||
{
|
||||
if (logText)
|
||||
log(name, dst, src);
|
||||
|
@ -313,7 +376,7 @@ void AssemblyBuilderA64::placeSR2(const char* name, RegisterA64 dst, RegisterA64
|
|||
|
||||
uint32_t sf = (dst.kind == KindA64::x) ? 0x80000000 : 0;
|
||||
|
||||
place(dst.index | (0x1f << 5) | (src.index << 16) | (op << 24) | sf);
|
||||
place(dst.index | (0x1f << 5) | (src.index << 16) | (op2 << 21) | (op << 24) | sf);
|
||||
commit();
|
||||
}
|
||||
|
||||
|
@ -336,10 +399,10 @@ void AssemblyBuilderA64::placeR1(const char* name, RegisterA64 dst, RegisterA64
|
|||
if (logText)
|
||||
log(name, dst, src);
|
||||
|
||||
LUAU_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x);
|
||||
LUAU_ASSERT(dst.kind == src.kind);
|
||||
LUAU_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x || dst == sp);
|
||||
LUAU_ASSERT(dst.kind == src.kind || (dst.kind == KindA64::x && src == sp) || (dst == sp && src.kind == KindA64::x));
|
||||
|
||||
uint32_t sf = (dst.kind == KindA64::x) ? 0x80000000 : 0;
|
||||
uint32_t sf = (dst.kind != KindA64::w) ? 0x80000000 : 0;
|
||||
|
||||
place(dst.index | (src.index << 5) | (op << 10) | sf);
|
||||
commit();
|
||||
|
@ -350,11 +413,11 @@ void AssemblyBuilderA64::placeI12(const char* name, RegisterA64 dst, RegisterA64
|
|||
if (logText)
|
||||
log(name, dst, src1, src2);
|
||||
|
||||
LUAU_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x);
|
||||
LUAU_ASSERT(dst.kind == src1.kind);
|
||||
LUAU_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x || dst == sp);
|
||||
LUAU_ASSERT(dst.kind == src1.kind || (dst.kind == KindA64::x && src1 == sp) || (dst == sp && src1.kind == KindA64::x));
|
||||
LUAU_ASSERT(src2 >= 0 && src2 < (1 << 12));
|
||||
|
||||
uint32_t sf = (dst.kind == KindA64::x) ? 0x80000000 : 0;
|
||||
uint32_t sf = (dst.kind != KindA64::w) ? 0x80000000 : 0;
|
||||
|
||||
place(dst.index | (src1.index << 5) | (src2 << 10) | (op << 24) | sf);
|
||||
commit();
|
||||
|
@ -383,8 +446,12 @@ void AssemblyBuilderA64::placeA(const char* name, RegisterA64 dst, AddressA64 sr
|
|||
switch (src.kind)
|
||||
{
|
||||
case AddressKindA64::imm:
|
||||
LUAU_ASSERT(src.data % (1 << size) == 0);
|
||||
place(dst.index | (src.base.index << 5) | ((src.data >> size) << 10) | (op << 22) | (1 << 24) | (size << 30));
|
||||
if (src.data >= 0 && src.data % (1 << size) == 0)
|
||||
place(dst.index | (src.base.index << 5) | ((src.data >> size) << 10) | (op << 22) | (1 << 24) | (size << 30));
|
||||
else if (src.data >= -256 && src.data <= 255)
|
||||
place(dst.index | (src.base.index << 5) | ((src.data & ((1 << 9) - 1)) << 12) | (op << 22) | (size << 30));
|
||||
else
|
||||
LUAU_ASSERT(!"Unable to encode large immediate offset");
|
||||
break;
|
||||
case AddressKindA64::reg:
|
||||
place(dst.index | (src.base.index << 5) | (0b10 << 10) | (0b011 << 13) | (src.offset.index << 16) | (1 << 21) | (op << 22) | (size << 30));
|
||||
|
@ -396,28 +463,50 @@ void AssemblyBuilderA64::placeA(const char* name, RegisterA64 dst, AddressA64 sr
|
|||
|
||||
void AssemblyBuilderA64::placeBC(const char* name, Label& label, uint8_t op, uint8_t cond)
|
||||
{
|
||||
placeLabel(label);
|
||||
place(cond | (op << 24));
|
||||
commit();
|
||||
|
||||
patchLabel(label);
|
||||
|
||||
if (logText)
|
||||
log(name, label);
|
||||
|
||||
place(cond | (op << 24));
|
||||
commit();
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::placeBR(const char* name, Label& label, uint8_t op, RegisterA64 cond)
|
||||
void AssemblyBuilderA64::placeBCR(const char* name, Label& label, uint8_t op, RegisterA64 cond)
|
||||
{
|
||||
placeLabel(label);
|
||||
|
||||
if (logText)
|
||||
log(name, cond, label);
|
||||
|
||||
LUAU_ASSERT(cond.kind == KindA64::w || cond.kind == KindA64::x);
|
||||
|
||||
uint32_t sf = (cond.kind == KindA64::x) ? 0x80000000 : 0;
|
||||
|
||||
place(cond.index | (op << 24) | sf);
|
||||
commit();
|
||||
|
||||
patchLabel(label);
|
||||
|
||||
if (logText)
|
||||
log(name, cond, label);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::placeBR(const char* name, RegisterA64 src, uint32_t op)
|
||||
{
|
||||
if (logText)
|
||||
log(name, src);
|
||||
|
||||
LUAU_ASSERT(src.kind == KindA64::x);
|
||||
|
||||
place((src.index << 5) | (op << 10));
|
||||
commit();
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::placeADR(const char* name, RegisterA64 dst, uint8_t op)
|
||||
{
|
||||
if (logText)
|
||||
log(name, dst);
|
||||
|
||||
LUAU_ASSERT(dst.kind == KindA64::x);
|
||||
|
||||
place(dst.index | (op << 24));
|
||||
commit();
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::place(uint32_t word)
|
||||
|
@ -426,8 +515,10 @@ void AssemblyBuilderA64::place(uint32_t word)
|
|||
*codePos++ = word;
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::placeLabel(Label& label)
|
||||
void AssemblyBuilderA64::patchLabel(Label& label)
|
||||
{
|
||||
uint32_t location = getCodeSize() - 1;
|
||||
|
||||
if (label.location == ~0u)
|
||||
{
|
||||
if (label.id == 0)
|
||||
|
@ -436,18 +527,26 @@ void AssemblyBuilderA64::placeLabel(Label& label)
|
|||
labelLocations.push_back(~0u);
|
||||
}
|
||||
|
||||
pendingLabels.push_back({label.id, getCodeSize()});
|
||||
pendingLabels.push_back({label.id, location});
|
||||
}
|
||||
else
|
||||
{
|
||||
// note: if label has an assigned location we can in theory avoid patching it later, but
|
||||
// we need to handle potential overflow of 19-bit offsets
|
||||
LUAU_ASSERT(label.id != 0);
|
||||
labelLocations[label.id - 1] = label.location;
|
||||
pendingLabels.push_back({label.id, getCodeSize()});
|
||||
int value = int(label.location) - int(location);
|
||||
|
||||
patchImm19(location, value);
|
||||
}
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::patchImm19(uint32_t location, int value)
|
||||
{
|
||||
// imm19 encoding word offset, at bit offset 5
|
||||
// note that 18 bits of word offsets = 20 bits of byte offsets = +-1MB
|
||||
if (value > -(1 << 18) && value < (1 << 18))
|
||||
code[location] |= (value & ((1 << 19) - 1)) << 5;
|
||||
else
|
||||
overflowed = true;
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::commit()
|
||||
{
|
||||
LUAU_ASSERT(codePos <= codeEnd);
|
||||
|
@ -491,8 +590,11 @@ void AssemblyBuilderA64::log(const char* opcode)
|
|||
void AssemblyBuilderA64::log(const char* opcode, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, int shift)
|
||||
{
|
||||
logAppend(" %-12s", opcode);
|
||||
log(dst);
|
||||
text.append(",");
|
||||
if (dst != xzr && dst != wzr)
|
||||
{
|
||||
log(dst);
|
||||
text.append(",");
|
||||
}
|
||||
log(src1);
|
||||
text.append(",");
|
||||
log(src2);
|
||||
|
@ -504,8 +606,11 @@ void AssemblyBuilderA64::log(const char* opcode, RegisterA64 dst, RegisterA64 sr
|
|||
void AssemblyBuilderA64::log(const char* opcode, RegisterA64 dst, RegisterA64 src1, int src2)
|
||||
{
|
||||
logAppend(" %-12s", opcode);
|
||||
log(dst);
|
||||
text.append(",");
|
||||
if (dst != xzr && dst != wzr)
|
||||
{
|
||||
log(dst);
|
||||
text.append(",");
|
||||
}
|
||||
log(src1);
|
||||
text.append(",");
|
||||
logAppend("#%d", src2);
|
||||
|
@ -549,6 +654,13 @@ void AssemblyBuilderA64::log(const char* opcode, RegisterA64 src, Label label)
|
|||
logAppend(".L%d\n", label.id);
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::log(const char* opcode, RegisterA64 src)
|
||||
{
|
||||
logAppend(" %-12s", opcode);
|
||||
log(src);
|
||||
text.append("\n");
|
||||
}
|
||||
|
||||
void AssemblyBuilderA64::log(const char* opcode, Label label)
|
||||
{
|
||||
logAppend(" %-12s.L%d\n", opcode, label.id);
|
||||
|
@ -565,20 +677,24 @@ void AssemblyBuilderA64::log(RegisterA64 reg)
|
|||
{
|
||||
case KindA64::w:
|
||||
if (reg.index == 31)
|
||||
logAppend("wzr");
|
||||
text.append("wzr");
|
||||
else
|
||||
logAppend("w%d", reg.index);
|
||||
break;
|
||||
|
||||
case KindA64::x:
|
||||
if (reg.index == 31)
|
||||
logAppend("xzr");
|
||||
text.append("xzr");
|
||||
else
|
||||
logAppend("x%d", reg.index);
|
||||
break;
|
||||
|
||||
case KindA64::none:
|
||||
LUAU_ASSERT(!"Unexpected register kind");
|
||||
if (reg.index == 31)
|
||||
text.append("sp");
|
||||
else
|
||||
LUAU_ASSERT(!"Unexpected register kind");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,15 +14,15 @@
|
|||
* Each line is 8 bytes, stack grows downwards.
|
||||
*
|
||||
* | ... previous frames ...
|
||||
* | rdx home space | (saved only on windows)
|
||||
* | rcx home space | (saved only on windows)
|
||||
* | rdx home space | (unused)
|
||||
* | rcx home space | (unused)
|
||||
* | return address |
|
||||
* | ... saved non-volatile registers ...
|
||||
* | ... saved non-volatile registers ... <-- rsp + kStackSize + kLocalsSize
|
||||
* | unused | for 16 byte alignment of the stack
|
||||
* | sCode |
|
||||
* | sClosure | <-- rbp points here
|
||||
* | argument 6 |
|
||||
* | argument 5 |
|
||||
* | sClosure | <-- rsp + kStackSize
|
||||
* | argument 6 | <-- rsp + 40
|
||||
* | argument 5 | <-- rsp + 32
|
||||
* | r9 home space |
|
||||
* | r8 home space |
|
||||
* | rdx home space |
|
||||
|
@ -48,28 +48,18 @@ bool initEntryFunction(NativeState& data)
|
|||
|
||||
unwind.start();
|
||||
|
||||
if (build.abi == ABIX64::Windows)
|
||||
{
|
||||
// Place arguments in home space
|
||||
build.mov(qword[rsp + 16], rArg2);
|
||||
unwind.spill(16, rArg2);
|
||||
build.mov(qword[rsp + 8], rArg1);
|
||||
unwind.spill(8, rArg1);
|
||||
|
||||
// Save non-volatile registers that are specific to Windows x64 ABI
|
||||
build.push(rdi);
|
||||
unwind.save(rdi);
|
||||
build.push(rsi);
|
||||
unwind.save(rsi);
|
||||
|
||||
// Once we start using non-volatile SIMD registers, we will save those here
|
||||
}
|
||||
|
||||
// Save common non-volatile registers
|
||||
build.push(rbx);
|
||||
unwind.save(rbx);
|
||||
build.push(rbp);
|
||||
unwind.save(rbp);
|
||||
|
||||
if (build.abi == ABIX64::SystemV)
|
||||
{
|
||||
build.mov(rbp, rsp);
|
||||
unwind.setupFrameReg(rbp, 0);
|
||||
}
|
||||
|
||||
build.push(rbx);
|
||||
unwind.save(rbx);
|
||||
build.push(r12);
|
||||
unwind.save(r12);
|
||||
build.push(r13);
|
||||
|
@ -79,16 +69,20 @@ bool initEntryFunction(NativeState& data)
|
|||
build.push(r15);
|
||||
unwind.save(r15);
|
||||
|
||||
int stacksize = 32 + 16; // 4 home locations for registers, 16 bytes for additional function call arguments
|
||||
int localssize = 24; // 3 local pointers that also correctly align the stack
|
||||
if (build.abi == ABIX64::Windows)
|
||||
{
|
||||
// Save non-volatile registers that are specific to Windows x64 ABI
|
||||
build.push(rdi);
|
||||
unwind.save(rdi);
|
||||
build.push(rsi);
|
||||
unwind.save(rsi);
|
||||
|
||||
// TODO: once we start using non-volatile SIMD registers on Windows, we will save those here
|
||||
}
|
||||
|
||||
// Allocate stack space (reg home area + local data)
|
||||
build.sub(rsp, stacksize + localssize);
|
||||
unwind.allocStack(stacksize + localssize);
|
||||
|
||||
// Setup frame pointer
|
||||
build.lea(rbp, addr[rsp + stacksize]);
|
||||
unwind.setupFrameReg(rbp, stacksize);
|
||||
build.sub(rsp, kStackSize + kLocalsSize);
|
||||
unwind.allocStack(kStackSize + kLocalsSize);
|
||||
|
||||
unwind.finish();
|
||||
|
||||
|
@ -113,13 +107,7 @@ bool initEntryFunction(NativeState& data)
|
|||
Label returnOff = build.setLabel();
|
||||
|
||||
// Cleanup and exit
|
||||
build.lea(rsp, addr[rbp + localssize]);
|
||||
build.pop(r15);
|
||||
build.pop(r14);
|
||||
build.pop(r13);
|
||||
build.pop(r12);
|
||||
build.pop(rbp);
|
||||
build.pop(rbx);
|
||||
build.add(rsp, kStackSize + kLocalsSize);
|
||||
|
||||
if (build.abi == ABIX64::Windows)
|
||||
{
|
||||
|
@ -127,6 +115,12 @@ bool initEntryFunction(NativeState& data)
|
|||
build.pop(rdi);
|
||||
}
|
||||
|
||||
build.pop(r15);
|
||||
build.pop(r14);
|
||||
build.pop(r13);
|
||||
build.pop(r12);
|
||||
build.pop(rbx);
|
||||
build.pop(rbp);
|
||||
build.ret();
|
||||
|
||||
build.finalize();
|
||||
|
|
|
@ -32,8 +32,12 @@ constexpr RegisterX64 rNativeContext = r13; // NativeContext* context
|
|||
constexpr RegisterX64 rConstants = r12; // TValue* k
|
||||
|
||||
// Native code is as stackless as the interpreter, so we can place some data on the stack once and have it accessible at any point
|
||||
constexpr OperandX64 sClosure = qword[rbp + 0]; // Closure* cl
|
||||
constexpr OperandX64 sCode = qword[rbp + 8]; // Instruction* code
|
||||
// See CodeGenX64.cpp for layout
|
||||
constexpr unsigned kStackSize = 32 + 16; // 4 home locations for registers, 16 bytes for additional function call arguments
|
||||
constexpr unsigned kLocalsSize = 24; // 3 extra slots for our custom locals (also aligns the stack to 16 byte boundary)
|
||||
|
||||
constexpr OperandX64 sClosure = qword[rsp + kStackSize + 0]; // Closure* cl
|
||||
constexpr OperandX64 sCode = qword[rsp + kStackSize + 8]; // Instruction* code
|
||||
|
||||
// TODO: These should be replaced with a portable call function that checks the ABI at runtime and reorders moves accordingly to avoid conflicts
|
||||
#if defined(_WIN32)
|
||||
|
|
|
@ -13,11 +13,10 @@
|
|||
// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf [System V Application Binary Interface (AMD64 Architecture Processor Supplement)]
|
||||
// Interaction between Dwarf2 and System V ABI can be found in sections '3.6.2 DWARF Register Number Mapping' and '4.2.4 EH_FRAME sections'
|
||||
|
||||
// Call frame instruction opcodes
|
||||
// Call frame instruction opcodes (Dwarf2, page 78, ch. 7.23 figure 37)
|
||||
#define DW_CFA_advance_loc 0x40
|
||||
#define DW_CFA_offset 0x80
|
||||
#define DW_CFA_restore 0xc0
|
||||
#define DW_CFA_nop 0x00
|
||||
#define DW_CFA_set_loc 0x01
|
||||
#define DW_CFA_advance_loc1 0x02
|
||||
#define DW_CFA_advance_loc2 0x03
|
||||
|
@ -33,17 +32,11 @@
|
|||
#define DW_CFA_def_cfa_register 0x0d
|
||||
#define DW_CFA_def_cfa_offset 0x0e
|
||||
#define DW_CFA_def_cfa_expression 0x0f
|
||||
#define DW_CFA_expression 0x10
|
||||
#define DW_CFA_offset_extended_sf 0x11
|
||||
#define DW_CFA_def_cfa_sf 0x12
|
||||
#define DW_CFA_def_cfa_offset_sf 0x13
|
||||
#define DW_CFA_val_offset 0x14
|
||||
#define DW_CFA_val_offset_sf 0x15
|
||||
#define DW_CFA_val_expression 0x16
|
||||
#define DW_CFA_nop 0x00
|
||||
#define DW_CFA_lo_user 0x1c
|
||||
#define DW_CFA_hi_user 0x3f
|
||||
|
||||
// Register numbers for x64
|
||||
// Register numbers for x64 (System V ABI, page 57, ch. 3.7, figure 3.36)
|
||||
#define DW_REG_RAX 0
|
||||
#define DW_REG_RDX 1
|
||||
#define DW_REG_RCX 2
|
||||
|
@ -197,7 +190,12 @@ void UnwindBuilderDwarf2::allocStack(int size)
|
|||
|
||||
void UnwindBuilderDwarf2::setupFrameReg(RegisterX64 reg, int espOffset)
|
||||
{
|
||||
// Not required for unwinding
|
||||
if (espOffset != 0)
|
||||
pos = advanceLocation(pos, 5); // REX.W lea rbp, [rsp + imm8]
|
||||
else
|
||||
pos = advanceLocation(pos, 3); // REX.W mov rbp, rsp
|
||||
|
||||
// Cfa is based on rsp, so no additonal commands are required
|
||||
}
|
||||
|
||||
void UnwindBuilderDwarf2::finish()
|
||||
|
|
|
@ -77,7 +77,11 @@ void UnwindBuilderWin::setupFrameReg(RegisterX64 reg, int espOffset)
|
|||
frameReg = reg;
|
||||
frameRegOffset = uint8_t(espOffset / 16);
|
||||
|
||||
prologSize += 5; // REX.W lea rbp, [rsp + imm8]
|
||||
if (espOffset != 0)
|
||||
prologSize += 5; // REX.W lea rbp, [rsp + imm8]
|
||||
else
|
||||
prologSize += 3; // REX.W mov rbp, rsp
|
||||
|
||||
unwindCodes.push_back({prologSize, UWOP_SET_FPREG, frameRegOffset});
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ inline bool isFlagExperimental(const char* flag)
|
|||
static const char* kList[] = {
|
||||
"LuauInterpolatedStringBaseSupport",
|
||||
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||
"LuauOptionalNextKey", // waiting for a fix to land in lua-apps
|
||||
"LuauTryhardAnd", // waiting for a fix in graphql-lua -> apollo-client-lia -> lua-apps
|
||||
// makes sure we always have at least one entry
|
||||
nullptr,
|
||||
};
|
||||
|
|
|
@ -69,6 +69,7 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "Unary")
|
|||
{
|
||||
SINGLE_COMPARE(neg(x0, x1), 0xCB0103E0);
|
||||
SINGLE_COMPARE(neg(w0, w1), 0x4B0103E0);
|
||||
SINGLE_COMPARE(mvn(x0, x1), 0xAA2103E0);
|
||||
|
||||
SINGLE_COMPARE(clz(x0, x1), 0xDAC01020);
|
||||
SINGLE_COMPARE(clz(w0, w1), 0x5AC01020);
|
||||
|
@ -91,19 +92,22 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "Binary")
|
|||
SINGLE_COMPARE(lsr(x0, x1, x2), 0x9AC22420);
|
||||
SINGLE_COMPARE(asr(x0, x1, x2), 0x9AC22820);
|
||||
SINGLE_COMPARE(ror(x0, x1, x2), 0x9AC22C20);
|
||||
SINGLE_COMPARE(cmp(x0, x1), 0xEB01001F);
|
||||
|
||||
// reg, imm
|
||||
SINGLE_COMPARE(add(x3, x7, 78), 0x910138E3);
|
||||
SINGLE_COMPARE(add(w3, w7, 78), 0x110138E3);
|
||||
SINGLE_COMPARE(sub(w3, w7, 78), 0x510138E3);
|
||||
SINGLE_COMPARE(cmp(w0, 42), 0x7100A81F);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "Loads")
|
||||
{
|
||||
// address forms
|
||||
SINGLE_COMPARE(ldr(x0, x1), 0xF9400020);
|
||||
SINGLE_COMPARE(ldr(x0, AddressA64(x1, 8)), 0xF9400420);
|
||||
SINGLE_COMPARE(ldr(x0, AddressA64(x1, x7)), 0xF8676820);
|
||||
SINGLE_COMPARE(ldr(x0, mem(x1, 8)), 0xF9400420);
|
||||
SINGLE_COMPARE(ldr(x0, mem(x1, x7)), 0xF8676820);
|
||||
SINGLE_COMPARE(ldr(x0, mem(x1, -7)), 0xF85F9020);
|
||||
|
||||
// load sizes
|
||||
SINGLE_COMPARE(ldr(x0, x1), 0xF9400020);
|
||||
|
@ -121,8 +125,9 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "Stores")
|
|||
{
|
||||
// address forms
|
||||
SINGLE_COMPARE(str(x0, x1), 0xF9000020);
|
||||
SINGLE_COMPARE(str(x0, AddressA64(x1, 8)), 0xF9000420);
|
||||
SINGLE_COMPARE(str(x0, AddressA64(x1, x7)), 0xF8276820);
|
||||
SINGLE_COMPARE(str(x0, mem(x1, 8)), 0xF9000420);
|
||||
SINGLE_COMPARE(str(x0, mem(x1, x7)), 0xF8276820);
|
||||
SINGLE_COMPARE(strh(w0, mem(x1, -7)), 0x781F9020);
|
||||
|
||||
// store sizes
|
||||
SINGLE_COMPARE(str(x0, x1), 0xF9000020);
|
||||
|
@ -169,26 +174,69 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "ControlFlow")
|
|||
build.cbz(x0, skip);
|
||||
build.cbnz(x0, skip);
|
||||
build.setLabel(skip);
|
||||
build.b(skip);
|
||||
},
|
||||
{0x54000060, 0xB4000040, 0xB5000020}));
|
||||
{0x54000060, 0xB4000040, 0xB5000020, 0x5400000E}));
|
||||
|
||||
// Basic control flow
|
||||
SINGLE_COMPARE(br(x0), 0xD61F0000);
|
||||
SINGLE_COMPARE(blr(x0), 0xD63F0000);
|
||||
SINGLE_COMPARE(ret(), 0xD65F03C0);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "StackOps")
|
||||
{
|
||||
SINGLE_COMPARE(mov(x0, sp), 0x910003E0);
|
||||
SINGLE_COMPARE(mov(sp, x0), 0x9100001F);
|
||||
|
||||
SINGLE_COMPARE(add(sp, sp, 4), 0x910013FF);
|
||||
SINGLE_COMPARE(sub(sp, sp, 4), 0xD10013FF);
|
||||
|
||||
SINGLE_COMPARE(add(x0, sp, 4), 0x910013E0);
|
||||
SINGLE_COMPARE(sub(sp, x0, 4), 0xD100101F);
|
||||
|
||||
SINGLE_COMPARE(ldr(x0, mem(sp, 8)), 0xF94007E0);
|
||||
SINGLE_COMPARE(str(x0, mem(sp, 8)), 0xF90007E0);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "Constants")
|
||||
{
|
||||
// clang-format off
|
||||
CHECK(check(
|
||||
[](AssemblyBuilderA64& build) {
|
||||
char arr[12] = "hello world";
|
||||
build.adr(x0, arr, 12);
|
||||
build.adr(x0, uint64_t(0x1234567887654321));
|
||||
build.adr(x0, 1.0);
|
||||
},
|
||||
{
|
||||
0x10ffffa0, 0x10ffff20, 0x10fffec0
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f,
|
||||
0x21, 0x43, 0x65, 0x87, 0x78, 0x56, 0x34, 0x12,
|
||||
0x00, 0x00, 0x00, 0x00, // 4b padding to align double
|
||||
'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0,
|
||||
}));
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
TEST_CASE("LogTest")
|
||||
{
|
||||
AssemblyBuilderA64 build(/* logText= */ true);
|
||||
|
||||
build.add(sp, sp, 4);
|
||||
build.add(w0, w1, w2);
|
||||
build.add(x0, x1, x2, 2);
|
||||
build.add(w7, w8, 5);
|
||||
build.add(x7, x8, 5);
|
||||
build.ldr(x7, x8);
|
||||
build.ldr(x7, AddressA64(x8, 8));
|
||||
build.ldr(x7, AddressA64(x8, x9));
|
||||
build.ldr(x7, mem(x8, 8));
|
||||
build.ldr(x7, mem(x8, x9));
|
||||
build.mov(x1, x2);
|
||||
build.movk(x1, 42, 16);
|
||||
build.cmp(x1, x2);
|
||||
build.blr(x0);
|
||||
|
||||
Label l;
|
||||
build.b(ConditionA64::Plus, l);
|
||||
|
@ -200,6 +248,7 @@ TEST_CASE("LogTest")
|
|||
build.finalize();
|
||||
|
||||
std::string expected = R"(
|
||||
add sp,sp,#4
|
||||
add w0,w1,w2
|
||||
add x0,x1,x2 LSL #2
|
||||
add w7,w8,#5
|
||||
|
@ -209,6 +258,8 @@ TEST_CASE("LogTest")
|
|||
ldr x7,[x8,x9]
|
||||
mov x1,x2
|
||||
movk x1,#42 LSL #16
|
||||
cmp x1,x2
|
||||
blr x0
|
||||
b.pl .L1
|
||||
cbz x7,.L1
|
||||
.L1:
|
||||
|
|
|
@ -185,7 +185,7 @@ TEST_CASE("Dwarf2UnwindCodesX64")
|
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x0e, 0x10, 0x85, 0x02, 0x02, 0x02, 0x0e, 0x18, 0x84, 0x03, 0x02, 0x02, 0x0e, 0x20, 0x83,
|
||||
0x04, 0x02, 0x02, 0x0e, 0x28, 0x86, 0x05, 0x02, 0x02, 0x0e, 0x30, 0x8c, 0x06, 0x02, 0x02, 0x0e, 0x38, 0x8d, 0x07, 0x02, 0x02, 0x0e, 0x40,
|
||||
0x8e, 0x08, 0x02, 0x02, 0x0e, 0x48, 0x8f, 0x09, 0x02, 0x04, 0x0e, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
0x8e, 0x08, 0x02, 0x02, 0x0e, 0x48, 0x8f, 0x09, 0x02, 0x04, 0x0e, 0x90, 0x01, 0x02, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
REQUIRE(data.size() == expected.size());
|
||||
CHECK(memcmp(data.data(), expected.data(), expected.size()) == 0);
|
||||
|
@ -446,7 +446,12 @@ TEST_CASE("GeneratedCodeExecutionA64")
|
|||
build.mov(x1, 0); // doesn't execute due to cbnz above
|
||||
build.setLabel(skip);
|
||||
|
||||
build.add(x1, x1, 1);
|
||||
uint8_t one = 1;
|
||||
build.adr(x2, &one, 1);
|
||||
build.ldrb(w2, x2);
|
||||
build.sub(x1, x1, x2);
|
||||
|
||||
build.add(x1, x1, 2);
|
||||
build.add(x0, x0, x1, /* LSL */ 1);
|
||||
build.ret();
|
||||
|
||||
|
|
|
@ -21,13 +21,13 @@ void ConstraintGraphBuilderFixture::generateConstraints(const std::string& code)
|
|||
frontend.getGlobalScope(), &logger, NotNull{dfg.get()});
|
||||
cgb->visit(root);
|
||||
rootScope = cgb->rootScope;
|
||||
constraints = Luau::collectConstraints(NotNull{cgb->rootScope});
|
||||
constraints = Luau::borrowConstraints(cgb->constraints);
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilderFixture::solve(const std::string& code)
|
||||
{
|
||||
generateConstraints(code);
|
||||
ConstraintSolver cs{NotNull{&normalizer}, NotNull{rootScope}, "MainModule", NotNull(&moduleResolver), {}, &logger};
|
||||
ConstraintSolver cs{NotNull{&normalizer}, NotNull{rootScope}, constraints, "MainModule", NotNull(&moduleResolver), {}, &logger};
|
||||
cs.run();
|
||||
}
|
||||
|
||||
|
|
|
@ -1054,10 +1054,6 @@ TEST_CASE("check_without_builtin_next")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "reexport_cyclic_type")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauForceExportSurfacesToBeNormal", true},
|
||||
};
|
||||
|
||||
fileResolver.source["Module/A"] = R"(
|
||||
type F<T> = (set: G<T>) -> ()
|
||||
|
||||
|
@ -1089,10 +1085,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "reexport_cyclic_type")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "reexport_type_alias")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauForceExportSurfacesToBeNormal", true},
|
||||
};
|
||||
|
||||
fileResolver.source["Module/A"] = R"(
|
||||
type KeyOfTestEvents = "test-file-start" | "test-file-success" | "test-file-failure" | "test-case-result"
|
||||
type MyAny = any
|
||||
|
|
|
@ -2823,4 +2823,21 @@ TEST_CASE_FIXTURE(Fixture, "get_a_nice_error_when_there_is_no_comma_after_last_t
|
|||
CHECK(table->items.size == 1);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "missing_default_type_pack_argument_after_variadic_type_parameter")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauParserErrorsOnMissingDefaultTypePackArgument", true};
|
||||
|
||||
ParseResult result = tryParse(R"(
|
||||
type Foo<T... = > = nil
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(2, result.errors.size());
|
||||
|
||||
CHECK_EQ(Location{{1, 23}, {1, 25}}, result.errors[0].getLocation());
|
||||
CHECK_EQ("Expected type, got '>'", result.errors[0].getMessage());
|
||||
|
||||
CHECK_EQ(Location{{1, 23}, {1, 24}}, result.errors[1].getLocation());
|
||||
CHECK_EQ("Expected type pack after '=', got type", result.errors[1].getMessage());
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -404,3 +404,20 @@ t60 = makeChainedTable(60)
|
|||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
TEST_SUITE_BEGIN("RegressionTests");
|
||||
|
||||
TEST_CASE_FIXTURE(ReplFixture, "InfiniteRecursion")
|
||||
{
|
||||
// If the infinite recrusion is not caught, test will fail
|
||||
runCode(L, R"(
|
||||
local NewProxyOne = newproxy(true)
|
||||
local MetaTableOne = getmetatable(NewProxyOne)
|
||||
MetaTableOne.__index = function()
|
||||
return NewProxyOne.Game
|
||||
end
|
||||
print(NewProxyOne.HelloICauseACrash)
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction);
|
||||
LUAU_FASTFLAG(LuauFixNameMaps);
|
||||
LUAU_FASTFLAG(LuauFunctionReturnStringificationFixup);
|
||||
|
||||
TEST_SUITE_BEGIN("ToString");
|
||||
|
@ -266,11 +265,23 @@ TEST_CASE_FIXTURE(Fixture, "quit_stringifying_type_when_length_is_exceeded")
|
|||
|
||||
ToStringOptions o;
|
||||
o.exhaustive = false;
|
||||
o.maxTypeLength = 40;
|
||||
CHECK_EQ(toString(requireType("f0"), o), "() -> ()");
|
||||
CHECK_EQ(toString(requireType("f1"), o), "(() -> ()) -> () -> ()");
|
||||
CHECK_EQ(toString(requireType("f2"), o), "((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED*");
|
||||
CHECK_EQ(toString(requireType("f3"), o), "(((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED*");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
o.maxTypeLength = 30;
|
||||
CHECK_EQ(toString(requireType("f0"), o), "() -> ()");
|
||||
CHECK_EQ(toString(requireType("f1"), o), "<a>(a) -> () -> ()");
|
||||
CHECK_EQ(toString(requireType("f2"), o), "<b>(b) -> <a>(a) -> () -> ()");
|
||||
CHECK_EQ(toString(requireType("f3"), o), "<c>(c) -> <b>(b) -> <a>(a) -> (... *TRUNCATED*");
|
||||
}
|
||||
else
|
||||
{
|
||||
o.maxTypeLength = 40;
|
||||
CHECK_EQ(toString(requireType("f0"), o), "() -> ()");
|
||||
CHECK_EQ(toString(requireType("f1"), o), "(() -> ()) -> () -> ()");
|
||||
CHECK_EQ(toString(requireType("f2"), o), "((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED*");
|
||||
CHECK_EQ(toString(requireType("f3"), o), "(((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED*");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "stringifying_type_is_still_capped_when_exhaustive")
|
||||
|
@ -285,11 +296,22 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_type_is_still_capped_when_exhaustive")
|
|||
|
||||
ToStringOptions o;
|
||||
o.exhaustive = true;
|
||||
o.maxTypeLength = 40;
|
||||
CHECK_EQ(toString(requireType("f0"), o), "() -> ()");
|
||||
CHECK_EQ(toString(requireType("f1"), o), "(() -> ()) -> () -> ()");
|
||||
CHECK_EQ(toString(requireType("f2"), o), "((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED*");
|
||||
CHECK_EQ(toString(requireType("f3"), o), "(((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED*");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
o.maxTypeLength = 30;
|
||||
CHECK_EQ(toString(requireType("f0"), o), "() -> ()");
|
||||
CHECK_EQ(toString(requireType("f1"), o), "<a>(a) -> () -> ()");
|
||||
CHECK_EQ(toString(requireType("f2"), o), "<b>(b) -> <a>(a) -> () -> ()");
|
||||
CHECK_EQ(toString(requireType("f3"), o), "<c>(c) -> <b>(b) -> <a>(a) -> (... *TRUNCATED*");
|
||||
}
|
||||
else
|
||||
{
|
||||
o.maxTypeLength = 40;
|
||||
CHECK_EQ(toString(requireType("f0"), o), "() -> ()");
|
||||
CHECK_EQ(toString(requireType("f1"), o), "(() -> ()) -> () -> ()");
|
||||
CHECK_EQ(toString(requireType("f2"), o), "((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED*");
|
||||
CHECK_EQ(toString(requireType("f3"), o), "(((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED*");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "stringifying_table_type_correctly_use_matching_table_state_braces")
|
||||
|
@ -423,28 +445,19 @@ TEST_CASE_FIXTURE(Fixture, "toStringDetailed")
|
|||
TypeId id3Type = requireType("id3");
|
||||
ToStringResult nameData = toStringDetailed(id3Type, opts);
|
||||
|
||||
if (FFlag::LuauFixNameMaps)
|
||||
REQUIRE(3 == opts.nameMap.typeVars.size());
|
||||
else
|
||||
REQUIRE_EQ(3, nameData.DEPRECATED_nameMap.typeVars.size());
|
||||
REQUIRE(3 == opts.nameMap.typeVars.size());
|
||||
|
||||
REQUIRE_EQ("<a, b, c>(a, b, c) -> (a, b, c)", nameData.name);
|
||||
|
||||
ToStringOptions opts2; // TODO: delete opts2 when clipping FFlag::LuauFixNameMaps
|
||||
if (FFlag::LuauFixNameMaps)
|
||||
opts2.nameMap = std::move(opts.nameMap);
|
||||
else
|
||||
opts2.DEPRECATED_nameMap = std::move(nameData.DEPRECATED_nameMap);
|
||||
|
||||
const FunctionTypeVar* ftv = get<FunctionTypeVar>(follow(id3Type));
|
||||
REQUIRE(ftv != nullptr);
|
||||
|
||||
auto params = flatten(ftv->argTypes).first;
|
||||
REQUIRE(3 == params.size());
|
||||
|
||||
CHECK("a" == toString(params[0], opts2));
|
||||
CHECK("b" == toString(params[1], opts2));
|
||||
CHECK("c" == toString(params[2], opts2));
|
||||
CHECK("a" == toString(params[0], opts));
|
||||
CHECK("b" == toString(params[1], opts));
|
||||
CHECK("c" == toString(params[2], opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "toStringDetailed2")
|
||||
|
@ -471,13 +484,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "toStringDetailed2")
|
|||
TypeId tType = requireType("inst");
|
||||
ToStringResult r = toStringDetailed(tType, opts);
|
||||
CHECK_EQ("{ @metatable { __index: { @metatable {| __index: base |}, child } }, inst }", r.name);
|
||||
if (FFlag::LuauFixNameMaps)
|
||||
CHECK(0 == opts.nameMap.typeVars.size());
|
||||
else
|
||||
CHECK_EQ(0, r.DEPRECATED_nameMap.typeVars.size());
|
||||
|
||||
if (!FFlag::LuauFixNameMaps)
|
||||
opts.DEPRECATED_nameMap = r.DEPRECATED_nameMap;
|
||||
CHECK(0 == opts.nameMap.typeVars.size());
|
||||
|
||||
const MetatableTypeVar* tMeta = get<MetatableTypeVar>(tType);
|
||||
REQUIRE(tMeta);
|
||||
|
@ -502,8 +509,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "toStringDetailed2")
|
|||
REQUIRE(tMeta6->props.count("two") > 0);
|
||||
|
||||
ToStringResult oneResult = toStringDetailed(tMeta5->props["one"].type, opts);
|
||||
if (!FFlag::LuauFixNameMaps)
|
||||
opts.DEPRECATED_nameMap = oneResult.DEPRECATED_nameMap;
|
||||
|
||||
std::string twoResult = toString(tMeta6->props["two"].type, opts);
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauNoMoreGlobalSingletonTypes)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeAliases");
|
||||
|
||||
|
@ -509,11 +510,21 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "general_require_multi_assign")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_import_mutation")
|
||||
{
|
||||
ScopedFastFlag luauNewLibraryTypeNames{"LuauNewLibraryTypeNames", true};
|
||||
|
||||
CheckResult result = check("type t10<x> = typeof(table)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
TypeId ty = getGlobalBinding(frontend, "table");
|
||||
CHECK_EQ(toString(ty), "table");
|
||||
|
||||
if (FFlag::LuauNoMoreGlobalSingletonTypes)
|
||||
{
|
||||
CHECK_EQ(toString(ty), "typeof(table)");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(ty), "table");
|
||||
}
|
||||
|
||||
const TableTypeVar* ttv = get<TableTypeVar>(ty);
|
||||
REQUIRE(ttv);
|
||||
|
|
|
@ -57,7 +57,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "next_iterator_should_infer_types_and_type_ch
|
|||
|
||||
local s = "foo"
|
||||
local t = { [s] = 1 }
|
||||
local c: string, d: number = next(t)
|
||||
local c: string?, d: number = next(t)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
@ -69,7 +69,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "pairs_iterator_should_infer_types_and_type_c
|
|||
type Map<K, V> = { [K]: V }
|
||||
local map: Map<string, number> = { ["foo"] = 1, ["bar"] = 2, ["baz"] = 3 }
|
||||
|
||||
local it: (Map<string, number>, string | nil) -> (string, number), t: Map<string, number>, i: nil = pairs(map)
|
||||
local it: (Map<string, number>, string | nil) -> (string?, number), t: Map<string, number>, i: nil = pairs(map)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
@ -81,7 +81,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "ipairs_iterator_should_infer_types_and_type_
|
|||
type Map<K, V> = { [K]: V }
|
||||
local array: Map<number, string> = { "foo", "bar", "baz" }
|
||||
|
||||
local it: (Map<number, string>, number) -> (number, string), t: Map<number, string>, i: number = ipairs(array)
|
||||
local it: (Map<number, string>, number) -> (number?, string), t: Map<number, string>, i: number = ipairs(array)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
|
|
@ -532,7 +532,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_higher_order_function")
|
|||
REQUIRE_EQ(2, argVec.size());
|
||||
|
||||
const FunctionTypeVar* fType = get<FunctionTypeVar>(follow(argVec[0]));
|
||||
REQUIRE(fType != nullptr);
|
||||
REQUIRE_MESSAGE(fType != nullptr, "Expected a function but got " << toString(argVec[0]));
|
||||
|
||||
std::vector<TypeId> fArgs = flatten(fType->argTypes).first;
|
||||
|
||||
|
|
|
@ -50,14 +50,18 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union")
|
|||
CHECK_EQ(*requireType("s"), *typeChecker.stringType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "and_adds_boolean")
|
||||
TEST_CASE_FIXTURE(Fixture, "and_does_not_always_add_boolean")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauTryhardAnd", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local s = "a" and 10
|
||||
local x:boolean|number = s
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(toString(*requireType("s")), "boolean | number");
|
||||
CHECK_EQ(toString(*requireType("s")), "number");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "and_adds_boolean_no_superfluous_union")
|
||||
|
@ -971,4 +975,79 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "mm_comparisons_must_return_a_boolean")
|
|||
CHECK(toString(result.errors[1]) == "Metamethod '__lt' must return type 'boolean'");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "reworked_and")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauTryhardAnd", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a: number? = 5
|
||||
local b: boolean = (a or 1) > 10
|
||||
local c -- free
|
||||
|
||||
local x = a and 1
|
||||
local y = 'a' and 1
|
||||
local z = b and 1
|
||||
local w = c and 1
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK("number?" == toString(requireType("x")));
|
||||
CHECK("number" == toString(requireType("y")));
|
||||
CHECK("false | number" == toString(requireType("z")));
|
||||
CHECK("number" == toString(requireType("w"))); // Normalizer considers free & falsy == never
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK("number?" == toString(requireType("x")));
|
||||
CHECK("number" == toString(requireType("y")));
|
||||
CHECK("boolean | number" == toString(requireType("z"))); // 'false' widened to boolean
|
||||
CHECK("(boolean | number)?" == toString(requireType("w")));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "reworked_or")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauTryhardAnd", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a: number | false = 5
|
||||
local b: number? = 6
|
||||
local c: boolean = true
|
||||
local d: true = true
|
||||
local e: false = false
|
||||
local f: nil = false
|
||||
|
||||
local a1 = a or 'a'
|
||||
local b1 = b or 4
|
||||
local c1 = c or 'c'
|
||||
local d1 = d or 'd'
|
||||
local e1 = e or 'e'
|
||||
local f1 = f or 'f'
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK("number | string" == toString(requireType("a1")));
|
||||
CHECK("number" == toString(requireType("b1")));
|
||||
CHECK("string | true" == toString(requireType("c1")));
|
||||
CHECK("string | true" == toString(requireType("d1")));
|
||||
CHECK("string" == toString(requireType("e1")));
|
||||
CHECK("string" == toString(requireType("f1")));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK("number | string" == toString(requireType("a1")));
|
||||
CHECK("number" == toString(requireType("b1")));
|
||||
CHECK("boolean | string" == toString(requireType("c1"))); // 'true' widened to boolean
|
||||
CHECK("boolean | string" == toString(requireType("d1"))); // 'true' widened to boolean
|
||||
CHECK("string" == toString(requireType("e1")));
|
||||
CHECK("string" == toString(requireType("f1")));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -461,23 +461,27 @@ TEST_CASE_FIXTURE(Fixture, "dcr_can_partially_dispatch_a_constraint")
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// Solving this requires recognizing that we can partially solve the
|
||||
// following constraint:
|
||||
// Solving this requires recognizing that we can't dispatch a constraint
|
||||
// like this without doing further work:
|
||||
//
|
||||
// (*blocked*) -> () <: (number) -> (b...)
|
||||
//
|
||||
// The correct thing for us to do is to consider the constraint dispatched,
|
||||
// but we need to also record a new constraint number <: *blocked* to finish
|
||||
// the job later.
|
||||
// We solve this by searching both types for BlockedTypeVars and block the
|
||||
// constraint on any we find. It also gets the job done, but I'm worried
|
||||
// about the efficiency of doing so many deep type traversals and it may
|
||||
// make us more prone to getting stuck on constraint cycles.
|
||||
//
|
||||
// If this doesn't pan out, a possible solution is to go further down the
|
||||
// path of supporting partial constraint dispatch. The way it would work is
|
||||
// that we'd dispatch the above constraint by binding b... to (), but we
|
||||
// would append a new constraint number <: *blocked* to the constraint set
|
||||
// to be solved later. This should be faster and theoretically less prone
|
||||
// to cyclic constraint dependencies.
|
||||
CHECK("<a>(a, number) -> ()" == toString(requireType("prime_iter")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauFixNameMaps", true},
|
||||
};
|
||||
|
||||
TypeArena arena;
|
||||
TypeId nilType = singletonTypes->nilType;
|
||||
|
||||
|
@ -522,8 +526,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_zero_iterators")
|
|||
// Ideally, we would not try to export a function type with generic types from incorrect scope
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "generic_type_leak_to_module_interface")
|
||||
{
|
||||
ScopedFastFlag LuauAnyifyModuleReturnGenerics{"LuauAnyifyModuleReturnGenerics", true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
local wrapStrictTable
|
||||
|
||||
|
@ -563,8 +565,6 @@ return wrapStrictTable(Constants, "Constants")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "generic_type_leak_to_module_interface_variadic")
|
||||
{
|
||||
ScopedFastFlag LuauAnyifyModuleReturnGenerics{"LuauAnyifyModuleReturnGenerics", true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
local wrapStrictTable
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ std::optional<WithPredicate<TypePackId>> magicFunctionInstanceIsA(
|
|||
return WithPredicate<TypePackId>{booleanPack, {IsAPredicate{std::move(*lvalue), expr.location, tfun->type}}};
|
||||
}
|
||||
|
||||
struct RefinementClassFixture : Fixture
|
||||
struct RefinementClassFixture : BuiltinsFixture
|
||||
{
|
||||
RefinementClassFixture()
|
||||
{
|
||||
|
@ -320,7 +320,7 @@ TEST_CASE_FIXTURE(Fixture, "type_assertion_expr_carry_its_constraints")
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "typeguard_in_if_condition_position")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_if_condition_position")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f(s: any)
|
||||
|
@ -332,7 +332,14 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_in_if_condition_position")
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({3, 26})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("any & number", toString(requireTypeAtPosition({3, 26})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({3, 26})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_assert_position")
|
||||
|
@ -344,10 +351,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_assert_position")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
REQUIRE_EQ("number", toString(requireType("b")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "call_a_more_specific_function_using_typeguard")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "call_an_incompatible_function_after_using_typeguard")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: number)
|
||||
|
@ -362,6 +370,7 @@ TEST_CASE_FIXTURE(Fixture, "call_a_more_specific_function_using_typeguard")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
|
@ -648,7 +657,7 @@ TEST_CASE_FIXTURE(Fixture, "narrow_property_of_a_bounded_variable")
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_narrow_to_vector")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_narrow_to_vector")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x)
|
||||
|
@ -663,7 +672,7 @@ TEST_CASE_FIXTURE(Fixture, "type_narrow_to_vector")
|
|||
CHECK_EQ("*error-type*", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "nonoptional_type_can_narrow_to_nil_if_sense_is_true")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_is_true")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local t = {"hello"}
|
||||
|
@ -690,7 +699,7 @@ TEST_CASE_FIXTURE(Fixture, "nonoptional_type_can_narrow_to_nil_if_sense_is_true"
|
|||
CHECK_EQ("string", toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil"
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "typeguard_not_to_be_string")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_not_to_be_string")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string | number | boolean)
|
||||
|
@ -704,11 +713,19 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_not_to_be_string")
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("boolean | number", toString(requireTypeAtPosition({3, 28}))); // type(x) ~= "string"
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) == "string"
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(boolean | number | string) & ~string", toString(requireTypeAtPosition({3, 28}))); // type(x) ~= "string"
|
||||
CHECK_EQ("(boolean | number | string) & string", toString(requireTypeAtPosition({5, 28}))); // type(x) == "string"
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("boolean | number", toString(requireTypeAtPosition({3, 28}))); // type(x) ~= "string"
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) == "string"
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "typeguard_narrows_for_table")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_table")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string | {x: number} | {y: boolean})
|
||||
|
@ -726,7 +743,7 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_narrows_for_table")
|
|||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "table"
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "typeguard_narrows_for_functions")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_functions")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function weird(x: string | ((number) -> string))
|
||||
|
@ -740,11 +757,19 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_narrows_for_functions")
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("(number) -> string", toString(requireTypeAtPosition({3, 28}))); // type(x) == "function"
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "function"
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(((number) -> string) | string) & function", toString(requireTypeAtPosition({3, 28}))); // type(x) == "function"
|
||||
CHECK_EQ("(((number) -> string) | string) & ~function", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "function"
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("(number) -> string", toString(requireTypeAtPosition({3, 28}))); // type(x) == "function"
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "function"
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_guard_can_filter_for_intersection_of_tables")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_can_filter_for_intersection_of_tables")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type XYCoord = {x: number} & {y: number}
|
||||
|
@ -763,7 +788,7 @@ TEST_CASE_FIXTURE(Fixture, "type_guard_can_filter_for_intersection_of_tables")
|
|||
CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_guard_can_filter_for_overloaded_function")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_can_filter_for_overloaded_function")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type SomeOverloadedFunction = ((number) -> string) & ((string) -> number)
|
||||
|
@ -778,8 +803,16 @@ TEST_CASE_FIXTURE(Fixture, "type_guard_can_filter_for_overloaded_function")
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("((number) -> string) & ((string) -> number)", toString(requireTypeAtPosition({4, 28})));
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("((((number) -> string) & ((string) -> number))?) & function", toString(requireTypeAtPosition({4, 28})));
|
||||
CHECK_EQ("((((number) -> string) & ((string) -> number))?) & ~function", toString(requireTypeAtPosition({6, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("((number) -> string) & ((string) -> number)", toString(requireTypeAtPosition({4, 28})));
|
||||
CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_narrowed_into_nothingness")
|
||||
|
@ -884,7 +917,7 @@ TEST_CASE_FIXTURE(Fixture, "not_a_and_not_b2")
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "either_number_or_string")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "either_number_or_string")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: any)
|
||||
|
@ -896,7 +929,14 @@ TEST_CASE_FIXTURE(Fixture, "either_number_or_string")
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("number | string", toString(requireTypeAtPosition({3, 28})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(number | string) & any", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("number | string", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "not_t_or_some_prop_of_t")
|
||||
|
@ -946,10 +986,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "merge_should_be_fully_agnostic_of_hashmap_or
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({6, 28})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(string | {| x: string |}) & string", toString(requireTypeAtPosition({6, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({6, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "refine_the_correct_types_opposite_of_when_a_is_not_number_or_string")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "refine_the_correct_types_opposite_of_when_a_is_not_number_or_string")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(a: string | number | boolean)
|
||||
|
@ -963,8 +1010,16 @@ TEST_CASE_FIXTURE(Fixture, "refine_the_correct_types_opposite_of_when_a_is_not_n
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("boolean", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("number | string", toString(requireTypeAtPosition({5, 28})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(boolean | number | string) & ~number & ~string", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("(boolean | number | string) & (number | string)", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("boolean", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("number | string", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "is_truthy_constraint_ifelse_expression")
|
||||
|
@ -995,7 +1050,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "invert_is_truthy_constraint_ifelse_expressio
|
|||
CHECK_EQ("string", toString(requireTypeAtPosition({2, 50})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_comparison_ifelse_expression")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_comparison_ifelse_expression")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function returnOne(x)
|
||||
|
@ -1027,7 +1082,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "correctly_lookup_a_shadowed_local_that_which
|
|||
CHECK_EQ("Type 'number' does not have key 'sub'", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "correctly_lookup_property_whose_base_was_previously_refined")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "correctly_lookup_property_whose_base_was_previously_refined")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type T = {x: string | number}
|
||||
|
@ -1246,8 +1301,16 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_instance_or_vector3_to
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("Vector3", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(Instance | Vector3) & Vector3", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("(Instance | Vector3) & ~Vector3", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Vector3", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "type_narrow_for_all_the_userdata")
|
||||
|
@ -1282,14 +1345,22 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "eliminate_subclasses_of_instance")
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("Folder | Part", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(Folder | Part | string) & Instance", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("(Folder | Part | string) & ~Instance", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Folder | Part", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "narrow_this_large_union")
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "narrow_from_subclasses_of_instance_or_string_or_vector3")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: Part | Folder | Instance | string | Vector3 | any)
|
||||
local function f(x: Part | Folder | string | Vector3)
|
||||
if typeof(x) == "Instance" then
|
||||
local foo = x
|
||||
else
|
||||
|
@ -1300,8 +1371,16 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "narrow_this_large_union")
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("Folder | Instance | Part", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Vector3 | any | string", toString(requireTypeAtPosition({5, 28})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(Folder | Part | Vector3 | string) & Instance", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("(Folder | Part | Vector3 | string) & ~Instance", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Folder | Part", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Vector3 | string", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "x_as_any_if_x_is_instance_elseif_x_is_table")
|
||||
|
@ -1342,7 +1421,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "x_is_not_instance_or_else_not_part")
|
|||
CHECK_EQ("Part", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "typeguard_doesnt_leak_to_elseif")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_doesnt_leak_to_elseif")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f(a)
|
||||
|
@ -1373,8 +1452,16 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknowns")
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("unknown", toString(requireTypeAtPosition({5, 28})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("unknown & string", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("unknown & ~string", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("unknown", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "falsiness_of_TruthyPredicate_narrows_into_nil")
|
||||
|
@ -1408,7 +1495,35 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "what_nonsensical_condition")
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({3, 28})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("a & number & string", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({3, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "else_with_no_explicit_expression_should_also_refine_the_tagged_union")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauImplicitElseRefinement", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Ok<T> = { tag: "ok", value: T }
|
||||
type Err<E> = { tag: "err", err: E }
|
||||
type Result<T, E> = Ok<T> | Err<E>
|
||||
|
||||
function and_then<T, U, E>(r: Result<T, E>, f: (T) -> U): Result<U, E>
|
||||
if r.tag == "ok" then
|
||||
return { tag = "ok", value = f(r.value) }
|
||||
else
|
||||
return r
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -17,6 +17,7 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauNoMoreGlobalSingletonTypes)
|
||||
|
||||
TEST_SUITE_BEGIN("TableTests");
|
||||
|
||||
|
@ -1721,6 +1722,8 @@ TEST_CASE_FIXTURE(Fixture, "hide_table_error_properties")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_table_names")
|
||||
{
|
||||
ScopedFastFlag luauNewLibraryTypeNames{"LuauNewLibraryTypeNames", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
os.h = 2
|
||||
string.k = 3
|
||||
|
@ -1728,19 +1731,36 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_table_names")
|
|||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
CHECK_EQ("Cannot add property 'h' to table 'os'", toString(result.errors[0]));
|
||||
CHECK_EQ("Cannot add property 'k' to table 'string'", toString(result.errors[1]));
|
||||
if (FFlag::LuauNoMoreGlobalSingletonTypes)
|
||||
{
|
||||
CHECK_EQ("Cannot add property 'h' to table 'typeof(os)'", toString(result.errors[0]));
|
||||
CHECK_EQ("Cannot add property 'k' to table 'typeof(string)'", toString(result.errors[1]));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Cannot add property 'h' to table 'os'", toString(result.errors[0]));
|
||||
CHECK_EQ("Cannot add property 'k' to table 'string'", toString(result.errors[1]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "persistent_sealed_table_is_immutable")
|
||||
{
|
||||
ScopedFastFlag luauNewLibraryTypeNames{"LuauNewLibraryTypeNames", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!nonstrict
|
||||
function os:bad() end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ("Cannot add property 'bad' to table 'os'", toString(result.errors[0]));
|
||||
if (FFlag::LuauNoMoreGlobalSingletonTypes)
|
||||
{
|
||||
CHECK_EQ("Cannot add property 'bad' to table 'typeof(os)'", toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Cannot add property 'bad' to table 'os'", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
const TableTypeVar* osType = get<TableTypeVar>(requireType("os"));
|
||||
REQUIRE(osType != nullptr);
|
||||
|
@ -3188,6 +3208,7 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_a_subtype_of_a_compatible_polymorphic_shap
|
|||
TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauScalarShapeSubtyping", true};
|
||||
ScopedFastFlag luauNewLibraryTypeNames{"LuauNewLibraryTypeNames", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(s)
|
||||
|
@ -3200,25 +3221,47 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
CHECK_EQ(R"(Type 'string' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
|
||||
if (FFlag::LuauNoMoreGlobalSingletonTypes)
|
||||
{
|
||||
CHECK_EQ(R"(Type 'string' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')",
|
||||
toString(result.errors[0]));
|
||||
CHECK_EQ(R"(Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')",
|
||||
toString(result.errors[1]));
|
||||
CHECK_EQ(R"(Type '"bar" | "baz"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
Not all union options are compatible. Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')",
|
||||
toString(result.errors[2]));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(R"(Type 'string' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements. Table type 'string' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')",
|
||||
toString(result.errors[0]));
|
||||
CHECK_EQ(R"(Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
toString(result.errors[0]));
|
||||
CHECK_EQ(R"(Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements. Table type 'string' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')",
|
||||
toString(result.errors[1]));
|
||||
CHECK_EQ(R"(Type '"bar" | "baz"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
toString(result.errors[1]));
|
||||
CHECK_EQ(R"(Type '"bar" | "baz"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
Not all union options are compatible. Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements. Table type 'string' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')",
|
||||
toString(result.errors[2]));
|
||||
toString(result.errors[2]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compatible")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauScalarShapeSubtyping", true};
|
||||
ScopedFastFlag luauScalarShapeUnifyToMtOwner{"LuauScalarShapeUnifyToMtOwner", true}; // Changes argument from table type to primitive
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(s): string
|
||||
|
@ -3234,6 +3277,7 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compati
|
|||
TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauScalarShapeSubtyping", true};
|
||||
ScopedFastFlag luauNewLibraryTypeNames{"LuauNewLibraryTypeNames", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(s): string
|
||||
|
@ -3243,11 +3287,42 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(R"(Type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' could not be converted into 'string'
|
||||
if (FFlag::LuauNoMoreGlobalSingletonTypes)
|
||||
{
|
||||
CHECK_EQ(R"(Type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' could not be converted into 'string'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements. Table type 'typeof(string)' not compatible with type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' because the former is missing field 'absolutely_no_scalar_has_this_method')",
|
||||
toString(result.errors[0]));
|
||||
CHECK_EQ("<a, b...>(t1) -> string where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}", toString(requireType("f")));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(R"(Type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' could not be converted into 'string'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements. Table type 'string' not compatible with type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' because the former is missing field 'absolutely_no_scalar_has_this_method')",
|
||||
toString(result.errors[0]));
|
||||
CHECK_EQ("<a, b...>(t1) -> string where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}", toString(requireType("f")));
|
||||
toString(result.errors[0]));
|
||||
CHECK_EQ("<a, b...>(t1) -> string where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}", toString(requireType("f")));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "a_free_shape_can_turn_into_a_scalar_directly")
|
||||
{
|
||||
ScopedFastFlag luauScalarShapeSubtyping{"LuauScalarShapeSubtyping", true};
|
||||
ScopedFastFlag luauScalarShapeUnifyToMtOwner{"LuauScalarShapeUnifyToMtOwner", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function stringByteList(str)
|
||||
local out = {}
|
||||
for i = 1, #str do
|
||||
table.insert(out, string.byte(str, i))
|
||||
end
|
||||
return table.concat(out, ",")
|
||||
end
|
||||
|
||||
local x = stringByteList("xoo")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_tables_in_call_is_unsound")
|
||||
|
|
|
@ -1125,43 +1125,6 @@ TEST_CASE_FIXTURE(Fixture, "bidirectional_checking_of_higher_order_function")
|
|||
CHECK(location.end.line == 4);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "dcr_can_partially_dispatch_a_constraint")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function hasDivisors(value: number)
|
||||
end
|
||||
|
||||
function prime_iter(state, index)
|
||||
hasDivisors(index)
|
||||
index += 1
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// Solving this requires recognizing that we can't dispatch a constraint
|
||||
// like this without doing further work:
|
||||
//
|
||||
// (*blocked*) -> () <: (number) -> (b...)
|
||||
//
|
||||
// We solve this by searching both types for BlockedTypeVars and block the
|
||||
// constraint on any we find. It also gets the job done, but I'm worried
|
||||
// about the efficiency of doing so many deep type traversals and it may
|
||||
// make us more prone to getting stuck on constraint cycles.
|
||||
//
|
||||
// If this doesn't pan out, a possible solution is to go further down the
|
||||
// path of supporting partial constraint dispatch. The way it would work is
|
||||
// that we'd dispatch the above constraint by binding b... to (), but we
|
||||
// would append a new constraint number <: *blocked* to the constraint set
|
||||
// to be solved later. This should be faster and theoretically less prone
|
||||
// to cyclic constraint dependencies.
|
||||
CHECK("<a>(a, number) -> ()" == toString(requireType("prime_iter")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "it_is_ok_to_have_inconsistent_number_of_return_values_in_nonstrict")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
|
|
@ -1002,8 +1002,6 @@ TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments_free")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_packs_with_tails_in_vararg_adjustment")
|
||||
{
|
||||
ScopedFastFlag luauFixVarargExprHeadType{"LuauFixVarargExprHeadType", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function wrapReject<TArg, TResult>(fn: (self: any, ...TArg) -> ...TResult): (self: any, ...TArg) -> ...TResult
|
||||
return function(self, ...)
|
||||
|
|
|
@ -395,6 +395,23 @@ local e = a.z
|
|||
CHECK_EQ("Type 'A | B | C | D' does not have key 'z'", toString(result.errors[3]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_iteration")
|
||||
{
|
||||
ScopedFastFlag luauNilIterator{"LuauNilIterator", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(values: {number}?)
|
||||
local s = 0
|
||||
for _, value in values do
|
||||
s += value
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ("Value of type '{number}?' could be nil", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unify_unsealed_table_union_check")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
|
|
@ -284,6 +284,7 @@ TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_i
|
|||
ScopedFastFlag sff[]{
|
||||
{"LuauUnknownAndNeverType", true},
|
||||
{"LuauNeverTypesAndOperatorsInference", true},
|
||||
{"LuauTryhardAnd", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -293,7 +294,8 @@ TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_i
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ("<a>(nil, a) -> boolean", toString(requireType("ord")));
|
||||
// Widening doesn't normalize yet, so the result is a bit strange
|
||||
CHECK_EQ("<a>(nil, a) -> boolean | boolean", toString(requireType("ord")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "math_operators_and_never")
|
||||
|
|
|
@ -32,10 +32,8 @@ AutocompleteTest.type_correct_expected_argument_type_suggestion_optional
|
|||
AutocompleteTest.type_correct_expected_argument_type_suggestion_self
|
||||
AutocompleteTest.type_correct_expected_return_type_pack_suggestion
|
||||
AutocompleteTest.type_correct_expected_return_type_suggestion
|
||||
AutocompleteTest.type_correct_full_type_suggestion
|
||||
AutocompleteTest.type_correct_function_no_parenthesis
|
||||
AutocompleteTest.type_correct_function_return_types
|
||||
AutocompleteTest.type_correct_function_type_suggestion
|
||||
AutocompleteTest.type_correct_keywords
|
||||
AutocompleteTest.type_correct_suggestion_for_overloads
|
||||
AutocompleteTest.type_correct_suggestion_in_argument
|
||||
|
@ -53,12 +51,10 @@ BuiltinTests.dont_add_definitions_to_persistent_types
|
|||
BuiltinTests.find_capture_types
|
||||
BuiltinTests.find_capture_types2
|
||||
BuiltinTests.find_capture_types3
|
||||
BuiltinTests.gmatch_capture_types
|
||||
BuiltinTests.gmatch_capture_types2
|
||||
BuiltinTests.gmatch_capture_types_balanced_escaped_parens
|
||||
BuiltinTests.gmatch_capture_types_default_capture
|
||||
BuiltinTests.gmatch_capture_types_parens_in_sets_are_ignored
|
||||
BuiltinTests.gmatch_capture_types_set_containing_lbracket
|
||||
BuiltinTests.gmatch_definition
|
||||
BuiltinTests.ipairs_iterator_should_infer_types_and_type_check
|
||||
BuiltinTests.match_capture_types
|
||||
|
@ -74,13 +70,12 @@ BuiltinTests.set_metatable_needs_arguments
|
|||
BuiltinTests.setmetatable_should_not_mutate_persisted_types
|
||||
BuiltinTests.sort_with_bad_predicate
|
||||
BuiltinTests.string_format_arg_count_mismatch
|
||||
BuiltinTests.string_format_arg_types_inference
|
||||
BuiltinTests.string_format_as_method
|
||||
BuiltinTests.string_format_correctly_ordered_types
|
||||
BuiltinTests.string_format_report_all_type_errors_at_correct_positions
|
||||
BuiltinTests.string_format_use_correct_argument
|
||||
BuiltinTests.string_format_use_correct_argument2
|
||||
BuiltinTests.string_format_use_correct_argument3
|
||||
BuiltinTests.strings_have_methods
|
||||
BuiltinTests.table_freeze_is_generic
|
||||
BuiltinTests.table_insert_correctly_infers_type_of_array_2_args_overload
|
||||
BuiltinTests.table_insert_correctly_infers_type_of_array_3_args_overload
|
||||
|
@ -114,7 +109,6 @@ GenericsTests.generic_factories
|
|||
GenericsTests.generic_functions_should_be_memory_safe
|
||||
GenericsTests.generic_table_method
|
||||
GenericsTests.generic_type_pack_parentheses
|
||||
GenericsTests.generic_type_pack_unification1
|
||||
GenericsTests.generic_type_pack_unification2
|
||||
GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments
|
||||
GenericsTests.infer_generic_function_function_argument
|
||||
|
@ -174,46 +168,36 @@ ProvisionalTests.while_body_are_also_refined
|
|||
RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string
|
||||
RefinementTest.assert_a_to_be_truthy_then_assert_a_to_be_number
|
||||
RefinementTest.assert_non_binary_expressions_actually_resolve_constraints
|
||||
RefinementTest.call_a_more_specific_function_using_typeguard
|
||||
RefinementTest.call_an_incompatible_function_after_using_typeguard
|
||||
RefinementTest.correctly_lookup_property_whose_base_was_previously_refined
|
||||
RefinementTest.correctly_lookup_property_whose_base_was_previously_refined2
|
||||
RefinementTest.discriminate_from_isa_of_x
|
||||
RefinementTest.discriminate_from_truthiness_of_x
|
||||
RefinementTest.discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false
|
||||
RefinementTest.discriminate_tag
|
||||
RefinementTest.either_number_or_string
|
||||
RefinementTest.eliminate_subclasses_of_instance
|
||||
RefinementTest.else_with_no_explicit_expression_should_also_refine_the_tagged_union
|
||||
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
|
||||
RefinementTest.index_on_a_refined_property
|
||||
RefinementTest.invert_is_truthy_constraint_ifelse_expression
|
||||
RefinementTest.is_truthy_constraint_ifelse_expression
|
||||
RefinementTest.merge_should_be_fully_agnostic_of_hashmap_ordering
|
||||
RefinementTest.narrow_property_of_a_bounded_variable
|
||||
RefinementTest.narrow_this_large_union
|
||||
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
|
||||
RefinementTest.not_t_or_some_prop_of_t
|
||||
RefinementTest.refine_a_property_not_to_be_nil_through_an_intersection_table
|
||||
RefinementTest.refine_the_correct_types_opposite_of_when_a_is_not_number_or_string
|
||||
RefinementTest.refine_unknowns
|
||||
RefinementTest.truthy_constraint_on_properties
|
||||
RefinementTest.type_comparison_ifelse_expression
|
||||
RefinementTest.type_guard_can_filter_for_intersection_of_tables
|
||||
RefinementTest.type_guard_can_filter_for_overloaded_function
|
||||
RefinementTest.type_guard_narrowed_into_nothingness
|
||||
RefinementTest.type_narrow_for_all_the_userdata
|
||||
RefinementTest.type_narrow_to_vector
|
||||
RefinementTest.typeguard_cast_free_table_to_vector
|
||||
RefinementTest.typeguard_cast_instance_or_vector3_to_vector
|
||||
RefinementTest.typeguard_doesnt_leak_to_elseif
|
||||
RefinementTest.typeguard_in_assert_position
|
||||
RefinementTest.typeguard_in_if_condition_position
|
||||
RefinementTest.typeguard_narrows_for_functions
|
||||
RefinementTest.typeguard_narrows_for_table
|
||||
RefinementTest.typeguard_not_to_be_string
|
||||
RefinementTest.what_nonsensical_condition
|
||||
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
|
||||
RefinementTest.x_is_not_instance_or_else_not_part
|
||||
RuntimeLimits.typescript_port_of_Result_type
|
||||
TableTests.a_free_shape_can_turn_into_a_scalar_directly
|
||||
TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible
|
||||
TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
|
||||
TableTests.access_index_metamethod_that_returns_variadic
|
||||
|
@ -249,7 +233,6 @@ TableTests.generic_table_instantiation_potential_regression
|
|||
TableTests.getmetatable_returns_pointer_to_metatable
|
||||
TableTests.give_up_after_one_metatable_index_look_up
|
||||
TableTests.hide_table_error_properties
|
||||
TableTests.indexer_fn
|
||||
TableTests.indexer_on_sealed_table_must_unify_with_free_table
|
||||
TableTests.indexing_from_a_table_should_prefer_properties_when_possible
|
||||
TableTests.inequality_operators_imply_exactly_matching_types
|
||||
|
@ -262,7 +245,6 @@ TableTests.invariant_table_properties_means_instantiating_tables_in_assignment_i
|
|||
TableTests.invariant_table_properties_means_instantiating_tables_in_call_is_unsound
|
||||
TableTests.leaking_bad_metatable_errors
|
||||
TableTests.less_exponential_blowup_please
|
||||
TableTests.meta_add
|
||||
TableTests.meta_add_both_ways
|
||||
TableTests.meta_add_inferred
|
||||
TableTests.metatable_mismatch_should_fail
|
||||
|
@ -389,7 +371,6 @@ TypeInferFunctions.improved_function_arg_mismatch_error_nonstrict
|
|||
TypeInferFunctions.improved_function_arg_mismatch_errors
|
||||
TypeInferFunctions.infer_anonymous_function_arguments
|
||||
TypeInferFunctions.infer_return_type_from_selected_overload
|
||||
TypeInferFunctions.infer_return_value_type
|
||||
TypeInferFunctions.infer_that_function_does_not_return_a_table
|
||||
TypeInferFunctions.list_all_overloads_if_no_overload_takes_given_argument_count
|
||||
TypeInferFunctions.list_only_alternative_overloads_that_match_argument_count
|
||||
|
@ -409,10 +390,12 @@ TypeInferFunctions.too_many_return_values
|
|||
TypeInferFunctions.too_many_return_values_in_parentheses
|
||||
TypeInferFunctions.too_many_return_values_no_function
|
||||
TypeInferFunctions.vararg_function_is_quantified
|
||||
TypeInferLoops.for_in_loop
|
||||
TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values
|
||||
TypeInferLoops.for_in_loop_with_next
|
||||
TypeInferLoops.for_in_with_generic_next
|
||||
TypeInferLoops.for_in_with_just_one_iterator_is_ok
|
||||
TypeInferLoops.loop_iter_metamethod_ok_with_inference
|
||||
TypeInferLoops.loop_iter_no_indexer_nonstrict
|
||||
TypeInferLoops.loop_iter_trailing_nil
|
||||
TypeInferLoops.unreachable_code_after_infinite_loop
|
||||
|
@ -430,8 +413,6 @@ TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon
|
|||
TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
|
||||
TypeInferOOP.methods_are_topologically_sorted
|
||||
TypeInferOOP.object_constructor_can_refer_to_method_of_self
|
||||
TypeInferOperators.and_or_ternary
|
||||
TypeInferOperators.CallAndOrOfFunctions
|
||||
TypeInferOperators.cannot_compare_tables_that_do_not_have_the_same_metatable
|
||||
TypeInferOperators.cannot_indirectly_compare_types_that_do_not_have_a_metatable
|
||||
TypeInferOperators.cannot_indirectly_compare_types_that_do_not_offer_overloaded_ordering_operators
|
||||
|
@ -517,6 +498,7 @@ UnionTypes.optional_assignment_errors
|
|||
UnionTypes.optional_call_error
|
||||
UnionTypes.optional_field_access_error
|
||||
UnionTypes.optional_index_error
|
||||
UnionTypes.optional_iteration
|
||||
UnionTypes.optional_length_error
|
||||
UnionTypes.optional_missing_key_error_details
|
||||
UnionTypes.optional_union_follow
|
||||
|
|
Loading…
Reference in a new issue