mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
Sync to upstream/release/607 (#1131)
# What's changed? * Fix up the `std::iterator_traits` definitions for some Luau data structures. * Replace some of the usages of `std::unordered_set` and `std::unordered_map` with Luau-provided data structures to increase performance and reduce overall number of heap allocations. * Update some of the documentation links in comments throughout the codebase to correctly point to the moved repository. * Expanded JSON encoder for AST to support singleton types. * Fixed a bug in `luau-analyze` where exceptions in the last module being checked during multithreaded analysis would not be rethrown. ### New type solver * Introduce a `refine` type family to handle deferred refinements during type inference, replacing the old `RefineConstraint`. * Continued work on the implementation of type states, fixing some known bugs/blockers. * Added support for variadic functions in new non-strict mode, enabling broader support for builtins and the Roblox API. ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
parent
2173938eb0
commit
ff502f0943
@ -197,24 +197,6 @@ struct UnpackConstraint
|
||||
bool resultIsLValue = false;
|
||||
};
|
||||
|
||||
// resultType ~ refine type mode discriminant
|
||||
//
|
||||
// Compute type & discriminant (or type | discriminant) as soon as possible (but
|
||||
// no sooner), simplify, and bind resultType to that type.
|
||||
struct RefineConstraint
|
||||
{
|
||||
enum
|
||||
{
|
||||
Intersection,
|
||||
Union
|
||||
} mode;
|
||||
|
||||
TypeId resultType;
|
||||
|
||||
TypeId type;
|
||||
TypeId discriminant;
|
||||
};
|
||||
|
||||
// resultType ~ T0 op T1 op ... op TN
|
||||
//
|
||||
// op is either union or intersection. If any of the input types are blocked,
|
||||
@ -249,7 +231,7 @@ struct ReducePackConstraint
|
||||
|
||||
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, IterableConstraint,
|
||||
NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, PrimitiveTypeConstraint, HasPropConstraint, SetPropConstraint,
|
||||
SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, RefineConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint>;
|
||||
SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint>;
|
||||
|
||||
struct Constraint
|
||||
{
|
||||
|
@ -132,7 +132,6 @@ struct ConstraintSolver
|
||||
bool tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint);
|
||||
bool tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint);
|
||||
bool tryDispatch(const RefineConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
|
@ -137,8 +137,8 @@ private:
|
||||
DfgScope* childScope(DfgScope* scope, DfgScope::ScopeType scopeType = DfgScope::Linear);
|
||||
|
||||
void join(DfgScope* p, DfgScope* a, DfgScope* b);
|
||||
void joinBindings(DfgScope::Bindings& p, const DfgScope::Bindings& a, const DfgScope::Bindings& b);
|
||||
void joinProps(DfgScope::Props& p, const DfgScope::Props& a, const DfgScope::Props& b);
|
||||
void joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b);
|
||||
void joinProps(DfgScope* p, const DfgScope& a, const DfgScope& b);
|
||||
|
||||
DefId lookup(DfgScope* scope, Symbol symbol);
|
||||
DefId lookup(DfgScope* scope, DefId def, const std::string& key);
|
||||
|
@ -118,6 +118,12 @@ public:
|
||||
class const_iterator
|
||||
{
|
||||
public:
|
||||
using value_type = T;
|
||||
using reference = T&;
|
||||
using pointer = T*;
|
||||
using difference_type = ptrdiff_t;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
const_iterator(typename Impl::const_iterator impl, typename Impl::const_iterator end)
|
||||
: impl(impl)
|
||||
, end(end)
|
||||
|
@ -176,7 +176,7 @@ struct PrimitiveType
|
||||
}
|
||||
};
|
||||
|
||||
// Singleton types https://github.com/Roblox/luau/blob/master/rfcs/syntax-singleton-types.md
|
||||
// Singleton types https://github.com/luau-lang/rfcs/blob/master/docs/syntax-singleton-types.md
|
||||
// Types for true and false
|
||||
struct BooleanSingleton
|
||||
{
|
||||
|
@ -162,6 +162,8 @@ struct BuiltinTypeFamilies
|
||||
TypeFamily leFamily;
|
||||
TypeFamily eqFamily;
|
||||
|
||||
TypeFamily refineFamily;
|
||||
|
||||
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
|
||||
};
|
||||
|
||||
|
@ -200,6 +200,23 @@ struct AstJsonEncoder : public AstVisitor
|
||||
{
|
||||
writeString(name.value ? name.value : "");
|
||||
}
|
||||
void write(std::optional<AstArgumentName> name)
|
||||
{
|
||||
if (name)
|
||||
write(*name);
|
||||
else
|
||||
writeRaw("null");
|
||||
}
|
||||
void write(AstArgumentName name)
|
||||
{
|
||||
writeRaw("{");
|
||||
bool c = pushComma();
|
||||
writeType("AstArgumentName");
|
||||
write("name", name.first);
|
||||
write("location", name.second);
|
||||
popComma(c);
|
||||
writeRaw("}");
|
||||
}
|
||||
|
||||
void write(const Position& position)
|
||||
{
|
||||
@ -848,6 +865,7 @@ struct AstJsonEncoder : public AstVisitor
|
||||
PROP(generics);
|
||||
PROP(genericPacks);
|
||||
PROP(argTypes);
|
||||
PROP(argNames);
|
||||
PROP(returnTypes);
|
||||
});
|
||||
}
|
||||
@ -902,6 +920,22 @@ struct AstJsonEncoder : public AstVisitor
|
||||
});
|
||||
}
|
||||
|
||||
bool visit(class AstTypeSingletonBool* node) override
|
||||
{
|
||||
writeNode(node, "AstTypeSingletonBool", [&]() {
|
||||
write("value", node->value);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(class AstTypeSingletonString* node) override
|
||||
{
|
||||
writeNode(node, "AstTypeSingletonString", [&]() {
|
||||
write("value", node->value);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(class AstExprGroup* node) override
|
||||
{
|
||||
write(node);
|
||||
|
@ -257,7 +257,7 @@ void ConstraintGenerator::unionRefinements(const RefinementContext& lhs, const R
|
||||
return types[0];
|
||||
else if (2 == types.size())
|
||||
{
|
||||
// TODO: It may be advantageous to create a RefineConstraint here when there are blockedTypes.
|
||||
// TODO: It may be advantageous to introduce a refine type family here when there are blockedTypes.
|
||||
SimplifyResult sr = simplifyIntersection(builtinTypes, arena, types[0], types[1]);
|
||||
if (sr.blockedTypes.empty())
|
||||
return sr.result;
|
||||
@ -441,10 +441,14 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
|
||||
{
|
||||
if (mustDeferIntersection(ty) || mustDeferIntersection(dt))
|
||||
{
|
||||
TypeId r = arena->addType(BlockedType{});
|
||||
addConstraint(scope, location, RefineConstraint{RefineConstraint::Intersection, r, ty, dt});
|
||||
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.refineFamily},
|
||||
{ty, dt},
|
||||
{},
|
||||
});
|
||||
addConstraint(scope, location, ReduceConstraint{resultType});
|
||||
|
||||
ty = r;
|
||||
ty = resultType;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1005,9 +1009,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* ass
|
||||
|
||||
checkLValue(scope, lvalue, assignee);
|
||||
assignees.push_back(assignee);
|
||||
|
||||
DefId def = dfg->getDef(lvalue);
|
||||
scope->lvalueTypes[def] = assignee;
|
||||
}
|
||||
|
||||
TypePackId resultPack = checkPack(scope, assign->values).tp;
|
||||
|
@ -545,8 +545,6 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
|
||||
success = tryDispatch(*sottc, constraint);
|
||||
else if (auto uc = get<UnpackConstraint>(*constraint))
|
||||
success = tryDispatch(*uc, constraint);
|
||||
else if (auto rc = get<RefineConstraint>(*constraint))
|
||||
success = tryDispatch(*rc, constraint, force);
|
||||
else if (auto soc = get<SetOpConstraint>(*constraint))
|
||||
success = tryDispatch(*soc, constraint, force);
|
||||
else if (auto rc = get<ReduceConstraint>(*constraint))
|
||||
@ -887,9 +885,9 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
||||
// In order to prevent infinite types from being expanded and causing us to
|
||||
// cycle infinitely, we need to scan the type function for cases where we
|
||||
// expand the same alias with different type saturatedTypeArguments. See
|
||||
// https://github.com/Roblox/luau/pull/68 for the RFC responsible for this.
|
||||
// This is a little nicer than using a recursion limit because we can catch
|
||||
// the infinite expansion before actually trying to expand it.
|
||||
// https://github.com/luau-lang/luau/pull/68 for the RFC responsible for
|
||||
// this. This is a little nicer than using a recursion limit because we can
|
||||
// catch the infinite expansion before actually trying to expand it.
|
||||
InfiniteTypeFinder itf{this, signature, constraint->scope};
|
||||
itf.traverse(tf->type);
|
||||
|
||||
@ -1505,151 +1503,6 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/*
|
||||
* Search for types that prevent us from being ready to dispatch a particular
|
||||
* RefineConstraint.
|
||||
*/
|
||||
struct FindRefineConstraintBlockers : TypeOnceVisitor
|
||||
{
|
||||
DenseHashSet<TypeId> found{nullptr};
|
||||
bool visit(TypeId ty, const BlockedType&) override
|
||||
{
|
||||
found.insert(ty);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const PendingExpansionType&) override
|
||||
{
|
||||
found.insert(ty);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const ClassType&) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
static bool isNegatedAny(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
const NegationType* nt = get<NegationType>(ty);
|
||||
if (!nt)
|
||||
return false;
|
||||
TypeId negatedTy = follow(nt->ty);
|
||||
return bool(get<AnyType>(negatedTy));
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const RefineConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
{
|
||||
if (isBlocked(c.discriminant))
|
||||
return block(c.discriminant, constraint);
|
||||
|
||||
FindRefineConstraintBlockers fbt;
|
||||
fbt.traverse(c.discriminant);
|
||||
|
||||
if (!fbt.found.empty())
|
||||
{
|
||||
bool foundOne = false;
|
||||
|
||||
for (TypeId blocked : fbt.found)
|
||||
{
|
||||
if (blocked == c.type)
|
||||
continue;
|
||||
|
||||
block(blocked, constraint);
|
||||
foundOne = true;
|
||||
}
|
||||
|
||||
if (foundOne)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* HACK: Refinements sometimes produce a type T & ~any under the assumption
|
||||
* that ~any is the same as any. This is so so weird, but refinements needs
|
||||
* some way to say "I may refine this, but I'm not sure."
|
||||
*
|
||||
* It does this by refining on a blocked type and deferring the decision
|
||||
* until it is unblocked.
|
||||
*
|
||||
* Refinements also get negated, so we wind up with types like T & ~*blocked*
|
||||
*
|
||||
* We need to treat T & ~any as T in this case.
|
||||
*/
|
||||
|
||||
if (c.mode == RefineConstraint::Intersection && isNegatedAny(c.discriminant))
|
||||
{
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(c.type);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
const TypeId type = follow(c.type);
|
||||
|
||||
if (hasUnresolvedConstraints(type))
|
||||
return block(type, constraint);
|
||||
|
||||
LUAU_ASSERT(get<BlockedType>(c.resultType));
|
||||
|
||||
if (type == c.resultType)
|
||||
{
|
||||
/*
|
||||
* Sometimes, we get a constraint of the form
|
||||
*
|
||||
* *blocked-N* ~ refine *blocked-N* & U
|
||||
*
|
||||
* The constraint essentially states that a particular type is a
|
||||
* refinement of itself. This is weird and I think vacuous.
|
||||
*
|
||||
* I *believe* it is safe to replace the result with a fresh type that
|
||||
* is constrained by U. We effect this by minting a fresh type for the
|
||||
* result when U = any, else we bind the result to whatever discriminant
|
||||
* was offered.
|
||||
*/
|
||||
if (get<AnyType>(follow(c.discriminant)))
|
||||
{
|
||||
TypeId f = freshType(arena, builtinTypes, constraint->scope);
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(f);
|
||||
}
|
||||
else
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(c.discriminant);
|
||||
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto [result, blockedTypes] = c.mode == RefineConstraint::Intersection ? simplifyIntersection(builtinTypes, NotNull{arena}, type, c.discriminant)
|
||||
: simplifyUnion(builtinTypes, NotNull{arena}, type, c.discriminant);
|
||||
|
||||
if (!force && !blockedTypes.empty())
|
||||
return block(blockedTypes, constraint);
|
||||
|
||||
switch (shouldSuppressErrors(normalizer, c.type))
|
||||
{
|
||||
case ErrorSuppression::Suppress:
|
||||
{
|
||||
auto resultOrError = simplifyUnion(builtinTypes, arena, result, builtinTypes->errorType).result;
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(resultOrError);
|
||||
break;
|
||||
}
|
||||
case ErrorSuppression::DoNotSuppress:
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(result);
|
||||
break;
|
||||
case ErrorSuppression::NormalizationFailed:
|
||||
reportError(NormalizationTooComplex{}, constraint->location);
|
||||
break;
|
||||
}
|
||||
|
||||
unblock(c.resultType, constraint->location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
{
|
||||
bool blocked = false;
|
||||
|
@ -180,36 +180,39 @@ DfgScope* DataFlowGraphBuilder::childScope(DfgScope* scope, DfgScope::ScopeType
|
||||
|
||||
void DataFlowGraphBuilder::join(DfgScope* p, DfgScope* a, DfgScope* b)
|
||||
{
|
||||
joinBindings(p->bindings, a->bindings, b->bindings);
|
||||
joinProps(p->props, a->props, b->props);
|
||||
joinBindings(p, *a, *b);
|
||||
joinProps(p, *a, *b);
|
||||
}
|
||||
|
||||
void DataFlowGraphBuilder::joinBindings(DfgScope::Bindings& p, const DfgScope::Bindings& a, const DfgScope::Bindings& b)
|
||||
void DataFlowGraphBuilder::joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b)
|
||||
{
|
||||
for (const auto& [sym, def1] : a)
|
||||
for (const auto& [sym, def1] : a.bindings)
|
||||
{
|
||||
if (auto def2 = b.find(sym))
|
||||
p[sym] = defArena->phi(NotNull{def1}, NotNull{*def2});
|
||||
else if (auto def2 = p.find(sym))
|
||||
p[sym] = defArena->phi(NotNull{def1}, NotNull{*def2});
|
||||
if (auto def2 = b.bindings.find(sym))
|
||||
p->bindings[sym] = defArena->phi(NotNull{def1}, NotNull{*def2});
|
||||
else if (auto def2 = p->lookup(sym))
|
||||
p->bindings[sym] = defArena->phi(NotNull{def1}, NotNull{*def2});
|
||||
}
|
||||
|
||||
for (const auto& [sym, def1] : b)
|
||||
for (const auto& [sym, def1] : b.bindings)
|
||||
{
|
||||
if (auto def2 = p.find(sym))
|
||||
p[sym] = defArena->phi(NotNull{def1}, NotNull{*def2});
|
||||
if (auto def2 = p->lookup(sym))
|
||||
p->bindings[sym] = defArena->phi(NotNull{def1}, NotNull{*def2});
|
||||
}
|
||||
}
|
||||
|
||||
void DataFlowGraphBuilder::joinProps(DfgScope::Props& p, const DfgScope::Props& a, const DfgScope::Props& b)
|
||||
void DataFlowGraphBuilder::joinProps(DfgScope* result, const DfgScope& a, const DfgScope& b)
|
||||
{
|
||||
auto phinodify = [this](auto& p, const auto& a, const auto& b) mutable {
|
||||
auto phinodify = [this](DfgScope* scope, const auto& a, const auto& b, DefId parent) mutable {
|
||||
auto& p = scope->props[parent];
|
||||
for (const auto& [k, defA] : a)
|
||||
{
|
||||
if (auto it = b.find(k); it != b.end())
|
||||
p[k] = defArena->phi(NotNull{it->second}, NotNull{defA});
|
||||
else if (auto it = p.find(k); it != p.end())
|
||||
p[k] = defArena->phi(NotNull{it->second}, NotNull{defA});
|
||||
else if (auto def2 = scope->lookup(parent, k))
|
||||
p[k] = defArena->phi(*def2, NotNull{defA});
|
||||
else
|
||||
p[k] = defA;
|
||||
}
|
||||
@ -220,27 +223,29 @@ void DataFlowGraphBuilder::joinProps(DfgScope::Props& p, const DfgScope::Props&
|
||||
continue;
|
||||
else if (auto it = p.find(k); it != p.end())
|
||||
p[k] = defArena->phi(NotNull{it->second}, NotNull{defB});
|
||||
else if (auto def2 = scope->lookup(parent, k))
|
||||
p[k] = defArena->phi(*def2, NotNull{defB});
|
||||
else
|
||||
p[k] = defB;
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& [def, a1] : a)
|
||||
for (const auto& [def, a1] : a.props)
|
||||
{
|
||||
p.try_insert(def, {});
|
||||
if (auto a2 = b.find(def))
|
||||
phinodify(p[def], a1, *a2);
|
||||
else if (auto a2 = p.find(def))
|
||||
phinodify(p[def], a1, *a2);
|
||||
result->props.try_insert(def, {});
|
||||
if (auto a2 = b.props.find(def))
|
||||
phinodify(result, a1, *a2, NotNull{def});
|
||||
else if (auto a2 = result->props.find(def))
|
||||
phinodify(result, a1, *a2, NotNull{def});
|
||||
}
|
||||
|
||||
for (const auto& [def, a1] : b)
|
||||
for (const auto& [def, a1] : b.props)
|
||||
{
|
||||
p.try_insert(def, {});
|
||||
if (a.find(def))
|
||||
result->props.try_insert(def, {});
|
||||
if (a.props.find(def))
|
||||
continue;
|
||||
else if (auto a2 = p.find(def))
|
||||
phinodify(p[def], a1, *a2);
|
||||
else if (auto a2 = result->props.find(def))
|
||||
phinodify(result, a1, *a2, NotNull{def});
|
||||
}
|
||||
}
|
||||
|
||||
@ -466,6 +471,14 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatLocal* l)
|
||||
// make sure that the non-aliased defs are also marked as a subscript for refinements.
|
||||
bool subscripted = i < defs.size() && containsSubscriptedDefinition(defs[i]);
|
||||
DefId def = defArena->freshCell(subscripted);
|
||||
if (i < l->values.size)
|
||||
{
|
||||
AstExpr* e = l->values.data[i];
|
||||
if (const AstExprTable* tbl = e->as<AstExprTable>())
|
||||
{
|
||||
def = defs[i];
|
||||
}
|
||||
}
|
||||
graph.localDefs[local] = def;
|
||||
scope->bindings[local] = def;
|
||||
captures[local].allVersions.push_back(def);
|
||||
@ -769,7 +782,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprIndexExpr
|
||||
return {def, keyArena->node(parentKey, def, index)};
|
||||
}
|
||||
|
||||
return {defArena->freshCell(/* subscripted= */true), nullptr};
|
||||
return {defArena->freshCell(/* subscripted= */ true), nullptr};
|
||||
}
|
||||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunction* f)
|
||||
@ -819,14 +832,20 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunction*
|
||||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprTable* t)
|
||||
{
|
||||
DefId tableCell = defArena->freshCell();
|
||||
scope->props[tableCell] = {};
|
||||
for (AstExprTable::Item item : t->items)
|
||||
{
|
||||
DataFlowResult result = visitExpr(scope, item.value);
|
||||
if (item.key)
|
||||
{
|
||||
visitExpr(scope, item.key);
|
||||
visitExpr(scope, item.value);
|
||||
if (auto string = item.key->as<AstExprConstantString>())
|
||||
scope->props[tableCell][string->value.data] = result.def;
|
||||
}
|
||||
}
|
||||
|
||||
return {defArena->freshCell(), nullptr};
|
||||
return {tableCell, nullptr};
|
||||
}
|
||||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprUnary* u)
|
||||
|
@ -37,6 +37,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
|
||||
LUAU_FASTFLAGVARIABLE(CorrectEarlyReturnInMarkDirty, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDefinitionFileSetModuleName, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRethrowSingleModuleIce, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -679,8 +680,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptio
|
||||
sendItemTask(i);
|
||||
nextItems.clear();
|
||||
|
||||
// If we aren't done, but don't have anything processing, we hit a cycle
|
||||
if (remaining != 0 && processing == 0)
|
||||
if (FFlag::LuauRethrowSingleModuleIce && processing == 0)
|
||||
{
|
||||
// Typechecking might have been cancelled by user, don't return partial results
|
||||
if (cancelled)
|
||||
@ -688,9 +688,24 @@ std::vector<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptio
|
||||
|
||||
// We might have stopped because of a pending exception
|
||||
if (itemWithException)
|
||||
{
|
||||
recordItemResult(buildQueueItems[*itemWithException]);
|
||||
break;
|
||||
}
|
||||
|
||||
// If we aren't done, but don't have anything processing, we hit a cycle
|
||||
if (remaining != 0 && processing == 0)
|
||||
{
|
||||
if (!FFlag::LuauRethrowSingleModuleIce)
|
||||
{
|
||||
// Typechecking might have been cancelled by user, don't return partial results
|
||||
if (cancelled)
|
||||
return {};
|
||||
|
||||
// We might have stopped because of a pending exception
|
||||
if (itemWithException)
|
||||
{
|
||||
recordItemResult(buildQueueItems[*itemWithException]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sendCycleItemTask();
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeFamily.h"
|
||||
#include "Luau/Def.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
@ -57,8 +58,6 @@ struct StackPusher
|
||||
|
||||
struct NonStrictContext
|
||||
{
|
||||
std::unordered_map<const Def*, TypeId> context;
|
||||
|
||||
NonStrictContext() = default;
|
||||
|
||||
NonStrictContext(const NonStrictContext&) = delete;
|
||||
@ -109,7 +108,12 @@ struct NonStrictContext
|
||||
// Returns true if the removal was successful
|
||||
bool remove(const DefId& def)
|
||||
{
|
||||
return context.erase(def.get()) == 1;
|
||||
std::vector<DefId> defs;
|
||||
collectOperands(def, &defs);
|
||||
bool result = true;
|
||||
for (DefId def : defs)
|
||||
result = result && context.erase(def.get()) == 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<TypeId> find(const DefId& def) const
|
||||
@ -118,6 +122,14 @@ struct NonStrictContext
|
||||
return find(d);
|
||||
}
|
||||
|
||||
void addContext(const DefId& def, TypeId ty)
|
||||
{
|
||||
std::vector<DefId> defs;
|
||||
collectOperands(def, &defs);
|
||||
for (DefId def : defs)
|
||||
context[def.get()] = ty;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<TypeId> find(const Def* d) const
|
||||
{
|
||||
@ -126,6 +138,9 @@ private:
|
||||
return {it->second};
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unordered_map<const Def*, TypeId> context;
|
||||
|
||||
};
|
||||
|
||||
struct NonStrictTypeChecker
|
||||
@ -508,8 +523,25 @@ struct NonStrictTypeChecker
|
||||
// ...
|
||||
// (unknown^N-1, ~S_N) -> error
|
||||
std::vector<TypeId> argTypes;
|
||||
for (TypeId ty : fn->argTypes)
|
||||
argTypes.push_back(ty);
|
||||
argTypes.reserve(call->args.size);
|
||||
// Pad out the arg types array with the types you would expect to see
|
||||
TypePackIterator curr = begin(fn->argTypes);
|
||||
TypePackIterator fin = end(fn->argTypes);
|
||||
while (curr != fin)
|
||||
{
|
||||
argTypes.push_back(*curr);
|
||||
++curr;
|
||||
}
|
||||
if (auto argTail = curr.tail())
|
||||
{
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*argTail)))
|
||||
{
|
||||
while (argTypes.size() < call->args.size)
|
||||
{
|
||||
argTypes.push_back(vtp->ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
// For a checked function, these gotta be the same size
|
||||
LUAU_ASSERT(call->args.size == argTypes.size());
|
||||
for (size_t i = 0; i < call->args.size; i++)
|
||||
@ -523,7 +555,7 @@ struct NonStrictTypeChecker
|
||||
TypeId expectedArgType = argTypes[i];
|
||||
DefId def = dfg->getDef(arg);
|
||||
TypeId runTimeErrorTy = getOrCreateNegation(expectedArgType);
|
||||
fresh.context[def.get()] = runTimeErrorTy;
|
||||
fresh.addContext(def, runTimeErrorTy);
|
||||
}
|
||||
|
||||
// Populate the context and now iterate through each of the arguments to the call to find out if we satisfy the types
|
||||
@ -613,15 +645,20 @@ struct NonStrictTypeChecker
|
||||
std::optional<TypeId> willRunTimeError(AstExpr* fragment, const NonStrictContext& context)
|
||||
{
|
||||
DefId def = dfg->getDef(fragment);
|
||||
if (std::optional<TypeId> contextTy = context.find(def))
|
||||
std::vector<DefId> defs;
|
||||
collectOperands(def, &defs);
|
||||
for (DefId def : defs)
|
||||
{
|
||||
if (std::optional<TypeId> contextTy = context.find(def))
|
||||
{
|
||||
|
||||
TypeId actualType = lookupType(fragment);
|
||||
SubtypingResult r = subtyping.isSubtype(actualType, *contextTy);
|
||||
if (r.normalizationTooComplex)
|
||||
reportError(NormalizationTooComplex{}, fragment->location);
|
||||
if (r.isSubtype)
|
||||
return {actualType};
|
||||
TypeId actualType = lookupType(fragment);
|
||||
SubtypingResult r = subtyping.isSubtype(actualType, *contextTy);
|
||||
if (r.normalizationTooComplex)
|
||||
reportError(NormalizationTooComplex{}, fragment->location);
|
||||
if (r.isSubtype)
|
||||
return {actualType};
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
@ -630,15 +667,20 @@ struct NonStrictTypeChecker
|
||||
std::optional<TypeId> willRunTimeErrorFunctionDefinition(AstLocal* fragment, const NonStrictContext& context)
|
||||
{
|
||||
DefId def = dfg->getDef(fragment);
|
||||
if (std::optional<TypeId> contextTy = context.find(def))
|
||||
std::vector<DefId> defs;
|
||||
collectOperands(def, &defs);
|
||||
for (DefId def : defs)
|
||||
{
|
||||
SubtypingResult r1 = subtyping.isSubtype(builtinTypes->unknownType, *contextTy);
|
||||
SubtypingResult r2 = subtyping.isSubtype(*contextTy, builtinTypes->unknownType);
|
||||
if (r1.normalizationTooComplex || r2.normalizationTooComplex)
|
||||
reportError(NormalizationTooComplex{}, fragment->location);
|
||||
bool isUnknown = r1.isSubtype && r2.isSubtype;
|
||||
if (isUnknown)
|
||||
return {builtinTypes->unknownType};
|
||||
if (std::optional<TypeId> contextTy = context.find(def))
|
||||
{
|
||||
SubtypingResult r1 = subtyping.isSubtype(builtinTypes->unknownType, *contextTy);
|
||||
SubtypingResult r2 = subtyping.isSubtype(*contextTy, builtinTypes->unknownType);
|
||||
if (r1.normalizationTooComplex || r2.normalizationTooComplex)
|
||||
reportError(NormalizationTooComplex{}, fragment->location);
|
||||
bool isUnknown = r1.isSubtype && r2.isSubtype;
|
||||
if (isUnknown)
|
||||
return {builtinTypes->unknownType};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Constraint.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/Set.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypePack.h"
|
||||
@ -53,8 +55,8 @@ struct FindCyclicTypes final : TypeVisitor
|
||||
FindCyclicTypes& operator=(const FindCyclicTypes&) = delete;
|
||||
|
||||
bool exhaustive = false;
|
||||
std::unordered_set<TypeId> visited;
|
||||
std::unordered_set<TypePackId> visitedPacks;
|
||||
Luau::Set<TypeId> visited{{}};
|
||||
Luau::Set<TypePackId> visitedPacks{{}};
|
||||
std::set<TypeId> cycles;
|
||||
std::set<TypePackId> cycleTPs;
|
||||
|
||||
@ -70,17 +72,17 @@ struct FindCyclicTypes final : TypeVisitor
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
return visited.insert(ty).second;
|
||||
return visited.insert(ty);
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp) override
|
||||
{
|
||||
return visitedPacks.insert(tp).second;
|
||||
return visitedPacks.insert(tp);
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const FreeType& ft) override
|
||||
{
|
||||
if (!visited.insert(ty).second)
|
||||
if (!visited.insert(ty))
|
||||
return false;
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
@ -102,7 +104,7 @@ struct FindCyclicTypes final : TypeVisitor
|
||||
|
||||
bool visit(TypeId ty, const LocalType& lt) override
|
||||
{
|
||||
if (!visited.insert(ty).second)
|
||||
if (!visited.insert(ty))
|
||||
return false;
|
||||
|
||||
traverse(lt.domain);
|
||||
@ -112,7 +114,7 @@ struct FindCyclicTypes final : TypeVisitor
|
||||
|
||||
bool visit(TypeId ty, const TableType& ttv) override
|
||||
{
|
||||
if (!visited.insert(ty).second)
|
||||
if (!visited.insert(ty))
|
||||
return false;
|
||||
|
||||
if (ttv.name || ttv.syntheticName)
|
||||
@ -175,10 +177,11 @@ struct StringifierState
|
||||
ToStringOptions& opts;
|
||||
ToStringResult& result;
|
||||
|
||||
std::unordered_map<TypeId, std::string> cycleNames;
|
||||
std::unordered_map<TypePackId, std::string> cycleTpNames;
|
||||
std::unordered_set<void*> seen;
|
||||
std::unordered_set<std::string> usedNames;
|
||||
DenseHashMap<TypeId, std::string> cycleNames{{}};
|
||||
DenseHashMap<TypePackId, std::string> cycleTpNames{{}};
|
||||
Set<void*> seen{{}};
|
||||
// `$$$` was chosen as the tombstone for `usedNames` since it is not a valid name syntactically and is relatively short for string comparison reasons.
|
||||
DenseHashSet<std::string> usedNames{"$$$"};
|
||||
size_t indentation = 0;
|
||||
|
||||
bool exhaustive;
|
||||
@ -197,7 +200,7 @@ struct StringifierState
|
||||
bool hasSeen(const void* tv)
|
||||
{
|
||||
void* ttv = const_cast<void*>(tv);
|
||||
if (seen.find(ttv) != seen.end())
|
||||
if (seen.contains(ttv))
|
||||
return true;
|
||||
|
||||
seen.insert(ttv);
|
||||
@ -207,9 +210,9 @@ struct StringifierState
|
||||
void unsee(const void* tv)
|
||||
{
|
||||
void* ttv = const_cast<void*>(tv);
|
||||
auto iter = seen.find(ttv);
|
||||
if (iter != seen.end())
|
||||
seen.erase(iter);
|
||||
|
||||
if (seen.contains(ttv))
|
||||
seen.erase(ttv);
|
||||
}
|
||||
|
||||
std::string getName(TypeId ty)
|
||||
@ -222,7 +225,7 @@ struct StringifierState
|
||||
for (int count = 0; count < 256; ++count)
|
||||
{
|
||||
std::string candidate = generateName(usedNames.size() + count);
|
||||
if (!usedNames.count(candidate))
|
||||
if (!usedNames.contains(candidate))
|
||||
{
|
||||
usedNames.insert(candidate);
|
||||
n = candidate;
|
||||
@ -245,7 +248,7 @@ struct StringifierState
|
||||
for (int count = 0; count < 256; ++count)
|
||||
{
|
||||
std::string candidate = generateName(previousNameIndex + count);
|
||||
if (!usedNames.count(candidate))
|
||||
if (!usedNames.contains(candidate))
|
||||
{
|
||||
previousNameIndex += count;
|
||||
usedNames.insert(candidate);
|
||||
@ -358,10 +361,9 @@ struct TypeStringifier
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = state.cycleNames.find(tv);
|
||||
if (it != state.cycleNames.end())
|
||||
if (auto p = state.cycleNames.find(tv))
|
||||
{
|
||||
state.emit(it->second);
|
||||
state.emit(*p);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -886,7 +888,7 @@ struct TypeStringifier
|
||||
|
||||
std::string saved = std::move(state.result.name);
|
||||
|
||||
bool needParens = !state.cycleNames.count(el) && (get<IntersectionType>(el) || get<FunctionType>(el));
|
||||
bool needParens = !state.cycleNames.contains(el) && (get<IntersectionType>(el) || get<FunctionType>(el));
|
||||
|
||||
if (needParens)
|
||||
state.emit("(");
|
||||
@ -953,7 +955,7 @@ struct TypeStringifier
|
||||
|
||||
std::string saved = std::move(state.result.name);
|
||||
|
||||
bool needParens = !state.cycleNames.count(el) && (get<UnionType>(el) || get<FunctionType>(el));
|
||||
bool needParens = !state.cycleNames.contains(el) && (get<UnionType>(el) || get<FunctionType>(el));
|
||||
|
||||
if (needParens)
|
||||
state.emit("(");
|
||||
@ -1101,10 +1103,9 @@ struct TypePackStringifier
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = state.cycleTpNames.find(tp);
|
||||
if (it != state.cycleTpNames.end())
|
||||
if (auto p = state.cycleTpNames.find(tp))
|
||||
{
|
||||
state.emit(it->second);
|
||||
state.emit(*p);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1278,7 +1279,7 @@ void TypeStringifier::stringify(TypePackId tpid, const std::vector<std::optional
|
||||
}
|
||||
|
||||
static void assignCycleNames(const std::set<TypeId>& cycles, const std::set<TypePackId>& cycleTPs,
|
||||
std::unordered_map<TypeId, std::string>& cycleNames, std::unordered_map<TypePackId, std::string>& cycleTpNames, bool exhaustive)
|
||||
DenseHashMap<TypeId, std::string>& cycleNames, DenseHashMap<TypePackId, std::string>& cycleTpNames, bool exhaustive)
|
||||
{
|
||||
int nextIndex = 1;
|
||||
|
||||
@ -1372,9 +1373,8 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts)
|
||||
*
|
||||
* t1 where t1 = the_whole_root_type
|
||||
*/
|
||||
auto it = state.cycleNames.find(ty);
|
||||
if (it != state.cycleNames.end())
|
||||
state.emit(it->second);
|
||||
if (auto p = state.cycleNames.find(ty))
|
||||
state.emit(*p);
|
||||
else
|
||||
tvs.stringify(ty);
|
||||
|
||||
@ -1466,9 +1466,8 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts)
|
||||
*
|
||||
* t1 where t1 = the_whole_root_type
|
||||
*/
|
||||
auto it = state.cycleTpNames.find(tp);
|
||||
if (it != state.cycleTpNames.end())
|
||||
state.emit(it->second);
|
||||
if (auto p = state.cycleTpNames.find(tp))
|
||||
state.emit(*p);
|
||||
else
|
||||
tvs.stringify(tp);
|
||||
|
||||
@ -1766,11 +1765,6 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, UnpackConstraint>)
|
||||
return tos(c.resultPack) + " ~ unpack " + tos(c.sourcePack);
|
||||
else if constexpr (std::is_same_v<T, RefineConstraint>)
|
||||
{
|
||||
const char* op = c.mode == RefineConstraint::Union ? "union" : "intersect";
|
||||
return tos(c.resultType) + " ~ refine " + tos(c.type) + " " + op + " " + tos(c.discriminant);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, SetOpConstraint>)
|
||||
{
|
||||
const char* op = c.mode == SetOpConstraint::Union ? " | " : " & ";
|
||||
|
@ -106,7 +106,12 @@ public:
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("thread"), std::nullopt, Location());
|
||||
case PrimitiveType::Buffer:
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("buffer"), std::nullopt, Location());
|
||||
case PrimitiveType::Function:
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("function"), std::nullopt, Location());
|
||||
case PrimitiveType::Table:
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("table"), std::nullopt, Location());
|
||||
default:
|
||||
LUAU_ASSERT(false); // this should be unreachable.
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -1603,8 +1603,8 @@ struct TypeChecker2
|
||||
visit(indexExpr->expr, ValueContext::RValue);
|
||||
visit(indexExpr->index, ValueContext::RValue);
|
||||
|
||||
TypeId exprType = lookupType(indexExpr->expr);
|
||||
TypeId indexType = lookupType(indexExpr->index);
|
||||
TypeId exprType = follow(lookupType(indexExpr->expr));
|
||||
TypeId indexType = follow(lookupType(indexExpr->index));
|
||||
|
||||
if (auto tt = get<TableType>(exprType))
|
||||
{
|
||||
|
@ -349,7 +349,8 @@ TypeFamilyReductionResult<TypeId> lenFamilyFn(const std::vector<TypeId>& typePar
|
||||
TypeId operandTy = follow(typeParams.at(0));
|
||||
|
||||
// check to see if the operand type is resolved enough, and wait to reduce if not
|
||||
if (isPending(operandTy, ctx->solver))
|
||||
// the use of `typeFromNormal` later necessitates blocking on local types.
|
||||
if (isPending(operandTy, ctx->solver) || get<LocalType>(operandTy))
|
||||
return {std::nullopt, false, {operandTy}, {}};
|
||||
|
||||
const NormalizedType* normTy = ctx->normalizer->normalize(operandTy);
|
||||
@ -964,6 +965,92 @@ TypeFamilyReductionResult<TypeId> eqFamilyFn(const std::vector<TypeId>& typePara
|
||||
return {ctx->builtins->booleanType, false, {}, {}};
|
||||
}
|
||||
|
||||
// Collect types that prevent us from reducing a particular refinement.
|
||||
struct FindRefinementBlockers : TypeOnceVisitor
|
||||
{
|
||||
DenseHashSet<TypeId> found{nullptr};
|
||||
bool visit(TypeId ty, const BlockedType&) override
|
||||
{
|
||||
found.insert(ty);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const PendingExpansionType&) override
|
||||
{
|
||||
found.insert(ty);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const LocalType&) override
|
||||
{
|
||||
found.insert(ty);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const ClassType&) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TypeFamilyReductionResult<TypeId> refineFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
{
|
||||
ctx->ice->ice("refine type family: encountered a type family instance without the required argument structure");
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
TypeId targetTy = follow(typeParams.at(0));
|
||||
TypeId discriminantTy = follow(typeParams.at(1));
|
||||
|
||||
// check to see if both operand types are resolved enough, and wait to reduce if not
|
||||
if (isPending(targetTy, ctx->solver))
|
||||
return {std::nullopt, false, {targetTy}, {}};
|
||||
else if (isPending(discriminantTy, ctx->solver))
|
||||
return {std::nullopt, false, {discriminantTy}, {}};
|
||||
|
||||
// we need a more complex check for blocking on the discriminant in particular
|
||||
FindRefinementBlockers frb;
|
||||
frb.traverse(discriminantTy);
|
||||
|
||||
if (!frb.found.empty())
|
||||
return {std::nullopt, false, {frb.found.begin(), frb.found.end()}, {}};
|
||||
|
||||
/* HACK: Refinements sometimes produce a type T & ~any under the assumption
|
||||
* that ~any is the same as any. This is so so weird, but refinements needs
|
||||
* some way to say "I may refine this, but I'm not sure."
|
||||
*
|
||||
* It does this by refining on a blocked type and deferring the decision
|
||||
* until it is unblocked.
|
||||
*
|
||||
* Refinements also get negated, so we wind up with types like T & ~*blocked*
|
||||
*
|
||||
* We need to treat T & ~any as T in this case.
|
||||
*/
|
||||
|
||||
if (auto nt = get<NegationType>(discriminantTy))
|
||||
if (get<AnyType>(follow(nt->ty)))
|
||||
return {targetTy, false, {}, {}};
|
||||
|
||||
TypeId intersection = ctx->arena->addType(IntersectionType{{targetTy, discriminantTy}});
|
||||
const NormalizedType* normIntersection = ctx->normalizer->normalize(intersection);
|
||||
const NormalizedType* normType = ctx->normalizer->normalize(targetTy);
|
||||
|
||||
// if the intersection failed to normalize, we can't reduce, but know nothing about inhabitance.
|
||||
if (!normIntersection || !normType)
|
||||
return {std::nullopt, false, {}, {}};
|
||||
|
||||
TypeId resultTy = ctx->normalizer->typeFromNormal(*normIntersection);
|
||||
|
||||
// include the error type if the target type is error-suppressing and the intersection we computed is not
|
||||
if (normType->shouldSuppressErrors() && !normIntersection->shouldSuppressErrors())
|
||||
resultTy = ctx->arena->addType(UnionType{{resultTy, ctx->builtins->errorType}});
|
||||
|
||||
return {resultTy, false, {}, {}};
|
||||
}
|
||||
|
||||
BuiltinTypeFamilies::BuiltinTypeFamilies()
|
||||
: notFamily{"not", notFamilyFn}
|
||||
, lenFamily{"len", lenFamilyFn}
|
||||
@ -981,6 +1068,7 @@ BuiltinTypeFamilies::BuiltinTypeFamilies()
|
||||
, ltFamily{"lt", ltFamilyFn}
|
||||
, leFamily{"le", leFamilyFn}
|
||||
, eqFamily{"eq", eqFamilyFn}
|
||||
, refineFamily{"refine", refineFamilyFn}
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -325,7 +325,7 @@ int main(int argc, char** argv)
|
||||
else if (strncmp(argv[i], "--fflags=", 9) == 0)
|
||||
setLuauFlags(argv[i] + 9);
|
||||
else if (strncmp(argv[i], "-j", 2) == 0)
|
||||
threadCount = strtol(argv[i] + 2, nullptr, 10);
|
||||
threadCount = int(strtol(argv[i] + 2, nullptr, 10));
|
||||
}
|
||||
|
||||
#if !defined(LUAU_ENABLE_TIME_TRACE)
|
||||
@ -363,6 +363,7 @@ int main(int argc, char** argv)
|
||||
if (threadCount <= 0)
|
||||
threadCount = std::min(TaskScheduler::getThreadCount(), 8u);
|
||||
|
||||
try
|
||||
{
|
||||
TaskScheduler scheduler(threadCount);
|
||||
|
||||
@ -370,6 +371,19 @@ int main(int argc, char** argv)
|
||||
scheduler.push(std::move(f));
|
||||
});
|
||||
}
|
||||
catch (const Luau::InternalCompilerError& ice)
|
||||
{
|
||||
Luau::Location location = ice.location ? *ice.location : Luau::Location();
|
||||
|
||||
std::string moduleName = ice.moduleName ? *ice.moduleName : "<unknown module>";
|
||||
std::string humanReadableName = frontend.fileResolver->getHumanReadableModuleName(moduleName);
|
||||
|
||||
Luau::TypeError error(location, moduleName, Luau::InternalError{ice.message});
|
||||
|
||||
report(format, humanReadableName.c_str(), location, "InternalCompilerError",
|
||||
Luau::toString(error, Luau::TypeErrorToStringOptions{frontend.fileResolver}).c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int failed = 0;
|
||||
|
||||
|
@ -22,16 +22,9 @@ RequireResolver::RequireResolver(lua_State* L, std::string path)
|
||||
if (isAbsolutePath(pathToResolve))
|
||||
luaL_argerrorL(L, 1, "cannot require an absolute path");
|
||||
|
||||
bool isAlias = !pathToResolve.empty() && pathToResolve[0] == '@';
|
||||
if (!isAlias && !isExplicitlyRelative(pathToResolve))
|
||||
luaL_argerrorL(L, 1, "must require an alias prepended with '@' or an explicitly relative path");
|
||||
|
||||
std::replace(pathToResolve.begin(), pathToResolve.end(), '\\', '/');
|
||||
if (isAlias)
|
||||
{
|
||||
pathToResolve = pathToResolve.substr(1);
|
||||
substituteAliasIfPresent(pathToResolve);
|
||||
}
|
||||
|
||||
substituteAliasIfPresent(pathToResolve);
|
||||
}
|
||||
|
||||
[[nodiscard]] RequireResolver::ResolvedRequire RequireResolver::resolveRequire(lua_State* L, std::string path)
|
||||
@ -209,16 +202,22 @@ std::string RequireResolver::getRequiringContextRelative()
|
||||
|
||||
void RequireResolver::substituteAliasIfPresent(std::string& path)
|
||||
{
|
||||
std::string potentialAlias = path.substr(0, path.find_first_of("\\/"));
|
||||
if (path.size() < 1 || path[0] != '@')
|
||||
return;
|
||||
std::string potentialAlias = path.substr(1, path.find_first_of("\\/"));
|
||||
|
||||
// Not worth searching when potentialAlias cannot be an alias
|
||||
if (!Luau::isValidAlias(potentialAlias))
|
||||
return;
|
||||
luaL_errorL(L, "@%s is not a valid alias", potentialAlias.c_str());
|
||||
|
||||
std::optional<std::string> alias = getAlias(potentialAlias);
|
||||
if (alias)
|
||||
{
|
||||
path = *alias + path.substr(potentialAlias.size());
|
||||
path = *alias + path.substr(potentialAlias.size() + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
luaL_errorL(L, "@%s is not a valid alias", potentialAlias.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
@ -285,9 +286,8 @@ public:
|
||||
using value_type = Item;
|
||||
using reference = Item&;
|
||||
using pointer = Item*;
|
||||
using iterator = pointer;
|
||||
using difference_type = size_t;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using difference_type = ptrdiff_t;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
const_iterator()
|
||||
: set(0)
|
||||
@ -348,6 +348,12 @@ public:
|
||||
class iterator
|
||||
{
|
||||
public:
|
||||
using value_type = MutableItem;
|
||||
using reference = MutableItem&;
|
||||
using pointer = MutableItem*;
|
||||
using difference_type = ptrdiff_t;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
iterator()
|
||||
: set(0)
|
||||
, index(0)
|
||||
|
@ -438,7 +438,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareClass")
|
||||
REQUIRE(2 == root->body.size);
|
||||
|
||||
std::string_view expected1 =
|
||||
R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","nameLocation":"2,18 - 2,24","parameters":[]}},{"name":"method","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeFunction","location":"3,21 - 4,11","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","nameLocation":"3,39 - 3,45","parameters":[]}]},"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","nameLocation":"3,48 - 3,54","parameters":[]}]}}}],"indexer":null})";
|
||||
R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","nameLocation":"2,18 - 2,24","parameters":[]}},{"name":"method","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeFunction","location":"3,21 - 4,11","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","nameLocation":"3,39 - 3,45","parameters":[]}]},"argNames":[{"type":"AstArgumentName","name":"foo","location":"3,34 - 3,37"}],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","nameLocation":"3,48 - 3,54","parameters":[]}]}}}],"indexer":null})";
|
||||
CHECK(toJson(root->body.data[0]) == expected1);
|
||||
|
||||
std::string_view expected2 =
|
||||
@ -451,7 +451,39 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation")
|
||||
AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())");
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"type":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeFunction","location":"0,10 - 0,36","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}]}},{"type":"AstTypeFunction","location":"0,41 - 0,55","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"returnTypes":{"type":"AstTypeList","types":[]}}]},"exported":false})";
|
||||
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"type":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeFunction","location":"0,10 - 0,36","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}]}},{"type":"AstTypeFunction","location":"0,41 - 0,55","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}]},"exported":false})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_type_literal")
|
||||
{
|
||||
AstStat* statement = expectParseStatement(R"(type Action = { strings: "A" | "B" | "C", mixed: "This" | "That" | true })");
|
||||
|
||||
auto json = toJson(statement);
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,73","name":"Action","generics":[],"genericPacks":[],"type":{"type":"AstTypeTable","location":"0,14 - 0,73","props":[{"name":"strings","type":"AstTableProp","location":"0,16 - 0,23","propType":{"type":"AstTypeUnion","location":"0,25 - 0,40","types":[{"type":"AstTypeSingletonString","location":"0,25 - 0,28","value":"A"},{"type":"AstTypeSingletonString","location":"0,31 - 0,34","value":"B"},{"type":"AstTypeSingletonString","location":"0,37 - 0,40","value":"C"}]}},{"name":"mixed","type":"AstTableProp","location":"0,42 - 0,47","propType":{"type":"AstTypeUnion","location":"0,49 - 0,71","types":[{"type":"AstTypeSingletonString","location":"0,49 - 0,55","value":"This"},{"type":"AstTypeSingletonString","location":"0,58 - 0,64","value":"That"},{"type":"AstTypeSingletonBool","location":"0,67 - 0,71","value":true}]}}],"indexer":null},"exported":false})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_indexed_type_literal")
|
||||
{
|
||||
AstStat* statement = expectParseStatement(R"(type StringSet = { [string]: true })");
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,35","name":"StringSet","generics":[],"genericPacks":[],"type":{"type":"AstTypeTable","location":"0,17 - 0,35","props":[],"indexer":{"location":"0,19 - 0,33","indexType":{"type":"AstTypeReference","location":"0,20 - 0,26","name":"string","nameLocation":"0,20 - 0,26","parameters":[]},"resultType":{"type":"AstTypeSingletonBool","location":"0,29 - 0,33","value":true}}},"exported":false})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypeFunction")
|
||||
{
|
||||
AstStat* statement = expectParseStatement(R"(type fun = (string, bool, named: number) -> ())");
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,46","name":"fun","generics":[],"genericPacks":[],"type":{"type":"AstTypeFunction","location":"0,11 - 0,46","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,12 - 0,18","name":"string","nameLocation":"0,12 - 0,18","parameters":[]},{"type":"AstTypeReference","location":"0,20 - 0,24","name":"bool","nameLocation":"0,20 - 0,24","parameters":[]},{"type":"AstTypeReference","location":"0,33 - 0,39","name":"number","nameLocation":"0,33 - 0,39","parameters":[]}]},"argNames":[null,null,{"type":"AstArgumentName","name":"named","location":"0,26 - 0,31"}],"returnTypes":{"type":"AstTypeList","types":[]}},"exported":false})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
@ -78,10 +78,14 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "class_method")
|
||||
declare class Foo
|
||||
function bar(self, x: string): number
|
||||
end
|
||||
|
||||
declare Foo: {
|
||||
new: () -> Foo
|
||||
}
|
||||
)");
|
||||
|
||||
std::optional<DocumentationSymbol> symbol = getDocSymbol(R"(
|
||||
local x: Foo
|
||||
local x: Foo = Foo.new()
|
||||
x:bar("asdf")
|
||||
)",
|
||||
Position(2, 11));
|
||||
@ -96,10 +100,14 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "overloaded_class_method")
|
||||
function bar(self, x: string): number
|
||||
function bar(self, x: number): string
|
||||
end
|
||||
|
||||
declare Foo: {
|
||||
new: () -> Foo
|
||||
}
|
||||
)");
|
||||
|
||||
std::optional<DocumentationSymbol> symbol = getDocSymbol(R"(
|
||||
local x: Foo
|
||||
local x: Foo = Foo.new()
|
||||
x:bar("asdf")
|
||||
)",
|
||||
Position(2, 11));
|
||||
|
@ -3265,8 +3265,9 @@ end
|
||||
|
||||
{
|
||||
check(R"(
|
||||
local t: Foo
|
||||
t:@1
|
||||
local function f(t: Foo)
|
||||
t:@1
|
||||
end
|
||||
)");
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
@ -3281,8 +3282,9 @@ t:@1
|
||||
|
||||
{
|
||||
check(R"(
|
||||
local t: Foo
|
||||
t.@1
|
||||
local function f(t: Foo)
|
||||
t.@1
|
||||
end
|
||||
)");
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
@ -1,5 +1,6 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
#include "Fixture.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Parser.h"
|
||||
|
||||
@ -560,4 +561,74 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "local_f_which_is_prototyped_enclosed_by
|
||||
CHECK(phi->operands.at(1) == f4);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DataFlowGraphFixture, "phi_node_if_case_binding")
|
||||
{
|
||||
dfg(R"(
|
||||
local x = nil
|
||||
if true then
|
||||
if true then
|
||||
x = 5
|
||||
end
|
||||
print(x)
|
||||
else
|
||||
print(x)
|
||||
end
|
||||
)");
|
||||
DefId x1 = graph->getDef(query<AstStatLocal>(module)->vars.data[0]);
|
||||
DefId x2 = getDef<AstExprLocal, 1>(); // x = 5
|
||||
DefId x3 = getDef<AstExprLocal, 2>(); // print(x)
|
||||
|
||||
const Phi* phi = get<Phi>(x3);
|
||||
REQUIRE(phi);
|
||||
CHECK(phi->operands.at(0) == x2);
|
||||
CHECK(phi->operands.at(1) == x1);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DataFlowGraphFixture, "phi_node_if_case_table_prop")
|
||||
{
|
||||
dfg(R"(
|
||||
local t = {}
|
||||
t.x = true
|
||||
if true then
|
||||
if true then
|
||||
t.x = 5
|
||||
end
|
||||
print(t.x)
|
||||
else
|
||||
print(t.x)
|
||||
end
|
||||
)");
|
||||
|
||||
DefId x1 = getDef<AstExprIndexName, 1>(); // t.x = true
|
||||
DefId x2 = getDef<AstExprIndexName, 2>(); // t.x = 5
|
||||
|
||||
DefId x3 = getDef<AstExprIndexName, 3>(); // print(t.x)
|
||||
const Phi* phi = get<Phi>(x3);
|
||||
REQUIRE(phi);
|
||||
CHECK(phi->operands.size() == 2);
|
||||
CHECK(phi->operands.at(0) == x1);
|
||||
CHECK(phi->operands.at(1) == x2);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DataFlowGraphFixture, "phi_node_if_case_table_prop_literal")
|
||||
{
|
||||
dfg(R"(
|
||||
local t = { x = true }
|
||||
if true then
|
||||
t.x = 5
|
||||
end
|
||||
print(t.x)
|
||||
|
||||
)");
|
||||
|
||||
DefId x1 = getDef<AstExprConstantBool, 1>(); // {x = true <- }
|
||||
DefId x2 = getDef<AstExprIndexName, 1>(); // t.x = 5
|
||||
DefId x3 = getDef<AstExprIndexName, 2>(); // print(t.x)
|
||||
const Phi* phi = get<Phi>(x3);
|
||||
REQUIRE(phi);
|
||||
CHECK(phi->operands.size() == 2);
|
||||
CHECK(phi->operands.at(0) == x1);
|
||||
CHECK(phi->operands.at(1) == x2);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -81,12 +81,41 @@ declare function @checked abs(n: number): number
|
||||
declare function @checked lower(s: string): string
|
||||
declare function cond() : boolean
|
||||
declare function @checked contrived(n : Not<number>) : number
|
||||
|
||||
-- interesting types of things that we would like to mark as checked
|
||||
declare function @checked onlyNums(...: number) : number
|
||||
declare function @checked mixedArgs(x: string, ...: number) : number
|
||||
declare function @checked optionalArg(x: string?) : number
|
||||
)BUILTIN_SRC";
|
||||
};
|
||||
|
||||
|
||||
TEST_SUITE_BEGIN("NonStrictTypeCheckerTest");
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "interesting_checked_functions")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
onlyNums(1,1,1)
|
||||
onlyNums(1, "a")
|
||||
|
||||
mixedArgs("a", 1, 2)
|
||||
mixedArgs(1, 1, 1)
|
||||
mixedArgs("a", true)
|
||||
|
||||
optionalArg(nil)
|
||||
optionalArg("a")
|
||||
optionalArg(3)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 12), "onlyNums", result); // onlyNums(1, "a")
|
||||
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(5, 10), "mixedArgs", result); // mixedArgs(1, 1, 1)
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(6, 15), "mixedArgs", result); // mixedArgs("a", true)
|
||||
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(10, 12), "optionalArg", result); // optionalArg(3)
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "simple_negation_caching_example")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
@ -387,4 +416,35 @@ lower(x)
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 6), "lower", result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "phi_node_assignment")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
local x = "a" -- x1
|
||||
if cond() then
|
||||
x = 3 -- x2
|
||||
end
|
||||
lower(x) -- phi {x1, x2}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "phi_node_assignment_err")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
local x = nil
|
||||
if cond() then
|
||||
if cond() then
|
||||
x = 5
|
||||
end
|
||||
abs(x)
|
||||
else
|
||||
lower(x)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(8, 10), "lower", result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -394,4 +394,39 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithParentAlias")
|
||||
assertOutputContainsAll({"true", "result from other_dependency"});
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireAliasThatDoesNotExist")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
std::string nonExistentAlias = "@this.alias.does.not.exist";
|
||||
|
||||
runProtectedRequire(nonExistentAlias);
|
||||
assertOutputContainsAll({"false", "@this.alias.does.not.exist is not a valid alias"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "AliasHasIllegalFormat")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
std::string illegalCharacter = "@@";
|
||||
|
||||
runProtectedRequire(illegalCharacter);
|
||||
assertOutputContainsAll({"false", "@@ is not a valid alias"});
|
||||
|
||||
std::string pathAlias1 = "@.";
|
||||
|
||||
runProtectedRequire(pathAlias1);
|
||||
assertOutputContainsAll({"false", ". is not a valid alias"});
|
||||
|
||||
|
||||
std::string pathAlias2 = "@..";
|
||||
|
||||
runProtectedRequire(pathAlias2);
|
||||
assertOutputContainsAll({"false", ".. is not a valid alias"});
|
||||
|
||||
std::string emptyAlias = "@";
|
||||
|
||||
runProtectedRequire(emptyAlias);
|
||||
assertOutputContainsAll({"false", " is not a valid alias"});
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -938,6 +938,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tonumber_returns_optional_number_type2")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "dont_add_definitions_to_persistent_types")
|
||||
{
|
||||
// This test makes no sense with type states and I think it generally makes no sense under the new solver.
|
||||
// TODO: clip.
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local f = math.sin
|
||||
local function g(x) return math.sin(x) end
|
||||
@ -1093,7 +1098,7 @@ end
|
||||
TEST_CASE_FIXTURE(Fixture, "string_match")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local s:string
|
||||
local s: string = "hello"
|
||||
local p = s:match("foo")
|
||||
)");
|
||||
|
||||
|
@ -230,10 +230,14 @@ TEST_CASE_FIXTURE(Fixture, "class_definition_function_prop")
|
||||
declare class Foo
|
||||
X: (number) -> string
|
||||
end
|
||||
|
||||
declare Foo: {
|
||||
new: () -> Foo
|
||||
}
|
||||
)");
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x: Foo
|
||||
local x: Foo = Foo.new()
|
||||
local prop = x.X
|
||||
)");
|
||||
|
||||
@ -250,10 +254,14 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_class_function_args")
|
||||
|
||||
y: (a: number, b: string) -> string
|
||||
end
|
||||
|
||||
declare Foo: {
|
||||
new: () -> Foo
|
||||
}
|
||||
)");
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x: Foo
|
||||
local x: Foo = Foo.new()
|
||||
local methodRef1 = x.foo1
|
||||
local methodRef2 = x.foo2
|
||||
local prop = x.y
|
||||
|
@ -2183,4 +2183,19 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "apply_of_lambda_with_inferred_and_explicit_t
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "regex_benchmark_string_format_minimization")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
(nil :: any)(function(n)
|
||||
if tonumber(n) then
|
||||
n = tonumber(n)
|
||||
elseif n ~= nil then
|
||||
string.format("invalid argument #4 to 'sub': number expected, got %s", typeof(n))
|
||||
end
|
||||
end);
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -304,8 +304,15 @@ TEST_CASE_FIXTURE(Fixture, "calling_self_generic_methods")
|
||||
end
|
||||
)");
|
||||
|
||||
// TODO: Should typecheck but currently errors CLI-54277
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("{ f: (t1) -> (), id: <a>(unknown, a) -> a } where t1 = { id: ((t1, number) -> number) & ((t1, string) -> string) }",
|
||||
toString(requireType("x"), {true}));
|
||||
}
|
||||
else
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "infer_generic_property")
|
||||
@ -692,7 +699,7 @@ local d: D = c
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "generic_functions_dont_cache_type_parameters")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
-- See https://github.com/Roblox/luau/issues/332
|
||||
-- See https://github.com/luau-lang/luau/issues/332
|
||||
-- This function has a type parameter with the same name as clones,
|
||||
-- so if we cache type parameter names for functions these get confused.
|
||||
-- function id<Z>(x : Z) : Z
|
||||
@ -1147,7 +1154,7 @@ TEST_CASE_FIXTURE(Fixture, "substitution_with_bound_table")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "apply_type_function_nested_generics1")
|
||||
{
|
||||
// https://github.com/Roblox/luau/issues/484
|
||||
// https://github.com/luau-lang/luau/issues/484
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
type MyObject = {
|
||||
@ -1175,7 +1182,7 @@ local complex: ComplexObject<string> = {
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "apply_type_function_nested_generics2")
|
||||
{
|
||||
// https://github.com/Roblox/luau/issues/484
|
||||
// https://github.com/luau-lang/luau/issues/484
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
type MyObject = {
|
||||
@ -1186,15 +1193,15 @@ type ComplexObject<T> = {
|
||||
nested: MyObject
|
||||
}
|
||||
|
||||
local complex2: ComplexObject<string> = nil
|
||||
function f(complex: ComplexObject<string>)
|
||||
local x = complex.nested.getReturnValue(function(): string
|
||||
return ""
|
||||
end)
|
||||
|
||||
local x = complex2.nested.getReturnValue(function(): string
|
||||
return ""
|
||||
end)
|
||||
|
||||
local y = complex2.nested.getReturnValue(function()
|
||||
return 3
|
||||
end)
|
||||
local y = complex.nested.getReturnValue(function()
|
||||
return 3
|
||||
end)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
@ -1855,7 +1855,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_annotations_arent_relevant_when_doing_d
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "function_call_with_colon_after_refining_not_to_be_nil")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
// don't run this test at all without DCR
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
|
@ -3,7 +3,6 @@
|
||||
#include "Fixture.h"
|
||||
|
||||
#include "doctest.h"
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
@ -141,9 +140,9 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons")
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons_mismatch")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f(a, b) end
|
||||
local g : ((true, string) -> ()) & ((false, number) -> ()) = (f::any)
|
||||
g(true, 37)
|
||||
function f(g: ((true, string) -> ()) & ((false, number) -> ()))
|
||||
g(true, 37)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
@ -429,7 +428,11 @@ TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ("string", toString(requireType("copy")));
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(R"("foo")", toString(requireType("copy")));
|
||||
else
|
||||
CHECK_EQ("string", toString(requireType("copy")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere_except_for_tables")
|
||||
|
@ -292,6 +292,7 @@ TEST_CASE_FIXTURE(TypeStateFixture, "invalidate_type_refinements_upon_assignment
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "local_t_is_assigned_a_fresh_table_with_x_assigned_a_union_and_then_assert_restricts_actual_outflow_of_types")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
@ -314,6 +315,7 @@ TEST_CASE_FIXTURE(TypeStateFixture, "local_t_is_assigned_a_fresh_table_with_x_as
|
||||
// CHECK("boolean | string" == toString(requireType("x")));
|
||||
CHECK("boolean | number | number | string" == toString(requireType("x")));
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_are_unions_of_all_assignments")
|
||||
{
|
||||
@ -399,4 +401,21 @@ TEST_CASE_FIXTURE(TypeStateFixture, "prototyped_recursive_functions_but_has_prev
|
||||
CHECK("((() -> ()) | number)?" == toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "multiple_assignments_in_loops")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x = nil
|
||||
|
||||
for i = 1, 10 do
|
||||
x = 5
|
||||
x = "hello"
|
||||
end
|
||||
|
||||
print(x)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK("(number | string)?" == toString(requireType("x")));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -45,8 +45,9 @@ TEST_CASE_FIXTURE(Fixture, "allow_specific_assign")
|
||||
TEST_CASE_FIXTURE(Fixture, "allow_more_specific_assign")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a:number|string = 22
|
||||
local b:number|string|nil = a
|
||||
function f(a: number | string, b: (number | string)?)
|
||||
b = a
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -55,25 +56,14 @@ TEST_CASE_FIXTURE(Fixture, "allow_more_specific_assign")
|
||||
TEST_CASE_FIXTURE(Fixture, "disallow_less_specific_assign")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a:number = 10
|
||||
local b:number|string = 20
|
||||
a = b
|
||||
function f(a: number, b: number | string)
|
||||
a = b
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "disallow_less_specific_assign2")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a:number? = 10
|
||||
local b:number|string? = 20
|
||||
a = b
|
||||
)");
|
||||
|
||||
REQUIRE_EQ(1, result.errors.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_arguments")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
@ -128,13 +118,14 @@ TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_property_guaranteed_to_ex
|
||||
CheckResult result = check(R"(
|
||||
type A = {x: number}
|
||||
type B = {x: number}
|
||||
local t: A | B
|
||||
|
||||
local r = t.x
|
||||
function f(t: A | B)
|
||||
return t.x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("r"));
|
||||
CHECK_EQ("(A | B) -> number", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_mixed_types")
|
||||
@ -142,13 +133,14 @@ TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_mixed_types")
|
||||
CheckResult result = check(R"(
|
||||
type A = {x: number}
|
||||
type B = {x: string}
|
||||
local t: A | B
|
||||
|
||||
local r = t.x
|
||||
function f(t: A | B)
|
||||
return t.x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ("number | string", toString(requireType("r")));
|
||||
CHECK_EQ("(A | B) -> number | string", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_works_at_arbitrary_depth")
|
||||
@ -156,13 +148,14 @@ TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_works_at_arbitrary_depth")
|
||||
CheckResult result = check(R"(
|
||||
type A = {x: {y: {z: {thing: number}}}}
|
||||
type B = {x: {y: {z: {thing: string}}}}
|
||||
local t: A | B
|
||||
|
||||
local r = t.x.y.z.thing
|
||||
function f(t: A | B)
|
||||
return t.x.y.z.thing
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ("number | string", toString(requireType("r")));
|
||||
CHECK_EQ("(A | B) -> number | string", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_one_optional_property")
|
||||
@ -170,13 +163,14 @@ TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_one_optional_property")
|
||||
CheckResult result = check(R"(
|
||||
type A = {x: number}
|
||||
type B = {x: number?}
|
||||
local t: A | B
|
||||
|
||||
local r = t.x
|
||||
function f(t: A | B)
|
||||
return t.x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ("number?", toString(requireType("r")));
|
||||
CHECK_EQ("(A | B) -> number?", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_missing_property")
|
||||
@ -184,22 +178,22 @@ TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_missing_property")
|
||||
CheckResult result = check(R"(
|
||||
type A = {x: number}
|
||||
type B = {}
|
||||
local t: A | B
|
||||
|
||||
local r = t.x
|
||||
function f(t: A | B)
|
||||
return t.x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
MissingUnionProperty* mup = get<MissingUnionProperty>(result.errors[0]);
|
||||
REQUIRE(mup);
|
||||
CHECK_EQ(mup->type, requireType("t"));
|
||||
REQUIRE(mup->missing.size() == 1);
|
||||
std::optional<TypeId> bTy = lookupType("B");
|
||||
REQUIRE(bTy);
|
||||
CHECK_EQ(mup->missing[0], *bTy);
|
||||
CHECK_EQ(mup->key, "x");
|
||||
CHECK_EQ("*error-type*", toString(requireType("r")));
|
||||
CHECK_EQ("Key 'x' is missing from 'B' in the type 'A | B'", toString(result.errors[0]));
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("(A | B) -> number | *error-type*", toString(requireType("f")));
|
||||
else
|
||||
CHECK_EQ("(A | B) -> *error-type*", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_one_property_of_type_any")
|
||||
@ -207,13 +201,14 @@ TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_one_property_of_type_any"
|
||||
CheckResult result = check(R"(
|
||||
type A = {x: number}
|
||||
type B = {x: any}
|
||||
local t: A | B
|
||||
|
||||
local r = t.x
|
||||
function f(t: A | B)
|
||||
return t.x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(*builtinTypes->anyType, *requireType("r"));
|
||||
CHECK_EQ("(A | B) -> any", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_equality_comparisons")
|
||||
@ -223,14 +218,13 @@ TEST_CASE_FIXTURE(Fixture, "union_equality_comparisons")
|
||||
type B = number | nil
|
||||
type C = number | boolean
|
||||
|
||||
local a = 1 :: A
|
||||
local b = nil :: B
|
||||
local c = true :: C
|
||||
local n = 1
|
||||
function f(a: A, b: B, c: C)
|
||||
local n = 1
|
||||
|
||||
local x = a == b
|
||||
local y = a == n
|
||||
local z = a == c
|
||||
local x = a == b
|
||||
local y = a == n
|
||||
local z = a == c
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -239,16 +233,21 @@ TEST_CASE_FIXTURE(Fixture, "union_equality_comparisons")
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_union_members")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a = { a = { x = 1, y = 2 }, b = 3 }
|
||||
type A = typeof(a)
|
||||
local b: A? = a
|
||||
local bf = b
|
||||
local c = bf.a.y
|
||||
local a = { a = { x = 1, y = 2 }, b = 3 }
|
||||
type A = typeof(a)
|
||||
function f(b: A?)
|
||||
return b.a.y
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("c"));
|
||||
|
||||
CHECK_EQ("Value of type 'A?' could be nil", toString(result.errors[0]));
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("(A?) -> number | *error-type*", toString(requireType("f")));
|
||||
else
|
||||
CHECK_EQ("(A?) -> number", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_union_functions")
|
||||
@ -257,37 +256,49 @@ TEST_CASE_FIXTURE(Fixture, "optional_union_functions")
|
||||
local a = {}
|
||||
function a.foo(x:number, y:number) return x + y end
|
||||
type A = typeof(a)
|
||||
local b: A? = a
|
||||
local c = b.foo(1, 2)
|
||||
function f(b: A?)
|
||||
return b.foo(1, 2)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("c"));
|
||||
|
||||
CHECK_EQ("Value of type 'A?' could be nil", toString(result.errors[0]));
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("(A?) -> number | *error-type*", toString(requireType("f")));
|
||||
else
|
||||
CHECK_EQ("(A?) -> number", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_union_methods")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a = {}
|
||||
function a:foo(x:number, y:number) return x + y end
|
||||
type A = typeof(a)
|
||||
local b: A? = a
|
||||
local c = b:foo(1, 2)
|
||||
local a = {}
|
||||
function a:foo(x:number, y:number) return x + y end
|
||||
type A = typeof(a)
|
||||
function f(b: A?)
|
||||
return b:foo(1, 2)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("c"));
|
||||
|
||||
CHECK_EQ("Value of type 'A?' could be nil", toString(result.errors[0]));
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("(A?) -> number | *error-type*", toString(requireType("f")));
|
||||
else
|
||||
CHECK_EQ("(A?) -> number", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_union_follow")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local y: number? = 2
|
||||
local x = y
|
||||
local function f(a: number, b: typeof(x), c: typeof(x)) return -a end
|
||||
return f()
|
||||
local y: number? = 2
|
||||
local x = y
|
||||
function f(a: number, b: number?, c: number?) return -a end
|
||||
return f()
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -302,10 +313,11 @@ return f()
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_field_access_error")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type A = { x: number }
|
||||
local b: A? = { x = 2 }
|
||||
local c = b.x
|
||||
local d = b.y
|
||||
type A = { x: number }
|
||||
function f(b: A?)
|
||||
local c = b.x
|
||||
local d = b.y
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
@ -317,9 +329,10 @@ local d = b.y
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_index_error")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type A = {number}
|
||||
local a: A? = {1, 2, 3}
|
||||
local b = a[1]
|
||||
type A = {number}
|
||||
function f(a: A?)
|
||||
local b = a[1]
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -329,9 +342,10 @@ local b = a[1]
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_call_error")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type A = (number) -> number
|
||||
local a: A? = function(a) return -a end
|
||||
local b = a(4)
|
||||
type A = (number) -> number
|
||||
function f(a: A?)
|
||||
local b = a(4)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -341,18 +355,23 @@ local b = a(4)
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_assignment_errors")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type A = { x: number }
|
||||
local a: A? = { x = 2 }
|
||||
a.x = 2
|
||||
type A = { x: number }
|
||||
function f(a: A?)
|
||||
a.x = 2
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ("Value of type 'A?' could be nil", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
result = check(R"(
|
||||
type A = { x: number } & { y: number }
|
||||
local a: A? = { x = 2, y = 3 }
|
||||
a.x = 2
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_assignment_errors_2")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type A = { x: number } & { y: number }
|
||||
function f(a: A?)
|
||||
a.x = 2
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -366,9 +385,10 @@ a.x = 2
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_length_error")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type A = {number}
|
||||
local a: A? = {1, 2, 3}
|
||||
local b = #a
|
||||
type A = {number}
|
||||
function f(a: A?)
|
||||
local b = #a
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -378,27 +398,27 @@ local b = #a
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_missing_key_error_details")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type A = { x: number, y: number }
|
||||
type B = { x: number, y: number }
|
||||
type C = { x: number }
|
||||
type D = { x: number }
|
||||
type A = { x: number, y: number }
|
||||
type B = { x: number, y: number }
|
||||
type C = { x: number }
|
||||
type D = { x: number }
|
||||
|
||||
local a: A|B|C|D
|
||||
local b = a.y
|
||||
function f(a: A | B | C | D)
|
||||
local y = a.y
|
||||
local z = a.z
|
||||
end
|
||||
|
||||
local c: A|(B|C)?|D
|
||||
local d = c.y
|
||||
|
||||
local e = a.z
|
||||
function g(c: A | B | C | D | nil)
|
||||
local d = c.y
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||
CHECK_EQ("Key 'y' is missing from 'C', 'D' in the type 'A | B | C | D'", toString(result.errors[0]));
|
||||
CHECK_EQ("Type 'A | B | C | D' does not have key 'z'", toString(result.errors[1]));
|
||||
|
||||
CHECK_EQ("Value of type '(A | B | C | D)?' could be nil", toString(result.errors[1]));
|
||||
CHECK_EQ("Key 'y' is missing from 'C', 'D' in the type 'A | B | C | D'", toString(result.errors[2]));
|
||||
|
||||
CHECK_EQ("Type 'A | B | C | D' does not have key 'z'", toString(result.errors[3]));
|
||||
CHECK_EQ("Value of type '(A | B | C | D)?' could be nil", toString(result.errors[2]));
|
||||
CHECK_EQ("Key 'y' is missing from 'C', 'D' in the type 'A | B | C | D'", toString(result.errors[3]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_iteration")
|
||||
@ -470,25 +490,27 @@ type Z = { z: number }
|
||||
|
||||
type XYZ = X | Y | Z
|
||||
|
||||
local a: XYZ
|
||||
local b: { w: number } = a
|
||||
function f(a: XYZ)
|
||||
local b: { w: number } = a
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
|
||||
CHECK_EQ(tm->reason, "Not all union options are compatible.");
|
||||
|
||||
CHECK_EQ("X | Y | Z", toString(tm->givenType));
|
||||
|
||||
const TableType* expected = get<TableType>(tm->wantedType);
|
||||
REQUIRE(expected);
|
||||
CHECK_EQ(TableState::Sealed, expected->state);
|
||||
CHECK_EQ(1, expected->props.size());
|
||||
auto propW = expected->props.find("w");
|
||||
REQUIRE_NE(expected->props.end(), propW);
|
||||
CHECK_EQ("number", toString(propW->second.type()));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
"Type 'X | Y | Z' could not be converted into '{ w: number }'; type X | Y | Z[0] (X) is not a subtype of { w: number } ({ w: number })\n\t"
|
||||
"type X | Y | Z[1] (Y) is not a subtype of { w: number } ({ w: number })\n\t"
|
||||
"type X | Y | Z[2] (Z) is not a subtype of { w: number } ({ w: number })");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'X | Y | Z' could not be converted into '{| w: number |}'
|
||||
caused by:
|
||||
Not all union options are compatible.
|
||||
Table type 'X' not compatible with type '{| w: number |}' because the former is missing field 'w')");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_union_all")
|
||||
@ -545,14 +567,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect")
|
||||
CheckResult result = check(R"(
|
||||
type A = { x: number, y: (number) -> string } | { z: number, y: (number) -> string }
|
||||
|
||||
local a:A = nil
|
||||
function f(a: A)
|
||||
function a.y(x)
|
||||
return tostring(x * 2)
|
||||
end
|
||||
|
||||
function a.y(x)
|
||||
return tostring(x * 2)
|
||||
end
|
||||
|
||||
function a.y(x: string): number
|
||||
return tonumber(x) or 0
|
||||
function a.y(x: string): number
|
||||
return tonumber(x) or 0
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
@ -568,12 +590,13 @@ could not be converted into
|
||||
TEST_CASE_FIXTURE(Fixture, "union_true_and_false")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : boolean
|
||||
local y1 : (true | false) = x -- OK
|
||||
local y2 : (true | false | (string & number)) = x -- OK
|
||||
local y3 : (true | (string & number) | false) = x -- OK
|
||||
local y4 : (true | (boolean & true) | false) = x -- OK
|
||||
)");
|
||||
function f(x : boolean)
|
||||
local y1 : (true | false) = x -- OK
|
||||
local y2 : (true | false | (string & number)) = x -- OK
|
||||
local y3 : (true | (string & number) | false) = x -- OK
|
||||
local y4 : (true | (boolean & true) | false) = x -- OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
@ -581,8 +604,9 @@ TEST_CASE_FIXTURE(Fixture, "union_true_and_false")
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : (number) -> number?
|
||||
local y : ((number?) -> number?) | ((number) -> number) = x -- OK
|
||||
function f(x : (number) -> number?)
|
||||
local y : ((number?) -> number?) | ((number) -> number) = x -- OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -591,8 +615,9 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions")
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_generic_functions")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : <a>(a) -> a?
|
||||
local y : (<a>(a?) -> a?) | (<b>(b) -> b) = x -- Not OK
|
||||
function f(x : <a>(a) -> a?)
|
||||
local y : (<a>(a?) -> a?) | (<b>(b) -> b) = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
// TODO: should this example typecheck?
|
||||
@ -602,8 +627,9 @@ TEST_CASE_FIXTURE(Fixture, "union_of_generic_functions")
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_generic_typepack_functions")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : <a...>(number, a...) -> (number?, a...)
|
||||
local y : (<a...>(number?, a...) -> (number?, a...)) | (<b...>(number, b...) -> (number, b...)) = x -- Not OK
|
||||
function f(x : <a...>(number, a...) -> (number?, a...))
|
||||
local y : (<a...>(number?, a...) -> (number?, a...)) | (<b...>(number, b...) -> (number, b...)) = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
// TODO: should this example typecheck?
|
||||
@ -613,12 +639,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_generic_typepack_functions")
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f<a,b>()
|
||||
local x : (a) -> a?
|
||||
local y : ((a?) -> nil) | ((a) -> a) = x -- OK
|
||||
local z : ((b?) -> nil) | ((b) -> b) = x -- Not OK
|
||||
end
|
||||
)");
|
||||
function f<a,b>()
|
||||
function g(x : (a) -> a?)
|
||||
local y : ((a?) -> nil) | ((a) -> a) = x -- OK
|
||||
local z : ((b?) -> nil) | ((b) -> b) = x -- Not OK
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
@ -628,12 +655,13 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics")
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f<a...>()
|
||||
local x : (number, a...) -> (number?, a...)
|
||||
local y : ((number | string, a...) -> (number, a...)) | ((number?, a...) -> (nil, a...)) = x -- OK
|
||||
local z : ((number) -> number) | ((number?, a...) -> (number?, a...)) = x -- Not OK
|
||||
end
|
||||
)");
|
||||
function f<a...>()
|
||||
function g(x : (number, a...) -> (number?, a...))
|
||||
local y : ((number | string, a...) -> (number, a...)) | ((number?, a...) -> (nil, a...)) = x -- OK
|
||||
local z : ((number) -> number) | ((number?, a...) -> (number?, a...)) = x -- Not OK
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
@ -646,9 +674,10 @@ could not be converted into
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : (number) -> number?
|
||||
local y : ((number?) -> number) | ((number | string) -> nil) = x -- OK
|
||||
local z : ((number, string?) -> number) | ((number) -> nil) = x -- Not OK
|
||||
function f(x : (number) -> number?)
|
||||
local y : ((number?) -> number) | ((number | string) -> nil) = x -- OK
|
||||
local z : ((number, string?) -> number) | ((number) -> nil) = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -661,11 +690,11 @@ could not be converted into
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
|
||||
{
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : () -> (number | string)
|
||||
local y : (() -> number) | (() -> string) = x -- OK
|
||||
local z : (() -> number) | (() -> (string, string)) = x -- Not OK
|
||||
function f(x : () -> (number | string))
|
||||
local y : (() -> number) | (() -> string) = x -- OK
|
||||
local z : (() -> number) | (() -> (string, string)) = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -678,11 +707,11 @@ could not be converted into
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
|
||||
{
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : (...nil) -> (...number?)
|
||||
local y : ((...string?) -> (...number)) | ((...number?) -> nil) = x -- OK
|
||||
local z : ((...string?) -> (...number)) | ((...string?) -> nil) = x -- OK
|
||||
function f(x : (...nil) -> (...number?))
|
||||
local y : ((...string?) -> (...number)) | ((...number?) -> nil) = x -- OK
|
||||
local z : ((...string?) -> (...number)) | ((...string?) -> nil) = x -- OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -695,11 +724,11 @@ could not be converted into
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
|
||||
{
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : (number) -> ()
|
||||
local y : ((number?) -> ()) | ((...number) -> ()) = x -- OK
|
||||
local z : ((number?) -> ()) | ((...number?) -> ()) = x -- Not OK
|
||||
function f(x : (number) -> ())
|
||||
local y : ((number?) -> ()) | ((...number) -> ()) = x -- OK
|
||||
local z : ((number?) -> ()) | ((...number?) -> ()) = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -712,11 +741,11 @@ could not be converted into
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics")
|
||||
{
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : () -> (number?, ...number)
|
||||
local y : (() -> (...number)) | (() -> nil) = x -- OK
|
||||
local z : (() -> (...number)) | (() -> number) = x -- OK
|
||||
function f(x : () -> (number?, ...number))
|
||||
local y : (() -> (...number)) | (() -> nil) = x -- OK
|
||||
local z : (() -> (...number)) | (() -> number) = x -- OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -782,9 +811,9 @@ TEST_CASE_FIXTURE(Fixture, "union_table_any_property")
|
||||
TEST_CASE_FIXTURE(Fixture, "union_function_any_args")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local sup : ((...any) -> (...any))?
|
||||
local sub : ((number) -> (...any))
|
||||
sup = sub
|
||||
function f(sup : ((...any) -> (...any))?, sub : ((number) -> (...any)))
|
||||
sup = sub
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -793,9 +822,9 @@ TEST_CASE_FIXTURE(Fixture, "union_function_any_args")
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_any")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local sup : any?
|
||||
local sub : number
|
||||
sup = sub
|
||||
function f(sup : any?, sub : number)
|
||||
sup = sub
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -844,9 +873,9 @@ TEST_CASE_FIXTURE(Fixture, "suppress_errors_for_prop_lookup_of_a_union_that_incl
|
||||
registerHiddenTypes(&frontend);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a = 5 :: err | Not<nil>
|
||||
|
||||
local b = a.foo
|
||||
function f(a: err | Not<nil>)
|
||||
local b = a.foo
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
@ -215,8 +215,9 @@ TEST_CASE_FIXTURE(Fixture, "assign_to_global_which_is_never")
|
||||
TEST_CASE_FIXTURE(Fixture, "assign_to_prop_which_is_never")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local t: never
|
||||
t.x = 5
|
||||
local function f(t: never)
|
||||
t.x = 5
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -225,8 +226,9 @@ TEST_CASE_FIXTURE(Fixture, "assign_to_prop_which_is_never")
|
||||
TEST_CASE_FIXTURE(Fixture, "assign_to_subscript_which_is_never")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local t: never
|
||||
t[5] = 7
|
||||
local function f(t: never)
|
||||
t[5] = 7
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -257,8 +259,12 @@ TEST_CASE_FIXTURE(Fixture, "index_on_union_of_tables_for_properties_that_is_neve
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type Disjoint = {foo: never, bar: unknown, tag: "ok"} | {foo: never, baz: unknown, tag: "err"}
|
||||
local disjoint: Disjoint = {foo = 5 :: never, bar = true, tag = "ok"}
|
||||
local foo = disjoint.foo
|
||||
|
||||
function f(disjoint: Disjoint)
|
||||
return disjoint.foo
|
||||
end
|
||||
|
||||
local foo = f({foo = 5 :: never, bar = true, tag = "ok"})
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -270,8 +276,12 @@ TEST_CASE_FIXTURE(Fixture, "index_on_union_of_tables_for_properties_that_is_sort
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type Disjoint = {foo: string, bar: unknown, tag: "ok"} | {foo: never, baz: unknown, tag: "err"}
|
||||
local disjoint: Disjoint = {foo = 5 :: never, bar = true, tag = "ok"}
|
||||
local foo = disjoint.foo
|
||||
|
||||
function f(disjoint: Disjoint)
|
||||
return disjoint.foo
|
||||
end
|
||||
|
||||
local foo = f({foo = 5 :: never, bar = true, tag = "ok"})
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
@ -1,2 +1,2 @@
|
||||
-- should be required using the paths array in the parent directory's .luaurc
|
||||
return require("@global_library")
|
||||
return require("global_library")
|
||||
|
@ -1,2 +1,2 @@
|
||||
-- should be required using the paths array in .luaurc
|
||||
return require("@library")
|
||||
return require("library")
|
||||
|
@ -1,3 +1,3 @@
|
||||
local result = require("./dependency")
|
||||
local result = require("dependency")
|
||||
result[#result+1] = "required into module"
|
||||
return result
|
||||
|
@ -1,17 +1,13 @@
|
||||
AnnotationTests.typeof_expr
|
||||
AstQuery.last_argument_function_call_type
|
||||
AstQuery::getDocumentationSymbolAtPosition.class_method
|
||||
AstQuery::getDocumentationSymbolAtPosition.overloaded_class_method
|
||||
AstQuery::getDocumentationSymbolAtPosition.table_overloaded_function_prop
|
||||
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
|
||||
AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg
|
||||
AutocompleteTest.autocomplete_interpolated_string_as_singleton
|
||||
AutocompleteTest.autocomplete_response_perf1
|
||||
AutocompleteTest.autocomplete_string_singleton_equality
|
||||
AutocompleteTest.autocomplete_string_singleton_escape
|
||||
AutocompleteTest.autocomplete_string_singletons
|
||||
AutocompleteTest.do_wrong_compatible_nonself_calls
|
||||
AutocompleteTest.no_incompatible_self_calls_on_class
|
||||
AutocompleteTest.string_singleton_in_if_statement
|
||||
AutocompleteTest.suggest_external_module_type
|
||||
AutocompleteTest.type_correct_expected_argument_type_pack_suggestion
|
||||
@ -33,7 +29,6 @@ BuiltinTests.assert_returns_false_and_string_iff_it_knows_the_first_argument_can
|
||||
BuiltinTests.bad_select_should_not_crash
|
||||
BuiltinTests.coroutine_resume_anything_goes
|
||||
BuiltinTests.debug_info_is_crazy
|
||||
BuiltinTests.dont_add_definitions_to_persistent_types
|
||||
BuiltinTests.global_singleton_types_are_sealed
|
||||
BuiltinTests.gmatch_capture_types
|
||||
BuiltinTests.gmatch_capture_types2
|
||||
@ -62,7 +57,6 @@ 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_argument2
|
||||
BuiltinTests.string_match
|
||||
BuiltinTests.table_concat_returns_string
|
||||
BuiltinTests.table_dot_remove_optionally_returns_generic
|
||||
BuiltinTests.table_freeze_is_generic
|
||||
@ -100,16 +94,15 @@ ControlFlowAnalysis.if_not_x_then_assert_false
|
||||
ControlFlowAnalysis.if_not_x_then_error
|
||||
ControlFlowAnalysis.prototyping_and_visiting_alias_has_the_same_scope_breaking
|
||||
ControlFlowAnalysis.prototyping_and_visiting_alias_has_the_same_scope_continuing
|
||||
ControlFlowAnalysis.tagged_unions
|
||||
ControlFlowAnalysis.tagged_unions_breaking
|
||||
ControlFlowAnalysis.tagged_unions_continuing
|
||||
ControlFlowAnalysis.type_alias_does_not_leak_out_breaking
|
||||
ControlFlowAnalysis.type_alias_does_not_leak_out_continuing
|
||||
DefinitionTests.class_definition_function_prop
|
||||
DefinitionTests.class_definition_indexer
|
||||
DefinitionTests.class_definition_overload_metamethods
|
||||
DefinitionTests.class_definition_string_props
|
||||
DefinitionTests.declaring_generic_functions
|
||||
DefinitionTests.definition_file_class_function_args
|
||||
DefinitionTests.definition_file_classes
|
||||
Differ.equal_generictp_cyclic
|
||||
Differ.equal_table_A_B_C
|
||||
@ -142,7 +135,6 @@ GenericsTests.apply_type_function_nested_generics1
|
||||
GenericsTests.apply_type_function_nested_generics2
|
||||
GenericsTests.better_mismatch_error_messages
|
||||
GenericsTests.bound_tables_do_not_clone_original_fields
|
||||
GenericsTests.calling_self_generic_methods
|
||||
GenericsTests.check_generic_function
|
||||
GenericsTests.check_generic_local_function
|
||||
GenericsTests.check_generic_typepack_function
|
||||
@ -230,6 +222,7 @@ Linter.DeprecatedApiFenv
|
||||
Linter.FormatStringTyped
|
||||
Linter.TableOperationsIndexer
|
||||
ModuleTests.clone_self_property
|
||||
Negations.cofinite_strings_can_be_compared_for_equality
|
||||
Negations.negated_string_is_a_subtype_of_string
|
||||
NonstrictModeTests.inconsistent_module_return_types_are_ok
|
||||
NonstrictModeTests.infer_nullary_function
|
||||
@ -244,6 +237,7 @@ Normalize.negations_of_tables
|
||||
Normalize.specific_functions_cannot_be_negated
|
||||
ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal
|
||||
ProvisionalTests.choose_the_right_overload_for_pcall
|
||||
ProvisionalTests.discriminate_from_x_not_equal_to_nil
|
||||
ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack
|
||||
ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean
|
||||
ProvisionalTests.expected_type_should_be_a_helpful_deduction_guide_for_function_calls
|
||||
@ -272,19 +266,27 @@ RefinementTest.assert_non_binary_expressions_actually_resolve_constraints
|
||||
RefinementTest.correctly_lookup_a_shadowed_local_that_which_was_previously_refined
|
||||
RefinementTest.correctly_lookup_property_whose_base_was_previously_refined
|
||||
RefinementTest.dataflow_analysis_can_tell_refinements_when_its_appropriate_to_refine_into_nil_or_never
|
||||
RefinementTest.discriminate_from_isa_of_x
|
||||
RefinementTest.discriminate_from_truthiness_of_x
|
||||
RefinementTest.discriminate_tag
|
||||
RefinementTest.discriminate_tag_with_implicit_else
|
||||
RefinementTest.either_number_or_string
|
||||
RefinementTest.else_with_no_explicit_expression_should_also_refine_the_tagged_union
|
||||
RefinementTest.fail_to_refine_a_property_of_subscript_expression
|
||||
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
|
||||
RefinementTest.function_call_with_colon_after_refining_not_to_be_nil
|
||||
RefinementTest.impossible_type_narrow_is_not_an_error
|
||||
RefinementTest.index_on_a_refined_property
|
||||
RefinementTest.isa_type_refinement_must_be_known_ahead_of_time
|
||||
RefinementTest.luau_polyfill_isindexkey_refine_conjunction
|
||||
RefinementTest.luau_polyfill_isindexkey_refine_conjunction_variant
|
||||
RefinementTest.merge_should_be_fully_agnostic_of_hashmap_ordering
|
||||
RefinementTest.narrow_property_of_a_bounded_variable
|
||||
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
|
||||
RefinementTest.not_t_or_some_prop_of_t
|
||||
RefinementTest.refine_a_param_that_got_resolved_during_constraint_solving_stage
|
||||
RefinementTest.refine_a_property_of_some_global
|
||||
RefinementTest.refine_param_of_type_folder_or_part_without_using_typeof
|
||||
RefinementTest.refine_unknown_to_table_then_clone_it
|
||||
RefinementTest.refinements_should_preserve_error_suppression
|
||||
RefinementTest.string_not_equal_to_string_or_nil
|
||||
@ -297,6 +299,7 @@ RefinementTest.typeguard_cast_free_table_to_vector
|
||||
RefinementTest.typeguard_in_assert_position
|
||||
RefinementTest.typeguard_in_if_condition_position
|
||||
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
|
||||
RefinementTest.x_is_not_instance_or_else_not_part
|
||||
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
|
||||
@ -459,7 +462,6 @@ TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error
|
||||
TypeInfer.dont_ice_when_failing_the_occurs_check
|
||||
TypeInfer.dont_report_type_errors_within_an_AstExprError
|
||||
TypeInfer.dont_report_type_errors_within_an_AstStatError
|
||||
TypeInfer.follow_on_new_types_in_substitution
|
||||
TypeInfer.globals
|
||||
TypeInfer.globals2
|
||||
TypeInfer.globals_are_banned_in_strict_mode
|
||||
@ -555,6 +557,7 @@ TypeInferFunctions.other_things_are_not_related_to_function
|
||||
TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible
|
||||
TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2
|
||||
TypeInferFunctions.record_matching_overload
|
||||
TypeInferFunctions.regex_benchmark_string_format_minimization
|
||||
TypeInferFunctions.report_exiting_without_return_nonstrict
|
||||
TypeInferFunctions.report_exiting_without_return_strict
|
||||
TypeInferFunctions.return_type_by_overload
|
||||
@ -600,6 +603,7 @@ TypeInferLoops.unreachable_code_after_infinite_loop
|
||||
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
||||
TypeInferLoops.while_loop
|
||||
TypeInferModules.bound_free_table_export_is_ok
|
||||
TypeInferModules.custom_require_global
|
||||
TypeInferModules.do_not_modify_imported_types
|
||||
TypeInferModules.do_not_modify_imported_types_5
|
||||
TypeInferModules.require
|
||||
@ -645,7 +649,6 @@ TypeInferPrimitives.CheckMethodsOfNumber
|
||||
TypeInferPrimitives.string_function_indirect
|
||||
TypeInferPrimitives.string_index
|
||||
TypeInferUnknownNever.assign_to_local_which_is_never
|
||||
TypeInferUnknownNever.assign_to_prop_which_is_never
|
||||
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never
|
||||
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never
|
||||
TypeInferUnknownNever.length_of_never
|
||||
@ -663,42 +666,25 @@ TypeSingletons.error_detailed_tagged_union_mismatch_string
|
||||
TypeSingletons.function_args_infer_singletons
|
||||
TypeSingletons.function_call_with_singletons
|
||||
TypeSingletons.function_call_with_singletons_mismatch
|
||||
TypeSingletons.overloaded_function_call_with_singletons_mismatch
|
||||
TypeSingletons.return_type_of_f_is_not_widened
|
||||
TypeSingletons.table_properties_type_error_escapes
|
||||
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
|
||||
TypeSingletons.widening_happens_almost_everywhere
|
||||
TypeStatesTest.invalidate_type_refinements_upon_assignments
|
||||
TypeStatesTest.local_t_is_assigned_a_fresh_table_with_x_assigned_a_union_and_then_assert_restricts_actual_outflow_of_types
|
||||
UnionTypes.disallow_less_specific_assign
|
||||
UnionTypes.disallow_less_specific_assign2
|
||||
UnionTypes.error_detailed_optional
|
||||
UnionTypes.error_detailed_union_all
|
||||
UnionTypes.error_detailed_union_part
|
||||
UnionTypes.error_takes_optional_arguments
|
||||
UnionTypes.generic_function_with_optional_arg
|
||||
UnionTypes.index_on_a_union_type_with_missing_property
|
||||
UnionTypes.index_on_a_union_type_with_mixed_types
|
||||
UnionTypes.index_on_a_union_type_with_one_optional_property
|
||||
UnionTypes.index_on_a_union_type_with_one_property_of_type_any
|
||||
UnionTypes.index_on_a_union_type_with_property_guaranteed_to_exist
|
||||
UnionTypes.index_on_a_union_type_works_at_arbitrary_depth
|
||||
UnionTypes.less_greedy_unification_with_union_types
|
||||
UnionTypes.optional_arguments_table
|
||||
UnionTypes.optional_assignment_errors
|
||||
UnionTypes.optional_call_error
|
||||
UnionTypes.optional_field_access_error
|
||||
UnionTypes.optional_index_error
|
||||
UnionTypes.optional_length_error
|
||||
UnionTypes.optional_missing_key_error_details
|
||||
UnionTypes.optional_union_follow
|
||||
UnionTypes.optional_union_functions
|
||||
UnionTypes.optional_union_members
|
||||
UnionTypes.optional_union_methods
|
||||
UnionTypes.return_types_can_be_disjoint
|
||||
UnionTypes.table_union_write_indirect
|
||||
UnionTypes.unify_unsealed_table_union_check
|
||||
UnionTypes.union_of_functions
|
||||
UnionTypes.union_function_any_args
|
||||
UnionTypes.union_of_functions_mentioning_generic_typepacks
|
||||
UnionTypes.union_of_functions_mentioning_generics
|
||||
UnionTypes.union_of_functions_with_mismatching_arg_arities
|
||||
@ -706,5 +692,4 @@ UnionTypes.union_of_functions_with_mismatching_arg_variadics
|
||||
UnionTypes.union_of_functions_with_mismatching_result_arities
|
||||
UnionTypes.union_of_functions_with_mismatching_result_variadics
|
||||
UnionTypes.union_of_functions_with_variadics
|
||||
UnionTypes.union_true_and_false
|
||||
VisitType.throw_when_limit_is_exceeded
|
||||
|
Loading…
Reference in New Issue
Block a user