Merge branch 'upstream' into merge

This commit is contained in:
Aaron Weiss 2023-12-15 12:53:11 -08:00
commit d6226187b0
38 changed files with 883 additions and 534 deletions

View File

@ -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
{

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -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
{

View File

@ -162,6 +162,8 @@ struct BuiltinTypeFamilies
TypeFamily leFamily;
TypeFamily eqFamily;
TypeFamily refineFamily;
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
};

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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();

View File

@ -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 {};
}

View File

@ -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 ? " | " : " & ";

View File

@ -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;
}
}

View File

@ -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))
{

View File

@ -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}
{
}

View File

@ -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;

View File

@ -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());
}
}

View File

@ -285,9 +285,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 +347,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)

View File

@ -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);
}

View File

@ -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));

View File

@ -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');

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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")
)");

View File

@ -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

View File

@ -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();

View File

@ -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);

View File

@ -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

View File

@ -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")

View File

@ -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();

View File

@ -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);

View File

@ -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);

View File

@ -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")

View File

@ -1,2 +1,2 @@
-- should be required using the paths array in .luaurc
return require("@library")
return require("library")

View File

@ -1,3 +1,3 @@
local result = require("./dependency")
local result = require("dependency")
result[#result+1] = "required into module"
return result

View File

@ -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