mirror of
https://github.com/luau-lang/luau.git
synced 2025-01-19 17:28:06 +00:00
Merge branch 'upstream' of https://github.com/luau-lang/luau into upstream
This commit is contained in:
commit
d4883bfb32
153 changed files with 1649 additions and 857 deletions
|
@ -197,24 +197,6 @@ struct UnpackConstraint
|
|||
bool resultIsLValue = false;
|
||||
};
|
||||
|
||||
// resultType ~ refine type mode discriminant
|
||||
//
|
||||
// Compute type & discriminant (or type | discriminant) as soon as possible (but
|
||||
// no sooner), simplify, and bind resultType to that type.
|
||||
struct RefineConstraint
|
||||
{
|
||||
enum
|
||||
{
|
||||
Intersection,
|
||||
Union
|
||||
} mode;
|
||||
|
||||
TypeId resultType;
|
||||
|
||||
TypeId type;
|
||||
TypeId discriminant;
|
||||
};
|
||||
|
||||
// resultType ~ T0 op T1 op ... op TN
|
||||
//
|
||||
// op is either union or intersection. If any of the input types are blocked,
|
||||
|
@ -249,7 +231,7 @@ struct ReducePackConstraint
|
|||
|
||||
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, IterableConstraint,
|
||||
NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, PrimitiveTypeConstraint, HasPropConstraint, SetPropConstraint,
|
||||
SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, RefineConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint>;
|
||||
SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint>;
|
||||
|
||||
struct Constraint
|
||||
{
|
||||
|
|
|
@ -150,7 +150,7 @@ private:
|
|||
*/
|
||||
ScopePtr childScope(AstNode* node, const ScopePtr& parent);
|
||||
|
||||
std::optional<TypeId> lookup(Scope* scope, DefId def);
|
||||
std::optional<TypeId> lookup(Scope* scope, DefId def, bool prototype = true);
|
||||
|
||||
/**
|
||||
* Adds a new constraint with no dependencies to a given scope.
|
||||
|
|
|
@ -132,7 +132,6 @@ struct ConstraintSolver
|
|||
bool tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint);
|
||||
bool tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint);
|
||||
bool tryDispatch(const RefineConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
|
|
|
@ -74,8 +74,15 @@ private:
|
|||
|
||||
struct DfgScope
|
||||
{
|
||||
enum ScopeType
|
||||
{
|
||||
Linear,
|
||||
Loop,
|
||||
Function,
|
||||
};
|
||||
|
||||
DfgScope* parent;
|
||||
bool isLoopScope;
|
||||
ScopeType scopeType;
|
||||
|
||||
using Bindings = DenseHashMap<Symbol, const Def*>;
|
||||
using Props = DenseHashMap<const Def*, std::unordered_map<std::string, const Def*>>;
|
||||
|
@ -117,11 +124,21 @@ private:
|
|||
|
||||
std::vector<std::unique_ptr<DfgScope>> scopes;
|
||||
|
||||
DfgScope* childScope(DfgScope* scope, bool isLoopScope = false);
|
||||
struct FunctionCapture
|
||||
{
|
||||
std::vector<DefId> captureDefs;
|
||||
std::vector<DefId> allVersions;
|
||||
size_t versionOffset = 0;
|
||||
};
|
||||
|
||||
DenseHashMap<Symbol, FunctionCapture> 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);
|
||||
|
|
|
@ -73,6 +73,7 @@ const T* get(DefId def)
|
|||
}
|
||||
|
||||
bool containsSubscriptedDefinition(DefId def);
|
||||
void collectOperands(DefId def, std::vector<DefId>* operands);
|
||||
|
||||
struct DefArena
|
||||
{
|
||||
|
|
|
@ -118,6 +118,12 @@ public:
|
|||
class const_iterator
|
||||
{
|
||||
public:
|
||||
using value_type = T;
|
||||
using reference = T&;
|
||||
using pointer = T*;
|
||||
using difference_type = ptrdiff_t;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
const_iterator(typename Impl::const_iterator impl, typename Impl::const_iterator end)
|
||||
: impl(impl)
|
||||
, end(end)
|
||||
|
|
|
@ -176,7 +176,7 @@ struct PrimitiveType
|
|||
}
|
||||
};
|
||||
|
||||
// Singleton types https://github.com/Roblox/luau/blob/master/rfcs/syntax-singleton-types.md
|
||||
// Singleton types https://github.com/luau-lang/rfcs/blob/master/docs/syntax-singleton-types.md
|
||||
// Types for true and false
|
||||
struct BooleanSingleton
|
||||
{
|
||||
|
|
|
@ -162,6 +162,8 @@ struct BuiltinTypeFamilies
|
|||
TypeFamily leFamily;
|
||||
TypeFamily eqFamily;
|
||||
|
||||
TypeFamily refineFamily;
|
||||
|
||||
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
|
||||
};
|
||||
|
||||
|
|
|
@ -200,6 +200,23 @@ struct AstJsonEncoder : public AstVisitor
|
|||
{
|
||||
writeString(name.value ? name.value : "");
|
||||
}
|
||||
void write(std::optional<AstArgumentName> name)
|
||||
{
|
||||
if (name)
|
||||
write(*name);
|
||||
else
|
||||
writeRaw("null");
|
||||
}
|
||||
void write(AstArgumentName name)
|
||||
{
|
||||
writeRaw("{");
|
||||
bool c = pushComma();
|
||||
writeType("AstArgumentName");
|
||||
write("name", name.first);
|
||||
write("location", name.second);
|
||||
popComma(c);
|
||||
writeRaw("}");
|
||||
}
|
||||
|
||||
void write(const Position& position)
|
||||
{
|
||||
|
@ -848,6 +865,7 @@ struct AstJsonEncoder : public AstVisitor
|
|||
PROP(generics);
|
||||
PROP(genericPacks);
|
||||
PROP(argTypes);
|
||||
PROP(argNames);
|
||||
PROP(returnTypes);
|
||||
});
|
||||
}
|
||||
|
@ -902,6 +920,22 @@ struct AstJsonEncoder : public AstVisitor
|
|||
});
|
||||
}
|
||||
|
||||
bool visit(class AstTypeSingletonBool* node) override
|
||||
{
|
||||
writeNode(node, "AstTypeSingletonBool", [&]() {
|
||||
write("value", node->value);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(class AstTypeSingletonString* node) override
|
||||
{
|
||||
writeNode(node, "AstTypeSingletonString", [&]() {
|
||||
write("value", node->value);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(class AstExprGroup* node) override
|
||||
{
|
||||
write(node);
|
||||
|
|
|
@ -205,7 +205,7 @@ ScopePtr ConstraintGenerator::childScope(AstNode* node, const ScopePtr& parent)
|
|||
return scope;
|
||||
}
|
||||
|
||||
std::optional<TypeId> ConstraintGenerator::lookup(Scope* scope, DefId def)
|
||||
std::optional<TypeId> ConstraintGenerator::lookup(Scope* scope, DefId def, bool prototype)
|
||||
{
|
||||
if (get<Cell>(def))
|
||||
return scope->lookup(def);
|
||||
|
@ -213,22 +213,24 @@ std::optional<TypeId> 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<TypeId> 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<Constraint*> excludeList{nullptr};
|
||||
|
||||
DefId def = dfg->getDef(function->name);
|
||||
std::optional<TypeId> existingFunctionTy = scope->lookup(def);
|
||||
std::optional<TypeId> existingFunctionTy = lookup(scope.get(), def);
|
||||
|
||||
if (AstExprLocal* localName = function->name->as<AstExprLocal>())
|
||||
{
|
||||
|
@ -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)
|
||||
|
|
|
@ -545,8 +545,6 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
|
|||
success = tryDispatch(*sottc, constraint);
|
||||
else if (auto uc = get<UnpackConstraint>(*constraint))
|
||||
success = tryDispatch(*uc, constraint);
|
||||
else if (auto rc = get<RefineConstraint>(*constraint))
|
||||
success = tryDispatch(*rc, constraint, force);
|
||||
else if (auto soc = get<SetOpConstraint>(*constraint))
|
||||
success = tryDispatch(*soc, constraint, force);
|
||||
else if (auto rc = get<ReduceConstraint>(*constraint))
|
||||
|
@ -887,9 +885,9 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
|||
// In order to prevent infinite types from being expanded and causing us to
|
||||
// cycle infinitely, we need to scan the type function for cases where we
|
||||
// expand the same alias with different type saturatedTypeArguments. See
|
||||
// https://github.com/Roblox/luau/pull/68 for the RFC responsible for this.
|
||||
// This is a little nicer than using a recursion limit because we can catch
|
||||
// the infinite expansion before actually trying to expand it.
|
||||
// https://github.com/luau-lang/luau/pull/68 for the RFC responsible for
|
||||
// this. This is a little nicer than using a recursion limit because we can
|
||||
// catch the infinite expansion before actually trying to expand it.
|
||||
InfiniteTypeFinder itf{this, signature, constraint->scope};
|
||||
itf.traverse(tf->type);
|
||||
|
||||
|
@ -1505,151 +1503,6 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
|||
return true;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/*
|
||||
* Search for types that prevent us from being ready to dispatch a particular
|
||||
* RefineConstraint.
|
||||
*/
|
||||
struct FindRefineConstraintBlockers : TypeOnceVisitor
|
||||
{
|
||||
DenseHashSet<TypeId> found{nullptr};
|
||||
bool visit(TypeId ty, const BlockedType&) override
|
||||
{
|
||||
found.insert(ty);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const PendingExpansionType&) override
|
||||
{
|
||||
found.insert(ty);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const ClassType&) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
static bool isNegatedAny(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
const NegationType* nt = get<NegationType>(ty);
|
||||
if (!nt)
|
||||
return false;
|
||||
TypeId negatedTy = follow(nt->ty);
|
||||
return bool(get<AnyType>(negatedTy));
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const RefineConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
{
|
||||
if (isBlocked(c.discriminant))
|
||||
return block(c.discriminant, constraint);
|
||||
|
||||
FindRefineConstraintBlockers fbt;
|
||||
fbt.traverse(c.discriminant);
|
||||
|
||||
if (!fbt.found.empty())
|
||||
{
|
||||
bool foundOne = false;
|
||||
|
||||
for (TypeId blocked : fbt.found)
|
||||
{
|
||||
if (blocked == c.type)
|
||||
continue;
|
||||
|
||||
block(blocked, constraint);
|
||||
foundOne = true;
|
||||
}
|
||||
|
||||
if (foundOne)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* HACK: Refinements sometimes produce a type T & ~any under the assumption
|
||||
* that ~any is the same as any. This is so so weird, but refinements needs
|
||||
* some way to say "I may refine this, but I'm not sure."
|
||||
*
|
||||
* It does this by refining on a blocked type and deferring the decision
|
||||
* until it is unblocked.
|
||||
*
|
||||
* Refinements also get negated, so we wind up with types like T & ~*blocked*
|
||||
*
|
||||
* We need to treat T & ~any as T in this case.
|
||||
*/
|
||||
|
||||
if (c.mode == RefineConstraint::Intersection && isNegatedAny(c.discriminant))
|
||||
{
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(c.type);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
const TypeId type = follow(c.type);
|
||||
|
||||
if (hasUnresolvedConstraints(type))
|
||||
return block(type, constraint);
|
||||
|
||||
LUAU_ASSERT(get<BlockedType>(c.resultType));
|
||||
|
||||
if (type == c.resultType)
|
||||
{
|
||||
/*
|
||||
* Sometimes, we get a constraint of the form
|
||||
*
|
||||
* *blocked-N* ~ refine *blocked-N* & U
|
||||
*
|
||||
* The constraint essentially states that a particular type is a
|
||||
* refinement of itself. This is weird and I think vacuous.
|
||||
*
|
||||
* I *believe* it is safe to replace the result with a fresh type that
|
||||
* is constrained by U. We effect this by minting a fresh type for the
|
||||
* result when U = any, else we bind the result to whatever discriminant
|
||||
* was offered.
|
||||
*/
|
||||
if (get<AnyType>(follow(c.discriminant)))
|
||||
{
|
||||
TypeId f = freshType(arena, builtinTypes, constraint->scope);
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(f);
|
||||
}
|
||||
else
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(c.discriminant);
|
||||
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto [result, blockedTypes] = c.mode == RefineConstraint::Intersection ? simplifyIntersection(builtinTypes, NotNull{arena}, type, c.discriminant)
|
||||
: simplifyUnion(builtinTypes, NotNull{arena}, type, c.discriminant);
|
||||
|
||||
if (!force && !blockedTypes.empty())
|
||||
return block(blockedTypes, constraint);
|
||||
|
||||
switch (shouldSuppressErrors(normalizer, c.type))
|
||||
{
|
||||
case ErrorSuppression::Suppress:
|
||||
{
|
||||
auto resultOrError = simplifyUnion(builtinTypes, arena, result, builtinTypes->errorType).result;
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(resultOrError);
|
||||
break;
|
||||
}
|
||||
case ErrorSuppression::DoNotSuppress:
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(result);
|
||||
break;
|
||||
case ErrorSuppression::NormalizationFailed:
|
||||
reportError(NormalizationTooComplex{}, constraint->location);
|
||||
break;
|
||||
}
|
||||
|
||||
unblock(c.resultType, constraint->location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
{
|
||||
bool blocked = false;
|
||||
|
|
|
@ -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<InternalE
|
|||
builder.handle = handle;
|
||||
builder.moduleScope = builder.childScope(nullptr); // nullptr is the root DFG scope.
|
||||
builder.visitBlockWithoutChildScope(builder.moduleScope, block);
|
||||
builder.resolveCaptures();
|
||||
|
||||
if (FFlag::DebugLuauFreezeArena)
|
||||
{
|
||||
|
@ -154,43 +155,64 @@ DataFlowGraph DataFlowGraphBuilder::build(AstStatBlock* block, NotNull<InternalE
|
|||
return std::move(builder.graph);
|
||||
}
|
||||
|
||||
DfgScope* DataFlowGraphBuilder::childScope(DfgScope* scope, bool isLoopScope)
|
||||
void DataFlowGraphBuilder::resolveCaptures()
|
||||
{
|
||||
return scopes.emplace_back(new DfgScope{scope, isLoopScope}).get();
|
||||
for (const auto& [_, capture] : captures)
|
||||
{
|
||||
std::vector<DefId> 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<Phi*>(get<Phi>(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<Phi>(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<Phi>(def); phi && phi->operands.empty()) // Unresolved phi nodes
|
||||
{
|
||||
DefId result = defArena->freshCell();
|
||||
scope->props[def][key] = result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto phi = get<Phi>(def))
|
||||
{
|
||||
std::vector<DefId> 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<AstExprTable>())
|
||||
{
|
||||
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<AstExprLocal>())
|
||||
{
|
||||
// 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<AstExprConstantString>())
|
||||
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<AstExprLocal>())
|
||||
return visitLValue(scope, l, incomingDef, isCompoundAssignment);
|
||||
else if (auto g = e->as<AstExprGlobal>())
|
||||
return visitLValue(scope, g, incomingDef, isCompoundAssignment);
|
||||
else if (auto i = e->as<AstExprIndexName>())
|
||||
return visitLValue(scope, i, incomingDef);
|
||||
else if (auto i = e->as<AstExprIndexExpr>())
|
||||
return visitLValue(scope, i, incomingDef);
|
||||
else if (auto error = e->as<AstExprError>())
|
||||
return visitLValue(scope, error, incomingDef);
|
||||
else
|
||||
handle->ice("Unknown AstExpr in DataFlowGraphBuilder::visitLValue");
|
||||
auto go = [&]() {
|
||||
if (auto l = e->as<AstExprLocal>())
|
||||
return visitLValue(scope, l, incomingDef, isCompoundAssignment);
|
||||
else if (auto g = e->as<AstExprGlobal>())
|
||||
return visitLValue(scope, g, incomingDef, isCompoundAssignment);
|
||||
else if (auto i = e->as<AstExprIndexName>())
|
||||
return visitLValue(scope, i, incomingDef);
|
||||
else if (auto i = e->as<AstExprIndexExpr>())
|
||||
return visitLValue(scope, i, incomingDef);
|
||||
else if (auto error = e->as<AstExprError>())
|
||||
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<AstExpr*>(l));
|
||||
return visitExpr(scope, static_cast<AstExpr*>(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<AstExpr*>(g));
|
||||
return visitExpr(scope, static_cast<AstExpr*>(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<AstExpr*>(i));
|
||||
return visitExpr(scope, static_cast<AstExpr*>(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<AstExpr*>(i));
|
||||
return visitExpr(scope, static_cast<AstExpr*>(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)
|
||||
|
|
|
@ -19,17 +19,13 @@ bool containsSubscriptedDefinition(DefId def)
|
|||
return false;
|
||||
}
|
||||
|
||||
DefId DefArena::freshCell(bool subscripted)
|
||||
void collectOperands(DefId def, std::vector<DefId>* operands)
|
||||
{
|
||||
return NotNull{allocator.allocate(Def{Cell{subscripted}})};
|
||||
}
|
||||
|
||||
static void collectOperands(DefId def, std::vector<DefId>& 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<Cell>(def))
|
||||
operands.push_back(def);
|
||||
operands->push_back(def);
|
||||
else if (auto phi = get<Phi>(def))
|
||||
{
|
||||
for (const Def* operand : phi->operands)
|
||||
|
@ -37,6 +33,11 @@ static void collectOperands(DefId def, std::vector<DefId>& 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<DefId>& defs)
|
|||
{
|
||||
std::vector<DefId> 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)
|
||||
|
|
|
@ -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<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptio
|
|||
sendItemTask(i);
|
||||
nextItems.clear();
|
||||
|
||||
// If we aren't done, but don't have anything processing, we hit a cycle
|
||||
if (remaining != 0 && processing == 0)
|
||||
if (FFlag::LuauRethrowSingleModuleIce && processing == 0)
|
||||
{
|
||||
// Typechecking might have been cancelled by user, don't return partial results
|
||||
if (cancelled)
|
||||
|
@ -690,9 +688,24 @@ std::vector<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptio
|
|||
|
||||
// We might have stopped because of a pending exception
|
||||
if (itemWithException)
|
||||
{
|
||||
recordItemResult(buildQueueItems[*itemWithException]);
|
||||
break;
|
||||
}
|
||||
|
||||
// If we aren't done, but don't have anything processing, we hit a cycle
|
||||
if (remaining != 0 && processing == 0)
|
||||
{
|
||||
if (!FFlag::LuauRethrowSingleModuleIce)
|
||||
{
|
||||
// Typechecking might have been cancelled by user, don't return partial results
|
||||
if (cancelled)
|
||||
return {};
|
||||
|
||||
// We might have stopped because of a pending exception
|
||||
if (itemWithException)
|
||||
{
|
||||
recordItemResult(buildQueueItems[*itemWithException]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sendCycleItemTask();
|
||||
|
@ -902,82 +915,41 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
|
|||
|
||||
TypeCheckLimits typeCheckLimits;
|
||||
|
||||
if (FFlag::LuauTypecheckLimitControls)
|
||||
if (item.options.moduleTimeLimitSec)
|
||||
typeCheckLimits.finishTime = TimeTrace::getClock() + *item.options.moduleTimeLimitSec;
|
||||
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 (item.options.applyInternalLimitScaling)
|
||||
{
|
||||
if (item.options.moduleTimeLimitSec)
|
||||
typeCheckLimits.finishTime = TimeTrace::getClock() + *item.options.moduleTimeLimitSec;
|
||||
if (FInt::LuauTarjanChildLimit > 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;
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeFamily.h"
|
||||
#include "Luau/Def.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
|
@ -57,8 +58,6 @@ struct StackPusher
|
|||
|
||||
struct NonStrictContext
|
||||
{
|
||||
std::unordered_map<const Def*, TypeId> context;
|
||||
|
||||
NonStrictContext() = default;
|
||||
|
||||
NonStrictContext(const NonStrictContext&) = delete;
|
||||
|
@ -109,7 +108,12 @@ struct NonStrictContext
|
|||
// Returns true if the removal was successful
|
||||
bool remove(const DefId& def)
|
||||
{
|
||||
return context.erase(def.get()) == 1;
|
||||
std::vector<DefId> defs;
|
||||
collectOperands(def, &defs);
|
||||
bool result = true;
|
||||
for (DefId def : defs)
|
||||
result = result && context.erase(def.get()) == 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<TypeId> find(const DefId& def) const
|
||||
|
@ -118,6 +122,14 @@ struct NonStrictContext
|
|||
return find(d);
|
||||
}
|
||||
|
||||
void addContext(const DefId& def, TypeId ty)
|
||||
{
|
||||
std::vector<DefId> defs;
|
||||
collectOperands(def, &defs);
|
||||
for (DefId def : defs)
|
||||
context[def.get()] = ty;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<TypeId> find(const Def* d) const
|
||||
{
|
||||
|
@ -126,6 +138,9 @@ private:
|
|||
return {it->second};
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unordered_map<const Def*, TypeId> context;
|
||||
|
||||
};
|
||||
|
||||
struct NonStrictTypeChecker
|
||||
|
@ -508,8 +523,25 @@ struct NonStrictTypeChecker
|
|||
// ...
|
||||
// (unknown^N-1, ~S_N) -> error
|
||||
std::vector<TypeId> argTypes;
|
||||
for (TypeId ty : fn->argTypes)
|
||||
argTypes.push_back(ty);
|
||||
argTypes.reserve(call->args.size);
|
||||
// Pad out the arg types array with the types you would expect to see
|
||||
TypePackIterator curr = begin(fn->argTypes);
|
||||
TypePackIterator fin = end(fn->argTypes);
|
||||
while (curr != fin)
|
||||
{
|
||||
argTypes.push_back(*curr);
|
||||
++curr;
|
||||
}
|
||||
if (auto argTail = curr.tail())
|
||||
{
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*argTail)))
|
||||
{
|
||||
while (argTypes.size() < call->args.size)
|
||||
{
|
||||
argTypes.push_back(vtp->ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
// For a checked function, these gotta be the same size
|
||||
LUAU_ASSERT(call->args.size == argTypes.size());
|
||||
for (size_t i = 0; i < call->args.size; i++)
|
||||
|
@ -523,7 +555,7 @@ struct NonStrictTypeChecker
|
|||
TypeId expectedArgType = argTypes[i];
|
||||
DefId def = dfg->getDef(arg);
|
||||
TypeId runTimeErrorTy = getOrCreateNegation(expectedArgType);
|
||||
fresh.context[def.get()] = runTimeErrorTy;
|
||||
fresh.addContext(def, runTimeErrorTy);
|
||||
}
|
||||
|
||||
// Populate the context and now iterate through each of the arguments to the call to find out if we satisfy the types
|
||||
|
@ -613,15 +645,20 @@ struct NonStrictTypeChecker
|
|||
std::optional<TypeId> willRunTimeError(AstExpr* fragment, const NonStrictContext& context)
|
||||
{
|
||||
DefId def = dfg->getDef(fragment);
|
||||
if (std::optional<TypeId> contextTy = context.find(def))
|
||||
std::vector<DefId> defs;
|
||||
collectOperands(def, &defs);
|
||||
for (DefId def : defs)
|
||||
{
|
||||
if (std::optional<TypeId> contextTy = context.find(def))
|
||||
{
|
||||
|
||||
TypeId actualType = lookupType(fragment);
|
||||
SubtypingResult r = subtyping.isSubtype(actualType, *contextTy);
|
||||
if (r.normalizationTooComplex)
|
||||
reportError(NormalizationTooComplex{}, fragment->location);
|
||||
if (r.isSubtype)
|
||||
return {actualType};
|
||||
TypeId actualType = lookupType(fragment);
|
||||
SubtypingResult r = subtyping.isSubtype(actualType, *contextTy);
|
||||
if (r.normalizationTooComplex)
|
||||
reportError(NormalizationTooComplex{}, fragment->location);
|
||||
if (r.isSubtype)
|
||||
return {actualType};
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
|
@ -630,15 +667,20 @@ struct NonStrictTypeChecker
|
|||
std::optional<TypeId> willRunTimeErrorFunctionDefinition(AstLocal* fragment, const NonStrictContext& context)
|
||||
{
|
||||
DefId def = dfg->getDef(fragment);
|
||||
if (std::optional<TypeId> contextTy = context.find(def))
|
||||
std::vector<DefId> defs;
|
||||
collectOperands(def, &defs);
|
||||
for (DefId def : defs)
|
||||
{
|
||||
SubtypingResult r1 = subtyping.isSubtype(builtinTypes->unknownType, *contextTy);
|
||||
SubtypingResult r2 = subtyping.isSubtype(*contextTy, builtinTypes->unknownType);
|
||||
if (r1.normalizationTooComplex || r2.normalizationTooComplex)
|
||||
reportError(NormalizationTooComplex{}, fragment->location);
|
||||
bool isUnknown = r1.isSubtype && r2.isSubtype;
|
||||
if (isUnknown)
|
||||
return {builtinTypes->unknownType};
|
||||
if (std::optional<TypeId> contextTy = context.find(def))
|
||||
{
|
||||
SubtypingResult r1 = subtyping.isSubtype(builtinTypes->unknownType, *contextTy);
|
||||
SubtypingResult r2 = subtyping.isSubtype(*contextTy, builtinTypes->unknownType);
|
||||
if (r1.normalizationTooComplex || r2.normalizationTooComplex)
|
||||
reportError(NormalizationTooComplex{}, fragment->location);
|
||||
bool isUnknown = r1.isSubtype && r2.isSubtype;
|
||||
if (isUnknown)
|
||||
return {builtinTypes->unknownType};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Constraint.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/Set.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypePack.h"
|
||||
|
@ -53,8 +55,8 @@ struct FindCyclicTypes final : TypeVisitor
|
|||
FindCyclicTypes& operator=(const FindCyclicTypes&) = delete;
|
||||
|
||||
bool exhaustive = false;
|
||||
std::unordered_set<TypeId> visited;
|
||||
std::unordered_set<TypePackId> visitedPacks;
|
||||
Luau::Set<TypeId> visited{{}};
|
||||
Luau::Set<TypePackId> visitedPacks{{}};
|
||||
std::set<TypeId> cycles;
|
||||
std::set<TypePackId> cycleTPs;
|
||||
|
||||
|
@ -70,17 +72,17 @@ struct FindCyclicTypes final : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
return visited.insert(ty).second;
|
||||
return visited.insert(ty);
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp) override
|
||||
{
|
||||
return visitedPacks.insert(tp).second;
|
||||
return visitedPacks.insert(tp);
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const FreeType& ft) override
|
||||
{
|
||||
if (!visited.insert(ty).second)
|
||||
if (!visited.insert(ty))
|
||||
return false;
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
|
@ -102,7 +104,7 @@ struct FindCyclicTypes final : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty, const LocalType& lt) override
|
||||
{
|
||||
if (!visited.insert(ty).second)
|
||||
if (!visited.insert(ty))
|
||||
return false;
|
||||
|
||||
traverse(lt.domain);
|
||||
|
@ -112,7 +114,7 @@ struct FindCyclicTypes final : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty, const TableType& ttv) override
|
||||
{
|
||||
if (!visited.insert(ty).second)
|
||||
if (!visited.insert(ty))
|
||||
return false;
|
||||
|
||||
if (ttv.name || ttv.syntheticName)
|
||||
|
@ -175,10 +177,11 @@ struct StringifierState
|
|||
ToStringOptions& opts;
|
||||
ToStringResult& result;
|
||||
|
||||
std::unordered_map<TypeId, std::string> cycleNames;
|
||||
std::unordered_map<TypePackId, std::string> cycleTpNames;
|
||||
std::unordered_set<void*> seen;
|
||||
std::unordered_set<std::string> usedNames;
|
||||
DenseHashMap<TypeId, std::string> cycleNames{{}};
|
||||
DenseHashMap<TypePackId, std::string> cycleTpNames{{}};
|
||||
Set<void*> seen{{}};
|
||||
// `$$$` was chosen as the tombstone for `usedNames` since it is not a valid name syntactically and is relatively short for string comparison reasons.
|
||||
DenseHashSet<std::string> usedNames{"$$$"};
|
||||
size_t indentation = 0;
|
||||
|
||||
bool exhaustive;
|
||||
|
@ -197,7 +200,7 @@ struct StringifierState
|
|||
bool hasSeen(const void* tv)
|
||||
{
|
||||
void* ttv = const_cast<void*>(tv);
|
||||
if (seen.find(ttv) != seen.end())
|
||||
if (seen.contains(ttv))
|
||||
return true;
|
||||
|
||||
seen.insert(ttv);
|
||||
|
@ -207,9 +210,9 @@ struct StringifierState
|
|||
void unsee(const void* tv)
|
||||
{
|
||||
void* ttv = const_cast<void*>(tv);
|
||||
auto iter = seen.find(ttv);
|
||||
if (iter != seen.end())
|
||||
seen.erase(iter);
|
||||
|
||||
if (seen.contains(ttv))
|
||||
seen.erase(ttv);
|
||||
}
|
||||
|
||||
std::string getName(TypeId ty)
|
||||
|
@ -222,7 +225,7 @@ struct StringifierState
|
|||
for (int count = 0; count < 256; ++count)
|
||||
{
|
||||
std::string candidate = generateName(usedNames.size() + count);
|
||||
if (!usedNames.count(candidate))
|
||||
if (!usedNames.contains(candidate))
|
||||
{
|
||||
usedNames.insert(candidate);
|
||||
n = candidate;
|
||||
|
@ -245,7 +248,7 @@ struct StringifierState
|
|||
for (int count = 0; count < 256; ++count)
|
||||
{
|
||||
std::string candidate = generateName(previousNameIndex + count);
|
||||
if (!usedNames.count(candidate))
|
||||
if (!usedNames.contains(candidate))
|
||||
{
|
||||
previousNameIndex += count;
|
||||
usedNames.insert(candidate);
|
||||
|
@ -358,10 +361,9 @@ struct TypeStringifier
|
|||
return;
|
||||
}
|
||||
|
||||
auto it = state.cycleNames.find(tv);
|
||||
if (it != state.cycleNames.end())
|
||||
if (auto p = state.cycleNames.find(tv))
|
||||
{
|
||||
state.emit(it->second);
|
||||
state.emit(*p);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -886,7 +888,7 @@ struct TypeStringifier
|
|||
|
||||
std::string saved = std::move(state.result.name);
|
||||
|
||||
bool needParens = !state.cycleNames.count(el) && (get<IntersectionType>(el) || get<FunctionType>(el));
|
||||
bool needParens = !state.cycleNames.contains(el) && (get<IntersectionType>(el) || get<FunctionType>(el));
|
||||
|
||||
if (needParens)
|
||||
state.emit("(");
|
||||
|
@ -953,7 +955,7 @@ struct TypeStringifier
|
|||
|
||||
std::string saved = std::move(state.result.name);
|
||||
|
||||
bool needParens = !state.cycleNames.count(el) && (get<UnionType>(el) || get<FunctionType>(el));
|
||||
bool needParens = !state.cycleNames.contains(el) && (get<UnionType>(el) || get<FunctionType>(el));
|
||||
|
||||
if (needParens)
|
||||
state.emit("(");
|
||||
|
@ -1101,10 +1103,9 @@ struct TypePackStringifier
|
|||
return;
|
||||
}
|
||||
|
||||
auto it = state.cycleTpNames.find(tp);
|
||||
if (it != state.cycleTpNames.end())
|
||||
if (auto p = state.cycleTpNames.find(tp))
|
||||
{
|
||||
state.emit(it->second);
|
||||
state.emit(*p);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1278,7 +1279,7 @@ void TypeStringifier::stringify(TypePackId tpid, const std::vector<std::optional
|
|||
}
|
||||
|
||||
static void assignCycleNames(const std::set<TypeId>& cycles, const std::set<TypePackId>& cycleTPs,
|
||||
std::unordered_map<TypeId, std::string>& cycleNames, std::unordered_map<TypePackId, std::string>& cycleTpNames, bool exhaustive)
|
||||
DenseHashMap<TypeId, std::string>& cycleNames, DenseHashMap<TypePackId, std::string>& cycleTpNames, bool exhaustive)
|
||||
{
|
||||
int nextIndex = 1;
|
||||
|
||||
|
@ -1372,9 +1373,8 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts)
|
|||
*
|
||||
* t1 where t1 = the_whole_root_type
|
||||
*/
|
||||
auto it = state.cycleNames.find(ty);
|
||||
if (it != state.cycleNames.end())
|
||||
state.emit(it->second);
|
||||
if (auto p = state.cycleNames.find(ty))
|
||||
state.emit(*p);
|
||||
else
|
||||
tvs.stringify(ty);
|
||||
|
||||
|
@ -1466,9 +1466,8 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts)
|
|||
*
|
||||
* t1 where t1 = the_whole_root_type
|
||||
*/
|
||||
auto it = state.cycleTpNames.find(tp);
|
||||
if (it != state.cycleTpNames.end())
|
||||
state.emit(it->second);
|
||||
if (auto p = state.cycleTpNames.find(tp))
|
||||
state.emit(*p);
|
||||
else
|
||||
tvs.stringify(tp);
|
||||
|
||||
|
@ -1766,11 +1765,6 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
|||
}
|
||||
else if constexpr (std::is_same_v<T, UnpackConstraint>)
|
||||
return tos(c.resultPack) + " ~ unpack " + tos(c.sourcePack);
|
||||
else if constexpr (std::is_same_v<T, RefineConstraint>)
|
||||
{
|
||||
const char* op = c.mode == RefineConstraint::Union ? "union" : "intersect";
|
||||
return tos(c.resultType) + " ~ refine " + tos(c.type) + " " + op + " " + tos(c.discriminant);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, SetOpConstraint>)
|
||||
{
|
||||
const char* op = c.mode == SetOpConstraint::Union ? " | " : " & ";
|
||||
|
|
|
@ -106,7 +106,12 @@ public:
|
|||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("thread"), std::nullopt, Location());
|
||||
case PrimitiveType::Buffer:
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("buffer"), std::nullopt, Location());
|
||||
case PrimitiveType::Function:
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("function"), std::nullopt, Location());
|
||||
case PrimitiveType::Table:
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("table"), std::nullopt, Location());
|
||||
default:
|
||||
LUAU_ASSERT(false); // this should be unreachable.
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1603,8 +1603,8 @@ struct TypeChecker2
|
|||
visit(indexExpr->expr, ValueContext::RValue);
|
||||
visit(indexExpr->index, ValueContext::RValue);
|
||||
|
||||
TypeId exprType = lookupType(indexExpr->expr);
|
||||
TypeId indexType = lookupType(indexExpr->index);
|
||||
TypeId exprType = follow(lookupType(indexExpr->expr));
|
||||
TypeId indexType = follow(lookupType(indexExpr->index));
|
||||
|
||||
if (auto tt = get<TableType>(exprType))
|
||||
{
|
||||
|
|
|
@ -349,7 +349,8 @@ TypeFamilyReductionResult<TypeId> lenFamilyFn(const std::vector<TypeId>& typePar
|
|||
TypeId operandTy = follow(typeParams.at(0));
|
||||
|
||||
// check to see if the operand type is resolved enough, and wait to reduce if not
|
||||
if (isPending(operandTy, ctx->solver))
|
||||
// the use of `typeFromNormal` later necessitates blocking on local types.
|
||||
if (isPending(operandTy, ctx->solver) || get<LocalType>(operandTy))
|
||||
return {std::nullopt, false, {operandTy}, {}};
|
||||
|
||||
const NormalizedType* normTy = ctx->normalizer->normalize(operandTy);
|
||||
|
@ -964,6 +965,92 @@ TypeFamilyReductionResult<TypeId> eqFamilyFn(const std::vector<TypeId>& typePara
|
|||
return {ctx->builtins->booleanType, false, {}, {}};
|
||||
}
|
||||
|
||||
// Collect types that prevent us from reducing a particular refinement.
|
||||
struct FindRefinementBlockers : TypeOnceVisitor
|
||||
{
|
||||
DenseHashSet<TypeId> found{nullptr};
|
||||
bool visit(TypeId ty, const BlockedType&) override
|
||||
{
|
||||
found.insert(ty);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const PendingExpansionType&) override
|
||||
{
|
||||
found.insert(ty);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const LocalType&) override
|
||||
{
|
||||
found.insert(ty);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const ClassType&) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TypeFamilyReductionResult<TypeId> refineFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
{
|
||||
ctx->ice->ice("refine type family: encountered a type family instance without the required argument structure");
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
TypeId targetTy = follow(typeParams.at(0));
|
||||
TypeId discriminantTy = follow(typeParams.at(1));
|
||||
|
||||
// check to see if both operand types are resolved enough, and wait to reduce if not
|
||||
if (isPending(targetTy, ctx->solver))
|
||||
return {std::nullopt, false, {targetTy}, {}};
|
||||
else if (isPending(discriminantTy, ctx->solver))
|
||||
return {std::nullopt, false, {discriminantTy}, {}};
|
||||
|
||||
// we need a more complex check for blocking on the discriminant in particular
|
||||
FindRefinementBlockers frb;
|
||||
frb.traverse(discriminantTy);
|
||||
|
||||
if (!frb.found.empty())
|
||||
return {std::nullopt, false, {frb.found.begin(), frb.found.end()}, {}};
|
||||
|
||||
/* HACK: Refinements sometimes produce a type T & ~any under the assumption
|
||||
* that ~any is the same as any. This is so so weird, but refinements needs
|
||||
* some way to say "I may refine this, but I'm not sure."
|
||||
*
|
||||
* It does this by refining on a blocked type and deferring the decision
|
||||
* until it is unblocked.
|
||||
*
|
||||
* Refinements also get negated, so we wind up with types like T & ~*blocked*
|
||||
*
|
||||
* We need to treat T & ~any as T in this case.
|
||||
*/
|
||||
|
||||
if (auto nt = get<NegationType>(discriminantTy))
|
||||
if (get<AnyType>(follow(nt->ty)))
|
||||
return {targetTy, false, {}, {}};
|
||||
|
||||
TypeId intersection = ctx->arena->addType(IntersectionType{{targetTy, discriminantTy}});
|
||||
const NormalizedType* normIntersection = ctx->normalizer->normalize(intersection);
|
||||
const NormalizedType* normType = ctx->normalizer->normalize(targetTy);
|
||||
|
||||
// if the intersection failed to normalize, we can't reduce, but know nothing about inhabitance.
|
||||
if (!normIntersection || !normType)
|
||||
return {std::nullopt, false, {}, {}};
|
||||
|
||||
TypeId resultTy = ctx->normalizer->typeFromNormal(*normIntersection);
|
||||
|
||||
// include the error type if the target type is error-suppressing and the intersection we computed is not
|
||||
if (normType->shouldSuppressErrors() && !normIntersection->shouldSuppressErrors())
|
||||
resultTy = ctx->arena->addType(UnionType{{resultTy, ctx->builtins->errorType}});
|
||||
|
||||
return {resultTy, false, {}, {}};
|
||||
}
|
||||
|
||||
BuiltinTypeFamilies::BuiltinTypeFamilies()
|
||||
: notFamily{"not", notFamilyFn}
|
||||
, lenFamily{"len", lenFamilyFn}
|
||||
|
@ -981,6 +1068,7 @@ BuiltinTypeFamilies::BuiltinTypeFamilies()
|
|||
, ltFamily{"lt", ltFamilyFn}
|
||||
, leFamily{"le", leFamilyFn}
|
||||
, eqFamily{"eq", eqFamilyFn}
|
||||
, refineFamily{"refine", refineFamilyFn}
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -325,7 +325,7 @@ int main(int argc, char** argv)
|
|||
else if (strncmp(argv[i], "--fflags=", 9) == 0)
|
||||
setLuauFlags(argv[i] + 9);
|
||||
else if (strncmp(argv[i], "-j", 2) == 0)
|
||||
threadCount = strtol(argv[i] + 2, nullptr, 10);
|
||||
threadCount = int(strtol(argv[i] + 2, nullptr, 10));
|
||||
}
|
||||
|
||||
#if !defined(LUAU_ENABLE_TIME_TRACE)
|
||||
|
@ -363,6 +363,7 @@ int main(int argc, char** argv)
|
|||
if (threadCount <= 0)
|
||||
threadCount = std::min(TaskScheduler::getThreadCount(), 8u);
|
||||
|
||||
try
|
||||
{
|
||||
TaskScheduler scheduler(threadCount);
|
||||
|
||||
|
@ -370,6 +371,19 @@ int main(int argc, char** argv)
|
|||
scheduler.push(std::move(f));
|
||||
});
|
||||
}
|
||||
catch (const Luau::InternalCompilerError& ice)
|
||||
{
|
||||
Luau::Location location = ice.location ? *ice.location : Luau::Location();
|
||||
|
||||
std::string moduleName = ice.moduleName ? *ice.moduleName : "<unknown module>";
|
||||
std::string humanReadableName = frontend.fileResolver->getHumanReadableModuleName(moduleName);
|
||||
|
||||
Luau::TypeError error(location, moduleName, Luau::InternalError{ice.message});
|
||||
|
||||
report(format, humanReadableName.c_str(), location, "InternalCompilerError",
|
||||
Luau::toString(error, Luau::TypeErrorToStringOptions{frontend.fileResolver}).c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int failed = 0;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -22,16 +22,9 @@ RequireResolver::RequireResolver(lua_State* L, std::string path)
|
|||
if (isAbsolutePath(pathToResolve))
|
||||
luaL_argerrorL(L, 1, "cannot require an absolute path");
|
||||
|
||||
bool isAlias = !pathToResolve.empty() && pathToResolve[0] == '@';
|
||||
if (!isAlias && !isExplicitlyRelative(pathToResolve))
|
||||
luaL_argerrorL(L, 1, "must require an alias prepended with '@' or an explicitly relative path");
|
||||
|
||||
std::replace(pathToResolve.begin(), pathToResolve.end(), '\\', '/');
|
||||
if (isAlias)
|
||||
{
|
||||
pathToResolve = pathToResolve.substr(1);
|
||||
substituteAliasIfPresent(pathToResolve);
|
||||
}
|
||||
|
||||
substituteAliasIfPresent(pathToResolve);
|
||||
}
|
||||
|
||||
[[nodiscard]] RequireResolver::ResolvedRequire RequireResolver::resolveRequire(lua_State* L, std::string path)
|
||||
|
@ -209,16 +202,22 @@ std::string RequireResolver::getRequiringContextRelative()
|
|||
|
||||
void RequireResolver::substituteAliasIfPresent(std::string& path)
|
||||
{
|
||||
std::string potentialAlias = path.substr(0, path.find_first_of("\\/"));
|
||||
if (path.size() < 1 || path[0] != '@')
|
||||
return;
|
||||
std::string potentialAlias = path.substr(1, path.find_first_of("\\/"));
|
||||
|
||||
// Not worth searching when potentialAlias cannot be an alias
|
||||
if (!Luau::isValidAlias(potentialAlias))
|
||||
return;
|
||||
luaL_errorL(L, "@%s is not a valid alias", potentialAlias.c_str());
|
||||
|
||||
std::optional<std::string> alias = getAlias(potentialAlias);
|
||||
if (alias)
|
||||
{
|
||||
path = *alias + path.substr(potentialAlias.size());
|
||||
path = *alias + path.substr(potentialAlias.size() + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
luaL_errorL(L, "@%s is not a valid alias", potentialAlias.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "Luau/UnwindBuilder.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#if defined(_WIN32) && defined(_M_X64)
|
||||
|
||||
|
|
|
@ -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<typename AssemblyBuilder>
|
||||
static std::optional<NativeProto> createNativeFunction(AssemblyBuilder& build, ModuleHelpers& helpers, Proto* proto)
|
||||
static std::optional<NativeProto> 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<NativeProto> results;
|
||||
results.reserve(protos.size());
|
||||
|
||||
uint32_t totalIrInstCount = 0;
|
||||
|
||||
for (Proto* p : protos)
|
||||
if (std::optional<NativeProto> np = createNativeFunction(build, helpers, p))
|
||||
{
|
||||
if (std::optional<NativeProto> 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())
|
||||
|
|
|
@ -253,11 +253,6 @@ inline bool lowerIr(A64::AssemblyBuilderA64& build, IrBuilder& ir, const std::ve
|
|||
template<typename AssemblyBuilder>
|
||||
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))
|
||||
|
|
|
@ -31,9 +31,6 @@ struct ModuleHelpers
|
|||
|
||||
// A64
|
||||
Label continueCall; // x0: closure
|
||||
|
||||
unsigned bytecodeInstructionCount = 0;
|
||||
unsigned preOptBlockCount = 0;
|
||||
};
|
||||
|
||||
} // namespace CodeGen
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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"}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue