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:
aaron 2023-12-15 13:29:06 -08:00 committed by GitHub
parent 2173938eb0
commit ff502f0943
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 884 additions and 534 deletions

View File

@ -197,24 +197,6 @@ struct UnpackConstraint
bool resultIsLValue = false; 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 // resultType ~ T0 op T1 op ... op TN
// //
// op is either union or intersection. If any of the input types are blocked, // 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, using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, IterableConstraint,
NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, PrimitiveTypeConstraint, HasPropConstraint, SetPropConstraint, NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, PrimitiveTypeConstraint, HasPropConstraint, SetPropConstraint,
SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, RefineConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint>; SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint>;
struct Constraint struct Constraint
{ {

View File

@ -132,7 +132,6 @@ struct ConstraintSolver
bool tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const UnpackConstraint& 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 SetOpConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const ReduceConstraint& 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); 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); DfgScope* childScope(DfgScope* scope, DfgScope::ScopeType scopeType = DfgScope::Linear);
void join(DfgScope* p, DfgScope* a, DfgScope* b); void join(DfgScope* p, DfgScope* a, DfgScope* b);
void joinBindings(DfgScope::Bindings& p, const DfgScope::Bindings& a, const DfgScope::Bindings& b); void joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b);
void joinProps(DfgScope::Props& p, const DfgScope::Props& a, const DfgScope::Props& b); void joinProps(DfgScope* p, const DfgScope& a, const DfgScope& b);
DefId lookup(DfgScope* scope, Symbol symbol); DefId lookup(DfgScope* scope, Symbol symbol);
DefId lookup(DfgScope* scope, DefId def, const std::string& key); DefId lookup(DfgScope* scope, DefId def, const std::string& key);

View File

@ -118,6 +118,12 @@ public:
class const_iterator class const_iterator
{ {
public: 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) const_iterator(typename Impl::const_iterator impl, typename Impl::const_iterator end)
: impl(impl) : impl(impl)
, end(end) , 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 // Types for true and false
struct BooleanSingleton struct BooleanSingleton
{ {

View File

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

View File

@ -200,6 +200,23 @@ struct AstJsonEncoder : public AstVisitor
{ {
writeString(name.value ? name.value : ""); 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) void write(const Position& position)
{ {
@ -848,6 +865,7 @@ struct AstJsonEncoder : public AstVisitor
PROP(generics); PROP(generics);
PROP(genericPacks); PROP(genericPacks);
PROP(argTypes); PROP(argTypes);
PROP(argNames);
PROP(returnTypes); 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 bool visit(class AstExprGroup* node) override
{ {
write(node); write(node);

View File

@ -257,7 +257,7 @@ void ConstraintGenerator::unionRefinements(const RefinementContext& lhs, const R
return types[0]; return types[0];
else if (2 == types.size()) 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]); SimplifyResult sr = simplifyIntersection(builtinTypes, arena, types[0], types[1]);
if (sr.blockedTypes.empty()) if (sr.blockedTypes.empty())
return sr.result; return sr.result;
@ -441,10 +441,14 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
{ {
if (mustDeferIntersection(ty) || mustDeferIntersection(dt)) if (mustDeferIntersection(ty) || mustDeferIntersection(dt))
{ {
TypeId r = arena->addType(BlockedType{}); TypeId resultType = arena->addType(TypeFamilyInstanceType{
addConstraint(scope, location, RefineConstraint{RefineConstraint::Intersection, r, ty, dt}); NotNull{&kBuiltinTypeFamilies.refineFamily},
{ty, dt},
{},
});
addConstraint(scope, location, ReduceConstraint{resultType});
ty = r; ty = resultType;
} }
else else
{ {
@ -1005,9 +1009,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* ass
checkLValue(scope, lvalue, assignee); checkLValue(scope, lvalue, assignee);
assignees.push_back(assignee); assignees.push_back(assignee);
DefId def = dfg->getDef(lvalue);
scope->lvalueTypes[def] = assignee;
} }
TypePackId resultPack = checkPack(scope, assign->values).tp; 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); success = tryDispatch(*sottc, constraint);
else if (auto uc = get<UnpackConstraint>(*constraint)) else if (auto uc = get<UnpackConstraint>(*constraint))
success = tryDispatch(*uc, constraint); success = tryDispatch(*uc, constraint);
else if (auto rc = get<RefineConstraint>(*constraint))
success = tryDispatch(*rc, constraint, force);
else if (auto soc = get<SetOpConstraint>(*constraint)) else if (auto soc = get<SetOpConstraint>(*constraint))
success = tryDispatch(*soc, constraint, force); success = tryDispatch(*soc, constraint, force);
else if (auto rc = get<ReduceConstraint>(*constraint)) 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 // 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 // cycle infinitely, we need to scan the type function for cases where we
// expand the same alias with different type saturatedTypeArguments. See // expand the same alias with different type saturatedTypeArguments. See
// https://github.com/Roblox/luau/pull/68 for the RFC responsible for this. // https://github.com/luau-lang/luau/pull/68 for the RFC responsible for
// This is a little nicer than using a recursion limit because we can catch // this. This is a little nicer than using a recursion limit because we can
// the infinite expansion before actually trying to expand it. // catch the infinite expansion before actually trying to expand it.
InfiniteTypeFinder itf{this, signature, constraint->scope}; InfiniteTypeFinder itf{this, signature, constraint->scope};
itf.traverse(tf->type); itf.traverse(tf->type);
@ -1505,151 +1503,6 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
return true; 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 ConstraintSolver::tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force)
{ {
bool blocked = false; 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) void DataFlowGraphBuilder::join(DfgScope* p, DfgScope* a, DfgScope* b)
{ {
joinBindings(p->bindings, a->bindings, b->bindings); joinBindings(p, *a, *b);
joinProps(p->props, a->props, b->props); 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)) if (auto def2 = b.bindings.find(sym))
p[sym] = defArena->phi(NotNull{def1}, NotNull{*def2}); p->bindings[sym] = defArena->phi(NotNull{def1}, NotNull{*def2});
else if (auto def2 = p.find(sym)) else if (auto def2 = p->lookup(sym))
p[sym] = defArena->phi(NotNull{def1}, NotNull{*def2}); 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)) if (auto def2 = p->lookup(sym))
p[sym] = defArena->phi(NotNull{def1}, NotNull{*def2}); 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) for (const auto& [k, defA] : a)
{ {
if (auto it = b.find(k); it != b.end()) if (auto it = b.find(k); it != b.end())
p[k] = defArena->phi(NotNull{it->second}, NotNull{defA}); p[k] = defArena->phi(NotNull{it->second}, NotNull{defA});
else if (auto it = p.find(k); it != p.end()) else if (auto it = p.find(k); it != p.end())
p[k] = defArena->phi(NotNull{it->second}, NotNull{defA}); 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 else
p[k] = defA; p[k] = defA;
} }
@ -220,27 +223,29 @@ void DataFlowGraphBuilder::joinProps(DfgScope::Props& p, const DfgScope::Props&
continue; continue;
else if (auto it = p.find(k); it != p.end()) else if (auto it = p.find(k); it != p.end())
p[k] = defArena->phi(NotNull{it->second}, NotNull{defB}); 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 else
p[k] = defB; p[k] = defB;
} }
}; };
for (const auto& [def, a1] : a) for (const auto& [def, a1] : a.props)
{ {
p.try_insert(def, {}); result->props.try_insert(def, {});
if (auto a2 = b.find(def)) if (auto a2 = b.props.find(def))
phinodify(p[def], a1, *a2); phinodify(result, a1, *a2, NotNull{def});
else if (auto a2 = p.find(def)) else if (auto a2 = result->props.find(def))
phinodify(p[def], a1, *a2); phinodify(result, a1, *a2, NotNull{def});
} }
for (const auto& [def, a1] : b) for (const auto& [def, a1] : b.props)
{ {
p.try_insert(def, {}); result->props.try_insert(def, {});
if (a.find(def)) if (a.props.find(def))
continue; continue;
else if (auto a2 = p.find(def)) else if (auto a2 = result->props.find(def))
phinodify(p[def], a1, *a2); 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. // make sure that the non-aliased defs are also marked as a subscript for refinements.
bool subscripted = i < defs.size() && containsSubscriptedDefinition(defs[i]); bool subscripted = i < defs.size() && containsSubscriptedDefinition(defs[i]);
DefId def = defArena->freshCell(subscripted); 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; graph.localDefs[local] = def;
scope->bindings[local] = def; scope->bindings[local] = def;
captures[local].allVersions.push_back(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 {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) DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunction* f)
@ -819,14 +832,20 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunction*
DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprTable* t) DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprTable* t)
{ {
DefId tableCell = defArena->freshCell();
scope->props[tableCell] = {};
for (AstExprTable::Item item : t->items) for (AstExprTable::Item item : t->items)
{ {
DataFlowResult result = visitExpr(scope, item.value);
if (item.key) if (item.key)
{
visitExpr(scope, 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) DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprUnary* u)

View File

@ -37,6 +37,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false) LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
LUAU_FASTFLAGVARIABLE(CorrectEarlyReturnInMarkDirty, false) LUAU_FASTFLAGVARIABLE(CorrectEarlyReturnInMarkDirty, false)
LUAU_FASTFLAGVARIABLE(LuauDefinitionFileSetModuleName, false) LUAU_FASTFLAGVARIABLE(LuauDefinitionFileSetModuleName, false)
LUAU_FASTFLAGVARIABLE(LuauRethrowSingleModuleIce, false)
namespace Luau namespace Luau
{ {
@ -679,8 +680,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptio
sendItemTask(i); sendItemTask(i);
nextItems.clear(); nextItems.clear();
// If we aren't done, but don't have anything processing, we hit a cycle if (FFlag::LuauRethrowSingleModuleIce && processing == 0)
if (remaining != 0 && processing == 0)
{ {
// Typechecking might have been cancelled by user, don't return partial results // Typechecking might have been cancelled by user, don't return partial results
if (cancelled) if (cancelled)
@ -688,9 +688,24 @@ std::vector<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptio
// We might have stopped because of a pending exception // We might have stopped because of a pending exception
if (itemWithException) if (itemWithException)
{
recordItemResult(buildQueueItems[*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(); sendCycleItemTask();

View File

@ -12,6 +12,7 @@
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/TypeFamily.h" #include "Luau/TypeFamily.h"
#include "Luau/Def.h" #include "Luau/Def.h"
#include "Luau/TypeFwd.h"
#include <iostream> #include <iostream>
#include <iterator> #include <iterator>
@ -57,8 +58,6 @@ struct StackPusher
struct NonStrictContext struct NonStrictContext
{ {
std::unordered_map<const Def*, TypeId> context;
NonStrictContext() = default; NonStrictContext() = default;
NonStrictContext(const NonStrictContext&) = delete; NonStrictContext(const NonStrictContext&) = delete;
@ -109,7 +108,12 @@ struct NonStrictContext
// Returns true if the removal was successful // Returns true if the removal was successful
bool remove(const DefId& def) 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 std::optional<TypeId> find(const DefId& def) const
@ -118,6 +122,14 @@ struct NonStrictContext
return find(d); 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: private:
std::optional<TypeId> find(const Def* d) const std::optional<TypeId> find(const Def* d) const
{ {
@ -126,6 +138,9 @@ private:
return {it->second}; return {it->second};
return {}; return {};
} }
std::unordered_map<const Def*, TypeId> context;
}; };
struct NonStrictTypeChecker struct NonStrictTypeChecker
@ -508,8 +523,25 @@ struct NonStrictTypeChecker
// ... // ...
// (unknown^N-1, ~S_N) -> error // (unknown^N-1, ~S_N) -> error
std::vector<TypeId> argTypes; std::vector<TypeId> argTypes;
for (TypeId ty : fn->argTypes) argTypes.reserve(call->args.size);
argTypes.push_back(ty); // 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 // For a checked function, these gotta be the same size
LUAU_ASSERT(call->args.size == argTypes.size()); LUAU_ASSERT(call->args.size == argTypes.size());
for (size_t i = 0; i < call->args.size; i++) for (size_t i = 0; i < call->args.size; i++)
@ -523,7 +555,7 @@ struct NonStrictTypeChecker
TypeId expectedArgType = argTypes[i]; TypeId expectedArgType = argTypes[i];
DefId def = dfg->getDef(arg); DefId def = dfg->getDef(arg);
TypeId runTimeErrorTy = getOrCreateNegation(expectedArgType); 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 // 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) std::optional<TypeId> willRunTimeError(AstExpr* fragment, const NonStrictContext& context)
{ {
DefId def = dfg->getDef(fragment); 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); TypeId actualType = lookupType(fragment);
SubtypingResult r = subtyping.isSubtype(actualType, *contextTy); SubtypingResult r = subtyping.isSubtype(actualType, *contextTy);
if (r.normalizationTooComplex) if (r.normalizationTooComplex)
reportError(NormalizationTooComplex{}, fragment->location); reportError(NormalizationTooComplex{}, fragment->location);
if (r.isSubtype) if (r.isSubtype)
return {actualType}; return {actualType};
}
} }
return {}; return {};
@ -630,15 +667,20 @@ struct NonStrictTypeChecker
std::optional<TypeId> willRunTimeErrorFunctionDefinition(AstLocal* fragment, const NonStrictContext& context) std::optional<TypeId> willRunTimeErrorFunctionDefinition(AstLocal* fragment, const NonStrictContext& context)
{ {
DefId def = dfg->getDef(fragment); 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); if (std::optional<TypeId> contextTy = context.find(def))
SubtypingResult r2 = subtyping.isSubtype(*contextTy, builtinTypes->unknownType); {
if (r1.normalizationTooComplex || r2.normalizationTooComplex) SubtypingResult r1 = subtyping.isSubtype(builtinTypes->unknownType, *contextTy);
reportError(NormalizationTooComplex{}, fragment->location); SubtypingResult r2 = subtyping.isSubtype(*contextTy, builtinTypes->unknownType);
bool isUnknown = r1.isSubtype && r2.isSubtype; if (r1.normalizationTooComplex || r2.normalizationTooComplex)
if (isUnknown) reportError(NormalizationTooComplex{}, fragment->location);
return {builtinTypes->unknownType}; bool isUnknown = r1.isSubtype && r2.isSubtype;
if (isUnknown)
return {builtinTypes->unknownType};
}
} }
return {}; return {};
} }

View File

@ -3,8 +3,10 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Constraint.h" #include "Luau/Constraint.h"
#include "Luau/DenseHash.h"
#include "Luau/Location.h" #include "Luau/Location.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/Set.h"
#include "Luau/TxnLog.h" #include "Luau/TxnLog.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
@ -53,8 +55,8 @@ struct FindCyclicTypes final : TypeVisitor
FindCyclicTypes& operator=(const FindCyclicTypes&) = delete; FindCyclicTypes& operator=(const FindCyclicTypes&) = delete;
bool exhaustive = false; bool exhaustive = false;
std::unordered_set<TypeId> visited; Luau::Set<TypeId> visited{{}};
std::unordered_set<TypePackId> visitedPacks; Luau::Set<TypePackId> visitedPacks{{}};
std::set<TypeId> cycles; std::set<TypeId> cycles;
std::set<TypePackId> cycleTPs; std::set<TypePackId> cycleTPs;
@ -70,17 +72,17 @@ struct FindCyclicTypes final : TypeVisitor
bool visit(TypeId ty) override bool visit(TypeId ty) override
{ {
return visited.insert(ty).second; return visited.insert(ty);
} }
bool visit(TypePackId tp) override bool visit(TypePackId tp) override
{ {
return visitedPacks.insert(tp).second; return visitedPacks.insert(tp);
} }
bool visit(TypeId ty, const FreeType& ft) override bool visit(TypeId ty, const FreeType& ft) override
{ {
if (!visited.insert(ty).second) if (!visited.insert(ty))
return false; return false;
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
@ -102,7 +104,7 @@ struct FindCyclicTypes final : TypeVisitor
bool visit(TypeId ty, const LocalType& lt) override bool visit(TypeId ty, const LocalType& lt) override
{ {
if (!visited.insert(ty).second) if (!visited.insert(ty))
return false; return false;
traverse(lt.domain); traverse(lt.domain);
@ -112,7 +114,7 @@ struct FindCyclicTypes final : TypeVisitor
bool visit(TypeId ty, const TableType& ttv) override bool visit(TypeId ty, const TableType& ttv) override
{ {
if (!visited.insert(ty).second) if (!visited.insert(ty))
return false; return false;
if (ttv.name || ttv.syntheticName) if (ttv.name || ttv.syntheticName)
@ -175,10 +177,11 @@ struct StringifierState
ToStringOptions& opts; ToStringOptions& opts;
ToStringResult& result; ToStringResult& result;
std::unordered_map<TypeId, std::string> cycleNames; DenseHashMap<TypeId, std::string> cycleNames{{}};
std::unordered_map<TypePackId, std::string> cycleTpNames; DenseHashMap<TypePackId, std::string> cycleTpNames{{}};
std::unordered_set<void*> seen; Set<void*> seen{{}};
std::unordered_set<std::string> usedNames; // `$$$` 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; size_t indentation = 0;
bool exhaustive; bool exhaustive;
@ -197,7 +200,7 @@ struct StringifierState
bool hasSeen(const void* tv) bool hasSeen(const void* tv)
{ {
void* ttv = const_cast<void*>(tv); void* ttv = const_cast<void*>(tv);
if (seen.find(ttv) != seen.end()) if (seen.contains(ttv))
return true; return true;
seen.insert(ttv); seen.insert(ttv);
@ -207,9 +210,9 @@ struct StringifierState
void unsee(const void* tv) void unsee(const void* tv)
{ {
void* ttv = const_cast<void*>(tv); void* ttv = const_cast<void*>(tv);
auto iter = seen.find(ttv);
if (iter != seen.end()) if (seen.contains(ttv))
seen.erase(iter); seen.erase(ttv);
} }
std::string getName(TypeId ty) std::string getName(TypeId ty)
@ -222,7 +225,7 @@ struct StringifierState
for (int count = 0; count < 256; ++count) for (int count = 0; count < 256; ++count)
{ {
std::string candidate = generateName(usedNames.size() + count); std::string candidate = generateName(usedNames.size() + count);
if (!usedNames.count(candidate)) if (!usedNames.contains(candidate))
{ {
usedNames.insert(candidate); usedNames.insert(candidate);
n = candidate; n = candidate;
@ -245,7 +248,7 @@ struct StringifierState
for (int count = 0; count < 256; ++count) for (int count = 0; count < 256; ++count)
{ {
std::string candidate = generateName(previousNameIndex + count); std::string candidate = generateName(previousNameIndex + count);
if (!usedNames.count(candidate)) if (!usedNames.contains(candidate))
{ {
previousNameIndex += count; previousNameIndex += count;
usedNames.insert(candidate); usedNames.insert(candidate);
@ -358,10 +361,9 @@ struct TypeStringifier
return; return;
} }
auto it = state.cycleNames.find(tv); if (auto p = state.cycleNames.find(tv))
if (it != state.cycleNames.end())
{ {
state.emit(it->second); state.emit(*p);
return; return;
} }
@ -886,7 +888,7 @@ struct TypeStringifier
std::string saved = std::move(state.result.name); 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) if (needParens)
state.emit("("); state.emit("(");
@ -953,7 +955,7 @@ struct TypeStringifier
std::string saved = std::move(state.result.name); 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) if (needParens)
state.emit("("); state.emit("(");
@ -1101,10 +1103,9 @@ struct TypePackStringifier
return; return;
} }
auto it = state.cycleTpNames.find(tp); if (auto p = state.cycleTpNames.find(tp))
if (it != state.cycleTpNames.end())
{ {
state.emit(it->second); state.emit(*p);
return; 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, 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; int nextIndex = 1;
@ -1372,9 +1373,8 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts)
* *
* t1 where t1 = the_whole_root_type * t1 where t1 = the_whole_root_type
*/ */
auto it = state.cycleNames.find(ty); if (auto p = state.cycleNames.find(ty))
if (it != state.cycleNames.end()) state.emit(*p);
state.emit(it->second);
else else
tvs.stringify(ty); tvs.stringify(ty);
@ -1466,9 +1466,8 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts)
* *
* t1 where t1 = the_whole_root_type * t1 where t1 = the_whole_root_type
*/ */
auto it = state.cycleTpNames.find(tp); if (auto p = state.cycleTpNames.find(tp))
if (it != state.cycleTpNames.end()) state.emit(*p);
state.emit(it->second);
else else
tvs.stringify(tp); tvs.stringify(tp);
@ -1766,11 +1765,6 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
} }
else if constexpr (std::is_same_v<T, UnpackConstraint>) else if constexpr (std::is_same_v<T, UnpackConstraint>)
return tos(c.resultPack) + " ~ unpack " + tos(c.sourcePack); 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>) else if constexpr (std::is_same_v<T, SetOpConstraint>)
{ {
const char* op = c.mode == SetOpConstraint::Union ? " | " : " & "; 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()); return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("thread"), std::nullopt, Location());
case PrimitiveType::Buffer: case PrimitiveType::Buffer:
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("buffer"), std::nullopt, Location()); 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: default:
LUAU_ASSERT(false); // this should be unreachable.
return nullptr; return nullptr;
} }
} }

View File

@ -1603,8 +1603,8 @@ struct TypeChecker2
visit(indexExpr->expr, ValueContext::RValue); visit(indexExpr->expr, ValueContext::RValue);
visit(indexExpr->index, ValueContext::RValue); visit(indexExpr->index, ValueContext::RValue);
TypeId exprType = lookupType(indexExpr->expr); TypeId exprType = follow(lookupType(indexExpr->expr));
TypeId indexType = lookupType(indexExpr->index); TypeId indexType = follow(lookupType(indexExpr->index));
if (auto tt = get<TableType>(exprType)) 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)); TypeId operandTy = follow(typeParams.at(0));
// check to see if the operand type is resolved enough, and wait to reduce if not // 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}, {}}; return {std::nullopt, false, {operandTy}, {}};
const NormalizedType* normTy = ctx->normalizer->normalize(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, {}, {}}; 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() BuiltinTypeFamilies::BuiltinTypeFamilies()
: notFamily{"not", notFamilyFn} : notFamily{"not", notFamilyFn}
, lenFamily{"len", lenFamilyFn} , lenFamily{"len", lenFamilyFn}
@ -981,6 +1068,7 @@ BuiltinTypeFamilies::BuiltinTypeFamilies()
, ltFamily{"lt", ltFamilyFn} , ltFamily{"lt", ltFamilyFn}
, leFamily{"le", leFamilyFn} , leFamily{"le", leFamilyFn}
, eqFamily{"eq", eqFamilyFn} , 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) else if (strncmp(argv[i], "--fflags=", 9) == 0)
setLuauFlags(argv[i] + 9); setLuauFlags(argv[i] + 9);
else if (strncmp(argv[i], "-j", 2) == 0) 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) #if !defined(LUAU_ENABLE_TIME_TRACE)
@ -363,6 +363,7 @@ int main(int argc, char** argv)
if (threadCount <= 0) if (threadCount <= 0)
threadCount = std::min(TaskScheduler::getThreadCount(), 8u); threadCount = std::min(TaskScheduler::getThreadCount(), 8u);
try
{ {
TaskScheduler scheduler(threadCount); TaskScheduler scheduler(threadCount);
@ -370,6 +371,19 @@ int main(int argc, char** argv)
scheduler.push(std::move(f)); 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; int failed = 0;

View File

@ -22,16 +22,9 @@ RequireResolver::RequireResolver(lua_State* L, std::string path)
if (isAbsolutePath(pathToResolve)) if (isAbsolutePath(pathToResolve))
luaL_argerrorL(L, 1, "cannot require an absolute path"); 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(), '\\', '/'); std::replace(pathToResolve.begin(), pathToResolve.end(), '\\', '/');
if (isAlias)
{ substituteAliasIfPresent(pathToResolve);
pathToResolve = pathToResolve.substr(1);
substituteAliasIfPresent(pathToResolve);
}
} }
[[nodiscard]] RequireResolver::ResolvedRequire RequireResolver::resolveRequire(lua_State* L, std::string path) [[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) 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 // Not worth searching when potentialAlias cannot be an alias
if (!Luau::isValidAlias(potentialAlias)) if (!Luau::isValidAlias(potentialAlias))
return; luaL_errorL(L, "@%s is not a valid alias", potentialAlias.c_str());
std::optional<std::string> alias = getAlias(potentialAlias); std::optional<std::string> alias = getAlias(potentialAlias);
if (alias) 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

@ -3,6 +3,7 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include <cstddef>
#include <functional> #include <functional>
#include <utility> #include <utility>
#include <type_traits> #include <type_traits>
@ -285,9 +286,8 @@ public:
using value_type = Item; using value_type = Item;
using reference = Item&; using reference = Item&;
using pointer = Item*; using pointer = Item*;
using iterator = pointer; using difference_type = ptrdiff_t;
using difference_type = size_t; using iterator_category = std::forward_iterator_tag;
using iterator_category = std::input_iterator_tag;
const_iterator() const_iterator()
: set(0) : set(0)
@ -348,6 +348,12 @@ public:
class iterator class iterator
{ {
public: public:
using value_type = MutableItem;
using reference = MutableItem&;
using pointer = MutableItem*;
using difference_type = ptrdiff_t;
using iterator_category = std::forward_iterator_tag;
iterator() iterator()
: set(0) : set(0)
, index(0) , index(0)

View File

@ -438,7 +438,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareClass")
REQUIRE(2 == root->body.size); REQUIRE(2 == root->body.size);
std::string_view expected1 = 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); CHECK(toJson(root->body.data[0]) == expected1);
std::string_view expected2 = std::string_view expected2 =
@ -451,7 +451,39 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation")
AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())"); AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())");
std::string_view expected = 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); CHECK(toJson(statement) == expected);
} }

View File

@ -78,10 +78,14 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "class_method")
declare class Foo declare class Foo
function bar(self, x: string): number function bar(self, x: string): number
end end
declare Foo: {
new: () -> Foo
}
)"); )");
std::optional<DocumentationSymbol> symbol = getDocSymbol(R"( std::optional<DocumentationSymbol> symbol = getDocSymbol(R"(
local x: Foo local x: Foo = Foo.new()
x:bar("asdf") x:bar("asdf")
)", )",
Position(2, 11)); Position(2, 11));
@ -96,10 +100,14 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "overloaded_class_method")
function bar(self, x: string): number function bar(self, x: string): number
function bar(self, x: number): string function bar(self, x: number): string
end end
declare Foo: {
new: () -> Foo
}
)"); )");
std::optional<DocumentationSymbol> symbol = getDocSymbol(R"( std::optional<DocumentationSymbol> symbol = getDocSymbol(R"(
local x: Foo local x: Foo = Foo.new()
x:bar("asdf") x:bar("asdf")
)", )",
Position(2, 11)); Position(2, 11));

View File

@ -3265,8 +3265,9 @@ end
{ {
check(R"( check(R"(
local t: Foo local function f(t: Foo)
t:@1 t:@1
end
)"); )");
auto ac = autocomplete('1'); auto ac = autocomplete('1');
@ -3281,8 +3282,9 @@ t:@1
{ {
check(R"( check(R"(
local t: Foo local function f(t: Foo)
t.@1 t.@1
end
)"); )");
auto ac = autocomplete('1'); 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 // 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 "Luau/DataFlowGraph.h"
#include "Fixture.h"
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/Parser.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); 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(); 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 @checked lower(s: string): string
declare function cond() : boolean declare function cond() : boolean
declare function @checked contrived(n : Not<number>) : number 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"; )BUILTIN_SRC";
}; };
TEST_SUITE_BEGIN("NonStrictTypeCheckerTest"); 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") TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "simple_negation_caching_example")
{ {
CheckResult result = checkNonStrict(R"( CheckResult result = checkNonStrict(R"(
@ -387,4 +416,35 @@ lower(x)
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 6), "lower", result); 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(); TEST_SUITE_END();

View File

@ -394,4 +394,39 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithParentAlias")
assertOutputContainsAll({"true", "result from other_dependency"}); 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(); 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") 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"( CheckResult result = check(R"(
local f = math.sin local f = math.sin
local function g(x) return math.sin(x) end local function g(x) return math.sin(x) end
@ -1093,7 +1098,7 @@ end
TEST_CASE_FIXTURE(Fixture, "string_match") TEST_CASE_FIXTURE(Fixture, "string_match")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local s:string local s: string = "hello"
local p = s:match("foo") local p = s:match("foo")
)"); )");

View File

@ -230,10 +230,14 @@ TEST_CASE_FIXTURE(Fixture, "class_definition_function_prop")
declare class Foo declare class Foo
X: (number) -> string X: (number) -> string
end end
declare Foo: {
new: () -> Foo
}
)"); )");
CheckResult result = check(R"( CheckResult result = check(R"(
local x: Foo local x: Foo = Foo.new()
local prop = x.X local prop = x.X
)"); )");
@ -250,10 +254,14 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_class_function_args")
y: (a: number, b: string) -> string y: (a: number, b: string) -> string
end end
declare Foo: {
new: () -> Foo
}
)"); )");
CheckResult result = check(R"( CheckResult result = check(R"(
local x: Foo local x: Foo = Foo.new()
local methodRef1 = x.foo1 local methodRef1 = x.foo1
local methodRef2 = x.foo2 local methodRef2 = x.foo2
local prop = x.y 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); 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(); TEST_SUITE_END();

View File

@ -304,8 +304,15 @@ TEST_CASE_FIXTURE(Fixture, "calling_self_generic_methods")
end end
)"); )");
// TODO: Should typecheck but currently errors CLI-54277 if (FFlag::DebugLuauDeferredConstraintResolution)
LUAU_REQUIRE_ERRORS(result); {
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") 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") TEST_CASE_FIXTURE(BuiltinsFixture, "generic_functions_dont_cache_type_parameters")
{ {
CheckResult result = check(R"( 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, -- This function has a type parameter with the same name as clones,
-- so if we cache type parameter names for functions these get confused. -- so if we cache type parameter names for functions these get confused.
-- function id<Z>(x : Z) : Z -- 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") 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"( CheckResult result = check(R"(
--!strict --!strict
type MyObject = { type MyObject = {
@ -1175,7 +1182,7 @@ local complex: ComplexObject<string> = {
TEST_CASE_FIXTURE(Fixture, "apply_type_function_nested_generics2") 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"( CheckResult result = check(R"(
--!strict --!strict
type MyObject = { type MyObject = {
@ -1186,15 +1193,15 @@ type ComplexObject<T> = {
nested: MyObject 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 local y = complex.nested.getReturnValue(function()
return "" return 3
end) end)
end
local y = complex2.nested.getReturnValue(function()
return 3
end)
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); 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") 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"( CheckResult result = check(R"(
--!strict --!strict

View File

@ -3,7 +3,6 @@
#include "Fixture.h" #include "Fixture.h"
#include "doctest.h" #include "doctest.h"
#include "Luau/BuiltinDefinitions.h"
using namespace Luau; 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") TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons_mismatch")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
function f(a, b) end function f(g: ((true, string) -> ()) & ((false, number) -> ()))
local g : ((true, string) -> ()) & ((false, number) -> ()) = (f::any) g(true, 37)
g(true, 37) end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(2, result); LUAU_REQUIRE_ERROR_COUNT(2, result);
@ -429,7 +428,11 @@ TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere")
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); 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") 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); 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") 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"( 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 | string" == toString(requireType("x")));
CHECK("boolean | number | number | string" == toString(requireType("x"))); CHECK("boolean | number | number | string" == toString(requireType("x")));
} }
#endif
TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_are_unions_of_all_assignments") 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"))); 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(); TEST_SUITE_END();

View File

@ -45,8 +45,9 @@ TEST_CASE_FIXTURE(Fixture, "allow_specific_assign")
TEST_CASE_FIXTURE(Fixture, "allow_more_specific_assign") TEST_CASE_FIXTURE(Fixture, "allow_more_specific_assign")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local a:number|string = 22 function f(a: number | string, b: (number | string)?)
local b:number|string|nil = a b = a
end
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); 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") TEST_CASE_FIXTURE(Fixture, "disallow_less_specific_assign")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local a:number = 10 function f(a: number, b: number | string)
local b:number|string = 20 a = b
a = b end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "optional_arguments")
{ {
CheckResult result = check(R"( 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"( CheckResult result = check(R"(
type A = {x: number} type A = {x: number}
type B = {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); 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") 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"( CheckResult result = check(R"(
type A = {x: number} type A = {x: number}
type B = {x: string} 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); 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") 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"( CheckResult result = check(R"(
type A = {x: {y: {z: {thing: number}}}} type A = {x: {y: {z: {thing: number}}}}
type B = {x: {y: {z: {thing: string}}}} 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); 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") 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"( CheckResult result = check(R"(
type A = {x: number} type A = {x: number}
type B = {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); 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") 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"( CheckResult result = check(R"(
type A = {x: number} type A = {x: number}
type B = {} 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); LUAU_REQUIRE_ERROR_COUNT(1, result);
MissingUnionProperty* mup = get<MissingUnionProperty>(result.errors[0]); MissingUnionProperty* mup = get<MissingUnionProperty>(result.errors[0]);
REQUIRE(mup); REQUIRE(mup);
CHECK_EQ(mup->type, requireType("t")); CHECK_EQ("Key 'x' is missing from 'B' in the type 'A | B'", toString(result.errors[0]));
REQUIRE(mup->missing.size() == 1);
std::optional<TypeId> bTy = lookupType("B"); if (FFlag::DebugLuauDeferredConstraintResolution)
REQUIRE(bTy); CHECK_EQ("(A | B) -> number | *error-type*", toString(requireType("f")));
CHECK_EQ(mup->missing[0], *bTy); else
CHECK_EQ(mup->key, "x"); CHECK_EQ("(A | B) -> *error-type*", toString(requireType("f")));
CHECK_EQ("*error-type*", toString(requireType("r")));
} }
TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_one_property_of_type_any") 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"( CheckResult result = check(R"(
type A = {x: number} type A = {x: number}
type B = {x: any} 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); 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") TEST_CASE_FIXTURE(Fixture, "union_equality_comparisons")
@ -223,14 +218,13 @@ TEST_CASE_FIXTURE(Fixture, "union_equality_comparisons")
type B = number | nil type B = number | nil
type C = number | boolean type C = number | boolean
local a = 1 :: A function f(a: A, b: B, c: C)
local b = nil :: B local n = 1
local c = true :: C
local n = 1
local x = a == b local x = a == b
local y = a == n local y = a == n
local z = a == c local z = a == c
end
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
@ -239,16 +233,21 @@ TEST_CASE_FIXTURE(Fixture, "union_equality_comparisons")
TEST_CASE_FIXTURE(Fixture, "optional_union_members") TEST_CASE_FIXTURE(Fixture, "optional_union_members")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local a = { a = { x = 1, y = 2 }, b = 3 } local a = { a = { x = 1, y = 2 }, b = 3 }
type A = typeof(a) type A = typeof(a)
local b: A? = a function f(b: A?)
local bf = b return b.a.y
local c = bf.a.y end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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])); 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") TEST_CASE_FIXTURE(Fixture, "optional_union_functions")
@ -257,37 +256,49 @@ TEST_CASE_FIXTURE(Fixture, "optional_union_functions")
local a = {} local a = {}
function a.foo(x:number, y:number) return x + y end function a.foo(x:number, y:number) return x + y end
type A = typeof(a) type A = typeof(a)
local b: A? = a function f(b: A?)
local c = b.foo(1, 2) return b.foo(1, 2)
end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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])); 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") TEST_CASE_FIXTURE(Fixture, "optional_union_methods")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local a = {} local a = {}
function a:foo(x:number, y:number) return x + y end function a:foo(x:number, y:number) return x + y end
type A = typeof(a) type A = typeof(a)
local b: A? = a function f(b: A?)
local c = b:foo(1, 2) return b:foo(1, 2)
end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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])); 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") TEST_CASE_FIXTURE(Fixture, "optional_union_follow")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local y: number? = 2 local y: number? = 2
local x = y local x = y
local function f(a: number, b: typeof(x), c: typeof(x)) return -a end function f(a: number, b: number?, c: number?) return -a end
return f() return f()
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -302,10 +313,11 @@ return f()
TEST_CASE_FIXTURE(Fixture, "optional_field_access_error") TEST_CASE_FIXTURE(Fixture, "optional_field_access_error")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
type A = { x: number } type A = { x: number }
local b: A? = { x = 2 } function f(b: A?)
local c = b.x local c = b.x
local d = b.y local d = b.y
end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(3, result); LUAU_REQUIRE_ERROR_COUNT(3, result);
@ -317,9 +329,10 @@ local d = b.y
TEST_CASE_FIXTURE(Fixture, "optional_index_error") TEST_CASE_FIXTURE(Fixture, "optional_index_error")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
type A = {number} type A = {number}
local a: A? = {1, 2, 3} function f(a: A?)
local b = a[1] local b = a[1]
end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -329,9 +342,10 @@ local b = a[1]
TEST_CASE_FIXTURE(Fixture, "optional_call_error") TEST_CASE_FIXTURE(Fixture, "optional_call_error")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
type A = (number) -> number type A = (number) -> number
local a: A? = function(a) return -a end function f(a: A?)
local b = a(4) local b = a(4)
end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -341,18 +355,23 @@ local b = a(4)
TEST_CASE_FIXTURE(Fixture, "optional_assignment_errors") TEST_CASE_FIXTURE(Fixture, "optional_assignment_errors")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
type A = { x: number } type A = { x: number }
local a: A? = { x = 2 } function f(a: A?)
a.x = 2 a.x = 2
end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Value of type 'A?' could be nil", toString(result.errors[0])); CHECK_EQ("Value of type 'A?' could be nil", toString(result.errors[0]));
}
result = check(R"( TEST_CASE_FIXTURE(Fixture, "optional_assignment_errors_2")
type A = { x: number } & { y: number } {
local a: A? = { x = 2, y = 3 } CheckResult result = check(R"(
a.x = 2 type A = { x: number } & { y: number }
function f(a: A?)
a.x = 2
end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -366,9 +385,10 @@ a.x = 2
TEST_CASE_FIXTURE(Fixture, "optional_length_error") TEST_CASE_FIXTURE(Fixture, "optional_length_error")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
type A = {number} type A = {number}
local a: A? = {1, 2, 3} function f(a: A?)
local b = #a local b = #a
end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -378,27 +398,27 @@ local b = #a
TEST_CASE_FIXTURE(Fixture, "optional_missing_key_error_details") TEST_CASE_FIXTURE(Fixture, "optional_missing_key_error_details")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
type A = { x: number, y: number } type A = { x: number, y: number }
type B = { x: number, y: number } type B = { x: number, y: number }
type C = { x: number } type C = { x: number }
type D = { x: number } type D = { x: number }
local a: A|B|C|D function f(a: A | B | C | D)
local b = a.y local y = a.y
local z = a.z
end
local c: A|(B|C)?|D function g(c: A | B | C | D | nil)
local d = c.y local d = c.y
end
local e = a.z
)"); )");
LUAU_REQUIRE_ERROR_COUNT(4, result); 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("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("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[2])); CHECK_EQ("Key 'y' is missing from 'C', 'D' in the type 'A | B | C | D'", toString(result.errors[3]));
CHECK_EQ("Type 'A | B | C | D' does not have key 'z'", toString(result.errors[3]));
} }
TEST_CASE_FIXTURE(Fixture, "optional_iteration") TEST_CASE_FIXTURE(Fixture, "optional_iteration")
@ -470,25 +490,27 @@ type Z = { z: number }
type XYZ = X | Y | Z type XYZ = X | Y | Z
local a: XYZ function f(a: XYZ)
local b: { w: number } = a local b: { w: number } = a
end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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."); if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ("X | Y | Z", toString(tm->givenType)); 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"
const TableType* expected = get<TableType>(tm->wantedType); "type X | Y | Z[1] (Y) is not a subtype of { w: number } ({ w: number })\n\t"
REQUIRE(expected); "type X | Y | Z[2] (Z) is not a subtype of { w: number } ({ w: number })");
CHECK_EQ(TableState::Sealed, expected->state); }
CHECK_EQ(1, expected->props.size()); else
auto propW = expected->props.find("w"); {
REQUIRE_NE(expected->props.end(), propW); CHECK_EQ(toString(result.errors[0]), R"(Type 'X | Y | Z' could not be converted into '{| w: number |}'
CHECK_EQ("number", toString(propW->second.type())); 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") TEST_CASE_FIXTURE(Fixture, "error_detailed_union_all")
@ -545,14 +567,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect")
CheckResult result = check(R"( CheckResult result = check(R"(
type A = { x: number, y: (number) -> string } | { z: number, y: (number) -> string } 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) function a.y(x: string): number
return tostring(x * 2) return tonumber(x) or 0
end end
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") TEST_CASE_FIXTURE(Fixture, "union_true_and_false")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local x : boolean function f(x : boolean)
local y1 : (true | false) = x -- OK local y1 : (true | false) = x -- OK
local y2 : (true | false | (string & number)) = x -- OK local y2 : (true | false | (string & number)) = x -- OK
local y3 : (true | (string & number) | false) = x -- OK local y3 : (true | (string & number) | false) = x -- OK
local y4 : (true | (boolean & true) | false) = x -- OK local y4 : (true | (boolean & true) | false) = x -- OK
)"); end
)");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
@ -581,8 +604,9 @@ TEST_CASE_FIXTURE(Fixture, "union_true_and_false")
TEST_CASE_FIXTURE(Fixture, "union_of_functions") TEST_CASE_FIXTURE(Fixture, "union_of_functions")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local x : (number) -> number? function f(x : (number) -> number?)
local y : ((number?) -> number?) | ((number) -> number) = x -- OK local y : ((number?) -> number?) | ((number) -> number) = x -- OK
end
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
@ -591,8 +615,9 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions")
TEST_CASE_FIXTURE(Fixture, "union_of_generic_functions") TEST_CASE_FIXTURE(Fixture, "union_of_generic_functions")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local x : <a>(a) -> a? function f(x : <a>(a) -> a?)
local y : (<a>(a?) -> a?) | (<b>(b) -> b) = x -- Not OK local y : (<a>(a?) -> a?) | (<b>(b) -> b) = x -- Not OK
end
)"); )");
// TODO: should this example typecheck? // 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") TEST_CASE_FIXTURE(Fixture, "union_of_generic_typepack_functions")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local x : <a...>(number, a...) -> (number?, a...) function f(x : <a...>(number, a...) -> (number?, a...))
local y : (<a...>(number?, a...) -> (number?, a...)) | (<b...>(number, b...) -> (number, b...)) = x -- Not OK local y : (<a...>(number?, a...) -> (number?, a...)) | (<b...>(number, b...) -> (number, b...)) = x -- Not OK
end
)"); )");
// TODO: should this example typecheck? // 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
function f<a,b>() function f<a,b>()
local x : (a) -> a? function g(x : (a) -> a?)
local y : ((a?) -> nil) | ((a) -> a) = x -- OK local y : ((a?) -> nil) | ((a) -> a) = x -- OK
local z : ((b?) -> nil) | ((b) -> b) = x -- Not OK local z : ((b?) -> nil) | ((b) -> b) = x -- Not OK
end end
)"); end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
function f<a...>() function f<a...>()
local x : (number, a...) -> (number?, a...) function g(x : (number, a...) -> (number?, a...))
local y : ((number | string, a...) -> (number, a...)) | ((number?, a...) -> (nil, a...)) = x -- OK local y : ((number | string, a...) -> (number, a...)) | ((number?, a...) -> (nil, a...)) = x -- OK
local z : ((number) -> number) | ((number?, a...) -> (number?, a...)) = x -- Not OK local z : ((number) -> number) | ((number?, a...) -> (number?, a...)) = x -- Not OK
end end
)"); end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local x : (number) -> number? function f(x : (number) -> number?)
local y : ((number?) -> number) | ((number | string) -> nil) = x -- OK local y : ((number?) -> number) | ((number | string) -> nil) = x -- OK
local z : ((number, string?) -> number) | ((number) -> nil) = x -- Not OK local z : ((number, string?) -> number) | ((number) -> nil) = x -- Not OK
end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local x : () -> (number | string) function f(x : () -> (number | string))
local y : (() -> number) | (() -> string) = x -- OK local y : (() -> number) | (() -> string) = x -- OK
local z : (() -> number) | (() -> (string, string)) = x -- Not OK local z : (() -> number) | (() -> (string, string)) = x -- Not OK
end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -678,11 +707,11 @@ could not be converted into
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local x : (...nil) -> (...number?) function f(x : (...nil) -> (...number?))
local y : ((...string?) -> (...number)) | ((...number?) -> nil) = x -- OK local y : ((...string?) -> (...number)) | ((...number?) -> nil) = x -- OK
local z : ((...string?) -> (...number)) | ((...string?) -> nil) = x -- OK local z : ((...string?) -> (...number)) | ((...string?) -> nil) = x -- OK
end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local x : (number) -> () function f(x : (number) -> ())
local y : ((number?) -> ()) | ((...number) -> ()) = x -- OK local y : ((number?) -> ()) | ((...number) -> ()) = x -- OK
local z : ((number?) -> ()) | ((...number?) -> ()) = x -- Not OK local z : ((number?) -> ()) | ((...number?) -> ()) = x -- Not OK
end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local x : () -> (number?, ...number) function f(x : () -> (number?, ...number))
local y : (() -> (...number)) | (() -> nil) = x -- OK local y : (() -> (...number)) | (() -> nil) = x -- OK
local z : (() -> (...number)) | (() -> number) = x -- OK local z : (() -> (...number)) | (() -> number) = x -- OK
end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "union_function_any_args")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local sup : ((...any) -> (...any))? function f(sup : ((...any) -> (...any))?, sub : ((number) -> (...any)))
local sub : ((number) -> (...any)) sup = sub
sup = sub end
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
@ -793,9 +822,9 @@ TEST_CASE_FIXTURE(Fixture, "union_function_any_args")
TEST_CASE_FIXTURE(Fixture, "optional_any") TEST_CASE_FIXTURE(Fixture, "optional_any")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local sup : any? function f(sup : any?, sub : number)
local sub : number sup = sub
sup = sub end
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); 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); registerHiddenTypes(&frontend);
CheckResult result = check(R"( CheckResult result = check(R"(
local a = 5 :: err | Not<nil> function f(a: err | Not<nil>)
local b = a.foo
local b = a.foo end
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); 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") TEST_CASE_FIXTURE(Fixture, "assign_to_prop_which_is_never")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local t: never local function f(t: never)
t.x = 5 t.x = 5
end
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); 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") TEST_CASE_FIXTURE(Fixture, "assign_to_subscript_which_is_never")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local t: never local function f(t: never)
t[5] = 7 t[5] = 7
end
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); 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"( CheckResult result = check(R"(
type Disjoint = {foo: never, bar: unknown, tag: "ok"} | {foo: never, baz: unknown, tag: "err"} 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); 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"( CheckResult result = check(R"(
type Disjoint = {foo: string, bar: unknown, tag: "ok"} | {foo: never, baz: unknown, tag: "err"} 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); LUAU_REQUIRE_NO_ERRORS(result);

View File

@ -1,2 +1,2 @@
-- should be required using the paths array in the parent directory's .luaurc -- 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 -- 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" result[#result+1] = "required into module"
return result return result

View File

@ -1,17 +1,13 @@
AnnotationTests.typeof_expr AnnotationTests.typeof_expr
AstQuery.last_argument_function_call_type AstQuery.last_argument_function_call_type
AstQuery::getDocumentationSymbolAtPosition.class_method
AstQuery::getDocumentationSymbolAtPosition.overloaded_class_method
AstQuery::getDocumentationSymbolAtPosition.table_overloaded_function_prop AstQuery::getDocumentationSymbolAtPosition.table_overloaded_function_prop
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg
AutocompleteTest.autocomplete_interpolated_string_as_singleton AutocompleteTest.autocomplete_interpolated_string_as_singleton
AutocompleteTest.autocomplete_response_perf1
AutocompleteTest.autocomplete_string_singleton_equality AutocompleteTest.autocomplete_string_singleton_equality
AutocompleteTest.autocomplete_string_singleton_escape AutocompleteTest.autocomplete_string_singleton_escape
AutocompleteTest.autocomplete_string_singletons AutocompleteTest.autocomplete_string_singletons
AutocompleteTest.do_wrong_compatible_nonself_calls AutocompleteTest.do_wrong_compatible_nonself_calls
AutocompleteTest.no_incompatible_self_calls_on_class
AutocompleteTest.string_singleton_in_if_statement AutocompleteTest.string_singleton_in_if_statement
AutocompleteTest.suggest_external_module_type AutocompleteTest.suggest_external_module_type
AutocompleteTest.type_correct_expected_argument_type_pack_suggestion 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.bad_select_should_not_crash
BuiltinTests.coroutine_resume_anything_goes BuiltinTests.coroutine_resume_anything_goes
BuiltinTests.debug_info_is_crazy BuiltinTests.debug_info_is_crazy
BuiltinTests.dont_add_definitions_to_persistent_types
BuiltinTests.global_singleton_types_are_sealed BuiltinTests.global_singleton_types_are_sealed
BuiltinTests.gmatch_capture_types BuiltinTests.gmatch_capture_types
BuiltinTests.gmatch_capture_types2 BuiltinTests.gmatch_capture_types2
@ -62,7 +57,6 @@ BuiltinTests.string_format_as_method
BuiltinTests.string_format_correctly_ordered_types BuiltinTests.string_format_correctly_ordered_types
BuiltinTests.string_format_report_all_type_errors_at_correct_positions BuiltinTests.string_format_report_all_type_errors_at_correct_positions
BuiltinTests.string_format_use_correct_argument2 BuiltinTests.string_format_use_correct_argument2
BuiltinTests.string_match
BuiltinTests.table_concat_returns_string BuiltinTests.table_concat_returns_string
BuiltinTests.table_dot_remove_optionally_returns_generic BuiltinTests.table_dot_remove_optionally_returns_generic
BuiltinTests.table_freeze_is_generic BuiltinTests.table_freeze_is_generic
@ -100,16 +94,15 @@ ControlFlowAnalysis.if_not_x_then_assert_false
ControlFlowAnalysis.if_not_x_then_error 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_breaking
ControlFlowAnalysis.prototyping_and_visiting_alias_has_the_same_scope_continuing ControlFlowAnalysis.prototyping_and_visiting_alias_has_the_same_scope_continuing
ControlFlowAnalysis.tagged_unions
ControlFlowAnalysis.tagged_unions_breaking ControlFlowAnalysis.tagged_unions_breaking
ControlFlowAnalysis.tagged_unions_continuing ControlFlowAnalysis.tagged_unions_continuing
ControlFlowAnalysis.type_alias_does_not_leak_out_breaking ControlFlowAnalysis.type_alias_does_not_leak_out_breaking
ControlFlowAnalysis.type_alias_does_not_leak_out_continuing ControlFlowAnalysis.type_alias_does_not_leak_out_continuing
DefinitionTests.class_definition_function_prop
DefinitionTests.class_definition_indexer DefinitionTests.class_definition_indexer
DefinitionTests.class_definition_overload_metamethods DefinitionTests.class_definition_overload_metamethods
DefinitionTests.class_definition_string_props DefinitionTests.class_definition_string_props
DefinitionTests.declaring_generic_functions DefinitionTests.declaring_generic_functions
DefinitionTests.definition_file_class_function_args
DefinitionTests.definition_file_classes DefinitionTests.definition_file_classes
Differ.equal_generictp_cyclic Differ.equal_generictp_cyclic
Differ.equal_table_A_B_C Differ.equal_table_A_B_C
@ -142,7 +135,6 @@ GenericsTests.apply_type_function_nested_generics1
GenericsTests.apply_type_function_nested_generics2 GenericsTests.apply_type_function_nested_generics2
GenericsTests.better_mismatch_error_messages GenericsTests.better_mismatch_error_messages
GenericsTests.bound_tables_do_not_clone_original_fields GenericsTests.bound_tables_do_not_clone_original_fields
GenericsTests.calling_self_generic_methods
GenericsTests.check_generic_function GenericsTests.check_generic_function
GenericsTests.check_generic_local_function GenericsTests.check_generic_local_function
GenericsTests.check_generic_typepack_function GenericsTests.check_generic_typepack_function
@ -230,6 +222,7 @@ Linter.DeprecatedApiFenv
Linter.FormatStringTyped Linter.FormatStringTyped
Linter.TableOperationsIndexer Linter.TableOperationsIndexer
ModuleTests.clone_self_property ModuleTests.clone_self_property
Negations.cofinite_strings_can_be_compared_for_equality
Negations.negated_string_is_a_subtype_of_string Negations.negated_string_is_a_subtype_of_string
NonstrictModeTests.inconsistent_module_return_types_are_ok NonstrictModeTests.inconsistent_module_return_types_are_ok
NonstrictModeTests.infer_nullary_function NonstrictModeTests.infer_nullary_function
@ -244,6 +237,7 @@ Normalize.negations_of_tables
Normalize.specific_functions_cannot_be_negated Normalize.specific_functions_cannot_be_negated
ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal
ProvisionalTests.choose_the_right_overload_for_pcall 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.do_not_ice_when_trying_to_pick_first_of_generic_type_pack
ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean
ProvisionalTests.expected_type_should_be_a_helpful_deduction_guide_for_function_calls 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_a_shadowed_local_that_which_was_previously_refined
RefinementTest.correctly_lookup_property_whose_base_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.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_from_truthiness_of_x
RefinementTest.discriminate_tag
RefinementTest.discriminate_tag_with_implicit_else
RefinementTest.either_number_or_string 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.fail_to_refine_a_property_of_subscript_expression
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
RefinementTest.function_call_with_colon_after_refining_not_to_be_nil RefinementTest.function_call_with_colon_after_refining_not_to_be_nil
RefinementTest.impossible_type_narrow_is_not_an_error RefinementTest.impossible_type_narrow_is_not_an_error
RefinementTest.index_on_a_refined_property RefinementTest.index_on_a_refined_property
RefinementTest.isa_type_refinement_must_be_known_ahead_of_time 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.merge_should_be_fully_agnostic_of_hashmap_ordering
RefinementTest.narrow_property_of_a_bounded_variable RefinementTest.narrow_property_of_a_bounded_variable
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
RefinementTest.not_t_or_some_prop_of_t 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_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.refine_unknown_to_table_then_clone_it
RefinementTest.refinements_should_preserve_error_suppression RefinementTest.refinements_should_preserve_error_suppression
RefinementTest.string_not_equal_to_string_or_nil 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_assert_position
RefinementTest.typeguard_in_if_condition_position RefinementTest.typeguard_in_if_condition_position
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table 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_directly
TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible
TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible TableTests.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_ice_when_failing_the_occurs_check
TypeInfer.dont_report_type_errors_within_an_AstExprError TypeInfer.dont_report_type_errors_within_an_AstExprError
TypeInfer.dont_report_type_errors_within_an_AstStatError TypeInfer.dont_report_type_errors_within_an_AstStatError
TypeInfer.follow_on_new_types_in_substitution
TypeInfer.globals TypeInfer.globals
TypeInfer.globals2 TypeInfer.globals2
TypeInfer.globals_are_banned_in_strict_mode 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
TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2 TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2
TypeInferFunctions.record_matching_overload TypeInferFunctions.record_matching_overload
TypeInferFunctions.regex_benchmark_string_format_minimization
TypeInferFunctions.report_exiting_without_return_nonstrict TypeInferFunctions.report_exiting_without_return_nonstrict
TypeInferFunctions.report_exiting_without_return_strict TypeInferFunctions.report_exiting_without_return_strict
TypeInferFunctions.return_type_by_overload 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.varlist_declared_by_for_in_loop_should_be_free
TypeInferLoops.while_loop TypeInferLoops.while_loop
TypeInferModules.bound_free_table_export_is_ok TypeInferModules.bound_free_table_export_is_ok
TypeInferModules.custom_require_global
TypeInferModules.do_not_modify_imported_types TypeInferModules.do_not_modify_imported_types
TypeInferModules.do_not_modify_imported_types_5 TypeInferModules.do_not_modify_imported_types_5
TypeInferModules.require TypeInferModules.require
@ -645,7 +649,6 @@ TypeInferPrimitives.CheckMethodsOfNumber
TypeInferPrimitives.string_function_indirect TypeInferPrimitives.string_function_indirect
TypeInferPrimitives.string_index TypeInferPrimitives.string_index
TypeInferUnknownNever.assign_to_local_which_is_never 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_never
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never
TypeInferUnknownNever.length_of_never TypeInferUnknownNever.length_of_never
@ -663,42 +666,25 @@ TypeSingletons.error_detailed_tagged_union_mismatch_string
TypeSingletons.function_args_infer_singletons TypeSingletons.function_args_infer_singletons
TypeSingletons.function_call_with_singletons TypeSingletons.function_call_with_singletons
TypeSingletons.function_call_with_singletons_mismatch TypeSingletons.function_call_with_singletons_mismatch
TypeSingletons.overloaded_function_call_with_singletons_mismatch
TypeSingletons.return_type_of_f_is_not_widened TypeSingletons.return_type_of_f_is_not_widened
TypeSingletons.table_properties_type_error_escapes TypeSingletons.table_properties_type_error_escapes
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
TypeSingletons.widening_happens_almost_everywhere
TypeStatesTest.invalidate_type_refinements_upon_assignments 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_optional
UnionTypes.error_detailed_union_all UnionTypes.error_detailed_union_all
UnionTypes.error_detailed_union_part
UnionTypes.error_takes_optional_arguments UnionTypes.error_takes_optional_arguments
UnionTypes.generic_function_with_optional_arg UnionTypes.generic_function_with_optional_arg
UnionTypes.index_on_a_union_type_with_missing_property 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.less_greedy_unification_with_union_types
UnionTypes.optional_arguments_table 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_length_error
UnionTypes.optional_missing_key_error_details
UnionTypes.optional_union_follow
UnionTypes.optional_union_functions UnionTypes.optional_union_functions
UnionTypes.optional_union_members UnionTypes.optional_union_members
UnionTypes.optional_union_methods UnionTypes.optional_union_methods
UnionTypes.return_types_can_be_disjoint UnionTypes.return_types_can_be_disjoint
UnionTypes.table_union_write_indirect UnionTypes.table_union_write_indirect
UnionTypes.unify_unsealed_table_union_check 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_generic_typepacks
UnionTypes.union_of_functions_mentioning_generics UnionTypes.union_of_functions_mentioning_generics
UnionTypes.union_of_functions_with_mismatching_arg_arities 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_arities
UnionTypes.union_of_functions_with_mismatching_result_variadics UnionTypes.union_of_functions_with_mismatching_result_variadics
UnionTypes.union_of_functions_with_variadics UnionTypes.union_of_functions_with_variadics
UnionTypes.union_true_and_false
VisitType.throw_when_limit_is_exceeded VisitType.throw_when_limit_is_exceeded