diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index bba3fced..e69346bd 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/ConstraintGenerator.h b/Analysis/include/Luau/ConstraintGenerator.h index a3e1092f..0808e84c 100644 --- a/Analysis/include/Luau/ConstraintGenerator.h +++ b/Analysis/include/Luau/ConstraintGenerator.h @@ -150,7 +150,7 @@ private: */ ScopePtr childScope(AstNode* node, const ScopePtr& parent); - std::optional lookup(Scope* scope, DefId def); + std::optional lookup(Scope* scope, DefId def, bool prototype = true); /** * Adds a new constraint with no dependencies to a given scope. diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index 4a4d639b..f258b28b 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 083e5046..3f1a4378 100644 --- a/Analysis/include/Luau/DataFlowGraph.h +++ b/Analysis/include/Luau/DataFlowGraph.h @@ -74,8 +74,15 @@ private: struct DfgScope { + enum ScopeType + { + Linear, + Loop, + Function, + }; + DfgScope* parent; - bool isLoopScope; + ScopeType scopeType; using Bindings = DenseHashMap; using Props = DenseHashMap>; @@ -117,11 +124,21 @@ private: std::vector> scopes; - DfgScope* childScope(DfgScope* scope, bool isLoopScope = false); + struct FunctionCapture + { + std::vector captureDefs; + std::vector allVersions; + size_t versionOffset = 0; + }; + + DenseHashMap captures{Symbol{}}; + void resolveCaptures(); + + 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); @@ -167,11 +184,11 @@ private: DataFlowResult visitExpr(DfgScope* scope, AstExprError* error); void visitLValue(DfgScope* scope, AstExpr* e, DefId incomingDef, bool isCompoundAssignment = false); - void visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef, bool isCompoundAssignment); - void visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef, bool isCompoundAssignment); - void visitLValue(DfgScope* scope, AstExprIndexName* i, DefId incomingDef); - void visitLValue(DfgScope* scope, AstExprIndexExpr* i, DefId incomingDef); - void visitLValue(DfgScope* scope, AstExprError* e, DefId incomingDef); + DefId visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef, bool isCompoundAssignment); + DefId visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef, bool isCompoundAssignment); + DefId visitLValue(DfgScope* scope, AstExprIndexName* i, DefId incomingDef); + DefId visitLValue(DfgScope* scope, AstExprIndexExpr* i, DefId incomingDef); + DefId visitLValue(DfgScope* scope, AstExprError* e, DefId incomingDef); void visitType(DfgScope* scope, AstType* t); void visitType(DfgScope* scope, AstTypeReference* r); diff --git a/Analysis/include/Luau/Def.h b/Analysis/include/Luau/Def.h index e3fec9b6..9627f998 100644 --- a/Analysis/include/Luau/Def.h +++ b/Analysis/include/Luau/Def.h @@ -73,6 +73,7 @@ const T* get(DefId def) } bool containsSubscriptedDefinition(DefId def); +void collectOperands(DefId def, std::vector* operands); struct DefArena { diff --git a/Analysis/include/Luau/Set.h b/Analysis/include/Luau/Set.h index 3f34c325..1cfdf5c6 100644 --- a/Analysis/include/Luau/Set.h +++ b/Analysis/include/Luau/Set.h @@ -118,6 +118,12 @@ public: class const_iterator { public: + using value_type = T; + using reference = T&; + using pointer = T*; + using difference_type = ptrdiff_t; + using iterator_category = std::forward_iterator_tag; + const_iterator(typename Impl::const_iterator impl, typename Impl::const_iterator end) : impl(impl) , end(end) diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index 70494685..ac104f90 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 4a736647..7efe338f 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 2d1940d4..dcee3492 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 cdfe9a7f..5bfe39e7 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -205,7 +205,7 @@ ScopePtr ConstraintGenerator::childScope(AstNode* node, const ScopePtr& parent) return scope; } -std::optional ConstraintGenerator::lookup(Scope* scope, DefId def) +std::optional ConstraintGenerator::lookup(Scope* scope, DefId def, bool prototype) { if (get(def)) return scope->lookup(def); @@ -213,22 +213,24 @@ std::optional ConstraintGenerator::lookup(Scope* scope, DefId def) { if (auto found = scope->lookup(def)) return *found; + else if (!prototype) + return std::nullopt; TypeId res = builtinTypes->neverType; for (DefId operand : phi->operands) { - // `scope->lookup(operand)` may return nothing because it could be a phi node of globals, but one of - // the operand of that global has never been assigned a type, and so it should be an error. - // e.g. - // ``` - // if foo() then - // g = 5 - // end - // -- `g` here is a phi node of the assignment to `g`, or the original revision of `g` before the branch. - // ``` - TypeId ty = scope->lookup(operand).value_or(builtinTypes->errorRecoveryType()); - res = simplifyUnion(builtinTypes, arena, res, ty).result; + // `scope->lookup(operand)` may return nothing because we only bind a type to that operand + // once we've seen that particular `DefId`. In this case, we need to prototype those types + // and use those at a later time. + std::optional ty = lookup(scope, operand, /*prototype*/false); + if (!ty) + { + ty = arena->addType(BlockedType{}); + rootScope->lvalueTypes[operand] = *ty; + } + + res = simplifyUnion(builtinTypes, arena, res, *ty).result; } scope->lvalueTypes[def] = res; @@ -255,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; @@ -439,10 +441,14 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat { if (mustDeferIntersection(ty) || mustDeferIntersection(dt)) { - TypeId r = arena->addType(BlockedType{}); - addConstraint(scope, location, RefineConstraint{RefineConstraint::Intersection, r, ty, dt}); + TypeId resultType = arena->addType(TypeFamilyInstanceType{ + NotNull{&kBuiltinTypeFamilies.refineFamily}, + {ty, dt}, + {}, + }); + addConstraint(scope, location, ReduceConstraint{resultType}); - ty = r; + ty = resultType; } else { @@ -861,7 +867,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f DenseHashSet excludeList{nullptr}; DefId def = dfg->getDef(function->name); - std::optional existingFunctionTy = scope->lookup(def); + std::optional existingFunctionTy = lookup(scope.get(), def); if (AstExprLocal* localName = function->name->as()) { @@ -1003,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; @@ -1724,16 +1727,14 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprGlobal* globa /* prepopulateGlobalScope() has already added all global functions to the environment by this point, so any * global that is not already in-scope is definitely an unknown symbol. */ - if (auto ty = lookup(scope.get(), def)) - return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)}; - else if (auto ty = scope->lookup(global->name)) + if (auto ty = lookup(scope.get(), def, /*prototype=*/false)) { rootScope->lvalueTypes[def] = *ty; return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)}; } else { - reportError(global->location, UnknownSymbol{global->name.value}); + reportError(global->location, UnknownSymbol{global->name.value, UnknownSymbol::Binding}); return Inference{builtinTypes->errorRecoveryType()}; } } @@ -3110,6 +3111,16 @@ struct GlobalPrepopulator : AstVisitor return true; } + + bool visit(AstType*) override + { + return true; + } + + bool visit(class AstTypePack* node) override + { + return true; + } }; void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program) diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index fa0f767b..9024bd85 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 bdefd7f0..c82ec9c9 100644 --- a/Analysis/src/DataFlowGraph.cpp +++ b/Analysis/src/DataFlowGraph.cpp @@ -116,7 +116,7 @@ bool DfgScope::canUpdateDefinition(Symbol symbol) const { if (current->bindings.find(symbol)) return true; - else if (current->isLoopScope) + else if (current->scopeType == DfgScope::Loop) return false; } @@ -129,7 +129,7 @@ bool DfgScope::canUpdateDefinition(DefId def, const std::string& key) const { if (auto props = current->props.find(def)) return true; - else if (current->isLoopScope) + else if (current->scopeType == DfgScope::Loop) return false; } @@ -144,6 +144,7 @@ DataFlowGraph DataFlowGraphBuilder::build(AstStatBlock* block, NotNull operands; + for (size_t i = capture.versionOffset; i < capture.allVersions.size(); ++i) + collectOperands(capture.allVersions[i], &operands); + + for (DefId captureDef : capture.captureDefs) + { + Phi* phi = const_cast(get(captureDef)); + LUAU_ASSERT(phi); + LUAU_ASSERT(phi->operands.empty()); + phi->operands = operands; + } + } +} + +DfgScope* DataFlowGraphBuilder::childScope(DfgScope* scope, DfgScope::ScopeType scopeType) +{ + return scopes.emplace_back(new DfgScope{scope, scopeType}).get(); } 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; } @@ -201,50 +223,72 @@ 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}); } } DefId DataFlowGraphBuilder::lookup(DfgScope* scope, Symbol symbol) { - if (auto found = scope->lookup(symbol)) - return *found; - else + for (DfgScope* current = scope; current; current = current->parent) { - DefId result = defArena->freshCell(); - if (symbol.local) - scope->bindings[symbol] = result; - else - moduleScope->bindings[symbol] = result; - return result; + if (auto found = current->bindings.find(symbol)) + return NotNull{*found}; + else if (current->scopeType == DfgScope::Function) + { + FunctionCapture& capture = captures[symbol]; + DefId captureDef = defArena->phi({}); + capture.captureDefs.push_back(captureDef); + scope->bindings[symbol] = captureDef; + return NotNull{captureDef}; + } } + + DefId result = defArena->freshCell(); + scope->bindings[symbol] = result; + captures[symbol].allVersions.push_back(result); + return result; } DefId DataFlowGraphBuilder::lookup(DfgScope* scope, DefId def, const std::string& key) { - if (auto found = scope->lookup(def, key)) - return *found; - else if (auto phi = get(def)) + for (DfgScope* current = scope; current; current = current->parent) + { + if (auto props = current->props.find(def)) + { + if (auto it = props->find(key); it != props->end()) + return NotNull{it->second}; + } + else if (auto phi = get(def); phi && phi->operands.empty()) // Unresolved phi nodes + { + DefId result = defArena->freshCell(); + scope->props[def][key] = result; + return result; + } + } + + if (auto phi = get(def)) { std::vector defs; for (DefId operand : phi->operands) @@ -361,7 +405,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatIf* i) ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatWhile* w) { // TODO(controlflow): entry point has a back edge from exit point - DfgScope* whileScope = childScope(scope, /*isLoopScope=*/true); + DfgScope* whileScope = childScope(scope, DfgScope::Loop); visitExpr(whileScope, w->condition); visit(whileScope, w->body); @@ -373,7 +417,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatWhile* w) ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatRepeat* r) { // TODO(controlflow): entry point has a back edge from exit point - DfgScope* repeatScope = childScope(scope, /*isLoopScope=*/true); + DfgScope* repeatScope = childScope(scope, DfgScope::Loop); visitBlockWithoutChildScope(repeatScope, r->body); visitExpr(repeatScope, r->condition); @@ -427,8 +471,17 @@ 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); } return ControlFlow::None; @@ -436,7 +489,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatLocal* l) ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatFor* f) { - DfgScope* forScope = childScope(scope, /*isLoopScope=*/true); + DfgScope* forScope = childScope(scope, DfgScope::Loop); visitExpr(scope, f->from); visitExpr(scope, f->to); @@ -449,6 +502,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatFor* f) DefId def = defArena->freshCell(); graph.localDefs[f->var] = def; scope->bindings[f->var] = def; + captures[f->var].allVersions.push_back(def); // TODO(controlflow): entry point has a back edge from exit point visit(forScope, f->body); @@ -460,7 +514,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatFor* f) ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatForIn* f) { - DfgScope* forScope = childScope(scope, /*isLoopScope=*/true); + DfgScope* forScope = childScope(scope, DfgScope::Loop); for (AstLocal* local : f->vars) { @@ -470,6 +524,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatForIn* f) DefId def = defArena->freshCell(); graph.localDefs[local] = def; forScope->bindings[local] = def; + captures[local].allVersions.push_back(def); } // TODO(controlflow): entry point has a back edge from exit point @@ -527,10 +582,21 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatFunction* f) // // which is evidence that references to variables must be a phi node of all possible definitions, // but for bug compatibility, we'll assume the same thing here. - DefId prototype = defArena->freshCell(); - visitLValue(scope, f->name, prototype); + visitLValue(scope, f->name, defArena->freshCell()); visitExpr(scope, f->func); + if (auto local = f->name->as()) + { + // local f + // function f() + // if cond() then + // f() -- should reference only the function version and other future version, and nothing prior + // end + // end + FunctionCapture& capture = captures[local->local]; + capture.versionOffset = capture.allVersions.size() - 1; + } + return ControlFlow::None; } @@ -539,6 +605,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatLocalFunction* l DefId def = defArena->freshCell(); graph.localDefs[l->name] = def; scope->bindings[l->name] = def; + captures[l->name].allVersions.push_back(def); visitExpr(scope, l->func); return ControlFlow::None; @@ -559,6 +626,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatDeclareGlobal* d DefId def = defArena->freshCell(); graph.declaredDefs[d] = def; scope->bindings[d->name] = def; + captures[d->name].allVersions.push_back(def); visitType(scope, d->type); @@ -570,6 +638,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatDeclareFunction* DefId def = defArena->freshCell(); graph.declaredDefs[d] = def; scope->bindings[d->name] = def; + captures[d->name].allVersions.push_back(def); DfgScope* unreachable = childScope(scope); visitGenerics(unreachable, d->generics); @@ -669,14 +738,9 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprGroup* gr DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprLocal* l) { - // DfgScope::lookup is intentional here: we want to be able to ice. - if (auto def = scope->lookup(l->local)) - { - const RefinementKey* key = keyArena->leaf(*def); - return {*def, key}; - } - - handle->ice("DFG: AstExprLocal came before its declaration?"); + DefId def = lookup(scope, l->local); + const RefinementKey* key = keyArena->leaf(def); + return {def, key}; } DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprGlobal* g) @@ -718,12 +782,12 @@ 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) { - DfgScope* signatureScope = childScope(scope); + DfgScope* signatureScope = childScope(scope, DfgScope::Function); if (AstLocal* self = f->self) { @@ -733,6 +797,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunction* DefId def = defArena->freshCell(); graph.localDefs[self] = def; signatureScope->bindings[self] = def; + captures[self].allVersions.push_back(def); } for (AstLocal* param : f->args) @@ -743,6 +808,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunction* DefId def = defArena->freshCell(); graph.localDefs[param] = def; signatureScope->bindings[param] = def; + captures[param].allVersions.push_back(def); } if (f->varargAnnotation) @@ -766,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) @@ -827,41 +899,46 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprError* er void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExpr* e, DefId incomingDef, bool isCompoundAssignment) { - if (auto l = e->as()) - return visitLValue(scope, l, incomingDef, isCompoundAssignment); - else if (auto g = e->as()) - return visitLValue(scope, g, incomingDef, isCompoundAssignment); - else if (auto i = e->as()) - return visitLValue(scope, i, incomingDef); - else if (auto i = e->as()) - return visitLValue(scope, i, incomingDef); - else if (auto error = e->as()) - return visitLValue(scope, error, incomingDef); - else - handle->ice("Unknown AstExpr in DataFlowGraphBuilder::visitLValue"); + auto go = [&]() { + if (auto l = e->as()) + return visitLValue(scope, l, incomingDef, isCompoundAssignment); + else if (auto g = e->as()) + return visitLValue(scope, g, incomingDef, isCompoundAssignment); + else if (auto i = e->as()) + return visitLValue(scope, i, incomingDef); + else if (auto i = e->as()) + return visitLValue(scope, i, incomingDef); + else if (auto error = e->as()) + return visitLValue(scope, error, incomingDef); + else + handle->ice("Unknown AstExpr in DataFlowGraphBuilder::visitLValue"); + }; + + graph.astDefs[e] = go(); } -void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef, bool isCompoundAssignment) +DefId DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef, bool isCompoundAssignment) { // We need to keep the previous def around for a compound assignment. if (isCompoundAssignment) { - if (auto def = scope->lookup(l->local)) - graph.compoundAssignDefs[l] = *def; + DefId def = lookup(scope, l->local); + graph.compoundAssignDefs[l] = def; } // In order to avoid alias tracking, we need to clip the reference to the parent def. if (scope->canUpdateDefinition(l->local)) { DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef)); - graph.astDefs[l] = updated; scope->bindings[l->local] = updated; + captures[l->local].allVersions.push_back(updated); + return updated; } else - visitExpr(scope, static_cast(l)); + return visitExpr(scope, static_cast(l)).def; } -void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef, bool isCompoundAssignment) +DefId DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef, bool isCompoundAssignment) { // We need to keep the previous def around for a compound assignment. if (isCompoundAssignment) @@ -874,28 +951,29 @@ void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprGlobal* g, DefId if (scope->canUpdateDefinition(g->name)) { DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef)); - graph.astDefs[g] = updated; scope->bindings[g->name] = updated; + captures[g->name].allVersions.push_back(updated); + return updated; } else - visitExpr(scope, static_cast(g)); + return visitExpr(scope, static_cast(g)).def; } -void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprIndexName* i, DefId incomingDef) +DefId DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprIndexName* i, DefId incomingDef) { DefId parentDef = visitExpr(scope, i->expr).def; if (scope->canUpdateDefinition(parentDef, i->index.value)) { DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef)); - graph.astDefs[i] = updated; scope->props[parentDef][i->index.value] = updated; + return updated; } else - visitExpr(scope, static_cast(i)); + return visitExpr(scope, static_cast(i)).def; } -void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprIndexExpr* i, DefId incomingDef) +DefId DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprIndexExpr* i, DefId incomingDef) { DefId parentDef = visitExpr(scope, i->expr).def; visitExpr(scope, i->index); @@ -905,20 +983,19 @@ void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprIndexExpr* i, Def if (scope->canUpdateDefinition(parentDef, string->value.data)) { DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef)); - graph.astDefs[i] = updated; scope->props[parentDef][string->value.data] = updated; + return updated; } else - visitExpr(scope, static_cast(i)); + return visitExpr(scope, static_cast(i)).def; } - - graph.astDefs[i] = defArena->freshCell(); + else + return defArena->freshCell(/*subscripted=*/true); } -void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprError* error, DefId incomingDef) +DefId DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprError* error, DefId incomingDef) { - DefId def = visitExpr(scope, error).def; - graph.astDefs[error] = def; + return visitExpr(scope, error).def; } void DataFlowGraphBuilder::visitType(DfgScope* scope, AstType* t) diff --git a/Analysis/src/Def.cpp b/Analysis/src/Def.cpp index 2b3bbeac..1ecaca22 100644 --- a/Analysis/src/Def.cpp +++ b/Analysis/src/Def.cpp @@ -19,17 +19,13 @@ bool containsSubscriptedDefinition(DefId def) return false; } -DefId DefArena::freshCell(bool subscripted) +void collectOperands(DefId def, std::vector* operands) { - return NotNull{allocator.allocate(Def{Cell{subscripted}})}; -} - -static void collectOperands(DefId def, std::vector& operands) -{ - if (std::find(operands.begin(), operands.end(), def) != operands.end()) + LUAU_ASSERT(operands); + if (std::find(operands->begin(), operands->end(), def) != operands->end()) return; else if (get(def)) - operands.push_back(def); + operands->push_back(def); else if (auto phi = get(def)) { for (const Def* operand : phi->operands) @@ -37,6 +33,11 @@ static void collectOperands(DefId def, std::vector& operands) } } +DefId DefArena::freshCell(bool subscripted) +{ + return NotNull{allocator.allocate(Def{Cell{subscripted}})}; +} + DefId DefArena::phi(DefId a, DefId b) { return phi({a, b}); @@ -46,7 +47,7 @@ DefId DefArena::phi(const std::vector& defs) { std::vector operands; for (DefId operand : defs) - collectOperands(operand, operands); + collectOperands(operand, &operands); // There's no need to allocate a Phi node for a singleton set. if (operands.size() == 1) diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 125f2457..13b8949e 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -32,13 +32,12 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false) -LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100) // TODO: Remove with FFlagLuauTypecheckLimitControls LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false) LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false) -LUAU_FASTFLAGVARIABLE(LuauTypecheckLimitControls, false) LUAU_FASTFLAGVARIABLE(CorrectEarlyReturnInMarkDirty, false) LUAU_FASTFLAGVARIABLE(LuauDefinitionFileSetModuleName, false) +LUAU_FASTFLAGVARIABLE(LuauRethrowSingleModuleIce, false) namespace Luau { @@ -681,8 +680,7 @@ std::vector Frontend::checkQueuedModules(std::optional Frontend::checkQueuedModules(std::optional 0) + typeCheckLimits.instantiationChildLimit = std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult)); else - typeCheckLimits.finishTime = std::nullopt; + typeCheckLimits.instantiationChildLimit = std::nullopt; - // TODO: This is a dirty ad hoc solution for autocomplete timeouts - // We are trying to dynamically adjust our existing limits to lower total typechecking time under the limit - // so that we'll have type information for the whole file at lower quality instead of a full abort in the middle - if (item.options.applyInternalLimitScaling) - { - if (FInt::LuauTarjanChildLimit > 0) - typeCheckLimits.instantiationChildLimit = std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult)); - else - typeCheckLimits.instantiationChildLimit = std::nullopt; - - if (FInt::LuauTypeInferIterationLimit > 0) - typeCheckLimits.unifierIterationLimit = std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult)); - else - typeCheckLimits.unifierIterationLimit = std::nullopt; - } - - typeCheckLimits.cancellationToken = item.options.cancellationToken; + if (FInt::LuauTypeInferIterationLimit > 0) + typeCheckLimits.unifierIterationLimit = std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult)); + else + typeCheckLimits.unifierIterationLimit = std::nullopt; } + typeCheckLimits.cancellationToken = item.options.cancellationToken; + if (item.options.forAutocomplete) { - double autocompleteTimeLimit = FInt::LuauAutocompleteCheckTimeoutMs / 1000.0; - - if (!FFlag::LuauTypecheckLimitControls) - { - // The autocomplete typecheck is always in strict mode with DM awareness - // to provide better type information for IDE features - - if (autocompleteTimeLimit != 0.0) - typeCheckLimits.finishTime = TimeTrace::getClock() + autocompleteTimeLimit; - else - typeCheckLimits.finishTime = std::nullopt; - - // TODO: This is a dirty ad hoc solution for autocomplete timeouts - // We are trying to dynamically adjust our existing limits to lower total typechecking time under the limit - // so that we'll have type information for the whole file at lower quality instead of a full abort in the middle - if (FInt::LuauTarjanChildLimit > 0) - typeCheckLimits.instantiationChildLimit = std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult)); - else - typeCheckLimits.instantiationChildLimit = std::nullopt; - - if (FInt::LuauTypeInferIterationLimit > 0) - typeCheckLimits.unifierIterationLimit = std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult)); - else - typeCheckLimits.unifierIterationLimit = std::nullopt; - - typeCheckLimits.cancellationToken = item.options.cancellationToken; - } - // The autocomplete typecheck is always in strict mode with DM awareness to provide better type information for IDE features ModulePtr moduleForAutocomplete = check(sourceModule, Mode::Strict, requireCycles, environmentScope, /*forAutocomplete*/ true, /*recordJsonLog*/ false, typeCheckLimits); double duration = getTimestamp() - timestamp; - if (FFlag::LuauTypecheckLimitControls) - { - moduleForAutocomplete->checkDurationSec = duration; + moduleForAutocomplete->checkDurationSec = duration; - if (item.options.moduleTimeLimitSec && item.options.applyInternalLimitScaling) - applyInternalLimitScaling(sourceNode, moduleForAutocomplete, *item.options.moduleTimeLimitSec); - } - else - { - if (moduleForAutocomplete->timeout) - sourceNode.autocompleteLimitsMult = sourceNode.autocompleteLimitsMult / 2.0; - else if (duration < autocompleteTimeLimit / 2.0) - sourceNode.autocompleteLimitsMult = std::min(sourceNode.autocompleteLimitsMult * 2.0, 1.0); - } + if (item.options.moduleTimeLimitSec && item.options.applyInternalLimitScaling) + applyInternalLimitScaling(sourceNode, moduleForAutocomplete, *item.options.moduleTimeLimitSec); item.stats.timeCheck += duration; item.stats.filesStrict += 1; @@ -986,29 +958,16 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item) return; } - if (!FFlag::LuauTypecheckLimitControls) - { - typeCheckLimits.cancellationToken = item.options.cancellationToken; - } - ModulePtr module = check(sourceModule, mode, requireCycles, environmentScope, /*forAutocomplete*/ false, item.recordJsonLog, typeCheckLimits); - if (FFlag::LuauTypecheckLimitControls) - { - double duration = getTimestamp() - timestamp; + double duration = getTimestamp() - timestamp; - module->checkDurationSec = duration; + module->checkDurationSec = duration; - if (item.options.moduleTimeLimitSec && item.options.applyInternalLimitScaling) - applyInternalLimitScaling(sourceNode, module, *item.options.moduleTimeLimitSec); - - item.stats.timeCheck += duration; - } - else - { - item.stats.timeCheck += getTimestamp() - timestamp; - } + if (item.options.moduleTimeLimitSec && item.options.applyInternalLimitScaling) + applyInternalLimitScaling(sourceNode, module, *item.options.moduleTimeLimitSec); + item.stats.timeCheck += duration; item.stats.filesStrict += mode == Mode::Strict; item.stats.filesNonstrict += mode == Mode::Nonstrict; diff --git a/Analysis/src/NonStrictTypeChecker.cpp b/Analysis/src/NonStrictTypeChecker.cpp index 2372a9a7..ef6343f2 100644 --- a/Analysis/src/NonStrictTypeChecker.cpp +++ b/Analysis/src/NonStrictTypeChecker.cpp @@ -12,6 +12,7 @@ #include "Luau/TypeArena.h" #include "Luau/TypeFamily.h" #include "Luau/Def.h" +#include "Luau/TypeFwd.h" #include #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 @@ private: 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 918da330..9eda8a0e 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 0e246204..f1fe83ee 100644 --- a/Analysis/src/TypeAttach.cpp +++ b/Analysis/src/TypeAttach.cpp @@ -106,7 +106,12 @@ public: 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 32b91637..f6728657 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 a3a67ace..3e2c9cc4 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 50fef7fc..abe28b11 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/Bytecode.cpp b/CLI/Bytecode.cpp index 5002ce1d..76faa6fe 100644 --- a/CLI/Bytecode.cpp +++ b/CLI/Bytecode.cpp @@ -124,7 +124,7 @@ static bool analyzeFile(const char* name, const unsigned nestingLimit, std::vect { Luau::BytecodeBuilder bcb; - compileOrThrow(bcb, source.value(), copts()); + compileOrThrow(bcb, *source, copts()); const std::string& bytecode = bcb.getBytecode(); diff --git a/CLI/Require.cpp b/CLI/Require.cpp index 3f4278ec..dd95d634 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/CodeGen/src/CodeBlockUnwind.cpp b/CodeGen/src/CodeBlockUnwind.cpp index b6f91437..f883d6e3 100644 --- a/CodeGen/src/CodeBlockUnwind.cpp +++ b/CodeGen/src/CodeBlockUnwind.cpp @@ -5,6 +5,7 @@ #include "Luau/UnwindBuilder.h" #include +#include #if defined(_WIN32) && defined(_M_X64) diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index 3e6f5e8b..84d3f900 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -42,8 +42,18 @@ LUAU_FASTFLAGVARIABLE(DebugCodegenNoOpt, false) LUAU_FASTFLAGVARIABLE(DebugCodegenOptSize, false) LUAU_FASTFLAGVARIABLE(DebugCodegenSkipNumbering, false) + +// Per-module IR instruction count limit LUAU_FASTINTVARIABLE(CodegenHeuristicsInstructionLimit, 1'048'576) // 1 M -LUAU_FASTINTVARIABLE(CodegenHeuristicsBlockLimit, 65'536) // 64 K + +// Per-function IR block limit +// Current value is based on some member variables being limited to 16 bits +// Because block check is made before optimization passes and optimization can generate new blocks, limit is lowered 2x +// The limit will probably be adjusted in the future to avoid performance issues with analysis that's more complex than O(n) +LUAU_FASTINTVARIABLE(CodegenHeuristicsBlockLimit, 32'768) // 32 K + +// Per-function IR instruction limit +// Current value is based on some member variables being limited to 16 bits LUAU_FASTINTVARIABLE(CodegenHeuristicsBlockInstructionLimit, 65'536) // 64 K namespace Luau @@ -104,11 +114,18 @@ static void logPerfFunction(Proto* p, uintptr_t addr, unsigned size) } template -static std::optional createNativeFunction(AssemblyBuilder& build, ModuleHelpers& helpers, Proto* proto) +static std::optional createNativeFunction(AssemblyBuilder& build, ModuleHelpers& helpers, Proto* proto, uint32_t& totalIrInstCount) { IrBuilder ir; ir.buildFunctionIr(proto); + unsigned instCount = unsigned(ir.function.instructions.size()); + + if (totalIrInstCount + instCount >= unsigned(FInt::CodegenHeuristicsInstructionLimit.value)) + return std::nullopt; + + totalIrInstCount += instCount; + if (!lowerFunction(ir, build, helpers, proto, {}, /* stats */ nullptr)) return std::nullopt; @@ -291,9 +308,13 @@ CodeGenCompilationResult compile(lua_State* L, int idx, unsigned int flags, Comp std::vector results; results.reserve(protos.size()); + uint32_t totalIrInstCount = 0; + for (Proto* p : protos) - if (std::optional np = createNativeFunction(build, helpers, p)) + { + if (std::optional np = createNativeFunction(build, helpers, p, totalIrInstCount)) results.push_back(*np); + } // Very large modules might result in overflowing a jump offset; in this case we currently abandon the entire module if (!build.finalize()) diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index 2ebe4349..3075ac9a 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -253,11 +253,6 @@ inline bool lowerIr(A64::AssemblyBuilderA64& build, IrBuilder& ir, const std::ve template inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options, LoweringStats* stats) { - helpers.bytecodeInstructionCount += unsigned(ir.function.instructions.size()); - - if (helpers.bytecodeInstructionCount >= unsigned(FInt::CodegenHeuristicsInstructionLimit.value)) - return false; - killUnusedBlocks(ir.function); unsigned preOptBlockCount = 0; @@ -268,9 +263,7 @@ inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers& preOptBlockCount += (block.kind != IrBlockKind::Dead); unsigned blockInstructions = block.finish - block.start; maxBlockInstructions = std::max(maxBlockInstructions, blockInstructions); - }; - - helpers.preOptBlockCount += preOptBlockCount; + } // we update stats before checking the heuristic so that even if we bail out // our stats include information about the limit that was exceeded. @@ -280,9 +273,7 @@ inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers& stats->maxBlockInstructions = maxBlockInstructions; } - // we use helpers.blocksPreOpt instead of stats.blocksPreOpt since - // stats can be null across some code paths. - if (helpers.preOptBlockCount >= unsigned(FInt::CodegenHeuristicsBlockLimit.value)) + if (preOptBlockCount >= unsigned(FInt::CodegenHeuristicsBlockLimit.value)) return false; if (maxBlockInstructions >= unsigned(FInt::CodegenHeuristicsBlockInstructionLimit.value)) diff --git a/CodeGen/src/EmitCommon.h b/CodeGen/src/EmitCommon.h index f0e14103..013ba88f 100644 --- a/CodeGen/src/EmitCommon.h +++ b/CodeGen/src/EmitCommon.h @@ -31,9 +31,6 @@ struct ModuleHelpers // A64 Label continueCall; // x0: closure - - unsigned bytecodeInstructionCount = 0; - unsigned preOptBlockCount = 0; }; } // namespace CodeGen diff --git a/Common/include/Luau/DenseHash.h b/Common/include/Luau/DenseHash.h index f175b169..fe12ebc6 100644 --- a/Common/include/Luau/DenseHash.h +++ b/Common/include/Luau/DenseHash.h @@ -285,9 +285,8 @@ public: using value_type = Item; using reference = Item&; using pointer = Item*; - using iterator = pointer; - using difference_type = size_t; - using iterator_category = std::input_iterator_tag; + using difference_type = ptrdiff_t; + using iterator_category = std::forward_iterator_tag; const_iterator() : set(0) @@ -348,6 +347,12 @@ public: class iterator { public: + using value_type = MutableItem; + using reference = MutableItem&; + using pointer = MutableItem*; + using difference_type = ptrdiff_t; + using iterator_category = std::forward_iterator_tag; + iterator() : set(0) , index(0) diff --git a/Common/include/Luau/ExperimentalFlags.h b/Common/include/Luau/ExperimentalFlags.h index 94d13ea8..7372cc0d 100644 --- a/Common/include/Luau/ExperimentalFlags.h +++ b/Common/include/Luau/ExperimentalFlags.h @@ -14,6 +14,7 @@ inline bool isFlagExperimental(const char* flag) "LuauInstantiateInSubtyping", // requires some fixes to lua-apps code "LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins "LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative + "LuauUpdatedRequireByStringSemantics", // requires some small fixes to fully implement some proposed changes // makes sure we always have at least one entry nullptr, }; diff --git a/bench/bench_support.lua b/bench/bench_support.lua index 9e415fc1..da637ac9 100644 --- a/bench/bench_support.lua +++ b/bench/bench_support.lua @@ -66,7 +66,8 @@ end -- and 'false' otherwise. -- -- Example usage: --- local bench = script and require(script.Parent.bench_support) or require("bench_support") +-- local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +-- local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") -- function testFunc() -- ... -- end diff --git a/bench/gc/test_BinaryTree.lua b/bench/gc/test_BinaryTree.lua index 2a797382..36dff9de 100644 --- a/bench/gc/test_BinaryTree.lua +++ b/bench/gc/test_BinaryTree.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/gc/test_GC_Boehm_Trees.lua b/bench/gc/test_GC_Boehm_Trees.lua index 08af2742..8170103d 100644 --- a/bench/gc/test_GC_Boehm_Trees.lua +++ b/bench/gc/test_GC_Boehm_Trees.lua @@ -1,5 +1,6 @@ --!nonstrict -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") local stretchTreeDepth = 18 -- about 16Mb local longLivedTreeDepth = 16 -- about 4Mb diff --git a/bench/gc/test_GC_Tree_Pruning_Eager.lua b/bench/gc/test_GC_Tree_Pruning_Eager.lua index 2111d9ff..38aa7626 100644 --- a/bench/gc/test_GC_Tree_Pruning_Eager.lua +++ b/bench/gc/test_GC_Tree_Pruning_Eager.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() local count = 1 diff --git a/bench/gc/test_GC_Tree_Pruning_Gen.lua b/bench/gc/test_GC_Tree_Pruning_Gen.lua index f88bd7f4..85081f70 100644 --- a/bench/gc/test_GC_Tree_Pruning_Gen.lua +++ b/bench/gc/test_GC_Tree_Pruning_Gen.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() local count = 1 diff --git a/bench/gc/test_GC_Tree_Pruning_Lazy.lua b/bench/gc/test_GC_Tree_Pruning_Lazy.lua index 3ea6bbef..834ec1ab 100644 --- a/bench/gc/test_GC_Tree_Pruning_Lazy.lua +++ b/bench/gc/test_GC_Tree_Pruning_Lazy.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() local count = 1 diff --git a/bench/gc/test_GC_hashtable_Keyval.lua b/bench/gc/test_GC_hashtable_Keyval.lua index fcb24826..aa7481d3 100644 --- a/bench/gc/test_GC_hashtable_Keyval.lua +++ b/bench/gc/test_GC_hashtable_Keyval.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() local t = {} diff --git a/bench/gc/test_LB_mandel.lua b/bench/gc/test_LB_mandel.lua index fe5b4eb2..a8beb4fd 100644 --- a/bench/gc/test_LB_mandel.lua +++ b/bench/gc/test_LB_mandel.lua @@ -21,7 +21,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/gc/test_LargeTableCtor_array.lua b/bench/gc/test_LargeTableCtor_array.lua index 535877f7..016dfd2d 100644 --- a/bench/gc/test_LargeTableCtor_array.lua +++ b/bench/gc/test_LargeTableCtor_array.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/gc/test_LargeTableCtor_hash.lua b/bench/gc/test_LargeTableCtor_hash.lua index 6faf766a..c46a7ab4 100644 --- a/bench/gc/test_LargeTableCtor_hash.lua +++ b/bench/gc/test_LargeTableCtor_hash.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/gc/test_Pcall_pcall_yield.lua b/bench/gc/test_Pcall_pcall_yield.lua index ac46c79e..ae0a4b46 100644 --- a/bench/gc/test_Pcall_pcall_yield.lua +++ b/bench/gc/test_Pcall_pcall_yield.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/gc/test_SunSpider_3d-raytrace.lua b/bench/gc/test_SunSpider_3d-raytrace.lua index 60e4f61e..3c050df7 100644 --- a/bench/gc/test_SunSpider_3d-raytrace.lua +++ b/bench/gc/test_SunSpider_3d-raytrace.lua @@ -22,7 +22,8 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ]] -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/gc/test_TableCreate_nil.lua b/bench/gc/test_TableCreate_nil.lua index 1eff20e9..707a2750 100644 --- a/bench/gc/test_TableCreate_nil.lua +++ b/bench/gc/test_TableCreate_nil.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/gc/test_TableCreate_number.lua b/bench/gc/test_TableCreate_number.lua index 620b5624..3e4305bd 100644 --- a/bench/gc/test_TableCreate_number.lua +++ b/bench/gc/test_TableCreate_number.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/gc/test_TableCreate_zerofill.lua b/bench/gc/test_TableCreate_zerofill.lua index 08c6c91b..fed439b4 100644 --- a/bench/gc/test_TableCreate_zerofill.lua +++ b/bench/gc/test_TableCreate_zerofill.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/gc/test_TableMarshal_select.lua b/bench/gc/test_TableMarshal_select.lua index 110d9125..9869da60 100644 --- a/bench/gc/test_TableMarshal_select.lua +++ b/bench/gc/test_TableMarshal_select.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/gc/test_TableMarshal_table_pack.lua b/bench/gc/test_TableMarshal_table_pack.lua index 45810a3f..3da855f5 100644 --- a/bench/gc/test_TableMarshal_table_pack.lua +++ b/bench/gc/test_TableMarshal_table_pack.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/gc/test_TableMarshal_varargs.lua b/bench/gc/test_TableMarshal_varargs.lua index 19ef81fc..64b41b43 100644 --- a/bench/gc/test_TableMarshal_varargs.lua +++ b/bench/gc/test_TableMarshal_varargs.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_AbsSum_abs.lua b/bench/micro_tests/test_AbsSum_abs.lua index 33b6d7a3..7e85646e 100644 --- a/bench/micro_tests/test_AbsSum_abs.lua +++ b/bench/micro_tests/test_AbsSum_abs.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_AbsSum_and_or.lua b/bench/micro_tests/test_AbsSum_and_or.lua index a613c466..c6ef3dea 100644 --- a/bench/micro_tests/test_AbsSum_and_or.lua +++ b/bench/micro_tests/test_AbsSum_and_or.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_AbsSum_math_abs.lua b/bench/micro_tests/test_AbsSum_math_abs.lua index 2f529c6a..e95ea674 100644 --- a/bench/micro_tests/test_AbsSum_math_abs.lua +++ b/bench/micro_tests/test_AbsSum_math_abs.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_Assert.lua b/bench/micro_tests/test_Assert.lua index fcc99b49..014de8dc 100644 --- a/bench/micro_tests/test_Assert.lua +++ b/bench/micro_tests/test_Assert.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_Factorial.lua b/bench/micro_tests/test_Factorial.lua index 1742c4cf..90cff22a 100644 --- a/bench/micro_tests/test_Factorial.lua +++ b/bench/micro_tests/test_Factorial.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_Failure_pcall_a_bar.lua b/bench/micro_tests/test_Failure_pcall_a_bar.lua index 16dd1bf7..5b6108ba 100644 --- a/bench/micro_tests/test_Failure_pcall_a_bar.lua +++ b/bench/micro_tests/test_Failure_pcall_a_bar.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_Failure_pcall_game_Foo.lua b/bench/micro_tests/test_Failure_pcall_game_Foo.lua index 90fb3fc8..6bd209ae 100644 --- a/bench/micro_tests/test_Failure_pcall_game_Foo.lua +++ b/bench/micro_tests/test_Failure_pcall_game_Foo.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_Failure_xpcall_a_bar.lua b/bench/micro_tests/test_Failure_xpcall_a_bar.lua index 50bff4ba..e00a3ca6 100644 --- a/bench/micro_tests/test_Failure_xpcall_a_bar.lua +++ b/bench/micro_tests/test_Failure_xpcall_a_bar.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_Failure_xpcall_game_Foo.lua b/bench/micro_tests/test_Failure_xpcall_game_Foo.lua index 5e91e38f..86dadc90 100644 --- a/bench/micro_tests/test_Failure_xpcall_game_Foo.lua +++ b/bench/micro_tests/test_Failure_xpcall_game_Foo.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_LargeTableCtor_array.lua b/bench/micro_tests/test_LargeTableCtor_array.lua index 535877f7..016dfd2d 100644 --- a/bench/micro_tests/test_LargeTableCtor_array.lua +++ b/bench/micro_tests/test_LargeTableCtor_array.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_LargeTableCtor_hash.lua b/bench/micro_tests/test_LargeTableCtor_hash.lua index 6faf766a..c46a7ab4 100644 --- a/bench/micro_tests/test_LargeTableCtor_hash.lua +++ b/bench/micro_tests/test_LargeTableCtor_hash.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_LargeTableSum_loop_index.lua b/bench/micro_tests/test_LargeTableSum_loop_index.lua index 2b65073c..2aae109e 100644 --- a/bench/micro_tests/test_LargeTableSum_loop_index.lua +++ b/bench/micro_tests/test_LargeTableSum_loop_index.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_LargeTableSum_loop_ipairs.lua b/bench/micro_tests/test_LargeTableSum_loop_ipairs.lua index b0c1085f..29205e26 100644 --- a/bench/micro_tests/test_LargeTableSum_loop_ipairs.lua +++ b/bench/micro_tests/test_LargeTableSum_loop_ipairs.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_LargeTableSum_loop_iter.lua b/bench/micro_tests/test_LargeTableSum_loop_iter.lua index 057420f6..ea2b157c 100644 --- a/bench/micro_tests/test_LargeTableSum_loop_iter.lua +++ b/bench/micro_tests/test_LargeTableSum_loop_iter.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_LargeTableSum_loop_pairs.lua b/bench/micro_tests/test_LargeTableSum_loop_pairs.lua index 55b84867..8d789fcf 100644 --- a/bench/micro_tests/test_LargeTableSum_loop_pairs.lua +++ b/bench/micro_tests/test_LargeTableSum_loop_pairs.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_MethodCalls.lua b/bench/micro_tests/test_MethodCalls.lua index fa56005f..f8b44527 100644 --- a/bench/micro_tests/test_MethodCalls.lua +++ b/bench/micro_tests/test_MethodCalls.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_OOP_constructor.lua b/bench/micro_tests/test_OOP_constructor.lua index 6e3633ce..9fec3b67 100644 --- a/bench/micro_tests/test_OOP_constructor.lua +++ b/bench/micro_tests/test_OOP_constructor.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_OOP_method_call.lua b/bench/micro_tests/test_OOP_method_call.lua index c2c6c4ed..1e5249c5 100644 --- a/bench/micro_tests/test_OOP_method_call.lua +++ b/bench/micro_tests/test_OOP_method_call.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_OOP_virtual_constructor.lua b/bench/micro_tests/test_OOP_virtual_constructor.lua index 48b1e559..df99e13b 100644 --- a/bench/micro_tests/test_OOP_virtual_constructor.lua +++ b/bench/micro_tests/test_OOP_virtual_constructor.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_Pcall_call_return.lua b/bench/micro_tests/test_Pcall_call_return.lua index 9d07708c..2a612175 100644 --- a/bench/micro_tests/test_Pcall_call_return.lua +++ b/bench/micro_tests/test_Pcall_call_return.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_Pcall_pcall_return.lua b/bench/micro_tests/test_Pcall_pcall_return.lua index a6ff3599..16bdfdd3 100644 --- a/bench/micro_tests/test_Pcall_pcall_return.lua +++ b/bench/micro_tests/test_Pcall_pcall_return.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_Pcall_pcall_yield.lua b/bench/micro_tests/test_Pcall_pcall_yield.lua index ac46c79e..ae0a4b46 100644 --- a/bench/micro_tests/test_Pcall_pcall_yield.lua +++ b/bench/micro_tests/test_Pcall_pcall_yield.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_Pcall_xpcall_return.lua b/bench/micro_tests/test_Pcall_xpcall_return.lua index a64eddfd..8ac2f0eb 100644 --- a/bench/micro_tests/test_Pcall_xpcall_return.lua +++ b/bench/micro_tests/test_Pcall_xpcall_return.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_SqrtSum_exponent.lua b/bench/micro_tests/test_SqrtSum_exponent.lua index eaddbfdd..bfd6fd72 100644 --- a/bench/micro_tests/test_SqrtSum_exponent.lua +++ b/bench/micro_tests/test_SqrtSum_exponent.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_SqrtSum_math_sqrt.lua b/bench/micro_tests/test_SqrtSum_math_sqrt.lua index 44b61cc9..1e1f42c7 100644 --- a/bench/micro_tests/test_SqrtSum_math_sqrt.lua +++ b/bench/micro_tests/test_SqrtSum_math_sqrt.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_SqrtSum_sqrt.lua b/bench/micro_tests/test_SqrtSum_sqrt.lua index 34d8b385..96880e7b 100644 --- a/bench/micro_tests/test_SqrtSum_sqrt.lua +++ b/bench/micro_tests/test_SqrtSum_sqrt.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_SqrtSum_sqrt_getfenv.lua b/bench/micro_tests/test_SqrtSum_sqrt_getfenv.lua index 242edb85..55f29e2e 100644 --- a/bench/micro_tests/test_SqrtSum_sqrt_getfenv.lua +++ b/bench/micro_tests/test_SqrtSum_sqrt_getfenv.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_SqrtSum_sqrt_roundabout.lua b/bench/micro_tests/test_SqrtSum_sqrt_roundabout.lua index fa8bfd0a..bbe48a64 100644 --- a/bench/micro_tests/test_SqrtSum_sqrt_roundabout.lua +++ b/bench/micro_tests/test_SqrtSum_sqrt_roundabout.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_StringInterp.lua b/bench/micro_tests/test_StringInterp.lua index 1e7ccbc7..55430519 100644 --- a/bench/micro_tests/test_StringInterp.lua +++ b/bench/micro_tests/test_StringInterp.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") bench.runCode(function() for j=1,1e6 do diff --git a/bench/micro_tests/test_TableCreate_nil.lua b/bench/micro_tests/test_TableCreate_nil.lua index 1eff20e9..707a2750 100644 --- a/bench/micro_tests/test_TableCreate_nil.lua +++ b/bench/micro_tests/test_TableCreate_nil.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_TableCreate_number.lua b/bench/micro_tests/test_TableCreate_number.lua index 620b5624..3e4305bd 100644 --- a/bench/micro_tests/test_TableCreate_number.lua +++ b/bench/micro_tests/test_TableCreate_number.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_TableCreate_zerofill.lua b/bench/micro_tests/test_TableCreate_zerofill.lua index 08c6c91b..fed439b4 100644 --- a/bench/micro_tests/test_TableCreate_zerofill.lua +++ b/bench/micro_tests/test_TableCreate_zerofill.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_TableFind_loop_ipairs.lua b/bench/micro_tests/test_TableFind_loop_ipairs.lua index f363013d..46560274 100644 --- a/bench/micro_tests/test_TableFind_loop_ipairs.lua +++ b/bench/micro_tests/test_TableFind_loop_ipairs.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_TableFind_table_find.lua b/bench/micro_tests/test_TableFind_table_find.lua index a7619fce..3f22122f 100644 --- a/bench/micro_tests/test_TableFind_table_find.lua +++ b/bench/micro_tests/test_TableFind_table_find.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_TableInsertion_index_cached.lua b/bench/micro_tests/test_TableInsertion_index_cached.lua index 7f75b022..0c34818f 100644 --- a/bench/micro_tests/test_TableInsertion_index_cached.lua +++ b/bench/micro_tests/test_TableInsertion_index_cached.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_TableInsertion_index_len.lua b/bench/micro_tests/test_TableInsertion_index_len.lua index b9f71e0e..120a5e28 100644 --- a/bench/micro_tests/test_TableInsertion_index_len.lua +++ b/bench/micro_tests/test_TableInsertion_index_len.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_TableInsertion_table_insert.lua b/bench/micro_tests/test_TableInsertion_table_insert.lua index 9efccd4d..1ad3fe22 100644 --- a/bench/micro_tests/test_TableInsertion_table_insert.lua +++ b/bench/micro_tests/test_TableInsertion_table_insert.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_TableInsertion_table_insert_index.lua b/bench/micro_tests/test_TableInsertion_table_insert_index.lua index af2292ba..41747139 100644 --- a/bench/micro_tests/test_TableInsertion_table_insert_index.lua +++ b/bench/micro_tests/test_TableInsertion_table_insert_index.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_TableIteration.lua b/bench/micro_tests/test_TableIteration.lua index 47a94a3b..5f78a48b 100644 --- a/bench/micro_tests/test_TableIteration.lua +++ b/bench/micro_tests/test_TableIteration.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_TableMarshal_select.lua b/bench/micro_tests/test_TableMarshal_select.lua index 110d9125..9869da60 100644 --- a/bench/micro_tests/test_TableMarshal_select.lua +++ b/bench/micro_tests/test_TableMarshal_select.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_TableMarshal_table_pack.lua b/bench/micro_tests/test_TableMarshal_table_pack.lua index 45810a3f..3da855f5 100644 --- a/bench/micro_tests/test_TableMarshal_table_pack.lua +++ b/bench/micro_tests/test_TableMarshal_table_pack.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_TableMarshal_table_unpack_array.lua b/bench/micro_tests/test_TableMarshal_table_unpack_array.lua index 67bc1efa..13d1d1c3 100644 --- a/bench/micro_tests/test_TableMarshal_table_unpack_array.lua +++ b/bench/micro_tests/test_TableMarshal_table_unpack_array.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_TableMarshal_table_unpack_range.lua b/bench/micro_tests/test_TableMarshal_table_unpack_range.lua index a678d8bf..e3aa68be 100644 --- a/bench/micro_tests/test_TableMarshal_table_unpack_range.lua +++ b/bench/micro_tests/test_TableMarshal_table_unpack_range.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_TableMarshal_varargs.lua b/bench/micro_tests/test_TableMarshal_varargs.lua index 19ef81fc..64b41b43 100644 --- a/bench/micro_tests/test_TableMarshal_varargs.lua +++ b/bench/micro_tests/test_TableMarshal_varargs.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_TableMove_empty_table.lua b/bench/micro_tests/test_TableMove_empty_table.lua index 75ce2729..39335564 100644 --- a/bench/micro_tests/test_TableMove_empty_table.lua +++ b/bench/micro_tests/test_TableMove_empty_table.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() local t = table.create(250001, 0) diff --git a/bench/micro_tests/test_TableMove_same_table.lua b/bench/micro_tests/test_TableMove_same_table.lua index 81576573..f62022b1 100644 --- a/bench/micro_tests/test_TableMove_same_table.lua +++ b/bench/micro_tests/test_TableMove_same_table.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() local t = table.create(5000001, 0) diff --git a/bench/micro_tests/test_TableMove_table_create.lua b/bench/micro_tests/test_TableMove_table_create.lua index 19dfd186..f03c4de7 100644 --- a/bench/micro_tests/test_TableMove_table_create.lua +++ b/bench/micro_tests/test_TableMove_table_create.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() local t = table.create(250001, 0) diff --git a/bench/micro_tests/test_TableRemoval_table_remove.lua b/bench/micro_tests/test_TableRemoval_table_remove.lua index 25acd541..13410116 100644 --- a/bench/micro_tests/test_TableRemoval_table_remove.lua +++ b/bench/micro_tests/test_TableRemoval_table_remove.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_TableSort.lua b/bench/micro_tests/test_TableSort.lua index 80031d1c..502cb2a5 100644 --- a/bench/micro_tests/test_TableSort.lua +++ b/bench/micro_tests/test_TableSort.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") local arr_months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} diff --git a/bench/micro_tests/test_ToNumberString.lua b/bench/micro_tests/test_ToNumberString.lua index 61104783..842b7c22 100644 --- a/bench/micro_tests/test_ToNumberString.lua +++ b/bench/micro_tests/test_ToNumberString.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") bench.runCode(function() for j=1,1e6 do diff --git a/bench/micro_tests/test_UpvalueCapture.lua b/bench/micro_tests/test_UpvalueCapture.lua index 96e8f576..4a2608c4 100644 --- a/bench/micro_tests/test_UpvalueCapture.lua +++ b/bench/micro_tests/test_UpvalueCapture.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_VariadicSelect.lua b/bench/micro_tests/test_VariadicSelect.lua index 668956f4..5a62f2d8 100644 --- a/bench/micro_tests/test_VariadicSelect.lua +++ b/bench/micro_tests/test_VariadicSelect.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/micro_tests/test_string_lib.lua b/bench/micro_tests/test_string_lib.lua index 3994c778..041f5b15 100644 --- a/bench/micro_tests/test_string_lib.lua +++ b/bench/micro_tests/test_string_lib.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") bench.runCode(function() local src = string.rep("abcdefghijklmnopqrstuvwxyz", 100) diff --git a/bench/micro_tests/test_table_concat.lua b/bench/micro_tests/test_table_concat.lua index 430ad0ab..590b7d4a 100644 --- a/bench/micro_tests/test_table_concat.lua +++ b/bench/micro_tests/test_table_concat.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") bench.runCode(function() for outer=1,28,3 do diff --git a/bench/tests/base64.lua b/bench/tests/base64.lua index 3755c54f..e580c595 100644 --- a/bench/tests/base64.lua +++ b/bench/tests/base64.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/tests/chess.lua b/bench/tests/chess.lua index f6ae2cc6..f551139e 100644 --- a/bench/tests/chess.lua +++ b/bench/tests/chess.lua @@ -1,5 +1,6 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") local RANKS = "12345678" local FILES = "abcdefgh" diff --git a/bench/tests/life.lua b/bench/tests/life.lua index 51586ad7..d050b013 100644 --- a/bench/tests/life.lua +++ b/bench/tests/life.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/tests/matrixmult.lua b/bench/tests/matrixmult.lua index d8dd4cc3..af38cb64 100644 --- a/bench/tests/matrixmult.lua +++ b/bench/tests/matrixmult.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") local function mmul(matrix1, matrix2) local shapeRows = #matrix1 diff --git a/bench/tests/mesh-normal-scalar.lua b/bench/tests/mesh-normal-scalar.lua index 368fa2fd..05bef373 100644 --- a/bench/tests/mesh-normal-scalar.lua +++ b/bench/tests/mesh-normal-scalar.lua @@ -1,5 +1,6 @@ --!strict -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/tests/pcmmix.lua b/bench/tests/pcmmix.lua index a1760f67..c98cee2c 100644 --- a/bench/tests/pcmmix.lua +++ b/bench/tests/pcmmix.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") local samples = 100_000 diff --git a/bench/tests/qsort.lua b/bench/tests/qsort.lua index 99037176..566c1b98 100644 --- a/bench/tests/qsort.lua +++ b/bench/tests/qsort.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/tests/sha256.lua b/bench/tests/sha256.lua index a01e801e..2ac0ab33 100644 --- a/bench/tests/sha256.lua +++ b/bench/tests/sha256.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/tests/shootout/ack.lua b/bench/tests/shootout/ack.lua index 540e8594..f7fd43a8 100644 --- a/bench/tests/shootout/ack.lua +++ b/bench/tests/shootout/ack.lua @@ -23,7 +23,8 @@ SOFTWARE. ]] -- http://www.bagley.org/~doug/shootout/ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/shootout/binary-trees.lua b/bench/tests/shootout/binary-trees.lua index a3c5ae64..89c5933c 100644 --- a/bench/tests/shootout/binary-trees.lua +++ b/bench/tests/shootout/binary-trees.lua @@ -25,7 +25,8 @@ SOFTWARE. -- http://benchmarksgame.alioth.debian.org/ -- contributed by Mike Pall -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/shootout/fannkuch-redux.lua b/bench/tests/shootout/fannkuch-redux.lua index e23f3a26..43bc9e41 100644 --- a/bench/tests/shootout/fannkuch-redux.lua +++ b/bench/tests/shootout/fannkuch-redux.lua @@ -25,7 +25,8 @@ SOFTWARE. -- http://benchmarksgame.alioth.debian.org/ -- contributed by Mike Pall -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/shootout/fixpoint-fact.lua b/bench/tests/shootout/fixpoint-fact.lua index 8e60e920..112acb4a 100644 --- a/bench/tests/shootout/fixpoint-fact.lua +++ b/bench/tests/shootout/fixpoint-fact.lua @@ -21,7 +21,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/shootout/heapsort.lua b/bench/tests/shootout/heapsort.lua index fe85859b..0daf97ab 100644 --- a/bench/tests/shootout/heapsort.lua +++ b/bench/tests/shootout/heapsort.lua @@ -21,7 +21,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/shootout/mandel.lua b/bench/tests/shootout/mandel.lua index fe5b4eb2..a3bbb7e5 100644 --- a/bench/tests/shootout/mandel.lua +++ b/bench/tests/shootout/mandel.lua @@ -21,7 +21,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/shootout/n-body.lua b/bench/tests/shootout/n-body.lua index 40341587..e0f9c63c 100644 --- a/bench/tests/shootout/n-body.lua +++ b/bench/tests/shootout/n-body.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/shootout/qt.lua b/bench/tests/shootout/qt.lua index 79cbe38b..d9b4a517 100644 --- a/bench/tests/shootout/qt.lua +++ b/bench/tests/shootout/qt.lua @@ -23,7 +23,8 @@ SOFTWARE. ]] -- Julia sets via interval cell-mapping (quadtree version) -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/shootout/queen.lua b/bench/tests/shootout/queen.lua index 185dabd8..c3508d60 100644 --- a/bench/tests/shootout/queen.lua +++ b/bench/tests/shootout/queen.lua @@ -21,7 +21,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/shootout/scimark.lua b/bench/tests/shootout/scimark.lua index ad0557b1..1b66df53 100644 --- a/bench/tests/shootout/scimark.lua +++ b/bench/tests/shootout/scimark.lua @@ -33,7 +33,8 @@ -- Modification to be compatible with Lua 5.3 ------------------------------------------------------------------------------ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/shootout/spectral-norm.lua b/bench/tests/shootout/spectral-norm.lua index 6d217aa6..b5116612 100644 --- a/bench/tests/shootout/spectral-norm.lua +++ b/bench/tests/shootout/spectral-norm.lua @@ -25,7 +25,8 @@ SOFTWARE. -- http://benchmarksgame.alioth.debian.org/ -- contributed by Mike Pall -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/sieve.lua b/bench/tests/sieve.lua index 718ec488..1bb45d99 100644 --- a/bench/tests/sieve.lua +++ b/bench/tests/sieve.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/tests/sunspider/3d-cube.lua b/bench/tests/sunspider/3d-cube.lua index 77fa0854..aac7a156 100644 --- a/bench/tests/sunspider/3d-cube.lua +++ b/bench/tests/sunspider/3d-cube.lua @@ -2,7 +2,8 @@ -- http://www.speich.net/computer/moztesting/3d.htm -- Created by Simon Speich -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/sunspider/3d-morph.lua b/bench/tests/sunspider/3d-morph.lua index 79e91419..8263f015 100644 --- a/bench/tests/sunspider/3d-morph.lua +++ b/bench/tests/sunspider/3d-morph.lua @@ -23,7 +23,8 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ]] -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/sunspider/3d-raytrace.lua b/bench/tests/sunspider/3d-raytrace.lua index 3d5276c7..33d464b8 100644 --- a/bench/tests/sunspider/3d-raytrace.lua +++ b/bench/tests/sunspider/3d-raytrace.lua @@ -22,7 +22,8 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ]] -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/sunspider/controlflow-recursive.lua b/bench/tests/sunspider/controlflow-recursive.lua index a2591b2f..1c78a3c2 100644 --- a/bench/tests/sunspider/controlflow-recursive.lua +++ b/bench/tests/sunspider/controlflow-recursive.lua @@ -3,7 +3,8 @@ http://shootout.alioth.debian.org/ contributed by Isaac Gouy ]] -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/sunspider/crypto-aes.lua b/bench/tests/sunspider/crypto-aes.lua index e3f54087..9692cf52 100644 --- a/bench/tests/sunspider/crypto-aes.lua +++ b/bench/tests/sunspider/crypto-aes.lua @@ -9,7 +9,8 @@ * returns byte-array encrypted value (16 bytes) */]] -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") -- Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [§5.1.1] local Sbox = { 0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, diff --git a/bench/tests/sunspider/fannkuch.lua b/bench/tests/sunspider/fannkuch.lua index ad77e83b..08cdcc24 100644 --- a/bench/tests/sunspider/fannkuch.lua +++ b/bench/tests/sunspider/fannkuch.lua @@ -3,7 +3,8 @@ http://shootout.alioth.debian.org/ contributed by Isaac Gouy ]] -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/sunspider/math-cordic.lua b/bench/tests/sunspider/math-cordic.lua index cdb10fa2..2b622377 100644 --- a/bench/tests/sunspider/math-cordic.lua +++ b/bench/tests/sunspider/math-cordic.lua @@ -23,7 +23,8 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ]] - local bench = script and require(script.Parent.bench_support) or require("bench_support") + local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/sunspider/math-partial-sums.lua b/bench/tests/sunspider/math-partial-sums.lua index 9977ceff..f0b4b0b7 100644 --- a/bench/tests/sunspider/math-partial-sums.lua +++ b/bench/tests/sunspider/math-partial-sums.lua @@ -3,7 +3,8 @@ http://shootout.alioth.debian.org/ contributed by Isaac Gouy ]] -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") function test() diff --git a/bench/tests/sunspider/n-body-oop.lua b/bench/tests/sunspider/n-body-oop.lua index adcc15a7..e04286c8 100644 --- a/bench/tests/sunspider/n-body-oop.lua +++ b/bench/tests/sunspider/n-body-oop.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support") local PI = 3.141592653589793 local SOLAR_MASS = 4 * PI * PI diff --git a/bench/tests/tictactoe.lua b/bench/tests/tictactoe.lua index 91d38f95..673dcd48 100644 --- a/bench/tests/tictactoe.lua +++ b/bench/tests/tictactoe.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/tests/trig.lua b/bench/tests/trig.lua index 702e699e..64bf611c 100644 --- a/bench/tests/trig.lua +++ b/bench/tests/trig.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") function test() diff --git a/bench/tests/voxelgen.lua b/bench/tests/voxelgen.lua index ae2cd861..b50a4592 100644 --- a/bench/tests/voxelgen.lua +++ b/bench/tests/voxelgen.lua @@ -1,4 +1,5 @@ -local bench = script and require(script.Parent.bench_support) or require("bench_support") +local function prequire(name) local success, result = pcall(require, name); return if success then result else nil end +local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support") -- Based on voxel terrain generator by Stickmasterluke diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt index 13d1a2a4..c18fbba5 100644 --- a/fuzz/CMakeLists.txt +++ b/fuzz/CMakeLists.txt @@ -1,5 +1,8 @@ # This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -cmake_minimum_required(VERSION 3.26) +if(${CMAKE_VERSION} VERSION_LESS "3.26") + message(WARNING "Building the Luau fuzzer requires Clang version 3.26 of higher.") + return() +endif() include(FetchContent) diff --git a/tests/AstJsonEncoder.test.cpp b/tests/AstJsonEncoder.test.cpp index 2fb1584d..ff63dd29 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 6570ec1c..769637a5 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 64d9348f..0a1e5a7e 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/Conformance.test.cpp b/tests/Conformance.test.cpp index 514211e1..a9c5bc37 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -33,6 +33,7 @@ LUAU_FASTFLAG(LuauCodeGenFixByteLower); LUAU_FASTFLAG(LuauCompileBufferAnnotation); LUAU_FASTFLAG(LuauLoopInterruptFix); LUAU_DYNAMIC_FASTFLAG(LuauStricterUtf8); +LUAU_FASTINT(CodegenHeuristicsInstructionLimit); static lua_CompileOptions defaultOptions() { @@ -2020,6 +2021,64 @@ TEST_CASE("HugeFunction") CHECK(lua_tonumber(L, -1) == 42); } +TEST_CASE("IrInstructionLimit") +{ + if (!codegen || !luau_codegen_supported()) + return; + + ScopedFastInt codegenHeuristicsInstructionLimit{FInt::CodegenHeuristicsInstructionLimit, 50'000}; + + std::string source; + + // Generate a hundred fat functions + for (int fn = 0; fn < 100; fn++) + { + source += "local function fn" + std::to_string(fn) + "(...)\n"; + source += "if ... then\n"; + source += "local p1, p2 = ...\n"; + source += "local _ = {\n"; + + for (int i = 0; i < 100; ++i) + { + source += "p1*0." + std::to_string(i) + ","; + source += "p2+0." + std::to_string(i) + ","; + } + + source += "}\n"; + source += "return _\n"; + source += "end\n"; + source += "end\n"; + } + + StateRef globalState(luaL_newstate(), lua_close); + lua_State* L = globalState.get(); + + luau_codegen_create(L); + + luaL_openlibs(L); + luaL_sandbox(L); + luaL_sandboxthread(L); + + size_t bytecodeSize = 0; + char* bytecode = luau_compile(source.data(), source.size(), nullptr, &bytecodeSize); + int result = luau_load(L, "=HugeFunction", bytecode, bytecodeSize, 0); + free(bytecode); + + REQUIRE(result == 0); + + Luau::CodeGen::CompilationStats nativeStats = {}; + Luau::CodeGen::CodeGenCompilationResult nativeResult = Luau::CodeGen::compile(L, -1, Luau::CodeGen::CodeGen_ColdFunctions, &nativeStats); + + // Limit is not hit immediately, so with some functions compiled it should be a success + CHECK(nativeResult != Luau::CodeGen::CodeGenCompilationResult::CodeGenFailed); + + // We should be able to compile at least one of our functions + CHECK(nativeStats.functionsCompiled > 0); + + // But because of the limit, not all of them (101 because there's an extra global function) + CHECK(nativeStats.functionsCompiled < 101); +} + TEST_CASE("BytecodeDistributionPerFunctionTest") { const char* source = R"( diff --git a/tests/DataFlowGraph.test.cpp b/tests/DataFlowGraph.test.cpp index 3488b90b..96c53f85 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" @@ -341,6 +342,7 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "property_lookup_on_a_phi_node") const Phi* phi = get(x3); REQUIRE(phi); + REQUIRE(phi->operands.size() == 2); CHECK(phi->operands.at(0) == x1); CHECK(phi->operands.at(1) == x2); } @@ -368,6 +370,7 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "property_lookup_on_a_phi_node_2") const Phi* phi = get(x3); REQUIRE(phi); + REQUIRE(phi->operands.size() == 2); CHECK(phi->operands.at(0) == x2); CHECK(phi->operands.at(1) == x1); } @@ -408,6 +411,222 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "property_lookup_on_a_phi_node_3") const Phi* phi = get(x3); REQUIRE(phi); + REQUIRE(phi->operands.size() == 2); + CHECK(phi->operands.at(0) == x1); + CHECK(phi->operands.at(1) == x2); +} + +TEST_CASE_FIXTURE(DataFlowGraphFixture, "function_captures_are_phi_nodes_of_all_versions") +{ + dfg(R"( + local x = 5 + + function f() + print(x) + x = nil + end + + f() + x = "five" + )"); + + DefId x1 = graph->getDef(query(module)->vars.data[0]); + DefId x2 = getDef(); // print(x) + DefId x3 = getDef(); // x = nil + DefId x4 = getDef(); // x = "five" + + CHECK(x1 != x2); + CHECK(x2 != x3); + CHECK(x3 != x4); + + const Phi* phi = get(x2); + REQUIRE(phi); + REQUIRE(phi->operands.size() == 3); + CHECK(phi->operands.at(0) == x1); + CHECK(phi->operands.at(1) == x3); + CHECK(phi->operands.at(2) == x4); +} + +TEST_CASE_FIXTURE(DataFlowGraphFixture, "function_captures_are_phi_nodes_of_all_versions_properties") +{ + dfg(R"( + local t = {} + t.x = 5 + + function f() + print(t.x) + t.x = nil + end + + f() + t.x = "five" + )"); + + DefId x1 = getDef(); // t.x = 5 + DefId x2 = getDef(); // print(t.x) + DefId x3 = getDef(); // t.x = nil + DefId x4 = getDef(); // t.x = "five" + + CHECK(x1 != x2); + CHECK(x2 != x3); + CHECK(x3 != x4); + + // When a local is referenced within a function, it is not pointer identical. + // Instead, it's a phi node of all possible versions, including just one version. + DefId t1 = graph->getDef(query(module)->vars.data[0]); + DefId t2 = getDef(); // print(t.x) + + const Phi* phi = get(t2); + REQUIRE(phi); + REQUIRE(phi->operands.size() == 1); + CHECK(phi->operands.at(0) == t1); +} + +TEST_CASE_FIXTURE(DataFlowGraphFixture, "local_f_which_is_prototyped_enclosed_by_function") +{ + dfg(R"( + local f + function f() + if cond() then + f() + end + end + )"); + + DefId f1 = graph->getDef(query(module)->vars.data[0]); + DefId f2 = getDef(); // function f() + DefId f3 = getDef(); // f() + + CHECK(f1 != f2); + CHECK(f2 != f3); + + const Phi* phi = get(f3); + REQUIRE(phi); + REQUIRE(phi->operands.size() == 1); + CHECK(phi->operands.at(0) == f2); +} + +TEST_CASE_FIXTURE(DataFlowGraphFixture, "local_f_which_is_prototyped_enclosed_by_function_has_some_prior_versions") +{ + dfg(R"( + local f + f = 5 + function f() + if cond() then + f() + end + end + )"); + + DefId f1 = graph->getDef(query(module)->vars.data[0]); + DefId f2 = getDef(); // f = 5 + DefId f3 = getDef(); // function f() + DefId f4 = getDef(); // f() + + CHECK(f1 != f2); + CHECK(f2 != f3); + CHECK(f3 != f4); + + const Phi* phi = get(f4); + REQUIRE(phi); + REQUIRE(phi->operands.size() == 1); + CHECK(phi->operands.at(0) == f3); +} + +TEST_CASE_FIXTURE(DataFlowGraphFixture, "local_f_which_is_prototyped_enclosed_by_function_has_some_future_versions") +{ + dfg(R"( + local f + function f() + if cond() then + f() + end + end + f = 5 + )"); + + DefId f1 = graph->getDef(query(module)->vars.data[0]); + DefId f2 = getDef(); // function f() + DefId f3 = getDef(); // f() + DefId f4 = getDef(); // f = 5 + + CHECK(f1 != f2); + CHECK(f2 != f3); + CHECK(f3 != f4); + + const Phi* phi = get(f3); + REQUIRE(phi); + REQUIRE(phi->operands.size() == 2); + CHECK(phi->operands.at(0) == f2); + 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); } diff --git a/tests/NonStrictTypeChecker.test.cpp b/tests/NonStrictTypeChecker.test.cpp index 59df5f4a..e4a4667f 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 49ee5050..8847ae05 100644 --- a/tests/RequireByString.test.cpp +++ b/tests/RequireByString.test.cpp @@ -56,15 +56,20 @@ public: for (int i = 0; i < 20; ++i) { - if (isDirectory(luauDirAbs + "/Luau/tests") || isDirectory(luauDirAbs + "/Client/Luau/tests")) + bool engineTestDir = isDirectory(luauDirAbs + "/Client/Luau/tests"); + bool luauTestDir = isDirectory(luauDirAbs + "/luau/tests"); + if (engineTestDir || luauTestDir) { - if (isDirectory(luauDirAbs + "/Client/Luau/tests")) + if (engineTestDir) { - luauDirRel += "/Client"; - luauDirAbs += "/Client"; + luauDirRel += "/Client/Luau"; + luauDirAbs += "/Client/Luau"; + } + else + { + luauDirRel += "/luau"; + luauDirAbs += "/luau"; } - luauDirRel += "/Luau"; - luauDirAbs += "/Luau"; if (type == PathType::Relative) return luauDirRel; @@ -388,4 +393,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 e34626ae..ef7c968a 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 293a6a8e..6de1b050 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 3cda9120..2ed91e96 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -736,7 +736,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "mutual_recursion") )"); LUAU_REQUIRE_NO_ERRORS(result); - dumpErrors(result); } TEST_CASE_FIXTURE(BuiltinsFixture, "toposort_doesnt_break_mutual_recursion") @@ -2184,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 32575840..f35d4139 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 e199ab1b..ed9c1fdd 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 @@ -1925,13 +1927,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table") // this test is DCR-only as an instance of DCR fixing a bug in the old solver CheckResult result = check(R"( - local idx, val - local function f(a: unknown) if typeof(a) == "table" then for i, v in a do - idx = i - val = v + return i, v end end end @@ -1939,17 +1938,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::DebugLuauDeferredConstraintResolution) - { - // Bug: We do not simplify at the right time - CHECK_EQ("unknown?", toString(requireType("idx"))); - CHECK_EQ("unknown?", toString(requireType("val"))); - } - else - { - CHECK_EQ("unknown", toString(requireType("idx"))); - CHECK_EQ("unknown", toString(requireType("val"))); - } + CHECK_EQ("(unknown) -> (unknown, unknown)", toString(requireType("f"))); } TEST_CASE_FIXTURE(BuiltinsFixture, "conditional_refinement_should_stay_error_suppressing") diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index 35e9ad56..b8ab635a 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 e8044968..07ee670c 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,5 +315,107 @@ 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") +{ + CheckResult result = check(R"( + local x = nil + + function f() + print(x) + x = "five" + end + + x = 5 + f() + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + CHECK("(number | string)?" == toString(requireTypeAtPosition({4, 18}))); +} + +TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_are_unions_of_all_assignments_2") +{ + CheckResult result = check(R"( + local t = {x = nil} + + function f() + print(t.x) + t = {x = "five"} + end + + t = {x = 5} + f() + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + CHECK("{ x: nil } | { x: number } | { x: string }" == toString(requireTypeAtPosition({4, 18}), {true})); + CHECK("(number | string)?" == toString(requireTypeAtPosition({4, 20}))); +} + +TEST_CASE_FIXTURE(TypeStateFixture, "prototyped_recursive_functions") +{ + CheckResult result = check(R"( + local f + function f() + if math.random() > 0.5 then + f() + end + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + CHECK("(() -> ())?" == toString(requireType("f"))); +} + +TEST_CASE_FIXTURE(TypeStateFixture, "prototyped_recursive_functions_but_has_future_assignments") +{ + CheckResult result = check(R"( + local f + function f() + if math.random() > 0.5 then + f() + end + end + f = 5 + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK("((() -> ()) | number)?" == toString(requireType("f"))); +} + +TEST_CASE_FIXTURE(TypeStateFixture, "prototyped_recursive_functions_but_has_previous_assignments") +{ + CheckResult result = check(R"( + local f + f = 5 + function f() + if math.random() > 0.5 then + f() + end + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + 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 dd5571c7..2cccd8f4 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 } + type A = { x: number, y: number } + type B = { x: number, y: number } + type C = { x: number } + type D = { x: number } -local a: A|B|C|D -local b = a.y + function f(a: A | B | C | D) + local y = a.y + local z = a.z + end -local c: A|(B|C)?|D -local d = c.y - -local e = a.z + function g(c: A | B | C | D | nil) + local d = c.y + end )"); LUAU_REQUIRE_ERROR_COUNT(4, result); CHECK_EQ("Key 'y' is missing from 'C', 'D' in the type 'A | B | C | D'", toString(result.errors[0])); + CHECK_EQ("Type 'A | B | C | D' does not have key 'z'", toString(result.errors[1])); - CHECK_EQ("Value of type '(A | B | C | D)?' could be nil", toString(result.errors[1])); - CHECK_EQ("Key 'y' is missing from 'C', 'D' in the type 'A | B | C | D'", toString(result.errors[2])); - - CHECK_EQ("Type 'A | B | C | D' does not have key 'z'", toString(result.errors[3])); + CHECK_EQ("Value of type '(A | B | C | D)?' could be nil", toString(result.errors[2])); + CHECK_EQ("Key 'y' is missing from 'C', 'D' in the type 'A | B | C | D'", toString(result.errors[3])); } TEST_CASE_FIXTURE(Fixture, "optional_iteration") @@ -470,25 +490,27 @@ type Z = { z: number } type XYZ = X | Y | Z -local a: XYZ -local b: { w: number } = a +function f(a: XYZ) + local b: { w: number } = a +end )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypeMismatch* tm = get(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 f(a: A) + function a.y(x) + return tostring(x * 2) + end - function a.y(x) - return tostring(x * 2) - end - - function a.y(x: string): number - return tonumber(x) or 0 + function a.y(x: string): number + return tonumber(x) or 0 + end end )"); @@ -568,12 +590,13 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "union_true_and_false") { CheckResult result = check(R"( - local x : boolean - local y1 : (true | false) = x -- OK - local y2 : (true | false | (string & number)) = x -- OK - local y3 : (true | (string & number) | false) = x -- OK - local y4 : (true | (boolean & true) | false) = x -- OK - )"); + function f(x : boolean) + local y1 : (true | false) = x -- OK + local y2 : (true | false | (string & number)) = x -- OK + local y3 : (true | (string & number) | false) = x -- OK + local y4 : (true | (boolean & true) | false) = x -- OK + end + )"); LUAU_REQUIRE_NO_ERRORS(result); } @@ -581,8 +604,9 @@ TEST_CASE_FIXTURE(Fixture, "union_true_and_false") TEST_CASE_FIXTURE(Fixture, "union_of_functions") { CheckResult result = check(R"( - local x : (number) -> number? - local y : ((number?) -> number?) | ((number) -> number) = x -- OK + function f(x : (number) -> number?) + local y : ((number?) -> number?) | ((number) -> number) = x -- OK + end )"); LUAU_REQUIRE_NO_ERRORS(result); @@ -591,8 +615,9 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions") TEST_CASE_FIXTURE(Fixture, "union_of_generic_functions") { CheckResult result = check(R"( - local x : (a) -> a? - 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 5e777fd4..8ec70d11 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 378f53a9..747e14f5 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 2a1c0e0a..67028abb 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 1d1393ff..94826b66 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 0ba9c249..e99cf70d 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 @@ -270,18 +264,27 @@ ProvisionalTests.xpcall_returns_what_f_returns RefinementTest.assert_a_to_be_truthy_then_assert_a_to_be_number 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 @@ -293,6 +296,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 @@ -394,6 +398,7 @@ TableTests.table_unifies_into_map TableTests.top_table_type TableTests.type_mismatch_on_massive_table_is_cut_short TableTests.unification_of_unions_in_a_self_referential_type +TableTests.unifying_tables_shouldnt_uaf1 TableTests.used_colon_instead_of_dot TableTests.used_dot_instead_of_colon TableTests.used_dot_instead_of_colon_but_correctly @@ -403,6 +408,7 @@ ToDot.function ToString.exhaustive_toString_of_cyclic_table ToString.free_types ToString.named_metatable_toStringNamedFunction +ToString.no_parentheses_around_cyclic_function_type_in_intersection ToString.pick_distinct_names_for_mixed_explicit_and_implicit_generics ToString.primitive ToString.tostring_unsee_ttv_if_array @@ -453,7 +459,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 @@ -466,6 +471,7 @@ TypeInfer.no_stack_overflow_from_isoptional TypeInfer.promote_tail_type_packs TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2 +TypeInfer.statements_are_topologically_sorted TypeInfer.stringify_nested_unions_with_optionals TypeInfer.tc_after_error_recovery_no_replacement_name_in_error TypeInfer.type_infer_recursion_limit_no_ice @@ -481,6 +487,7 @@ TypeInferAnyError.for_in_loop_iterator_is_error TypeInferAnyError.for_in_loop_iterator_is_error2 TypeInferAnyError.for_in_loop_iterator_returns_any TypeInferAnyError.intersection_of_any_can_have_props +TypeInferAnyError.metatable_of_any_can_be_a_table TypeInferAnyError.quantify_any_does_not_bind_to_itself TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any TypeInferAnyError.type_error_addition @@ -502,6 +509,7 @@ TypeInferFunctions.apply_of_lambda_with_inferred_and_explicit_types TypeInferFunctions.calling_function_with_anytypepack_doesnt_leak_free_types TypeInferFunctions.cannot_hoist_interior_defns_into_signature TypeInferFunctions.check_function_bodies +TypeInferFunctions.complicated_return_types_require_an_explicit_annotation TypeInferFunctions.concrete_functions_are_not_supertypes_of_function TypeInferFunctions.dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization TypeInferFunctions.dont_give_other_overloads_message_if_only_one_argument_matching_overload_exists @@ -538,12 +546,15 @@ TypeInferFunctions.it_is_ok_to_oversaturate_a_higher_order_function_argument TypeInferFunctions.list_all_overloads_if_no_overload_takes_given_argument_count TypeInferFunctions.list_only_alternative_overloads_that_match_argument_count TypeInferFunctions.luau_subtyping_is_np_hard +TypeInferFunctions.mutual_recursion TypeInferFunctions.no_lossy_function_type +TypeInferFunctions.num_is_solved_after_num_or_str TypeInferFunctions.occurs_check_failure_in_function_return_type 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 @@ -554,6 +565,7 @@ TypeInferFunctions.too_many_arguments TypeInferFunctions.too_many_arguments_error_location TypeInferFunctions.too_many_return_values_in_parentheses TypeInferFunctions.too_many_return_values_no_function +TypeInferFunctions.toposort_doesnt_break_mutual_recursion TypeInferFunctions.vararg_function_is_quantified TypeInferLoops.cli_68448_iterators_need_not_accept_nil TypeInferLoops.dcr_iteration_explore_raycast_minimization @@ -588,6 +600,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 @@ -598,6 +611,7 @@ TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_another_overload_works TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2 TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory +TypeInferOOP.method_depends_on_table TypeInferOOP.methods_are_topologically_sorted TypeInferOOP.object_constructor_can_refer_to_method_of_self TypeInferOOP.promise_type_error_too_complex @@ -632,7 +646,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 @@ -650,40 +663,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 @@ -691,5 +689,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