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