mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
Sync to upstream/release/605
This commit is contained in:
parent
674c6c40c0
commit
41669c9f30
@ -49,8 +49,8 @@ struct InstantiationConstraint
|
||||
TypeId superType;
|
||||
};
|
||||
|
||||
// iteratee is iterable
|
||||
// iterators is the iteration types.
|
||||
// variables ~ iterate iterator
|
||||
// Unpack the iterator, figure out what types it iterates over, and bind those types to variables.
|
||||
struct IterableConstraint
|
||||
{
|
||||
TypePackId iterator;
|
||||
|
@ -225,6 +225,7 @@ private:
|
||||
Inference check(const ScopePtr& scope, AstExprConstantBool* bool_, std::optional<TypeId> expectedType, bool forceSingleton);
|
||||
Inference check(const ScopePtr& scope, AstExprLocal* local);
|
||||
Inference check(const ScopePtr& scope, AstExprGlobal* global);
|
||||
Inference checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, std::string index);
|
||||
Inference check(const ScopePtr& scope, AstExprIndexName* indexName);
|
||||
Inference check(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
|
||||
Inference check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType, bool generalize);
|
||||
|
@ -372,13 +372,21 @@ struct CheckedFunctionCallError
|
||||
bool operator==(const CheckedFunctionCallError& rhs) const;
|
||||
};
|
||||
|
||||
struct NonStrictFunctionDefinitionError
|
||||
{
|
||||
std::string functionName;
|
||||
std::string argument;
|
||||
TypeId argumentType;
|
||||
bool operator==(const NonStrictFunctionDefinitionError& rhs) const;
|
||||
};
|
||||
|
||||
using TypeErrorData = Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods,
|
||||
DuplicateTypeDefinition, CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire,
|
||||
IncorrectGenericParameterCount, SyntaxError, CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, InternalError,
|
||||
CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning,
|
||||
DuplicateGenericParameter, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty,
|
||||
TypesAreUnrelated, NormalizationTooComplex, TypePackMismatch, DynamicPropertyLookupOnClassesUnsafe, UninhabitedTypeFamily,
|
||||
UninhabitedTypePackFamily, WhereClauseNeeded, PackWhereClauseNeeded, CheckedFunctionCallError>;
|
||||
UninhabitedTypePackFamily, WhereClauseNeeded, PackWhereClauseNeeded, CheckedFunctionCallError, NonStrictFunctionDefinitionError>;
|
||||
|
||||
struct TypeErrorSummary
|
||||
{
|
||||
|
@ -32,12 +32,17 @@ enum class SubtypingVariance
|
||||
// Used for an empty key. Should never appear in actual code.
|
||||
Invalid,
|
||||
Covariant,
|
||||
// This is used to identify cases where we have a covariant + a
|
||||
// contravariant reason and we need to merge them.
|
||||
Contravariant,
|
||||
Invariant,
|
||||
};
|
||||
|
||||
struct SubtypingReasoning
|
||||
{
|
||||
// The path, relative to the _root subtype_, where subtyping failed.
|
||||
Path subPath;
|
||||
// The path, relative to the _root supertype_, where subtyping failed.
|
||||
Path superPath;
|
||||
SubtypingVariance variance = SubtypingVariance::Covariant;
|
||||
|
||||
@ -49,6 +54,9 @@ struct SubtypingReasoningHash
|
||||
size_t operator()(const SubtypingReasoning& r) const;
|
||||
};
|
||||
|
||||
using SubtypingReasonings = DenseHashSet<SubtypingReasoning, SubtypingReasoningHash>;
|
||||
static const SubtypingReasoning kEmptyReasoning = SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invalid};
|
||||
|
||||
struct SubtypingResult
|
||||
{
|
||||
bool isSubtype = false;
|
||||
@ -58,8 +66,7 @@ struct SubtypingResult
|
||||
|
||||
/// The reason for isSubtype to be false. May not be present even if
|
||||
/// isSubtype is false, depending on the input types.
|
||||
DenseHashSet<SubtypingReasoning, SubtypingReasoningHash> reasoning{
|
||||
SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invalid}};
|
||||
SubtypingReasonings reasoning{kEmptyReasoning};
|
||||
|
||||
SubtypingResult& andAlso(const SubtypingResult& other);
|
||||
SubtypingResult& orElse(const SubtypingResult& other);
|
||||
@ -69,7 +76,6 @@ struct SubtypingResult
|
||||
SubtypingResult& withBothPath(TypePath::Path path);
|
||||
SubtypingResult& withSubPath(TypePath::Path path);
|
||||
SubtypingResult& withSuperPath(TypePath::Path path);
|
||||
SubtypingResult& withVariance(SubtypingVariance variance);
|
||||
|
||||
// Only negates the `isSubtype`.
|
||||
static SubtypingResult negate(const SubtypingResult& result);
|
||||
|
@ -61,7 +61,7 @@ struct Unifier2
|
||||
bool unify(TypeId subTy, const UnionType* superUnion);
|
||||
bool unify(const IntersectionType* subIntersection, TypeId superTy);
|
||||
bool unify(TypeId subTy, const IntersectionType* superIntersection);
|
||||
bool unify(const TableType* subTable, const TableType* superTable);
|
||||
bool unify(TableType* subTable, const TableType* superTable);
|
||||
bool unify(const MetatableType* subMetatable, const MetatableType* superMetatable);
|
||||
|
||||
// TODO think about this one carefully. We don't do unions or intersections of type packs
|
||||
|
@ -750,17 +750,18 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI
|
||||
|
||||
for (AstLocal* var : forIn->vars)
|
||||
{
|
||||
TypeId ty = nullptr;
|
||||
if (var->annotation)
|
||||
ty = resolveType(loopScope, var->annotation, /*inTypeArguments*/ false);
|
||||
else
|
||||
ty = freshType(loopScope);
|
||||
|
||||
loopScope->bindings[var] = Binding{ty, var->location};
|
||||
|
||||
TypeId assignee = arena->addType(BlockedType{});
|
||||
variableTypes.push_back(assignee);
|
||||
|
||||
if (var->annotation)
|
||||
{
|
||||
TypeId annotationTy = resolveType(loopScope, var->annotation, /*inTypeArguments*/ false);
|
||||
loopScope->bindings[var] = Binding{annotationTy, var->location};
|
||||
addConstraint(scope, var->location, SubtypeConstraint{assignee, annotationTy});
|
||||
}
|
||||
else
|
||||
loopScope->bindings[var] = Binding{assignee, var->location};
|
||||
|
||||
DefId def = dfg->getDef(var);
|
||||
loopScope->lvalueTypes[def] = assignee;
|
||||
}
|
||||
@ -1439,9 +1440,6 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
||||
module->astOriginalCallTypes[call->func] = fnType;
|
||||
module->astOriginalCallTypes[call] = fnType;
|
||||
|
||||
TypeId instantiatedFnType = arena->addType(BlockedType{});
|
||||
addConstraint(scope, call->location, InstantiationConstraint{instantiatedFnType, fnType});
|
||||
|
||||
Checkpoint argBeginCheckpoint = checkpoint(this);
|
||||
|
||||
std::vector<TypeId> args;
|
||||
@ -1740,12 +1738,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprGlobal* globa
|
||||
}
|
||||
}
|
||||
|
||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexName* indexName)
|
||||
Inference ConstraintGenerator::checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, std::string index)
|
||||
{
|
||||
TypeId obj = check(scope, indexName->expr).ty;
|
||||
TypeId obj = check(scope, indexee).ty;
|
||||
TypeId result = arena->addType(BlockedType{});
|
||||
|
||||
const RefinementKey* key = dfg->getRefinementKey(indexName);
|
||||
if (key)
|
||||
{
|
||||
if (auto ty = lookup(scope.get(), key->def))
|
||||
@ -1754,7 +1751,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexName* in
|
||||
scope->rvalueRefinements[key->def] = result;
|
||||
}
|
||||
|
||||
addConstraint(scope, indexName->expr->location, HasPropConstraint{result, obj, indexName->index.value});
|
||||
addConstraint(scope, indexee->location, HasPropConstraint{result, obj, std::move(index)});
|
||||
|
||||
if (key)
|
||||
return Inference{result, refinementArena.proposition(key, builtinTypes->truthyType)};
|
||||
@ -1762,10 +1759,23 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexName* in
|
||||
return Inference{result};
|
||||
}
|
||||
|
||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexName* indexName)
|
||||
{
|
||||
const RefinementKey* key = dfg->getRefinementKey(indexName);
|
||||
return checkIndexName(scope, key, indexName->expr, indexName->index.value);
|
||||
}
|
||||
|
||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexExpr* indexExpr)
|
||||
{
|
||||
if (auto constantString = indexExpr->index->as<AstExprConstantString>())
|
||||
{
|
||||
const RefinementKey* key = dfg->getRefinementKey(indexExpr);
|
||||
return checkIndexName(scope, key, indexExpr->expr, constantString->value.data);
|
||||
}
|
||||
|
||||
TypeId obj = check(scope, indexExpr->expr).ty;
|
||||
TypeId indexType = check(scope, indexExpr->index).ty;
|
||||
|
||||
TypeId result = freshType(scope);
|
||||
|
||||
const RefinementKey* key = dfg->getRefinementKey(indexExpr);
|
||||
@ -3079,15 +3089,23 @@ struct GlobalPrepopulator : AstVisitor
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(AstExprGlobal* global) override
|
||||
{
|
||||
if (auto ty = globalScope->lookup(global->name))
|
||||
{
|
||||
DefId def = dfg->getDef(global);
|
||||
globalScope->lvalueTypes[def] = *ty;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstStatFunction* function) override
|
||||
{
|
||||
if (AstExprGlobal* g = function->name->as<AstExprGlobal>())
|
||||
{
|
||||
TypeId bt = arena->addType(BlockedType{});
|
||||
globalScope->bindings[g->name] = Binding{bt};
|
||||
|
||||
DefId def = dfg->getDef(function->name);
|
||||
globalScope->lvalueTypes[def] = bt;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Unifier2.h"
|
||||
#include "Luau/VisitType.h"
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
||||
@ -1262,9 +1263,6 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
||||
if (isBlocked(subjectType))
|
||||
return block(subjectType, constraint);
|
||||
|
||||
if (!force && get<FreeType>(subjectType))
|
||||
return block(subjectType, constraint);
|
||||
|
||||
std::optional<TypeId> existingPropType = subjectType;
|
||||
for (const std::string& segment : c.path)
|
||||
{
|
||||
@ -1300,25 +1298,13 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
||||
|
||||
if (get<FreeType>(subjectType))
|
||||
{
|
||||
TypeId ty = freshType(arena, builtinTypes, constraint->scope);
|
||||
|
||||
// Mint a chain of free tables per c.path
|
||||
for (auto it = rbegin(c.path); it != rend(c.path); ++it)
|
||||
{
|
||||
TableType t{TableState::Free, TypeLevel{}, constraint->scope};
|
||||
t.props[*it] = {ty};
|
||||
|
||||
ty = arena->addType(std::move(t));
|
||||
}
|
||||
|
||||
LUAU_ASSERT(ty);
|
||||
|
||||
bind(subjectType, ty);
|
||||
if (follow(c.resultType) != follow(ty))
|
||||
bind(c.resultType, ty);
|
||||
unblock(subjectType, constraint->location);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
/*
|
||||
* This should never occur because lookupTableProp() will add bounds to
|
||||
* any free types it encounters. There will always be an
|
||||
* existingPropType if the subject is free.
|
||||
*/
|
||||
LUAU_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
else if (auto ttv = getMutable<TableType>(subjectType))
|
||||
{
|
||||
@ -1327,7 +1313,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
||||
LUAU_ASSERT(!subjectType->persistent);
|
||||
|
||||
ttv->props[c.path[0]] = Property{c.propType};
|
||||
bind(c.resultType, c.subjectType);
|
||||
bind(c.resultType, subjectType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
@ -1336,26 +1322,12 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
||||
LUAU_ASSERT(!subjectType->persistent);
|
||||
|
||||
updateTheTableType(builtinTypes, NotNull{arena}, subjectType, c.path, c.propType);
|
||||
bind(c.resultType, c.subjectType);
|
||||
unblock(subjectType, constraint->location);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bind(c.resultType, subjectType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Other kinds of types don't change shape when properties are assigned
|
||||
// to them. (if they allow properties at all!)
|
||||
bind(c.resultType, subjectType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
bind(c.resultType, subjectType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
@ -1907,6 +1879,7 @@ bool ConstraintSolver::tryDispatchIterableFunction(
|
||||
TypeId retIndex;
|
||||
if (isNil(firstIndexTy) || isOptional(firstIndexTy))
|
||||
{
|
||||
// FIXME freshType is suspect here
|
||||
firstIndex = arena->addType(UnionType{{freshType(arena, builtinTypes, constraint->scope), builtinTypes->nilType}});
|
||||
retIndex = firstIndex;
|
||||
}
|
||||
@ -1948,7 +1921,7 @@ bool ConstraintSolver::tryDispatchIterableFunction(
|
||||
modifiedNextRetHead.push_back(*it);
|
||||
|
||||
TypePackId modifiedNextRetPack = arena->addTypePack(std::move(modifiedNextRetHead), it.tail());
|
||||
auto psc = pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{c.variables, modifiedNextRetPack});
|
||||
auto psc = pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, modifiedNextRetPack});
|
||||
inheritBlocks(constraint, psc);
|
||||
|
||||
return true;
|
||||
|
@ -533,6 +533,12 @@ struct ErrorConverter
|
||||
return "Function '" + e.checkedFunctionName + "' expects '" + toString(e.expected) + "' at argument #" + std::to_string(e.argumentIndex) +
|
||||
", but got '" + Luau::toString(e.passed) + "'";
|
||||
}
|
||||
|
||||
std::string operator()(const NonStrictFunctionDefinitionError& e) const
|
||||
{
|
||||
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
|
||||
"' is used in a way that will run time error";
|
||||
}
|
||||
};
|
||||
|
||||
struct InvalidNameChecker
|
||||
@ -861,6 +867,11 @@ bool CheckedFunctionCallError::operator==(const CheckedFunctionCallError& rhs) c
|
||||
argumentIndex == rhs.argumentIndex;
|
||||
}
|
||||
|
||||
bool NonStrictFunctionDefinitionError::operator==(const NonStrictFunctionDefinitionError& rhs) const
|
||||
{
|
||||
return functionName == rhs.functionName && argument == rhs.argument && argumentType == rhs.argumentType;
|
||||
}
|
||||
|
||||
std::string toString(const TypeError& error)
|
||||
{
|
||||
return toString(error, TypeErrorToStringOptions{});
|
||||
@ -1032,6 +1043,10 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
|
||||
e.expected = clone(e.expected);
|
||||
e.passed = clone(e.passed);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, NonStrictFunctionDefinitionError>)
|
||||
{
|
||||
e.argumentType = clone(e.argumentType);
|
||||
}
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckLimitControls, false)
|
||||
LUAU_FASTFLAGVARIABLE(CorrectEarlyReturnInMarkDirty, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDefinitionFileSetModuleName, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -165,6 +166,11 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(GlobalTypes& globals, Scop
|
||||
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
|
||||
|
||||
Luau::SourceModule sourceModule;
|
||||
if (FFlag::LuauDefinitionFileSetModuleName)
|
||||
{
|
||||
sourceModule.name = packageName;
|
||||
sourceModule.humanReadableName = packageName;
|
||||
}
|
||||
Luau::ParseResult parseResult = parseSourceForModule(source, sourceModule, captureComments);
|
||||
if (parseResult.errors.size() > 0)
|
||||
return LoadDefinitionFileResult{false, parseResult, sourceModule, nullptr};
|
||||
|
@ -7,6 +7,8 @@
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
namespace Luau
|
||||
|
@ -204,6 +204,9 @@ static void errorToString(std::ostream& stream, const T& err)
|
||||
else if constexpr (std::is_same_v<T, CheckedFunctionCallError>)
|
||||
stream << "CheckedFunctionCallError { expected = '" << toString(err.expected) << "', passed = '" << toString(err.passed)
|
||||
<< "', checkedFunctionName = " << err.checkedFunctionName << ", argumentIndex = " << std::to_string(err.argumentIndex) << " }";
|
||||
else if constexpr (std::is_same_v<T, NonStrictFunctionDefinitionError>)
|
||||
stream << "NonStrictFunctionDefinitionError { functionName = '" + err.functionName + "', argument = '" + err.argument +
|
||||
"', argumentType = '" + toString(err.argumentType) + "' }";
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "Luau/Def.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -105,9 +106,10 @@ struct NonStrictContext
|
||||
return conj;
|
||||
}
|
||||
|
||||
void removeFromContext(const std::vector<DefId>& defs)
|
||||
// Returns true if the removal was successful
|
||||
bool remove(const DefId& def)
|
||||
{
|
||||
// TODO: unimplemented
|
||||
return context.erase(def.get()) == 1;
|
||||
}
|
||||
|
||||
std::optional<TypeId> find(const DefId& def) const
|
||||
@ -138,6 +140,7 @@ struct NonStrictTypeChecker
|
||||
NotNull<const DataFlowGraph> dfg;
|
||||
DenseHashSet<TypeId> noTypeFamilyErrors{nullptr};
|
||||
std::vector<NotNull<Scope>> stack;
|
||||
DenseHashMap<TypeId, TypeId> cachedNegations{nullptr};
|
||||
|
||||
const NotNull<TypeCheckLimits> limits;
|
||||
|
||||
@ -271,8 +274,22 @@ struct NonStrictTypeChecker
|
||||
{
|
||||
auto StackPusher = pushStack(block);
|
||||
NonStrictContext ctx;
|
||||
for (AstStat* statement : block->body)
|
||||
ctx = NonStrictContext::disjunction(builtinTypes, NotNull{&arena}, ctx, visit(statement));
|
||||
|
||||
|
||||
for (auto it = block->body.rbegin(); it != block->body.rend(); it++)
|
||||
{
|
||||
AstStat* stat = *it;
|
||||
if (AstStatLocal* local = stat->as<AstStatLocal>())
|
||||
{
|
||||
// Iterating in reverse order
|
||||
// local x ; B generates the context of B without x
|
||||
visit(local);
|
||||
for (auto local : local->vars)
|
||||
ctx.remove(dfg->getDef(local));
|
||||
}
|
||||
else
|
||||
ctx = NonStrictContext::disjunction(builtinTypes, NotNull{&arena}, visit(stat), ctx);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
@ -505,9 +522,7 @@ struct NonStrictTypeChecker
|
||||
AstExpr* arg = call->args.data[i];
|
||||
TypeId expectedArgType = argTypes[i];
|
||||
DefId def = dfg->getDef(arg);
|
||||
// TODO: Cache negations created here!!!
|
||||
// See Jira Ticket: https://roblox.atlassian.net/browse/CLI-87539
|
||||
TypeId runTimeErrorTy = arena.addType(NegationType{expectedArgType});
|
||||
TypeId runTimeErrorTy = getOrCreateNegation(expectedArgType);
|
||||
fresh.context[def.get()] = runTimeErrorTy;
|
||||
}
|
||||
|
||||
@ -537,8 +552,16 @@ struct NonStrictTypeChecker
|
||||
|
||||
NonStrictContext visit(AstExprFunction* exprFn)
|
||||
{
|
||||
// TODO: should a function being used as an expression generate a context without the arguments?
|
||||
auto pusher = pushStack(exprFn);
|
||||
return visit(exprFn->body);
|
||||
NonStrictContext remainder = visit(exprFn->body);
|
||||
for (AstLocal* local : exprFn->args)
|
||||
{
|
||||
if (std::optional<TypeId> ty = willRunTimeErrorFunctionDefinition(local, remainder))
|
||||
reportError(NonStrictFunctionDefinitionError{exprFn->debugname.value, local->name.value, *ty}, local->location);
|
||||
remainder.remove(dfg->getDef(local));
|
||||
}
|
||||
return remainder;
|
||||
}
|
||||
|
||||
NonStrictContext visit(AstExprTable* table)
|
||||
@ -603,6 +626,31 @@ struct NonStrictTypeChecker
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<TypeId> willRunTimeErrorFunctionDefinition(AstLocal* fragment, const NonStrictContext& context)
|
||||
{
|
||||
DefId def = dfg->getDef(fragment);
|
||||
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 {};
|
||||
}
|
||||
|
||||
private:
|
||||
TypeId getOrCreateNegation(TypeId baseType)
|
||||
{
|
||||
TypeId& cachedResult = cachedNegations[baseType];
|
||||
if (!cachedResult)
|
||||
cachedResult = arena.addType(NegationType{baseType});
|
||||
return cachedResult;
|
||||
};
|
||||
};
|
||||
|
||||
void checkNonStrict(NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice, NotNull<UnifierSharedState> unifierState,
|
||||
|
@ -2772,7 +2772,7 @@ bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there, Set<T
|
||||
return true;
|
||||
}
|
||||
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) ||
|
||||
get<TypeFamilyInstanceType>(there))
|
||||
get<TypeFamilyInstanceType>(there) || get<LocalType>(there))
|
||||
{
|
||||
NormalizedType thereNorm{builtinTypes};
|
||||
NormalizedType topNorm{builtinTypes};
|
||||
@ -2780,6 +2780,10 @@ bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there, Set<T
|
||||
thereNorm.tyvars.insert_or_assign(there, std::make_unique<NormalizedType>(std::move(topNorm)));
|
||||
return intersectNormals(here, thereNorm);
|
||||
}
|
||||
else if (auto lt = get<LocalType>(there))
|
||||
{
|
||||
return intersectNormalWithTy(here, lt->domain, seenSetTypes);
|
||||
}
|
||||
|
||||
NormalizedTyvars tyvars = std::move(here.tyvars);
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity, false);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -55,16 +57,69 @@ size_t SubtypingReasoningHash::operator()(const SubtypingReasoning& r) const
|
||||
return TypePath::PathHash()(r.subPath) ^ (TypePath::PathHash()(r.superPath) << 1) ^ (static_cast<size_t>(r.variance) << 1);
|
||||
}
|
||||
|
||||
template<typename TID>
|
||||
static void assertReasoningValid(TID subTy, TID superTy, const SubtypingResult& result, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
if (!FFlag::DebugLuauSubtypingCheckPathValidity)
|
||||
return;
|
||||
|
||||
for (const SubtypingReasoning& reasoning : result.reasoning)
|
||||
{
|
||||
LUAU_ASSERT(traverse(subTy, reasoning.subPath, builtinTypes));
|
||||
LUAU_ASSERT(traverse(superTy, reasoning.superPath, builtinTypes));
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void assertReasoningValid<TableIndexer>(TableIndexer subIdx, TableIndexer superIdx, const SubtypingResult& result, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
// Empty method to satisfy the compiler.
|
||||
}
|
||||
|
||||
static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const SubtypingReasonings& b)
|
||||
{
|
||||
SubtypingReasonings result{kEmptyReasoning};
|
||||
|
||||
for (const SubtypingReasoning& r : a)
|
||||
{
|
||||
if (r.variance == SubtypingVariance::Invariant)
|
||||
result.insert(r);
|
||||
else if (r.variance == SubtypingVariance::Covariant || r.variance == SubtypingVariance::Contravariant)
|
||||
{
|
||||
SubtypingReasoning inverseReasoning = SubtypingReasoning{
|
||||
r.subPath, r.superPath, r.variance == SubtypingVariance::Covariant ? SubtypingVariance::Contravariant : SubtypingVariance::Covariant};
|
||||
if (b.contains(inverseReasoning))
|
||||
result.insert(SubtypingReasoning{r.subPath, r.superPath, SubtypingVariance::Invariant});
|
||||
else
|
||||
result.insert(r);
|
||||
}
|
||||
}
|
||||
|
||||
for (const SubtypingReasoning& r : b)
|
||||
{
|
||||
if (r.variance == SubtypingVariance::Invariant)
|
||||
result.insert(r);
|
||||
else if (r.variance == SubtypingVariance::Covariant || r.variance == SubtypingVariance::Contravariant)
|
||||
{
|
||||
SubtypingReasoning inverseReasoning = SubtypingReasoning{
|
||||
r.subPath, r.superPath, r.variance == SubtypingVariance::Covariant ? SubtypingVariance::Contravariant : SubtypingVariance::Covariant};
|
||||
if (a.contains(inverseReasoning))
|
||||
result.insert(SubtypingReasoning{r.subPath, r.superPath, SubtypingVariance::Invariant});
|
||||
else
|
||||
result.insert(r);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
SubtypingResult& SubtypingResult::andAlso(const SubtypingResult& other)
|
||||
{
|
||||
// If the other result is not a subtype, we want to join all of its
|
||||
// reasonings to this one. If this result already has reasonings of its own,
|
||||
// those need to be attributed here.
|
||||
if (!other.isSubtype)
|
||||
{
|
||||
for (const SubtypingReasoning& r : other.reasoning)
|
||||
reasoning.insert(r);
|
||||
}
|
||||
reasoning = mergeReasonings(reasoning, other.reasoning);
|
||||
|
||||
isSubtype &= other.isSubtype;
|
||||
// `|=` is intentional here, we want to preserve error related flags.
|
||||
@ -86,10 +141,7 @@ SubtypingResult& SubtypingResult::orElse(const SubtypingResult& other)
|
||||
if (other.isSubtype)
|
||||
reasoning.clear();
|
||||
else
|
||||
{
|
||||
for (const SubtypingReasoning& r : other.reasoning)
|
||||
reasoning.insert(r);
|
||||
}
|
||||
reasoning = mergeReasonings(reasoning, other.reasoning);
|
||||
}
|
||||
|
||||
isSubtype |= other.isSubtype;
|
||||
@ -162,19 +214,6 @@ SubtypingResult& SubtypingResult::withSuperPath(TypePath::Path path)
|
||||
return *this;
|
||||
}
|
||||
|
||||
SubtypingResult& SubtypingResult::withVariance(SubtypingVariance variance)
|
||||
{
|
||||
if (reasoning.empty())
|
||||
reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, variance});
|
||||
else
|
||||
{
|
||||
for (auto& r : reasoning)
|
||||
r.variance = variance;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
SubtypingResult SubtypingResult::negate(const SubtypingResult& result)
|
||||
{
|
||||
return SubtypingResult{
|
||||
@ -245,7 +284,10 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
|
||||
result.isSubtype = false;
|
||||
}
|
||||
|
||||
result.andAlso(isCovariantWith(env, lowerBound, upperBound));
|
||||
SubtypingResult boundsResult = isCovariantWith(env, lowerBound, upperBound);
|
||||
boundsResult.reasoning.clear();
|
||||
|
||||
result.andAlso(boundsResult);
|
||||
}
|
||||
|
||||
/* TODO: We presently don't store subtype test results in the persistent
|
||||
@ -370,7 +412,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||
{
|
||||
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
|
||||
if (semantic.isSubtype)
|
||||
{
|
||||
semantic.reasoning.clear();
|
||||
result = semantic;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto superIntersection = get<IntersectionType>(superTy))
|
||||
@ -382,7 +427,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||
{
|
||||
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
|
||||
if (semantic.isSubtype)
|
||||
{
|
||||
// Clear the semantic reasoning, as any reasonings within
|
||||
// potentially contain invalid paths.
|
||||
semantic.reasoning.clear();
|
||||
result = semantic;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (get<AnyType>(superTy))
|
||||
@ -411,9 +461,31 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||
else if (auto p = get2<NegationType, NegationType>(subTy, superTy))
|
||||
result = isCovariantWith(env, p.first->ty, p.second->ty).withBothComponent(TypePath::TypeField::Negated);
|
||||
else if (auto subNegation = get<NegationType>(subTy))
|
||||
{
|
||||
result = isCovariantWith(env, subNegation, superTy);
|
||||
if (!result.isSubtype && !result.isErrorSuppressing && !result.normalizationTooComplex)
|
||||
{
|
||||
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
|
||||
if (semantic.isSubtype)
|
||||
{
|
||||
semantic.reasoning.clear();
|
||||
result = semantic;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto superNegation = get<NegationType>(superTy))
|
||||
{
|
||||
result = isCovariantWith(env, subTy, superNegation);
|
||||
if (!result.isSubtype && !result.isErrorSuppressing && !result.normalizationTooComplex)
|
||||
{
|
||||
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
|
||||
if (semantic.isSubtype)
|
||||
{
|
||||
semantic.reasoning.clear();
|
||||
result = semantic;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto subGeneric = get<GenericType>(subTy); subGeneric && variance == Variance::Covariant)
|
||||
{
|
||||
bool ok = bindGeneric(env, subTy, superTy);
|
||||
@ -449,6 +521,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||
else if (auto p = get2<SingletonType, TableType>(subTy, superTy))
|
||||
result = isCovariantWith(env, p);
|
||||
|
||||
assertReasoningValid(subTy, superTy, result, builtinTypes);
|
||||
|
||||
return cache(env, result, subTy, superTy);
|
||||
}
|
||||
|
||||
@ -536,7 +610,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
for (size_t i = headSize; i < subHead.size(); ++i)
|
||||
results.push_back(isCovariantWith(env, subHead[i], vt->ty)
|
||||
.withSubComponent(TypePath::Index{i})
|
||||
.withSuperComponent(TypePath::TypeField::Variadic));
|
||||
.withSuperPath(TypePath::PathBuilder().tail().variadic().build()));
|
||||
}
|
||||
else if (auto gt = get<GenericTypePack>(*superTail))
|
||||
{
|
||||
@ -664,19 +738,38 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
iceReporter->ice("Subtyping test encountered the unexpected type pack: " + toString(*superTail));
|
||||
}
|
||||
|
||||
return SubtypingResult::all(results);
|
||||
SubtypingResult result = SubtypingResult::all(results);
|
||||
assertReasoningValid(subTp, superTp, result, builtinTypes);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy)
|
||||
{
|
||||
SubtypingResult result = isCovariantWith(env, superTy, subTy);
|
||||
// If we don't swap the paths here, we will end up producing an invalid path
|
||||
// whenever we involve contravariance. We'll end up appending path
|
||||
// components that should belong to the supertype to the subtype, and vice
|
||||
// versa.
|
||||
for (auto& reasoning : result.reasoning)
|
||||
std::swap(reasoning.subPath, reasoning.superPath);
|
||||
if (result.reasoning.empty())
|
||||
result.reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Contravariant});
|
||||
else
|
||||
{
|
||||
// If we don't swap the paths here, we will end up producing an invalid path
|
||||
// whenever we involve contravariance. We'll end up appending path
|
||||
// components that should belong to the supertype to the subtype, and vice
|
||||
// versa.
|
||||
for (auto& reasoning : result.reasoning)
|
||||
{
|
||||
std::swap(reasoning.subPath, reasoning.superPath);
|
||||
|
||||
// Also swap covariant/contravariant, since those are also the other way
|
||||
// around.
|
||||
if (reasoning.variance == SubtypingVariance::Covariant)
|
||||
reasoning.variance = SubtypingVariance::Contravariant;
|
||||
else if (reasoning.variance == SubtypingVariance::Contravariant)
|
||||
reasoning.variance = SubtypingVariance::Covariant;
|
||||
}
|
||||
}
|
||||
|
||||
assertReasoningValid(subTy, superTy, result, builtinTypes);
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -684,7 +777,17 @@ SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy&
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy)
|
||||
{
|
||||
return isCovariantWith(env, subTy, superTy).andAlso(isContravariantWith(env, subTy, superTy)).withVariance(SubtypingVariance::Invariant);
|
||||
SubtypingResult result = isCovariantWith(env, subTy, superTy).andAlso(isContravariantWith(env, subTy, superTy));
|
||||
if (result.reasoning.empty())
|
||||
result.reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invariant});
|
||||
else
|
||||
{
|
||||
for (auto& reasoning : result.reasoning)
|
||||
reasoning.variance = SubtypingVariance::Invariant;
|
||||
}
|
||||
|
||||
assertReasoningValid(subTy, superTy, result, builtinTypes);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename SubTy, typename SuperTy>
|
||||
@ -696,13 +799,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TryP
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair)
|
||||
{
|
||||
return isCovariantWith(env, pair.second, pair.first);
|
||||
return isContravariantWith(env, pair.first, pair.second);
|
||||
}
|
||||
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair)
|
||||
{
|
||||
return isCovariantWith(env, pair).andAlso(isContravariantWith(pair)).withVariance(SubtypingVariance::Invariant);
|
||||
return isInvariantWith(env, pair.first, pair.second);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -788,17 +891,17 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Nega
|
||||
if (is<NeverType>(negatedTy))
|
||||
{
|
||||
// ¬never ~ unknown
|
||||
result = isCovariantWith(env, builtinTypes->unknownType, superTy);
|
||||
result = isCovariantWith(env, builtinTypes->unknownType, superTy).withSubComponent(TypePath::TypeField::Negated);
|
||||
}
|
||||
else if (is<UnknownType>(negatedTy))
|
||||
{
|
||||
// ¬unknown ~ never
|
||||
result = isCovariantWith(env, builtinTypes->neverType, superTy);
|
||||
result = isCovariantWith(env, builtinTypes->neverType, superTy).withSubComponent(TypePath::TypeField::Negated);
|
||||
}
|
||||
else if (is<AnyType>(negatedTy))
|
||||
{
|
||||
// ¬any ~ any
|
||||
result = isCovariantWith(env, negatedTy, superTy);
|
||||
result = isCovariantWith(env, negatedTy, superTy).withSubComponent(TypePath::TypeField::Negated);
|
||||
}
|
||||
else if (auto u = get<UnionType>(negatedTy))
|
||||
{
|
||||
@ -808,8 +911,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Nega
|
||||
|
||||
for (TypeId ty : u)
|
||||
{
|
||||
NegationType negatedTmp{ty};
|
||||
subtypings.push_back(isCovariantWith(env, &negatedTmp, superTy));
|
||||
if (auto negatedPart = get<NegationType>(follow(ty)))
|
||||
subtypings.push_back(isCovariantWith(env, negatedPart->ty, superTy).withSubComponent(TypePath::TypeField::Negated));
|
||||
else
|
||||
{
|
||||
NegationType negatedTmp{ty};
|
||||
subtypings.push_back(isCovariantWith(env, &negatedTmp, superTy));
|
||||
}
|
||||
}
|
||||
|
||||
result = SubtypingResult::all(subtypings);
|
||||
@ -823,7 +931,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Nega
|
||||
for (TypeId ty : i)
|
||||
{
|
||||
if (auto negatedPart = get<NegationType>(follow(ty)))
|
||||
subtypings.push_back(isCovariantWith(env, negatedPart->ty, superTy));
|
||||
subtypings.push_back(isCovariantWith(env, negatedPart->ty, superTy).withSubComponent(TypePath::TypeField::Negated));
|
||||
else
|
||||
{
|
||||
NegationType negatedTmp{ty};
|
||||
@ -841,10 +949,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Nega
|
||||
// subtype of other stuff.
|
||||
else
|
||||
{
|
||||
result = {false};
|
||||
result = SubtypingResult{false}.withSubComponent(TypePath::TypeField::Negated);
|
||||
}
|
||||
|
||||
return result.withSubComponent(TypePath::TypeField::Negated);
|
||||
return result;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const NegationType* superNegation)
|
||||
@ -885,7 +993,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
|
||||
}
|
||||
}
|
||||
|
||||
result = SubtypingResult::all(subtypings);
|
||||
return SubtypingResult::all(subtypings);
|
||||
}
|
||||
else if (auto i = get<IntersectionType>(negatedTy))
|
||||
{
|
||||
@ -904,7 +1012,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
|
||||
}
|
||||
}
|
||||
|
||||
result = SubtypingResult::any(subtypings);
|
||||
return SubtypingResult::any(subtypings);
|
||||
}
|
||||
else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, negatedTy))
|
||||
{
|
||||
@ -986,8 +1094,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
|
||||
{
|
||||
std::vector<SubtypingResult> results;
|
||||
if (auto it = subTable->props.find(name); it != subTable->props.end())
|
||||
results.push_back(isInvariantWith(env, it->second.type(), prop.type())
|
||||
.withBothComponent(TypePath::Property(name)));
|
||||
results.push_back(isInvariantWith(env, it->second.type(), prop.type()).withBothComponent(TypePath::Property(name)));
|
||||
|
||||
if (subTable->indexer)
|
||||
{
|
||||
@ -1122,7 +1229,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
|
||||
{
|
||||
return isInvariantWith(env, subIndexer.indexType, superIndexer.indexType)
|
||||
.withBothComponent(TypePath::TypeField::IndexLookup)
|
||||
.andAlso(isInvariantWith(env, superIndexer.indexResultType, subIndexer.indexResultType).withBothComponent(TypePath::TypeField::IndexResult));
|
||||
.andAlso(isInvariantWith(env, subIndexer.indexResultType, superIndexer.indexResultType).withBothComponent(TypePath::TypeField::IndexResult));
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedType* subNorm, const NormalizedType* superNorm)
|
||||
@ -1249,12 +1356,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
|
||||
{
|
||||
std::vector<SubtypingResult> results;
|
||||
|
||||
size_t i = 0;
|
||||
for (TypeId subTy : subTypes)
|
||||
{
|
||||
results.emplace_back();
|
||||
for (TypeId superTy : superTypes)
|
||||
results.back().orElse(isCovariantWith(env, subTy, superTy).withBothComponent(TypePath::Index{i++}));
|
||||
results.back().orElse(isCovariantWith(env, subTy, superTy));
|
||||
}
|
||||
|
||||
return SubtypingResult::all(results);
|
||||
|
@ -1721,7 +1721,7 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
||||
std::string iteratorStr = tos(c.iterator);
|
||||
std::string variableStr = tos(c.variables);
|
||||
|
||||
return variableStr + " ~ Iterate<" + iteratorStr + ">";
|
||||
return variableStr + " ~ iterate " + iteratorStr;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, NameConstraint>)
|
||||
{
|
||||
|
@ -2467,6 +2467,8 @@ struct TypeChecker2
|
||||
std::string relation = "a subtype of";
|
||||
if (reasoning.variance == SubtypingVariance::Invariant)
|
||||
relation = "exactly";
|
||||
else if (reasoning.variance == SubtypingVariance::Contravariant)
|
||||
relation = "a supertype of";
|
||||
|
||||
std::string reason;
|
||||
if (reasoning.subPath == reasoning.superPath)
|
||||
|
@ -12,7 +12,6 @@
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
#include <unordered_set>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties);
|
||||
|
||||
@ -252,8 +251,6 @@ struct TraversalState
|
||||
|
||||
TypeOrPack current;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
|
||||
DenseHashSet<const void*> seen{nullptr};
|
||||
int steps = 0;
|
||||
|
||||
void updateCurrent(TypeId ty)
|
||||
@ -268,18 +265,6 @@ struct TraversalState
|
||||
current = follow(tp);
|
||||
}
|
||||
|
||||
bool haveCycle()
|
||||
{
|
||||
const void* currentPtr = ptr(current);
|
||||
|
||||
if (seen.contains(currentPtr))
|
||||
return true;
|
||||
else
|
||||
seen.insert(currentPtr);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool tooLong()
|
||||
{
|
||||
return ++steps > DFInt::LuauTypePathMaximumTraverseSteps;
|
||||
@ -287,7 +272,7 @@ struct TraversalState
|
||||
|
||||
bool checkInvariants()
|
||||
{
|
||||
return haveCycle() || tooLong();
|
||||
return tooLong();
|
||||
}
|
||||
|
||||
bool traverse(const TypePath::Property& property)
|
||||
@ -313,18 +298,36 @@ struct TraversalState
|
||||
{
|
||||
prop = lookupClassProp(c, property.name);
|
||||
}
|
||||
else if (auto m = getMetatable(*currentType, builtinTypes))
|
||||
// For a metatable type, the table takes priority; check that before
|
||||
// falling through to the metatable entry below.
|
||||
else if (auto m = get<MetatableType>(*currentType))
|
||||
{
|
||||
// Weird: rather than use findMetatableEntry, which requires a lot
|
||||
// of stuff that we don't have and don't want to pull in, we use the
|
||||
// path traversal logic to grab __index and then re-enter the lookup
|
||||
// logic there.
|
||||
updateCurrent(*m);
|
||||
TypeOrPack pinned = current;
|
||||
updateCurrent(m->table);
|
||||
|
||||
if (!traverse(TypePath::Property{"__index"}))
|
||||
return false;
|
||||
if (traverse(property))
|
||||
return true;
|
||||
|
||||
return traverse(property);
|
||||
// Restore the old current type if we didn't traverse the metatable
|
||||
// successfully; we'll use the next branch to address this.
|
||||
current = pinned;
|
||||
}
|
||||
|
||||
if (!prop)
|
||||
{
|
||||
if (auto m = getMetatable(*currentType, builtinTypes))
|
||||
{
|
||||
// Weird: rather than use findMetatableEntry, which requires a lot
|
||||
// of stuff that we don't have and don't want to pull in, we use the
|
||||
// path traversal logic to grab __index and then re-enter the lookup
|
||||
// logic there.
|
||||
updateCurrent(*m);
|
||||
|
||||
if (!traverse(TypePath::Property{"__index"}))
|
||||
return false;
|
||||
|
||||
return traverse(property);
|
||||
}
|
||||
}
|
||||
|
||||
if (prop)
|
||||
|
@ -113,7 +113,7 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
||||
return argResult && retResult;
|
||||
}
|
||||
|
||||
auto subTable = get<TableType>(subTy);
|
||||
auto subTable = getMutable<TableType>(subTy);
|
||||
auto superTable = get<TableType>(superTy);
|
||||
if (subTable && superTable)
|
||||
{
|
||||
@ -210,7 +210,7 @@ bool Unifier2::unify(TypeId subTy, const IntersectionType* superIntersection)
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Unifier2::unify(const TableType* subTable, const TableType* superTable)
|
||||
bool Unifier2::unify(TableType* subTable, const TableType* superTable)
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
@ -256,6 +256,21 @@ bool Unifier2::unify(const TableType* subTable, const TableType* superTable)
|
||||
result &= unify(subTable->indexer->indexResultType, superTable->indexer->indexResultType);
|
||||
}
|
||||
|
||||
if (!subTable->indexer && subTable->state == TableState::Unsealed && superTable->indexer)
|
||||
{
|
||||
/*
|
||||
* Unsealed tables are always created from literal table expressions. We
|
||||
* can't be completely certain whether such a table has an indexer just
|
||||
* by the content of the expression itself, so we need to be a bit more
|
||||
* flexible here.
|
||||
*
|
||||
* If we are trying to reconcile an unsealed table with a table that has
|
||||
* an indexer, we therefore conclude that the unsealed table has the
|
||||
* same indexer.
|
||||
*/
|
||||
subTable->indexer = *superTable->indexer;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include "Luau/Location.h"
|
||||
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
@ -91,10 +92,21 @@ struct AstArray
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
const T* end() const
|
||||
{
|
||||
return data + size;
|
||||
}
|
||||
|
||||
std::reverse_iterator<const T*> rbegin() const
|
||||
{
|
||||
return std::make_reverse_iterator(end());
|
||||
}
|
||||
|
||||
std::reverse_iterator<const T*> rend() const
|
||||
{
|
||||
return std::make_reverse_iterator(begin());
|
||||
}
|
||||
};
|
||||
|
||||
struct AstTypeList
|
||||
|
@ -19,8 +19,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||
LUAU_FASTFLAGVARIABLE(LuauClipExtraHasEndProps, false)
|
||||
LUAU_FASTFLAG(LuauCheckedFunctionSyntax)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauParseImpreciseNumber, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -2168,11 +2166,8 @@ static ConstantNumberParseResult parseInteger(double& result, const char* data,
|
||||
return base == 2 ? ConstantNumberParseResult::BinOverflow : ConstantNumberParseResult::HexOverflow;
|
||||
}
|
||||
|
||||
if (FFlag::LuauParseImpreciseNumber)
|
||||
{
|
||||
if (value >= (1ull << 53) && static_cast<unsigned long long>(result) != value)
|
||||
return ConstantNumberParseResult::Imprecise;
|
||||
}
|
||||
if (value >= (1ull << 53) && static_cast<unsigned long long>(result) != value)
|
||||
return ConstantNumberParseResult::Imprecise;
|
||||
|
||||
return ConstantNumberParseResult::Ok;
|
||||
}
|
||||
@ -2190,32 +2185,24 @@ static ConstantNumberParseResult parseDouble(double& result, const char* data)
|
||||
char* end = nullptr;
|
||||
double value = strtod(data, &end);
|
||||
|
||||
if (FFlag::LuauParseImpreciseNumber)
|
||||
// trailing non-numeric characters
|
||||
if (*end != 0)
|
||||
return ConstantNumberParseResult::Malformed;
|
||||
|
||||
result = value;
|
||||
|
||||
// for linting, we detect integer constants that are parsed imprecisely
|
||||
// since the check is expensive we only perform it when the number is larger than the precise integer range
|
||||
if (value >= double(1ull << 53) && strspn(data, "0123456789") == strlen(data))
|
||||
{
|
||||
// trailing non-numeric characters
|
||||
if (*end != 0)
|
||||
return ConstantNumberParseResult::Malformed;
|
||||
char repr[512];
|
||||
snprintf(repr, sizeof(repr), "%.0f", value);
|
||||
|
||||
result = value;
|
||||
|
||||
// for linting, we detect integer constants that are parsed imprecisely
|
||||
// since the check is expensive we only perform it when the number is larger than the precise integer range
|
||||
if (value >= double(1ull << 53) && strspn(data, "0123456789") == strlen(data))
|
||||
{
|
||||
char repr[512];
|
||||
snprintf(repr, sizeof(repr), "%.0f", value);
|
||||
|
||||
if (strcmp(repr, data) != 0)
|
||||
return ConstantNumberParseResult::Imprecise;
|
||||
}
|
||||
|
||||
return ConstantNumberParseResult::Ok;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = value;
|
||||
return *end == 0 ? ConstantNumberParseResult::Ok : ConstantNumberParseResult::Malformed;
|
||||
if (strcmp(repr, data) != 0)
|
||||
return ConstantNumberParseResult::Imprecise;
|
||||
}
|
||||
|
||||
return ConstantNumberParseResult::Ok;
|
||||
}
|
||||
|
||||
// simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor | FUNCTION body | primaryexp
|
||||
|
@ -37,13 +37,19 @@ enum class RecordStats
|
||||
{
|
||||
None,
|
||||
Total,
|
||||
Split
|
||||
File,
|
||||
Function
|
||||
};
|
||||
|
||||
struct GlobalOptions
|
||||
{
|
||||
int optimizationLevel = 1;
|
||||
int debugLevel = 1;
|
||||
|
||||
std::string vectorLib;
|
||||
std::string vectorCtor;
|
||||
std::string vectorType;
|
||||
|
||||
} globalOptions;
|
||||
|
||||
static Luau::CompileOptions copts()
|
||||
@ -52,6 +58,11 @@ static Luau::CompileOptions copts()
|
||||
result.optimizationLevel = globalOptions.optimizationLevel;
|
||||
result.debugLevel = globalOptions.debugLevel;
|
||||
|
||||
// globalOptions outlive the CompileOptions, so it's safe to use string data pointers here
|
||||
result.vectorLib = globalOptions.vectorLib.c_str();
|
||||
result.vectorCtor = globalOptions.vectorCtor.c_str();
|
||||
result.vectorType = globalOptions.vectorType.c_str();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -159,11 +170,26 @@ struct CompileStats
|
||||
\"blockLinearizationStats\": {\
|
||||
\"constPropInstructionCount\": %u, \
|
||||
\"timeSeconds\": %f\
|
||||
}}",
|
||||
}",
|
||||
lines, bytecode, bytecodeInstructionCount, codegen, readTime, miscTime, parseTime, compileTime, codegenTime, lowerStats.totalFunctions,
|
||||
lowerStats.skippedFunctions, lowerStats.spillsToSlot, lowerStats.spillsToRestore, lowerStats.maxSpillSlotsUsed, lowerStats.blocksPreOpt,
|
||||
lowerStats.blocksPostOpt, lowerStats.maxBlockInstructions, lowerStats.regAllocErrors, lowerStats.loweringErrors,
|
||||
lowerStats.blockLinearizationStats.constPropInstructionCount, lowerStats.blockLinearizationStats.timeSeconds);
|
||||
if (lowerStats.collectFunctionStats)
|
||||
{
|
||||
fprintf(fp, ", \"functions\": [");
|
||||
auto functionCount = lowerStats.functions.size();
|
||||
for (size_t i = 0; i < functionCount; ++i)
|
||||
{
|
||||
const Luau::CodeGen::FunctionStats& fstat = lowerStats.functions[i];
|
||||
fprintf(fp, "{\"name\": \"%s\", \"line\": %d, \"bcodeCount\": %u, \"irCount\": %u, \"asmCount\": %u}", fstat.name.c_str(), fstat.line,
|
||||
fstat.bcodeCount, fstat.irCount, fstat.asmCount);
|
||||
if (i < functionCount - 1)
|
||||
fprintf(fp, ", ");
|
||||
}
|
||||
fprintf(fp, "]");
|
||||
}
|
||||
fprintf(fp, "}");
|
||||
}
|
||||
|
||||
CompileStats& operator+=(const CompileStats& that)
|
||||
@ -321,7 +347,11 @@ static void displayHelp(const char* argv0)
|
||||
printf(" -g<n>: compile with debug level n (default 1, n should be between 0 and 2).\n");
|
||||
printf(" --target=<target>: compile code for specific architecture (a64, x64, a64_nf, x64_ms).\n");
|
||||
printf(" --timetrace: record compiler time tracing information into trace.json\n");
|
||||
printf(" --record-stats=<style>: records compilation stats in stats.json (total, split).\n");
|
||||
printf(" --stats-file=<filename>: file in which compilation stats will be recored (default 'stats.json').\n");
|
||||
printf(" --record-stats=<granularity>: granularity of compilation stats recorded in stats.json (total, file, function).\n");
|
||||
printf(" --vector-lib=<name>: name of the library providing vector type operations.\n");
|
||||
printf(" --vector-ctor=<name>: name of the function constructing a vector value.\n");
|
||||
printf(" --vector-type=<name>: name of the vector type.\n");
|
||||
}
|
||||
|
||||
static int assertionHandler(const char* expr, const char* file, int line, const char* function)
|
||||
@ -420,11 +450,13 @@ int main(int argc, char** argv)
|
||||
|
||||
if (strcmp(value, "total") == 0)
|
||||
recordStats = RecordStats::Total;
|
||||
else if (strcmp(value, "split") == 0)
|
||||
recordStats = RecordStats::Split;
|
||||
else if (strcmp(value, "file") == 0)
|
||||
recordStats = RecordStats::File;
|
||||
else if (strcmp(value, "function") == 0)
|
||||
recordStats = RecordStats::Function;
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Error: unknown 'style' for '--record-stats'\n");
|
||||
fprintf(stderr, "Error: unknown 'granularity' for '--record-stats'\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@ -442,6 +474,18 @@ int main(int argc, char** argv)
|
||||
{
|
||||
setLuauFlags(argv[i] + 9);
|
||||
}
|
||||
else if (strncmp(argv[i], "--vector-lib=", 13) == 0)
|
||||
{
|
||||
globalOptions.vectorLib = argv[i] + 13;
|
||||
}
|
||||
else if (strncmp(argv[i], "--vector-ctor=", 14) == 0)
|
||||
{
|
||||
globalOptions.vectorCtor = argv[i] + 14;
|
||||
}
|
||||
else if (strncmp(argv[i], "--vector-type=", 14) == 0)
|
||||
{
|
||||
globalOptions.vectorType = argv[i] + 14;
|
||||
}
|
||||
else if (argv[i][0] == '-' && argv[i][1] == '-' && getCompileFormat(argv[i] + 2))
|
||||
{
|
||||
compileFormat = *getCompileFormat(argv[i] + 2);
|
||||
@ -473,7 +517,7 @@ int main(int argc, char** argv)
|
||||
CompileStats stats = {};
|
||||
|
||||
std::vector<CompileStats> fileStats;
|
||||
if (recordStats == RecordStats::Split)
|
||||
if (recordStats == RecordStats::File || recordStats == RecordStats::Function)
|
||||
fileStats.reserve(fileCount);
|
||||
|
||||
int failed = 0;
|
||||
@ -481,9 +525,10 @@ int main(int argc, char** argv)
|
||||
for (const std::string& path : files)
|
||||
{
|
||||
CompileStats fileStat = {};
|
||||
fileStat.lowerStats.collectFunctionStats = (recordStats == RecordStats::Function);
|
||||
failed += !compileFile(path.c_str(), compileFormat, assemblyTarget, fileStat);
|
||||
stats += fileStat;
|
||||
if (recordStats == RecordStats::Split)
|
||||
if (recordStats == RecordStats::File || recordStats == RecordStats::Function)
|
||||
fileStats.push_back(fileStat);
|
||||
}
|
||||
|
||||
@ -506,7 +551,6 @@ int main(int argc, char** argv)
|
||||
|
||||
if (recordStats != RecordStats::None)
|
||||
{
|
||||
|
||||
FILE* fp = fopen(statsFile.c_str(), "w");
|
||||
|
||||
if (!fp)
|
||||
@ -519,7 +563,7 @@ int main(int argc, char** argv)
|
||||
{
|
||||
stats.serializeToJson(fp);
|
||||
}
|
||||
else if (recordStats == RecordStats::Split)
|
||||
else if (recordStats == RecordStats::File || recordStats == RecordStats::Function)
|
||||
{
|
||||
fprintf(fp, "{\n");
|
||||
for (size_t i = 0; i < fileCount; ++i)
|
||||
|
@ -10,6 +10,7 @@
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <direct.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <dirent.h>
|
||||
@ -44,6 +45,148 @@ static std::string toUtf8(const std::wstring& path)
|
||||
}
|
||||
#endif
|
||||
|
||||
bool isAbsolutePath(std::string_view path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Must either begin with "X:/", "X:\", "/", or "\", where X is a drive letter
|
||||
return (path.size() >= 3 && isalpha(path[0]) && path[1] == ':' && (path[2] == '/' || path[2] == '\\')) ||
|
||||
(path.size() >= 1 && (path[0] == '/' || path[0] == '\\'));
|
||||
#else
|
||||
// Must begin with '/'
|
||||
return path.size() >= 1 && path[0] == '/';
|
||||
#endif
|
||||
}
|
||||
|
||||
bool isExplicitlyRelative(std::string_view path)
|
||||
{
|
||||
return (path == ".") || (path == "..") || (path.size() >= 2 && path[0] == '.' && path[1] == '/') ||
|
||||
(path.size() >= 3 && path[0] == '.' && path[1] == '.' && path[2] == '/');
|
||||
}
|
||||
|
||||
std::optional<std::string> getCurrentWorkingDirectory()
|
||||
{
|
||||
// 2^17 - derived from the Windows path length limit
|
||||
constexpr size_t maxPathLength = 131072;
|
||||
constexpr size_t initialPathLength = 260;
|
||||
|
||||
std::string directory(initialPathLength, '\0');
|
||||
char* cstr = nullptr;
|
||||
|
||||
while (!cstr && directory.size() <= maxPathLength)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
cstr = _getcwd(directory.data(), static_cast<int>(directory.size()));
|
||||
#else
|
||||
cstr = getcwd(directory.data(), directory.size());
|
||||
#endif
|
||||
if (cstr)
|
||||
{
|
||||
directory.resize(strlen(cstr));
|
||||
return directory;
|
||||
}
|
||||
else if (errno != ERANGE || directory.size() * 2 > maxPathLength)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
else
|
||||
{
|
||||
directory.resize(directory.size() * 2);
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Returns the normal/canonical form of a path (e.g. "../subfolder/../module.luau" -> "../module.luau")
|
||||
std::string normalizePath(std::string_view path)
|
||||
{
|
||||
return resolvePath(path, "");
|
||||
}
|
||||
|
||||
// Takes a path that is relative to the file at baseFilePath and returns the path explicitly rebased onto baseFilePath.
|
||||
// For absolute paths, baseFilePath will be ignored, and this function will resolve the path to a canonical path:
|
||||
// (e.g. "/Users/.././Users/johndoe" -> "/Users/johndoe").
|
||||
std::string resolvePath(std::string_view path, std::string_view baseFilePath)
|
||||
{
|
||||
std::vector<std::string_view> pathComponents;
|
||||
std::vector<std::string_view> baseFilePathComponents;
|
||||
|
||||
// Dependent on whether the final resolved path is absolute or relative
|
||||
// - if relative (when path and baseFilePath are both relative), resolvedPathPrefix remains empty
|
||||
// - if absolute (if either path or baseFilePath are absolute), resolvedPathPrefix is "C:\", "/", etc.
|
||||
std::string resolvedPathPrefix;
|
||||
|
||||
if (isAbsolutePath(path))
|
||||
{
|
||||
// path is absolute, we use path's prefix and ignore baseFilePath
|
||||
size_t afterPrefix = path.find_first_of("\\/") + 1;
|
||||
resolvedPathPrefix = path.substr(0, afterPrefix);
|
||||
pathComponents = splitPath(path.substr(afterPrefix));
|
||||
}
|
||||
else
|
||||
{
|
||||
pathComponents = splitPath(path);
|
||||
if (isAbsolutePath(baseFilePath))
|
||||
{
|
||||
// path is relative and baseFilePath is absolute, we use baseFilePath's prefix
|
||||
size_t afterPrefix = baseFilePath.find_first_of("\\/") + 1;
|
||||
resolvedPathPrefix = baseFilePath.substr(0, afterPrefix);
|
||||
baseFilePathComponents = splitPath(baseFilePath.substr(afterPrefix));
|
||||
}
|
||||
else
|
||||
{
|
||||
// path and baseFilePath are both relative, we do not set a prefix (resolved path will be relative)
|
||||
baseFilePathComponents = splitPath(baseFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove filename from components
|
||||
if (!baseFilePathComponents.empty())
|
||||
baseFilePathComponents.pop_back();
|
||||
|
||||
// Resolve the path by applying pathComponents to baseFilePathComponents
|
||||
int numPrependedParents = 0;
|
||||
for (std::string_view component : pathComponents)
|
||||
{
|
||||
if (component == "..")
|
||||
{
|
||||
if (baseFilePathComponents.empty())
|
||||
{
|
||||
if (resolvedPathPrefix.empty()) // only when final resolved path will be relative
|
||||
numPrependedParents++; // "../" will later be added to the beginning of the resolved path
|
||||
}
|
||||
else if (baseFilePathComponents.back() != "..")
|
||||
{
|
||||
baseFilePathComponents.pop_back(); // Resolve cases like "folder/subfolder/../../file" to "file"
|
||||
}
|
||||
}
|
||||
else if (component != "." && !component.empty())
|
||||
{
|
||||
baseFilePathComponents.push_back(component);
|
||||
}
|
||||
}
|
||||
|
||||
// Join baseFilePathComponents to form the resolved path
|
||||
std::string resolvedPath = resolvedPathPrefix;
|
||||
// Only when resolvedPath will be relative
|
||||
for (int i = 0; i < numPrependedParents; i++)
|
||||
{
|
||||
resolvedPath += "../";
|
||||
}
|
||||
for (auto iter = baseFilePathComponents.begin(); iter != baseFilePathComponents.end(); ++iter)
|
||||
{
|
||||
if (iter != baseFilePathComponents.begin())
|
||||
resolvedPath += "/";
|
||||
|
||||
resolvedPath += *iter;
|
||||
}
|
||||
if (resolvedPath.size() > resolvedPathPrefix.size() && resolvedPath.back() == '/')
|
||||
{
|
||||
// Remove trailing '/' if present
|
||||
resolvedPath.pop_back();
|
||||
}
|
||||
return resolvedPath;
|
||||
}
|
||||
|
||||
std::optional<std::string> readFile(const std::string& name)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
@ -224,6 +367,24 @@ bool isDirectory(const std::string& path)
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<std::string_view> splitPath(std::string_view path)
|
||||
{
|
||||
std::vector<std::string_view> components;
|
||||
|
||||
size_t pos = 0;
|
||||
size_t nextPos = path.find_first_of("\\/", pos);
|
||||
|
||||
while (nextPos != std::string::npos)
|
||||
{
|
||||
components.push_back(path.substr(pos, nextPos - pos));
|
||||
pos = nextPos + 1;
|
||||
nextPos = path.find_first_of("\\/", pos);
|
||||
}
|
||||
components.push_back(path.substr(pos));
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
std::string joinPaths(const std::string& lhs, const std::string& rhs)
|
||||
{
|
||||
std::string result = lhs;
|
||||
|
@ -3,15 +3,24 @@
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
std::optional<std::string> getCurrentWorkingDirectory();
|
||||
|
||||
std::string normalizePath(std::string_view path);
|
||||
std::string resolvePath(std::string_view relativePath, std::string_view baseFilePath);
|
||||
|
||||
std::optional<std::string> readFile(const std::string& name);
|
||||
std::optional<std::string> readStdin();
|
||||
|
||||
bool isAbsolutePath(std::string_view path);
|
||||
bool isExplicitlyRelative(std::string_view path);
|
||||
bool isDirectory(const std::string& path);
|
||||
bool traverseDirectory(const std::string& path, const std::function<void(const std::string& name)>& callback);
|
||||
|
||||
std::vector<std::string_view> splitPath(std::string_view path);
|
||||
std::string joinPaths(const std::string& lhs, const std::string& rhs);
|
||||
std::optional<std::string> getParentPath(const std::string& path);
|
||||
|
||||
|
183
CLI/Repl.cpp
183
CLI/Repl.cpp
@ -1,6 +1,7 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Repl.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "lua.h"
|
||||
#include "lualib.h"
|
||||
|
||||
@ -13,6 +14,7 @@
|
||||
#include "FileUtils.h"
|
||||
#include "Flags.h"
|
||||
#include "Profiler.h"
|
||||
#include "Require.h"
|
||||
|
||||
#include "isocline.h"
|
||||
|
||||
@ -39,6 +41,8 @@
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauTimeTracing)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauUpdatedRequireByStringSemantics, false)
|
||||
|
||||
constexpr int MaxTraversalLimit = 50;
|
||||
|
||||
static bool codegen = false;
|
||||
@ -115,74 +119,129 @@ static int finishrequire(lua_State* L)
|
||||
|
||||
static int lua_require(lua_State* L)
|
||||
{
|
||||
std::string name = luaL_checkstring(L, 1);
|
||||
std::string chunkname = "=" + name;
|
||||
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
|
||||
// return the module from the cache
|
||||
lua_getfield(L, -1, name.c_str());
|
||||
if (!lua_isnil(L, -1))
|
||||
if (FFlag::LuauUpdatedRequireByStringSemantics)
|
||||
{
|
||||
// L stack: _MODULES result
|
||||
std::string name = luaL_checkstring(L, 1);
|
||||
|
||||
RequireResolver::ResolvedRequire resolvedRequire = RequireResolver::resolveRequire(L, std::move(name));
|
||||
|
||||
if (resolvedRequire.status == RequireResolver::ModuleStatus::Cached)
|
||||
return finishrequire(L);
|
||||
else if (resolvedRequire.status == RequireResolver::ModuleStatus::NotFound)
|
||||
luaL_errorL(L, "error requiring module");
|
||||
|
||||
// module needs to run in a new thread, isolated from the rest
|
||||
// note: we create ML on main thread so that it doesn't inherit environment of L
|
||||
lua_State* GL = lua_mainthread(L);
|
||||
lua_State* ML = lua_newthread(GL);
|
||||
lua_xmove(GL, L, 1);
|
||||
|
||||
// new thread needs to have the globals sandboxed
|
||||
luaL_sandboxthread(ML);
|
||||
|
||||
// now we can compile & run module on the new thread
|
||||
std::string bytecode = Luau::compile(resolvedRequire.sourceCode, copts());
|
||||
if (luau_load(ML, resolvedRequire.chunkName.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
|
||||
{
|
||||
if (codegen)
|
||||
Luau::CodeGen::compile(ML, -1);
|
||||
|
||||
if (coverageActive())
|
||||
coverageTrack(ML, -1);
|
||||
|
||||
int status = lua_resume(ML, L, 0);
|
||||
|
||||
if (status == 0)
|
||||
{
|
||||
if (lua_gettop(ML) == 0)
|
||||
lua_pushstring(ML, "module must return a value");
|
||||
else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1))
|
||||
lua_pushstring(ML, "module must return a table or function");
|
||||
}
|
||||
else if (status == LUA_YIELD)
|
||||
{
|
||||
lua_pushstring(ML, "module can not yield");
|
||||
}
|
||||
else if (!lua_isstring(ML, -1))
|
||||
{
|
||||
lua_pushstring(ML, "unknown error while running module");
|
||||
}
|
||||
}
|
||||
|
||||
// there's now a return value on top of ML; L stack: _MODULES ML
|
||||
lua_xmove(ML, L, 1);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, -4, resolvedRequire.absolutePath.c_str());
|
||||
|
||||
// L stack: _MODULES ML result
|
||||
return finishrequire(L);
|
||||
}
|
||||
|
||||
lua_pop(L, 1);
|
||||
|
||||
std::optional<std::string> source = readFile(name + ".luau");
|
||||
if (!source)
|
||||
else
|
||||
{
|
||||
source = readFile(name + ".lua"); // try .lua if .luau doesn't exist
|
||||
std::string name = luaL_checkstring(L, 1);
|
||||
std::string chunkname = "=" + name;
|
||||
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
|
||||
// return the module from the cache
|
||||
lua_getfield(L, -1, name.c_str());
|
||||
if (!lua_isnil(L, -1))
|
||||
{
|
||||
// L stack: _MODULES result
|
||||
return finishrequire(L);
|
||||
}
|
||||
|
||||
lua_pop(L, 1);
|
||||
|
||||
std::optional<std::string> source = readFile(name + ".luau");
|
||||
if (!source)
|
||||
luaL_argerrorL(L, 1, ("error loading " + name).c_str()); // if neither .luau nor .lua exist, we have an error
|
||||
{
|
||||
source = readFile(name + ".lua"); // try .lua if .luau doesn't exist
|
||||
if (!source)
|
||||
luaL_argerrorL(L, 1, ("error loading " + name).c_str()); // if neither .luau nor .lua exist, we have an error
|
||||
}
|
||||
|
||||
// module needs to run in a new thread, isolated from the rest
|
||||
// note: we create ML on main thread so that it doesn't inherit environment of L
|
||||
lua_State* GL = lua_mainthread(L);
|
||||
lua_State* ML = lua_newthread(GL);
|
||||
lua_xmove(GL, L, 1);
|
||||
// new thread needs to have the globals sandboxed
|
||||
luaL_sandboxthread(ML);
|
||||
|
||||
// now we can compile & run module on the new thread
|
||||
std::string bytecode = Luau::compile(*source, copts());
|
||||
if (luau_load(ML, chunkname.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
|
||||
{
|
||||
if (codegen)
|
||||
Luau::CodeGen::compile(ML, -1);
|
||||
if (coverageActive())
|
||||
coverageTrack(ML, -1);
|
||||
int status = lua_resume(ML, L, 0);
|
||||
if (status == 0)
|
||||
{
|
||||
if (lua_gettop(ML) == 0)
|
||||
lua_pushstring(ML, "module must return a value");
|
||||
else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1))
|
||||
lua_pushstring(ML, "module must return a table or function");
|
||||
}
|
||||
else if (status == LUA_YIELD)
|
||||
{
|
||||
lua_pushstring(ML, "module can not yield");
|
||||
}
|
||||
else if (!lua_isstring(ML, -1))
|
||||
{
|
||||
lua_pushstring(ML, "unknown error while running module");
|
||||
}
|
||||
}
|
||||
// there's now a return value on top of ML; L stack: _MODULES ML
|
||||
lua_xmove(ML, L, 1);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, -4, name.c_str());
|
||||
|
||||
// L stack: _MODULES ML result
|
||||
return finishrequire(L);
|
||||
}
|
||||
|
||||
// module needs to run in a new thread, isolated from the rest
|
||||
// note: we create ML on main thread so that it doesn't inherit environment of L
|
||||
lua_State* GL = lua_mainthread(L);
|
||||
lua_State* ML = lua_newthread(GL);
|
||||
lua_xmove(GL, L, 1);
|
||||
|
||||
// new thread needs to have the globals sandboxed
|
||||
luaL_sandboxthread(ML);
|
||||
|
||||
// now we can compile & run module on the new thread
|
||||
std::string bytecode = Luau::compile(*source, copts());
|
||||
if (luau_load(ML, chunkname.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
|
||||
{
|
||||
if (codegen)
|
||||
Luau::CodeGen::compile(ML, -1);
|
||||
|
||||
if (coverageActive())
|
||||
coverageTrack(ML, -1);
|
||||
|
||||
int status = lua_resume(ML, L, 0);
|
||||
|
||||
if (status == 0)
|
||||
{
|
||||
if (lua_gettop(ML) == 0)
|
||||
lua_pushstring(ML, "module must return a value");
|
||||
else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1))
|
||||
lua_pushstring(ML, "module must return a table or function");
|
||||
}
|
||||
else if (status == LUA_YIELD)
|
||||
{
|
||||
lua_pushstring(ML, "module can not yield");
|
||||
}
|
||||
else if (!lua_isstring(ML, -1))
|
||||
{
|
||||
lua_pushstring(ML, "unknown error while running module");
|
||||
}
|
||||
}
|
||||
|
||||
// there's now a return value on top of ML; L stack: _MODULES ML
|
||||
lua_xmove(ML, L, 1);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, -4, name.c_str());
|
||||
|
||||
// L stack: _MODULES ML result
|
||||
return finishrequire(L);
|
||||
}
|
||||
|
||||
static int lua_collectgarbage(lua_State* L)
|
||||
|
290
CLI/Require.cpp
Normal file
290
CLI/Require.cpp
Normal file
@ -0,0 +1,290 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Require.h"
|
||||
|
||||
#include "FileUtils.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
RequireResolver::RequireResolver(lua_State* L, std::string path)
|
||||
: pathToResolve(std::move(path))
|
||||
, L(L)
|
||||
{
|
||||
lua_Debug ar;
|
||||
lua_getinfo(L, 1, "s", &ar);
|
||||
sourceChunkname = ar.source;
|
||||
|
||||
if (!isRequireAllowed(sourceChunkname))
|
||||
luaL_errorL(L, "require is not supported in this context");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] RequireResolver::ResolvedRequire RequireResolver::resolveRequire(lua_State* L, std::string path)
|
||||
{
|
||||
RequireResolver resolver(L, std::move(path));
|
||||
ModuleStatus status = resolver.findModule();
|
||||
if (status != ModuleStatus::FileRead)
|
||||
return ResolvedRequire{status};
|
||||
else
|
||||
return ResolvedRequire{status, std::move(resolver.chunkname), std::move(resolver.absolutePath), std::move(resolver.sourceCode)};
|
||||
}
|
||||
|
||||
RequireResolver::ModuleStatus RequireResolver::findModule()
|
||||
{
|
||||
resolveAndStoreDefaultPaths();
|
||||
|
||||
// Put _MODULES table on stack for checking and saving to the cache
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
|
||||
RequireResolver::ModuleStatus moduleStatus = findModuleImpl();
|
||||
|
||||
if (moduleStatus != RequireResolver::ModuleStatus::NotFound)
|
||||
return moduleStatus;
|
||||
|
||||
if (!shouldSearchPathsArray())
|
||||
return moduleStatus;
|
||||
|
||||
if (!isConfigFullyResolved)
|
||||
parseNextConfig();
|
||||
|
||||
// Index-based iteration because std::iterator may be invalidated if config.paths is reallocated
|
||||
for (size_t i = 0; i < config.paths.size(); ++i)
|
||||
{
|
||||
// "placeholder" acts as a requiring file in the relevant directory
|
||||
std::optional<std::string> absolutePathOpt = resolvePath(pathToResolve, joinPaths(config.paths[i], "placeholder"));
|
||||
|
||||
if (!absolutePathOpt)
|
||||
luaL_errorL(L, "error requiring module");
|
||||
|
||||
chunkname = *absolutePathOpt;
|
||||
absolutePath = *absolutePathOpt;
|
||||
|
||||
moduleStatus = findModuleImpl();
|
||||
|
||||
if (moduleStatus != RequireResolver::ModuleStatus::NotFound)
|
||||
return moduleStatus;
|
||||
|
||||
// Before finishing the loop, parse more config files if there are any
|
||||
if (i == config.paths.size() - 1 && !isConfigFullyResolved)
|
||||
parseNextConfig(); // could reallocate config.paths when paths are parsed and added
|
||||
}
|
||||
|
||||
return RequireResolver::ModuleStatus::NotFound;
|
||||
}
|
||||
|
||||
RequireResolver::ModuleStatus RequireResolver::findModuleImpl()
|
||||
{
|
||||
static const std::array<const char*, 4> possibleSuffixes = {".luau", ".lua", "/init.luau", "/init.lua"};
|
||||
|
||||
size_t unsuffixedAbsolutePathSize = absolutePath.size();
|
||||
|
||||
for (const char* possibleSuffix : possibleSuffixes)
|
||||
{
|
||||
absolutePath += possibleSuffix;
|
||||
|
||||
// Check cache for module
|
||||
lua_getfield(L, -1, absolutePath.c_str());
|
||||
if (!lua_isnil(L, -1))
|
||||
{
|
||||
return ModuleStatus::Cached;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Try to read the matching file
|
||||
std::optional<std::string> source = readFile(absolutePath);
|
||||
if (source)
|
||||
{
|
||||
chunkname = "=" + chunkname + possibleSuffix;
|
||||
sourceCode = *source;
|
||||
return ModuleStatus::FileRead;
|
||||
}
|
||||
|
||||
absolutePath.resize(unsuffixedAbsolutePathSize); // truncate to remove suffix
|
||||
}
|
||||
|
||||
return ModuleStatus::NotFound;
|
||||
}
|
||||
|
||||
bool RequireResolver::isRequireAllowed(std::string_view sourceChunkname)
|
||||
{
|
||||
LUAU_ASSERT(!sourceChunkname.empty());
|
||||
return (sourceChunkname[0] == '=' || sourceChunkname[0] == '@');
|
||||
}
|
||||
|
||||
bool RequireResolver::shouldSearchPathsArray()
|
||||
{
|
||||
return !isAbsolutePath(pathToResolve) && !isExplicitlyRelative(pathToResolve);
|
||||
}
|
||||
|
||||
void RequireResolver::resolveAndStoreDefaultPaths()
|
||||
{
|
||||
if (!isAbsolutePath(pathToResolve))
|
||||
{
|
||||
std::string chunknameContext = getRequiringContextRelative();
|
||||
std::optional<std::string> absolutePathContext = getRequiringContextAbsolute();
|
||||
|
||||
if (!absolutePathContext)
|
||||
luaL_errorL(L, "error requiring module");
|
||||
|
||||
// resolvePath automatically sanitizes/normalizes the paths
|
||||
std::optional<std::string> chunknameOpt = resolvePath(pathToResolve, chunknameContext);
|
||||
std::optional<std::string> absolutePathOpt = resolvePath(pathToResolve, *absolutePathContext);
|
||||
|
||||
if (!chunknameOpt || !absolutePathOpt)
|
||||
luaL_errorL(L, "error requiring module");
|
||||
|
||||
chunkname = std::move(*chunknameOpt);
|
||||
absolutePath = std::move(*absolutePathOpt);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Here we must explicitly sanitize, as the path is taken as is
|
||||
std::optional<std::string> sanitizedPath = normalizePath(pathToResolve);
|
||||
if (!sanitizedPath)
|
||||
luaL_errorL(L, "error requiring module");
|
||||
|
||||
chunkname = *sanitizedPath;
|
||||
absolutePath = std::move(*sanitizedPath);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> RequireResolver::getRequiringContextAbsolute()
|
||||
{
|
||||
std::string requiringFile;
|
||||
if (isAbsolutePath(sourceChunkname.substr(1)))
|
||||
{
|
||||
// We already have an absolute path for the requiring file
|
||||
requiringFile = sourceChunkname.substr(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Requiring file's stored path is relative to the CWD, must make absolute
|
||||
std::optional<std::string> cwd = getCurrentWorkingDirectory();
|
||||
if (!cwd)
|
||||
return std::nullopt;
|
||||
|
||||
if (sourceChunkname.substr(1) == "stdin")
|
||||
{
|
||||
// Require statement is being executed from REPL input prompt
|
||||
// The requiring context is the pseudo-file "stdin" in the CWD
|
||||
requiringFile = joinPaths(*cwd, "stdin");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Require statement is being executed in a file, must resolve relative to CWD
|
||||
std::optional<std::string> requiringFileOpt = resolvePath(sourceChunkname.substr(1), joinPaths(*cwd, "stdin"));
|
||||
if (!requiringFileOpt)
|
||||
return std::nullopt;
|
||||
|
||||
requiringFile = *requiringFileOpt;
|
||||
}
|
||||
}
|
||||
std::replace(requiringFile.begin(), requiringFile.end(), '\\', '/');
|
||||
return requiringFile;
|
||||
}
|
||||
|
||||
std::string RequireResolver::getRequiringContextRelative()
|
||||
{
|
||||
std::string baseFilePath;
|
||||
if (sourceChunkname.substr(1) != "stdin")
|
||||
baseFilePath = sourceChunkname.substr(1);
|
||||
|
||||
return baseFilePath;
|
||||
}
|
||||
|
||||
void RequireResolver::substituteAliasIfPresent(std::string& path)
|
||||
{
|
||||
std::string potentialAlias = path.substr(0, path.find_first_of("\\/"));
|
||||
|
||||
// Not worth searching when potentialAlias cannot be an alias
|
||||
if (!Luau::isValidAlias(potentialAlias))
|
||||
return;
|
||||
|
||||
std::optional<std::string> alias = getAlias(potentialAlias);
|
||||
if (alias)
|
||||
{
|
||||
path = *alias + path.substr(potentialAlias.size());
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> RequireResolver::getAlias(std::string alias)
|
||||
{
|
||||
std::transform(alias.begin(), alias.end(), alias.begin(), [](unsigned char c) {
|
||||
return ('A' <= c && c <= 'Z') ? (c + ('a' - 'A')) : c;
|
||||
});
|
||||
while (!config.aliases.count(alias) && !isConfigFullyResolved)
|
||||
{
|
||||
parseNextConfig();
|
||||
}
|
||||
if (!config.aliases.count(alias) && isConfigFullyResolved)
|
||||
return std::nullopt; // could not find alias
|
||||
|
||||
return resolvePath(config.aliases[alias], joinPaths(lastSearchedDir, Luau::kConfigName));
|
||||
}
|
||||
|
||||
void RequireResolver::parseNextConfig()
|
||||
{
|
||||
if (isConfigFullyResolved)
|
||||
return; // no config files left to parse
|
||||
|
||||
std::optional<std::string> directory;
|
||||
if (lastSearchedDir.empty())
|
||||
{
|
||||
std::optional<std::string> requiringFile = getRequiringContextAbsolute();
|
||||
if (!requiringFile)
|
||||
luaL_errorL(L, "error requiring module");
|
||||
|
||||
directory = getParentPath(*requiringFile);
|
||||
}
|
||||
else
|
||||
directory = getParentPath(lastSearchedDir);
|
||||
|
||||
if (directory)
|
||||
{
|
||||
lastSearchedDir = *directory;
|
||||
parseConfigInDirectory(*directory);
|
||||
}
|
||||
else
|
||||
isConfigFullyResolved = true;
|
||||
}
|
||||
|
||||
void RequireResolver::parseConfigInDirectory(const std::string& directory)
|
||||
{
|
||||
std::string configPath = joinPaths(directory, Luau::kConfigName);
|
||||
|
||||
size_t numPaths = config.paths.size();
|
||||
|
||||
if (std::optional<std::string> contents = readFile(configPath))
|
||||
{
|
||||
std::optional<std::string> error = Luau::parseConfig(*contents, config);
|
||||
if (error)
|
||||
luaL_errorL(L, "error parsing %s (%s)", configPath.c_str(), (*error).c_str());
|
||||
}
|
||||
|
||||
// Resolve any newly obtained relative paths in "paths" in relation to configPath
|
||||
for (auto it = config.paths.begin() + numPaths; it != config.paths.end(); ++it)
|
||||
{
|
||||
if (!isAbsolutePath(*it))
|
||||
{
|
||||
if (std::optional<std::string> resolvedPath = resolvePath(*it, configPath))
|
||||
*it = std::move(*resolvedPath);
|
||||
else
|
||||
luaL_errorL(L, "error requiring module");
|
||||
}
|
||||
}
|
||||
}
|
62
CLI/Require.h
Normal file
62
CLI/Require.h
Normal file
@ -0,0 +1,62 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "lua.h"
|
||||
#include "lualib.h"
|
||||
|
||||
#include "Luau/Config.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
class RequireResolver
|
||||
{
|
||||
public:
|
||||
std::string chunkname;
|
||||
std::string absolutePath;
|
||||
std::string sourceCode;
|
||||
|
||||
enum class ModuleStatus
|
||||
{
|
||||
Cached,
|
||||
FileRead,
|
||||
NotFound
|
||||
};
|
||||
|
||||
struct ResolvedRequire
|
||||
{
|
||||
ModuleStatus status;
|
||||
std::string chunkName;
|
||||
std::string absolutePath;
|
||||
std::string sourceCode;
|
||||
};
|
||||
|
||||
[[nodiscard]] ResolvedRequire static resolveRequire(lua_State* L, std::string path);
|
||||
|
||||
private:
|
||||
std::string pathToResolve;
|
||||
std::string_view sourceChunkname;
|
||||
|
||||
RequireResolver(lua_State* L, std::string path);
|
||||
|
||||
ModuleStatus findModule();
|
||||
lua_State* L;
|
||||
Luau::Config config;
|
||||
std::string lastSearchedDir;
|
||||
bool isConfigFullyResolved = false;
|
||||
|
||||
bool isRequireAllowed(std::string_view sourceChunkname);
|
||||
bool shouldSearchPathsArray();
|
||||
|
||||
void resolveAndStoreDefaultPaths();
|
||||
ModuleStatus findModuleImpl();
|
||||
|
||||
std::optional<std::string> getRequiringContextAbsolute();
|
||||
std::string getRequiringContextRelative();
|
||||
|
||||
void substituteAliasIfPresent(std::string& path);
|
||||
std::optional<std::string> getAlias(std::string alias);
|
||||
|
||||
void parseNextConfig();
|
||||
void parseConfigInDirectory(const std::string& path);
|
||||
};
|
@ -193,7 +193,7 @@ if(LUAU_BUILD_CLI)
|
||||
|
||||
target_include_directories(Luau.Repl.CLI PRIVATE extern extern/isocline/include)
|
||||
|
||||
target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.CodeGen Luau.VM isocline)
|
||||
target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.VM isocline)
|
||||
|
||||
if(UNIX)
|
||||
find_library(LIBPTHREAD pthread)
|
||||
@ -230,7 +230,7 @@ if(LUAU_BUILD_TESTS)
|
||||
|
||||
target_compile_options(Luau.CLI.Test PRIVATE ${LUAU_OPTIONS})
|
||||
target_include_directories(Luau.CLI.Test PRIVATE extern CLI)
|
||||
target_link_libraries(Luau.CLI.Test PRIVATE Luau.Compiler Luau.CodeGen Luau.VM isocline)
|
||||
target_link_libraries(Luau.CLI.Test PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.VM isocline)
|
||||
if(UNIX)
|
||||
find_library(LIBPTHREAD pthread)
|
||||
if (LIBPTHREAD)
|
||||
@ -254,6 +254,8 @@ if(LUAU_BUILD_WEB)
|
||||
target_link_options(Luau.Web PRIVATE -sSINGLE_FILE=1)
|
||||
endif()
|
||||
|
||||
add_subdirectory(fuzz)
|
||||
|
||||
# validate dependencies for internal libraries
|
||||
foreach(LIB Luau.Ast Luau.Compiler Luau.Config Luau.Analysis Luau.CodeGen Luau.VM)
|
||||
if(TARGET ${LIB})
|
||||
|
47
CMakePresets.json
Normal file
47
CMakePresets.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"version": 6,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "fuzz",
|
||||
"displayName": "Fuzz",
|
||||
"description": "Configures required fuzzer settings.",
|
||||
"binaryDir": "build",
|
||||
"condition": {
|
||||
"type": "anyOf",
|
||||
"conditions": [
|
||||
{
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Darwin"
|
||||
},
|
||||
{
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Linux"
|
||||
}
|
||||
]
|
||||
},
|
||||
"cacheVariables": {
|
||||
"CMAKE_OSX_ARCHITECTURES": "x86_64",
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"CMAKE_CXX_STANDARD": "17",
|
||||
"CMAKE_CXX_EXTENSIONS": false
|
||||
},
|
||||
"warnings": {
|
||||
"dev": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "fuzz-proto",
|
||||
"displayName": "Protobuf Fuzzer",
|
||||
"description": "Builds the protobuf-based fuzzer and transpiler tools.",
|
||||
"configurePreset": "fuzz",
|
||||
"targets": [
|
||||
"Luau.Fuzz.Proto",
|
||||
"Luau.Fuzz.ProtoTest"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
21
CodeGen/include/Luau/BytecodeAnalysis.h
Normal file
21
CodeGen/include/Luau/BytecodeAnalysis.h
Normal file
@ -0,0 +1,21 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
struct IrFunction;
|
||||
|
||||
void buildBytecodeBlocks(IrFunction& function, const std::vector<uint8_t>& jumpTargets);
|
||||
void analyzeBytecodeTypes(IrFunction& function);
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
@ -7,6 +7,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct lua_State;
|
||||
struct Proto;
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@ -101,6 +102,15 @@ struct BlockLinearizationStats
|
||||
}
|
||||
};
|
||||
|
||||
struct FunctionStats
|
||||
{
|
||||
std::string name;
|
||||
int line = -1;
|
||||
unsigned bcodeCount = 0;
|
||||
unsigned irCount = 0;
|
||||
unsigned asmCount = 0;
|
||||
};
|
||||
|
||||
struct LoweringStats
|
||||
{
|
||||
unsigned totalFunctions = 0;
|
||||
@ -117,6 +127,9 @@ struct LoweringStats
|
||||
|
||||
BlockLinearizationStats blockLinearizationStats;
|
||||
|
||||
bool collectFunctionStats = false;
|
||||
std::vector<FunctionStats> functions;
|
||||
|
||||
LoweringStats operator+(const LoweringStats& other) const
|
||||
{
|
||||
LoweringStats result(*this);
|
||||
@ -137,6 +150,8 @@ struct LoweringStats
|
||||
this->regAllocErrors += that.regAllocErrors;
|
||||
this->loweringErrors += that.loweringErrors;
|
||||
this->blockLinearizationStats += that.blockLinearizationStats;
|
||||
if (this->collectFunctionStats)
|
||||
this->functions.insert(this->functions.end(), that.functions.begin(), that.functions.end());
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
@ -78,7 +78,13 @@ struct IrBuilder
|
||||
|
||||
std::vector<uint32_t> instIndexToBlock; // Block index at the bytecode instruction
|
||||
|
||||
std::vector<IrOp> loopStepStack;
|
||||
struct LoopInfo
|
||||
{
|
||||
IrOp step;
|
||||
int startpc = 0;
|
||||
};
|
||||
|
||||
std::vector<LoopInfo> numericLoopStack;
|
||||
|
||||
// Similar to BytecodeBuilder, duplicate constants are removed used the same method
|
||||
struct ConstantKey
|
||||
|
@ -1,6 +1,7 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Bytecode.h"
|
||||
#include "Luau/IrAnalysis.h"
|
||||
#include "Luau/Label.h"
|
||||
#include "Luau/RegisterX64.h"
|
||||
@ -12,6 +13,8 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauKeepVmapLinear2)
|
||||
|
||||
struct Proto;
|
||||
|
||||
namespace Luau
|
||||
@ -930,18 +933,37 @@ struct BytecodeMapping
|
||||
uint32_t asmLocation;
|
||||
};
|
||||
|
||||
struct BytecodeBlock
|
||||
{
|
||||
// 'start' and 'finish' define an inclusive range of instructions which belong to the block
|
||||
int startpc = -1;
|
||||
int finishpc = -1;
|
||||
};
|
||||
|
||||
struct BytecodeTypes
|
||||
{
|
||||
uint8_t result = LBC_TYPE_ANY;
|
||||
uint8_t a = LBC_TYPE_ANY;
|
||||
uint8_t b = LBC_TYPE_ANY;
|
||||
uint8_t c = LBC_TYPE_ANY;
|
||||
};
|
||||
|
||||
struct IrFunction
|
||||
{
|
||||
std::vector<IrBlock> blocks;
|
||||
std::vector<IrInst> instructions;
|
||||
std::vector<IrConst> constants;
|
||||
|
||||
std::vector<BytecodeBlock> bcBlocks;
|
||||
std::vector<BytecodeTypes> bcTypes;
|
||||
|
||||
std::vector<BytecodeMapping> bcMapping;
|
||||
uint32_t entryBlock = 0;
|
||||
uint32_t entryLocation = 0;
|
||||
|
||||
// For each instruction, an operand that can be used to recompute the value
|
||||
std::vector<IrOp> valueRestoreOps;
|
||||
std::vector<uint32_t> validRestoreOpBlocks;
|
||||
uint32_t validRestoreOpBlockIdx = 0;
|
||||
|
||||
Proto* proto = nullptr;
|
||||
@ -1086,22 +1108,53 @@ struct IrFunction
|
||||
if (instIdx >= valueRestoreOps.size())
|
||||
return {};
|
||||
|
||||
const IrBlock& block = blocks[validRestoreOpBlockIdx];
|
||||
|
||||
// When spilled, values can only reference restore operands in the current block
|
||||
if (limitToCurrentBlock)
|
||||
if (FFlag::LuauKeepVmapLinear2)
|
||||
{
|
||||
if (instIdx < block.start || instIdx > block.finish)
|
||||
return {};
|
||||
}
|
||||
// When spilled, values can only reference restore operands in the current block chain
|
||||
if (limitToCurrentBlock)
|
||||
{
|
||||
for (uint32_t blockIdx : validRestoreOpBlocks)
|
||||
{
|
||||
const IrBlock& block = blocks[blockIdx];
|
||||
|
||||
return valueRestoreOps[instIdx];
|
||||
if (instIdx >= block.start && instIdx <= block.finish)
|
||||
return valueRestoreOps[instIdx];
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
return valueRestoreOps[instIdx];
|
||||
}
|
||||
else
|
||||
{
|
||||
const IrBlock& block = blocks[validRestoreOpBlockIdx];
|
||||
|
||||
// When spilled, values can only reference restore operands in the current block
|
||||
if (limitToCurrentBlock)
|
||||
{
|
||||
if (instIdx < block.start || instIdx > block.finish)
|
||||
return {};
|
||||
}
|
||||
|
||||
return valueRestoreOps[instIdx];
|
||||
}
|
||||
}
|
||||
|
||||
IrOp findRestoreOp(const IrInst& inst, bool limitToCurrentBlock) const
|
||||
{
|
||||
return findRestoreOp(getInstIndex(inst), limitToCurrentBlock);
|
||||
}
|
||||
|
||||
BytecodeTypes getBytecodeTypesAt(int pcpos) const
|
||||
{
|
||||
LUAU_ASSERT(pcpos >= 0);
|
||||
|
||||
if (size_t(pcpos) < bcTypes.size())
|
||||
return bcTypes[pcpos];
|
||||
|
||||
return BytecodeTypes();
|
||||
}
|
||||
};
|
||||
|
||||
inline IrCondition conditionOp(IrOp op)
|
||||
|
@ -29,6 +29,7 @@ void toString(IrToStringContext& ctx, const IrBlock& block, uint32_t index); //
|
||||
void toString(IrToStringContext& ctx, IrOp op);
|
||||
|
||||
void toString(std::string& result, IrConst constant);
|
||||
void toString(std::string& result, const BytecodeTypes& bcTypes);
|
||||
|
||||
void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, const IrInst& inst, uint32_t instIdx, bool includeUseInfo);
|
||||
void toStringDetailed(IrToStringContext& ctx, const IrBlock& block, uint32_t index, bool includeUseInfo); // Block title
|
||||
|
@ -41,6 +41,11 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i
|
||||
break;
|
||||
// A <- B, C
|
||||
case IrCmd::DO_ARITH:
|
||||
visitor.maybeUse(inst.b); // Argument can also be a VmConst
|
||||
visitor.maybeUse(inst.c); // Argument can also be a VmConst
|
||||
|
||||
visitor.def(inst.a);
|
||||
break;
|
||||
case IrCmd::GET_TABLE:
|
||||
visitor.use(inst.b);
|
||||
visitor.maybeUse(inst.c); // Argument can also be a VmConst
|
||||
|
884
CodeGen/src/BytecodeAnalysis.cpp
Normal file
884
CodeGen/src/BytecodeAnalysis.cpp
Normal file
@ -0,0 +1,884 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/BytecodeAnalysis.h"
|
||||
|
||||
#include "Luau/BytecodeUtils.h"
|
||||
#include "Luau/IrData.h"
|
||||
#include "Luau/IrUtils.h"
|
||||
|
||||
#include "lobject.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
static bool hasTypedParameters(Proto* proto)
|
||||
{
|
||||
return proto->typeinfo && proto->numparams != 0;
|
||||
}
|
||||
|
||||
static uint8_t getBytecodeConstantTag(Proto* proto, unsigned ki)
|
||||
{
|
||||
TValue protok = proto->k[ki];
|
||||
|
||||
switch (protok.tt)
|
||||
{
|
||||
case LUA_TNIL:
|
||||
return LBC_TYPE_NIL;
|
||||
case LUA_TBOOLEAN:
|
||||
return LBC_TYPE_BOOLEAN;
|
||||
case LUA_TLIGHTUSERDATA:
|
||||
return LBC_TYPE_USERDATA;
|
||||
case LUA_TNUMBER:
|
||||
return LBC_TYPE_NUMBER;
|
||||
case LUA_TVECTOR:
|
||||
return LBC_TYPE_VECTOR;
|
||||
case LUA_TSTRING:
|
||||
return LBC_TYPE_STRING;
|
||||
case LUA_TTABLE:
|
||||
return LBC_TYPE_TABLE;
|
||||
case LUA_TFUNCTION:
|
||||
return LBC_TYPE_FUNCTION;
|
||||
case LUA_TUSERDATA:
|
||||
return LBC_TYPE_USERDATA;
|
||||
case LUA_TTHREAD:
|
||||
return LBC_TYPE_THREAD;
|
||||
case LUA_TBUFFER:
|
||||
return LBC_TYPE_BUFFER;
|
||||
}
|
||||
|
||||
return LBC_TYPE_ANY;
|
||||
}
|
||||
|
||||
static void applyBuiltinCall(int bfid, BytecodeTypes& types)
|
||||
{
|
||||
switch (bfid)
|
||||
{
|
||||
case LBF_NONE:
|
||||
case LBF_ASSERT:
|
||||
types.result = LBC_TYPE_ANY;
|
||||
break;
|
||||
case LBF_MATH_ABS:
|
||||
case LBF_MATH_ACOS:
|
||||
case LBF_MATH_ASIN:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_MATH_ATAN2:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_MATH_ATAN:
|
||||
case LBF_MATH_CEIL:
|
||||
case LBF_MATH_COSH:
|
||||
case LBF_MATH_COS:
|
||||
case LBF_MATH_DEG:
|
||||
case LBF_MATH_EXP:
|
||||
case LBF_MATH_FLOOR:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_MATH_FMOD:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_MATH_FREXP:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_MATH_LDEXP:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_MATH_LOG10:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_MATH_LOG:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
types.b = LBC_TYPE_NUMBER; // We can mark optional arguments
|
||||
break;
|
||||
case LBF_MATH_MAX:
|
||||
case LBF_MATH_MIN:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
types.c = LBC_TYPE_NUMBER; // We can mark optional arguments
|
||||
break;
|
||||
case LBF_MATH_MODF:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_MATH_POW:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_MATH_RAD:
|
||||
case LBF_MATH_SINH:
|
||||
case LBF_MATH_SIN:
|
||||
case LBF_MATH_SQRT:
|
||||
case LBF_MATH_TANH:
|
||||
case LBF_MATH_TAN:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_BIT32_ARSHIFT:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_BIT32_BAND:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
types.c = LBC_TYPE_NUMBER; // We can mark optional arguments
|
||||
break;
|
||||
case LBF_BIT32_BNOT:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_BIT32_BOR:
|
||||
case LBF_BIT32_BXOR:
|
||||
case LBF_BIT32_BTEST:
|
||||
case LBF_BIT32_EXTRACT:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
types.c = LBC_TYPE_NUMBER; // We can mark optional arguments
|
||||
break;
|
||||
case LBF_BIT32_LROTATE:
|
||||
case LBF_BIT32_LSHIFT:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_BIT32_REPLACE:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
types.c = LBC_TYPE_NUMBER; // We can mark optional arguments
|
||||
break;
|
||||
case LBF_BIT32_RROTATE:
|
||||
case LBF_BIT32_RSHIFT:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_TYPE:
|
||||
types.result = LBC_TYPE_STRING;
|
||||
break;
|
||||
case LBF_STRING_BYTE:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_STRING;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_STRING_CHAR:
|
||||
types.result = LBC_TYPE_STRING;
|
||||
|
||||
// We can mark optional arguments
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
types.c = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_STRING_LEN:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_STRING;
|
||||
break;
|
||||
case LBF_TYPEOF:
|
||||
types.result = LBC_TYPE_STRING;
|
||||
break;
|
||||
case LBF_STRING_SUB:
|
||||
types.result = LBC_TYPE_STRING;
|
||||
types.a = LBC_TYPE_STRING;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
types.c = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_MATH_CLAMP:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
types.c = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_MATH_SIGN:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_MATH_ROUND:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_RAWGET:
|
||||
types.result = LBC_TYPE_ANY;
|
||||
types.a = LBC_TYPE_TABLE;
|
||||
break;
|
||||
case LBF_RAWEQUAL:
|
||||
types.result = LBC_TYPE_BOOLEAN;
|
||||
break;
|
||||
case LBF_TABLE_UNPACK:
|
||||
types.result = LBC_TYPE_ANY;
|
||||
types.a = LBC_TYPE_TABLE;
|
||||
types.b = LBC_TYPE_NUMBER; // We can mark optional arguments
|
||||
break;
|
||||
case LBF_VECTOR:
|
||||
types.result = LBC_TYPE_VECTOR;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
types.c = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_BIT32_COUNTLZ:
|
||||
case LBF_BIT32_COUNTRZ:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_SELECT_VARARG:
|
||||
types.result = LBC_TYPE_ANY;
|
||||
break;
|
||||
case LBF_RAWLEN:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_BIT32_EXTRACTK:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_GETMETATABLE:
|
||||
types.result = LBC_TYPE_TABLE;
|
||||
break;
|
||||
case LBF_TONUMBER:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_TOSTRING:
|
||||
types.result = LBC_TYPE_STRING;
|
||||
break;
|
||||
case LBF_BIT32_BYTESWAP:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_BUFFER_READI8:
|
||||
case LBF_BUFFER_READU8:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_BUFFER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_BUFFER_WRITEU8:
|
||||
types.result = LBC_TYPE_NIL;
|
||||
types.a = LBC_TYPE_BUFFER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
types.c = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_BUFFER_READI16:
|
||||
case LBF_BUFFER_READU16:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_BUFFER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_BUFFER_WRITEU16:
|
||||
types.result = LBC_TYPE_NIL;
|
||||
types.a = LBC_TYPE_BUFFER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
types.c = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_BUFFER_READI32:
|
||||
case LBF_BUFFER_READU32:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_BUFFER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_BUFFER_WRITEU32:
|
||||
types.result = LBC_TYPE_NIL;
|
||||
types.a = LBC_TYPE_BUFFER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
types.c = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_BUFFER_READF32:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_BUFFER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_BUFFER_WRITEF32:
|
||||
types.result = LBC_TYPE_NIL;
|
||||
types.a = LBC_TYPE_BUFFER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
types.c = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_BUFFER_READF64:
|
||||
types.result = LBC_TYPE_NUMBER;
|
||||
types.a = LBC_TYPE_BUFFER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_BUFFER_WRITEF64:
|
||||
types.result = LBC_TYPE_NIL;
|
||||
types.a = LBC_TYPE_BUFFER;
|
||||
types.b = LBC_TYPE_NUMBER;
|
||||
types.c = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
case LBF_TABLE_INSERT:
|
||||
types.result = LBC_TYPE_NIL;
|
||||
types.a = LBC_TYPE_TABLE;
|
||||
break;
|
||||
case LBF_RAWSET:
|
||||
types.result = LBC_TYPE_ANY;
|
||||
types.a = LBC_TYPE_TABLE;
|
||||
break;
|
||||
case LBF_SETMETATABLE:
|
||||
types.result = LBC_TYPE_TABLE;
|
||||
types.a = LBC_TYPE_TABLE;
|
||||
types.b = LBC_TYPE_TABLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void buildBytecodeBlocks(IrFunction& function, const std::vector<uint8_t>& jumpTargets)
|
||||
{
|
||||
Proto* proto = function.proto;
|
||||
LUAU_ASSERT(proto);
|
||||
|
||||
std::vector<BytecodeBlock>& bcBlocks = function.bcBlocks;
|
||||
|
||||
// Using the same jump targets, create VM bytecode basic blocks
|
||||
bcBlocks.push_back(BytecodeBlock{0, -1});
|
||||
|
||||
int previ = 0;
|
||||
|
||||
for (int i = 0; i < proto->sizecode;)
|
||||
{
|
||||
const Instruction* pc = &proto->code[i];
|
||||
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(*pc));
|
||||
|
||||
int nexti = i + getOpLength(op);
|
||||
|
||||
// If instruction is a jump target, begin new block starting from it
|
||||
if (i != 0 && jumpTargets[i])
|
||||
{
|
||||
bcBlocks.back().finishpc = previ;
|
||||
bcBlocks.push_back(BytecodeBlock{i, -1});
|
||||
}
|
||||
|
||||
int target = getJumpTarget(*pc, uint32_t(i));
|
||||
|
||||
// Implicit fallthroughs terminate the block and might start a new one
|
||||
if (target >= 0 && !isFastCall(op))
|
||||
{
|
||||
bcBlocks.back().finishpc = i;
|
||||
|
||||
// Start a new block if there was no explicit jump for the fallthrough
|
||||
if (!jumpTargets[nexti])
|
||||
bcBlocks.push_back(BytecodeBlock{nexti, -1});
|
||||
}
|
||||
// Returns just terminate the block
|
||||
else if (op == LOP_RETURN)
|
||||
{
|
||||
bcBlocks.back().finishpc = i;
|
||||
}
|
||||
|
||||
previ = i;
|
||||
i = nexti;
|
||||
LUAU_ASSERT(i <= proto->sizecode);
|
||||
}
|
||||
}
|
||||
|
||||
void analyzeBytecodeTypes(IrFunction& function)
|
||||
{
|
||||
Proto* proto = function.proto;
|
||||
LUAU_ASSERT(proto);
|
||||
|
||||
// Setup our current knowledge of type tags based on arguments
|
||||
uint8_t regTags[256];
|
||||
memset(regTags, LBC_TYPE_ANY, 256);
|
||||
|
||||
function.bcTypes.resize(proto->sizecode);
|
||||
|
||||
// Now that we have VM basic blocks, we can attempt to track register type tags locally
|
||||
for (const BytecodeBlock& block : function.bcBlocks)
|
||||
{
|
||||
LUAU_ASSERT(block.startpc != -1);
|
||||
LUAU_ASSERT(block.finishpc != -1);
|
||||
|
||||
// At the block start, reset or knowledge to the starting state
|
||||
// In the future we might be able to propagate some info between the blocks as well
|
||||
if (hasTypedParameters(proto))
|
||||
{
|
||||
for (int i = 0; i < proto->numparams; ++i)
|
||||
{
|
||||
uint8_t et = proto->typeinfo[2 + i];
|
||||
|
||||
// TODO: if argument is optional, this might force a VM exit unnecessarily
|
||||
regTags[i] = et & ~LBC_TYPE_OPTIONAL_BIT;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = proto->numparams; i < proto->maxstacksize; ++i)
|
||||
regTags[i] = LBC_TYPE_ANY;
|
||||
|
||||
for (int i = block.startpc; i <= block.finishpc;)
|
||||
{
|
||||
const Instruction* pc = &proto->code[i];
|
||||
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(*pc));
|
||||
|
||||
BytecodeTypes& bcType = function.bcTypes[i];
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case LOP_NOP:
|
||||
break;
|
||||
case LOP_LOADNIL:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
regTags[ra] = LBC_TYPE_NIL;
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_LOADB:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
regTags[ra] = LBC_TYPE_BOOLEAN;
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_LOADN:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_LOADK:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int kb = LUAU_INSN_D(*pc);
|
||||
bcType.a = getBytecodeConstantTag(proto, kb);
|
||||
regTags[ra] = bcType.a;
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_LOADKX:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int kb = int(pc[1]);
|
||||
bcType.a = getBytecodeConstantTag(proto, kb);
|
||||
regTags[ra] = bcType.a;
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_MOVE:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
bcType.a = regTags[rb];
|
||||
regTags[ra] = regTags[rb];
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_GETTABLE:
|
||||
{
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
int rc = LUAU_INSN_C(*pc);
|
||||
bcType.a = regTags[rb];
|
||||
bcType.b = regTags[rc];
|
||||
break;
|
||||
}
|
||||
case LOP_SETTABLE:
|
||||
{
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
int rc = LUAU_INSN_C(*pc);
|
||||
bcType.a = regTags[rb];
|
||||
bcType.b = regTags[rc];
|
||||
break;
|
||||
}
|
||||
case LOP_GETTABLEKS:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
uint32_t kc = pc[1];
|
||||
|
||||
bcType.a = regTags[rb];
|
||||
bcType.b = getBytecodeConstantTag(proto, kc);
|
||||
|
||||
regTags[ra] = LBC_TYPE_ANY;
|
||||
|
||||
// Assuming that vector component is being indexed
|
||||
// TODO: check what key is used
|
||||
if (bcType.a == LBC_TYPE_VECTOR)
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_SETTABLEKS:
|
||||
{
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
bcType.a = regTags[rb];
|
||||
bcType.b = LBC_TYPE_STRING;
|
||||
break;
|
||||
}
|
||||
case LOP_GETTABLEN:
|
||||
case LOP_SETTABLEN:
|
||||
{
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
bcType.a = regTags[rb];
|
||||
bcType.b = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
}
|
||||
case LOP_ADD:
|
||||
case LOP_SUB:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
int rc = LUAU_INSN_C(*pc);
|
||||
|
||||
bcType.a = regTags[rb];
|
||||
bcType.b = regTags[rc];
|
||||
|
||||
regTags[ra] = LBC_TYPE_ANY;
|
||||
|
||||
if (bcType.a == LBC_TYPE_NUMBER && bcType.b == LBC_TYPE_NUMBER)
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == LBC_TYPE_VECTOR)
|
||||
regTags[ra] = LBC_TYPE_VECTOR;
|
||||
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_MUL:
|
||||
case LOP_DIV:
|
||||
case LOP_IDIV:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
int rc = LUAU_INSN_C(*pc);
|
||||
|
||||
bcType.a = regTags[rb];
|
||||
bcType.b = regTags[rc];
|
||||
|
||||
regTags[ra] = LBC_TYPE_ANY;
|
||||
|
||||
if (bcType.a == LBC_TYPE_NUMBER)
|
||||
{
|
||||
if (bcType.b == LBC_TYPE_NUMBER)
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
else if (bcType.b == LBC_TYPE_VECTOR)
|
||||
regTags[ra] = LBC_TYPE_VECTOR;
|
||||
}
|
||||
else if (bcType.a == LBC_TYPE_VECTOR)
|
||||
{
|
||||
if (bcType.b == LBC_TYPE_NUMBER || bcType.b == LBC_TYPE_VECTOR)
|
||||
regTags[ra] = LBC_TYPE_VECTOR;
|
||||
}
|
||||
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_MOD:
|
||||
case LOP_POW:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
int rc = LUAU_INSN_C(*pc);
|
||||
|
||||
bcType.a = regTags[rb];
|
||||
bcType.b = regTags[rc];
|
||||
|
||||
regTags[ra] = LBC_TYPE_ANY;
|
||||
|
||||
if (bcType.a == LBC_TYPE_NUMBER && bcType.b == LBC_TYPE_NUMBER)
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_ADDK:
|
||||
case LOP_SUBK:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
int kc = LUAU_INSN_C(*pc);
|
||||
|
||||
bcType.a = regTags[rb];
|
||||
bcType.b = getBytecodeConstantTag(proto, kc);
|
||||
|
||||
regTags[ra] = LBC_TYPE_ANY;
|
||||
|
||||
if (bcType.a == LBC_TYPE_NUMBER && bcType.b == LBC_TYPE_NUMBER)
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == LBC_TYPE_VECTOR)
|
||||
regTags[ra] = LBC_TYPE_VECTOR;
|
||||
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_MULK:
|
||||
case LOP_DIVK:
|
||||
case LOP_IDIVK:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
int kc = LUAU_INSN_C(*pc);
|
||||
|
||||
bcType.a = regTags[rb];
|
||||
bcType.b = getBytecodeConstantTag(proto, kc);
|
||||
|
||||
regTags[ra] = LBC_TYPE_ANY;
|
||||
|
||||
if (bcType.a == LBC_TYPE_NUMBER)
|
||||
{
|
||||
if (bcType.b == LBC_TYPE_NUMBER)
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
else if (bcType.b == LBC_TYPE_VECTOR)
|
||||
regTags[ra] = LBC_TYPE_VECTOR;
|
||||
}
|
||||
else if (bcType.a == LBC_TYPE_VECTOR)
|
||||
{
|
||||
if (bcType.b == LBC_TYPE_NUMBER || bcType.b == LBC_TYPE_VECTOR)
|
||||
regTags[ra] = LBC_TYPE_VECTOR;
|
||||
}
|
||||
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_MODK:
|
||||
case LOP_POWK:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
int kc = LUAU_INSN_C(*pc);
|
||||
|
||||
bcType.a = regTags[rb];
|
||||
bcType.b = getBytecodeConstantTag(proto, kc);
|
||||
|
||||
regTags[ra] = LBC_TYPE_ANY;
|
||||
|
||||
if (bcType.a == LBC_TYPE_NUMBER && bcType.b == LBC_TYPE_NUMBER)
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_SUBRK:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int kb = LUAU_INSN_B(*pc);
|
||||
int rc = LUAU_INSN_C(*pc);
|
||||
|
||||
bcType.a = getBytecodeConstantTag(proto, kb);
|
||||
bcType.b = regTags[rc];
|
||||
|
||||
regTags[ra] = LBC_TYPE_ANY;
|
||||
|
||||
if (bcType.a == LBC_TYPE_NUMBER && bcType.b == LBC_TYPE_NUMBER)
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == LBC_TYPE_VECTOR)
|
||||
regTags[ra] = LBC_TYPE_VECTOR;
|
||||
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_DIVRK:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
int kc = LUAU_INSN_C(*pc);
|
||||
|
||||
bcType.a = regTags[rb];
|
||||
bcType.b = getBytecodeConstantTag(proto, kc);
|
||||
|
||||
regTags[ra] = LBC_TYPE_ANY;
|
||||
|
||||
if (bcType.a == LBC_TYPE_NUMBER)
|
||||
{
|
||||
if (bcType.b == LBC_TYPE_NUMBER)
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
else if (bcType.b == LBC_TYPE_VECTOR)
|
||||
regTags[ra] = LBC_TYPE_VECTOR;
|
||||
}
|
||||
else if (bcType.a == LBC_TYPE_VECTOR)
|
||||
{
|
||||
if (bcType.b == LBC_TYPE_NUMBER || bcType.b == LBC_TYPE_VECTOR)
|
||||
regTags[ra] = LBC_TYPE_VECTOR;
|
||||
}
|
||||
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_NOT:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
|
||||
bcType.a = regTags[rb];
|
||||
|
||||
regTags[ra] = LBC_TYPE_BOOLEAN;
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_MINUS:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
|
||||
bcType.a = regTags[rb];
|
||||
|
||||
regTags[ra] = LBC_TYPE_ANY;
|
||||
|
||||
if (bcType.a == LBC_TYPE_NUMBER)
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
else if (bcType.a == LBC_TYPE_VECTOR)
|
||||
regTags[ra] = LBC_TYPE_VECTOR;
|
||||
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_LENGTH:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
|
||||
bcType.a = regTags[rb];
|
||||
|
||||
regTags[ra] = LBC_TYPE_NUMBER; // Even if it's a custom __len, it's ok to assume a sane result
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_NEWTABLE:
|
||||
case LOP_DUPTABLE:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
regTags[ra] = LBC_TYPE_TABLE;
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_FASTCALL:
|
||||
{
|
||||
int bfid = LUAU_INSN_A(*pc);
|
||||
int skip = LUAU_INSN_C(*pc);
|
||||
|
||||
Instruction call = pc[skip + 1];
|
||||
LUAU_ASSERT(LUAU_INSN_OP(call) == LOP_CALL);
|
||||
int ra = LUAU_INSN_A(call);
|
||||
|
||||
applyBuiltinCall(bfid, bcType);
|
||||
regTags[ra + 1] = bcType.a;
|
||||
regTags[ra + 2] = bcType.b;
|
||||
regTags[ra + 3] = bcType.c;
|
||||
regTags[ra] = bcType.result;
|
||||
break;
|
||||
}
|
||||
case LOP_FASTCALL1:
|
||||
case LOP_FASTCALL2K:
|
||||
{
|
||||
int bfid = LUAU_INSN_A(*pc);
|
||||
int skip = LUAU_INSN_C(*pc);
|
||||
|
||||
Instruction call = pc[skip + 1];
|
||||
LUAU_ASSERT(LUAU_INSN_OP(call) == LOP_CALL);
|
||||
int ra = LUAU_INSN_A(call);
|
||||
|
||||
applyBuiltinCall(bfid, bcType);
|
||||
|
||||
regTags[LUAU_INSN_B(*pc)] = bcType.a;
|
||||
regTags[ra] = bcType.result;
|
||||
break;
|
||||
}
|
||||
case LOP_FASTCALL2:
|
||||
{
|
||||
int bfid = LUAU_INSN_A(*pc);
|
||||
int skip = LUAU_INSN_C(*pc);
|
||||
|
||||
Instruction call = pc[skip + 1];
|
||||
LUAU_ASSERT(LUAU_INSN_OP(call) == LOP_CALL);
|
||||
int ra = LUAU_INSN_A(call);
|
||||
|
||||
applyBuiltinCall(bfid, bcType);
|
||||
|
||||
regTags[LUAU_INSN_B(*pc)] = bcType.a;
|
||||
regTags[int(pc[1])] = bcType.b;
|
||||
regTags[ra] = bcType.result;
|
||||
break;
|
||||
}
|
||||
case LOP_FORNPREP:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
regTags[ra + 1] = LBC_TYPE_NUMBER;
|
||||
regTags[ra + 2] = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
}
|
||||
case LOP_FORNLOOP:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
|
||||
// These types are established by LOP_FORNPREP and we reinforce that here
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
regTags[ra + 1] = LBC_TYPE_NUMBER;
|
||||
regTags[ra + 2] = LBC_TYPE_NUMBER;
|
||||
break;
|
||||
}
|
||||
case LOP_CONCAT:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
regTags[ra] = LBC_TYPE_STRING;
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_NEWCLOSURE:
|
||||
case LOP_DUPCLOSURE:
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
regTags[ra] = LBC_TYPE_FUNCTION;
|
||||
bcType.result = regTags[ra];
|
||||
break;
|
||||
}
|
||||
case LOP_GETGLOBAL:
|
||||
case LOP_SETGLOBAL:
|
||||
case LOP_CALL:
|
||||
case LOP_RETURN:
|
||||
case LOP_JUMP:
|
||||
case LOP_JUMPBACK:
|
||||
case LOP_JUMPIF:
|
||||
case LOP_JUMPIFNOT:
|
||||
case LOP_JUMPIFEQ:
|
||||
case LOP_JUMPIFLE:
|
||||
case LOP_JUMPIFLT:
|
||||
case LOP_JUMPIFNOTEQ:
|
||||
case LOP_JUMPIFNOTLE:
|
||||
case LOP_JUMPIFNOTLT:
|
||||
case LOP_JUMPX:
|
||||
case LOP_JUMPXEQKNIL:
|
||||
case LOP_JUMPXEQKB:
|
||||
case LOP_JUMPXEQKN:
|
||||
case LOP_JUMPXEQKS:
|
||||
case LOP_SETLIST:
|
||||
case LOP_GETUPVAL:
|
||||
case LOP_SETUPVAL:
|
||||
case LOP_CLOSEUPVALS:
|
||||
case LOP_FORGLOOP:
|
||||
case LOP_FORGPREP_NEXT:
|
||||
case LOP_FORGPREP_INEXT:
|
||||
case LOP_AND:
|
||||
case LOP_ANDK:
|
||||
case LOP_OR:
|
||||
case LOP_ORK:
|
||||
case LOP_COVERAGE:
|
||||
case LOP_GETIMPORT:
|
||||
case LOP_CAPTURE:
|
||||
case LOP_NAMECALL:
|
||||
case LOP_PREPVARARGS:
|
||||
case LOP_GETVARARGS:
|
||||
case LOP_FORGPREP:
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unknown instruction");
|
||||
}
|
||||
|
||||
i += getOpLength(op);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
@ -1,5 +1,6 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/CodeGen.h"
|
||||
#include "Luau/BytecodeUtils.h"
|
||||
|
||||
#include "CodeGenLower.h"
|
||||
|
||||
@ -42,6 +43,17 @@ static void logFunctionHeader(AssemblyBuilder& build, Proto* proto)
|
||||
build.logAppend("\n");
|
||||
}
|
||||
|
||||
unsigned getInstructionCount(const Instruction* insns, const unsigned size)
|
||||
{
|
||||
unsigned count = 0;
|
||||
for (unsigned i = 0; i < size;)
|
||||
{
|
||||
++count;
|
||||
i += Luau::getOpLength(LuauOpcode(LUAU_INSN_OP(insns[i])));
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
template<typename AssemblyBuilder>
|
||||
static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, AssemblyOptions options, LoweringStats* stats)
|
||||
{
|
||||
@ -53,7 +65,11 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
|
||||
std::vector<Proto*> protos;
|
||||
gatherFunctions(protos, root, options.flags);
|
||||
|
||||
protos.erase(std::remove_if(protos.begin(), protos.end(), [](Proto* p) { return p == nullptr; }), protos.end());
|
||||
protos.erase(std::remove_if(protos.begin(), protos.end(),
|
||||
[](Proto* p) {
|
||||
return p == nullptr;
|
||||
}),
|
||||
protos.end());
|
||||
|
||||
if (stats)
|
||||
stats->totalFunctions += unsigned(protos.size());
|
||||
@ -77,6 +93,7 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
|
||||
{
|
||||
IrBuilder ir;
|
||||
ir.buildFunctionIr(p);
|
||||
unsigned asmCount = build.getCodeSize();
|
||||
|
||||
if (options.includeAssembly || options.includeIr)
|
||||
logFunctionHeader(build, p);
|
||||
@ -86,9 +103,24 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
|
||||
if (build.logText)
|
||||
build.logAppend("; skipping (can't lower)\n");
|
||||
|
||||
asmCount = 0;
|
||||
|
||||
if (stats)
|
||||
stats->skippedFunctions += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
asmCount = build.getCodeSize() - asmCount;
|
||||
}
|
||||
|
||||
if (stats && stats->collectFunctionStats)
|
||||
{
|
||||
const char* name = p->debugname ? getstr(p->debugname) : "";
|
||||
int line = p->linedefined;
|
||||
unsigned bcodeCount = getInstructionCount(p->code, p->sizecode);
|
||||
unsigned irCount = unsigned(ir.function.instructions.size());
|
||||
stats->functions.push_back({name, line, bcodeCount, irCount, asmCount});
|
||||
}
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("\n");
|
||||
|
@ -26,6 +26,7 @@ LUAU_FASTFLAG(DebugCodegenSkipNumbering)
|
||||
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
|
||||
LUAU_FASTINT(CodegenHeuristicsBlockLimit)
|
||||
LUAU_FASTINT(CodegenHeuristicsBlockInstructionLimit)
|
||||
LUAU_FASTFLAG(LuauKeepVmapLinear2)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -112,8 +113,16 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
|
||||
toStringDetailed(ctx, block, blockIndex, /* includeUseInfo */ true);
|
||||
}
|
||||
|
||||
// Values can only reference restore operands in the current block
|
||||
function.validRestoreOpBlockIdx = blockIndex;
|
||||
if (FFlag::LuauKeepVmapLinear2)
|
||||
{
|
||||
// Values can only reference restore operands in the current block chain
|
||||
function.validRestoreOpBlocks.push_back(blockIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Values can only reference restore operands in the current block
|
||||
function.validRestoreOpBlockIdx = blockIndex;
|
||||
}
|
||||
|
||||
build.setLabel(block.label);
|
||||
|
||||
@ -139,6 +148,15 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
|
||||
if (outputEnabled && options.annotator && bcLocation != ~0u)
|
||||
{
|
||||
options.annotator(options.annotatorContext, build.text, bytecodeid, bcLocation);
|
||||
|
||||
// If available, report inferred register tags
|
||||
BytecodeTypes bcTypes = function.getBytecodeTypesAt(bcLocation);
|
||||
|
||||
if (bcTypes.result != LBC_TYPE_ANY || bcTypes.a != LBC_TYPE_ANY || bcTypes.b != LBC_TYPE_ANY || bcTypes.c != LBC_TYPE_ANY)
|
||||
{
|
||||
toString(ctx.result, bcTypes);
|
||||
build.logAppend("\n");
|
||||
}
|
||||
}
|
||||
|
||||
// If bytecode needs the location of this instruction for jumps, record it
|
||||
@ -190,6 +208,9 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
|
||||
|
||||
if (options.includeIr)
|
||||
build.logAppend("#\n");
|
||||
|
||||
if (FFlag::LuauKeepVmapLinear2 && block.expectedNextBlock == ~0u)
|
||||
function.validRestoreOpBlocks.clear();
|
||||
}
|
||||
|
||||
if (!seenFallback)
|
||||
|
@ -426,7 +426,7 @@ const Instruction* executeGETTABLEKS(lua_State* L, const Instruction* pc, StkId
|
||||
|
||||
if (unsigned(ic) < LUA_VECTOR_SIZE && name[1] == '\0')
|
||||
{
|
||||
const float* v = rb->value.v; // silences ubsan when indexing v[]
|
||||
const float* v = vvalue(rb); // silences ubsan when indexing v[]
|
||||
setnvalue(ra, v[ic]);
|
||||
return pc;
|
||||
}
|
||||
|
@ -148,12 +148,12 @@ void convertNumberToIndexOrJump(AssemblyBuilderX64& build, RegisterX64 tmp, Regi
|
||||
build.jcc(ConditionX64::NotZero, label);
|
||||
}
|
||||
|
||||
void callArithHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, OperandX64 c, TMS tm)
|
||||
void callArithHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, OperandX64 b, OperandX64 c, TMS tm)
|
||||
{
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(ra));
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(rb));
|
||||
callWrap.addArgument(SizeX64::qword, b);
|
||||
callWrap.addArgument(SizeX64::qword, c);
|
||||
callWrap.addArgument(SizeX64::dword, tm);
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_doarith)]);
|
||||
|
@ -200,7 +200,7 @@ ConditionX64 getConditionInt(IrCondition cond);
|
||||
void getTableNodeAtCachedSlot(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 node, RegisterX64 table, int pcpos);
|
||||
void convertNumberToIndexOrJump(AssemblyBuilderX64& build, RegisterX64 tmp, RegisterX64 numd, RegisterX64 numi, Label& label);
|
||||
|
||||
void callArithHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, OperandX64 c, TMS tm);
|
||||
void callArithHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, OperandX64 b, OperandX64 c, TMS tm);
|
||||
void callLengthHelper(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb);
|
||||
void callGetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra);
|
||||
void callSetTable(IrRegAllocX64& regs, AssemblyBuilderX64& build, int rb, OperandX64 c, int ra);
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "lobject.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
|
||||
#include <stddef.h>
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "Luau/IrBuilder.h"
|
||||
|
||||
#include "Luau/Bytecode.h"
|
||||
#include "Luau/BytecodeAnalysis.h"
|
||||
#include "Luau/BytecodeUtils.h"
|
||||
#include "Luau/IrData.h"
|
||||
#include "Luau/IrUtils.h"
|
||||
@ -12,6 +13,8 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodegenBytecodeInfer, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
@ -119,6 +122,10 @@ void IrBuilder::buildFunctionIr(Proto* proto)
|
||||
// Rebuild original control flow blocks
|
||||
rebuildBytecodeBasicBlocks(proto);
|
||||
|
||||
// Infer register tags in bytecode
|
||||
if (FFlag::LuauCodegenBytecodeInfer)
|
||||
analyzeBytecodeTypes(function);
|
||||
|
||||
function.bcMapping.resize(proto->sizecode, {~0u, ~0u});
|
||||
|
||||
if (generateTypeChecks)
|
||||
@ -152,7 +159,7 @@ void IrBuilder::buildFunctionIr(Proto* proto)
|
||||
// Numeric for loops require additional processing to maintain loop stack
|
||||
// Notably, this must be performed even when the block is dead so that we maintain the pairing FORNPREP-FORNLOOP
|
||||
if (op == LOP_FORNPREP)
|
||||
beforeInstForNPrep(*this, pc);
|
||||
beforeInstForNPrep(*this, pc, i);
|
||||
|
||||
// We skip dead bytecode instructions when they appear after block was already terminated
|
||||
if (!inTerminatedBlock)
|
||||
@ -212,7 +219,6 @@ void IrBuilder::rebuildBytecodeBasicBlocks(Proto* proto)
|
||||
LUAU_ASSERT(i <= proto->sizecode);
|
||||
}
|
||||
|
||||
|
||||
// Bytecode blocks are created at bytecode jump targets and the start of a function
|
||||
jumpTargets[0] = true;
|
||||
|
||||
@ -224,6 +230,9 @@ void IrBuilder::rebuildBytecodeBasicBlocks(Proto* proto)
|
||||
instIndexToBlock[i] = b.index;
|
||||
}
|
||||
}
|
||||
|
||||
if (FFlag::LuauCodegenBytecodeInfer)
|
||||
buildBytecodeBlocks(function, jumpTargets);
|
||||
}
|
||||
|
||||
void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
|
||||
@ -381,6 +390,12 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
|
||||
case LOP_POWK:
|
||||
translateInstBinaryK(*this, pc, i, TM_POW);
|
||||
break;
|
||||
case LOP_SUBRK:
|
||||
translateInstBinaryRK(*this, pc, i, TM_SUB);
|
||||
break;
|
||||
case LOP_DIVRK:
|
||||
translateInstBinaryRK(*this, pc, i, TM_DIV);
|
||||
break;
|
||||
case LOP_NOT:
|
||||
translateInstNot(*this, pc);
|
||||
break;
|
||||
|
@ -432,7 +432,10 @@ void toString(IrToStringContext& ctx, IrOp op)
|
||||
append(ctx.result, "U%d", vmUpvalueOp(op));
|
||||
break;
|
||||
case IrOpKind::VmExit:
|
||||
append(ctx.result, "exit(%d)", vmExitOp(op));
|
||||
if (vmExitOp(op) == kVmExitEntryGuardPc)
|
||||
append(ctx.result, "exit(entry)");
|
||||
else
|
||||
append(ctx.result, "exit(%d)", vmExitOp(op));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -459,6 +462,47 @@ void toString(std::string& result, IrConst constant)
|
||||
}
|
||||
}
|
||||
|
||||
const char* getBytecodeTypeName(uint8_t type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case LBC_TYPE_NIL:
|
||||
return "nil";
|
||||
case LBC_TYPE_BOOLEAN:
|
||||
return "boolean";
|
||||
case LBC_TYPE_NUMBER:
|
||||
return "number";
|
||||
case LBC_TYPE_STRING:
|
||||
return "string";
|
||||
case LBC_TYPE_TABLE:
|
||||
return "table";
|
||||
case LBC_TYPE_FUNCTION:
|
||||
return "function";
|
||||
case LBC_TYPE_THREAD:
|
||||
return "thread";
|
||||
case LBC_TYPE_USERDATA:
|
||||
return "userdata";
|
||||
case LBC_TYPE_VECTOR:
|
||||
return "vector";
|
||||
case LBC_TYPE_BUFFER:
|
||||
return "buffer";
|
||||
case LBC_TYPE_ANY:
|
||||
return "any";
|
||||
}
|
||||
|
||||
LUAU_ASSERT(!"Unhandled type in getBytecodeTypeName");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void toString(std::string& result, const BytecodeTypes& bcTypes)
|
||||
{
|
||||
if (bcTypes.c != LBC_TYPE_ANY)
|
||||
append(result, "%s <- %s, %s, %s", getBytecodeTypeName(bcTypes.result), getBytecodeTypeName(bcTypes.a), getBytecodeTypeName(bcTypes.b),
|
||||
getBytecodeTypeName(bcTypes.c));
|
||||
else
|
||||
append(result, "%s <- %s, %s", getBytecodeTypeName(bcTypes.result), getBytecodeTypeName(bcTypes.a), getBytecodeTypeName(bcTypes.b));
|
||||
}
|
||||
|
||||
static void appendBlockSet(IrToStringContext& ctx, BlockIteratorWrapper blocks)
|
||||
{
|
||||
bool comma = false;
|
||||
|
@ -1067,7 +1067,11 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
|
||||
regs.spill(build, index);
|
||||
build.mov(x0, rState);
|
||||
build.add(x1, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue)));
|
||||
build.add(x2, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue)));
|
||||
|
||||
if (inst.b.kind == IrOpKind::VmConst)
|
||||
emitAddOffset(build, x2, rConstants, vmConstOp(inst.b) * sizeof(TValue));
|
||||
else
|
||||
build.add(x2, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue)));
|
||||
|
||||
if (inst.c.kind == IrOpKind::VmConst)
|
||||
emitAddOffset(build, x3, rConstants, vmConstOp(inst.c) * sizeof(TValue));
|
||||
|
@ -962,11 +962,12 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
|
||||
break;
|
||||
}
|
||||
case IrCmd::DO_ARITH:
|
||||
if (inst.c.kind == IrOpKind::VmReg)
|
||||
callArithHelper(regs, build, vmRegOp(inst.a), vmRegOp(inst.b), luauRegAddress(vmRegOp(inst.c)), TMS(intOp(inst.d)));
|
||||
else
|
||||
callArithHelper(regs, build, vmRegOp(inst.a), vmRegOp(inst.b), luauConstantAddress(vmConstOp(inst.c)), TMS(intOp(inst.d)));
|
||||
{
|
||||
OperandX64 opb = inst.b.kind == IrOpKind::VmReg ? luauRegAddress(vmRegOp(inst.b)) : luauConstantAddress(vmConstOp(inst.b));
|
||||
OperandX64 opc = inst.c.kind == IrOpKind::VmReg ? luauRegAddress(vmRegOp(inst.c)) : luauConstantAddress(vmConstOp(inst.c));
|
||||
callArithHelper(regs, build, vmRegOp(inst.a), opb, opc, TMS(intOp(inst.d)));
|
||||
break;
|
||||
}
|
||||
case IrCmd::DO_LEN:
|
||||
callLengthHelper(regs, build, vmRegOp(inst.a), vmRegOp(inst.b));
|
||||
break;
|
||||
@ -1911,7 +1912,7 @@ void IrLoweringX64::finishBlock(const IrBlock& curr, const IrBlock& next)
|
||||
{
|
||||
// If we have spills remaining, we have to immediately lower the successor block
|
||||
for (uint32_t predIdx : predecessors(function.cfg, function.getBlockIndex(next)))
|
||||
LUAU_ASSERT(predIdx == function.getBlockIndex(curr));
|
||||
LUAU_ASSERT(predIdx == function.getBlockIndex(curr) || function.blocks[predIdx].kind == IrBlockKind::Dead);
|
||||
|
||||
// And the next block cannot be a join block in cfg
|
||||
LUAU_ASSERT(next.useCount == 1);
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include <math.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBufferTranslateIr, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauImproveInsertIr, false)
|
||||
|
||||
// TODO: when nresults is less than our actual result count, we can skip computing/writing unused results
|
||||
|
||||
@ -691,34 +690,24 @@ static BuiltinImplResult translateBuiltinTableInsert(IrBuilder& build, int npara
|
||||
|
||||
IrOp setnum = build.inst(IrCmd::TABLE_SETNUM, table, pos);
|
||||
|
||||
if (FFlag::LuauImproveInsertIr)
|
||||
if (args.kind == IrOpKind::Constant)
|
||||
{
|
||||
if (args.kind == IrOpKind::Constant)
|
||||
{
|
||||
LUAU_ASSERT(build.function.constOp(args).kind == IrConstKind::Double);
|
||||
LUAU_ASSERT(build.function.constOp(args).kind == IrConstKind::Double);
|
||||
|
||||
// No barrier necessary since numbers aren't collectable
|
||||
build.inst(IrCmd::STORE_DOUBLE, setnum, args);
|
||||
build.inst(IrCmd::STORE_TAG, setnum, build.constTag(LUA_TNUMBER));
|
||||
}
|
||||
else
|
||||
{
|
||||
IrOp va = build.inst(IrCmd::LOAD_TVALUE, args);
|
||||
build.inst(IrCmd::STORE_TVALUE, setnum, va);
|
||||
|
||||
// Compiler only generates FASTCALL*K for source-level constants, so dynamic imports are not affected
|
||||
LUAU_ASSERT(build.function.proto);
|
||||
IrOp argstag = args.kind == IrOpKind::VmConst ? build.constTag(build.function.proto->k[vmConstOp(args)].tt) : build.undef();
|
||||
|
||||
build.inst(IrCmd::BARRIER_TABLE_FORWARD, table, args, argstag);
|
||||
}
|
||||
// No barrier necessary since numbers aren't collectable
|
||||
build.inst(IrCmd::STORE_DOUBLE, setnum, args);
|
||||
build.inst(IrCmd::STORE_TAG, setnum, build.constTag(LUA_TNUMBER));
|
||||
}
|
||||
else
|
||||
{
|
||||
IrOp va = build.inst(IrCmd::LOAD_TVALUE, args);
|
||||
build.inst(IrCmd::STORE_TVALUE, setnum, va);
|
||||
|
||||
build.inst(IrCmd::BARRIER_TABLE_FORWARD, table, args, build.undef());
|
||||
// Compiler only generates FASTCALL*K for source-level constants, so dynamic imports are not affected
|
||||
LUAU_ASSERT(build.function.proto);
|
||||
IrOp argstag = args.kind == IrOpKind::VmConst ? build.constTag(build.function.proto->k[vmConstOp(args)].tt) : build.undef();
|
||||
|
||||
build.inst(IrCmd::BARRIER_TABLE_FORWARD, table, args, argstag);
|
||||
}
|
||||
|
||||
return {BuiltinImplType::Full, 0};
|
||||
|
@ -12,9 +12,8 @@
|
||||
#include "lstate.h"
|
||||
#include "ltm.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauLowerAltLoopForn, false)
|
||||
LUAU_FASTFLAG(LuauImproveInsertIr)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFullLoopLuserdata, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLoopInterruptFix, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -44,6 +43,14 @@ struct FallbackStreamScope
|
||||
IrOp next;
|
||||
};
|
||||
|
||||
static IrOp getInitializedFallback(IrBuilder& build, IrOp& fallback)
|
||||
{
|
||||
if (fallback.kind == IrOpKind::None)
|
||||
fallback = build.block(IrBlockKind::Fallback);
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
void translateInstLoadNil(IrBuilder& build, const Instruction* pc)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
@ -327,25 +334,43 @@ void translateInstJumpxEqS(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
build.beginBlock(next);
|
||||
}
|
||||
|
||||
static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc, IrOp opc, int pcpos, TMS tm)
|
||||
static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc, IrOp opb, IrOp opc, int pcpos, TMS tm)
|
||||
{
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
IrOp fallback;
|
||||
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
|
||||
|
||||
// fast-path: number
|
||||
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TNUMBER), fallback);
|
||||
|
||||
if (rc != -1 && rc != rb) // TODO: optimization should handle second check, but we'll test it later
|
||||
if (rb != -1)
|
||||
{
|
||||
IrOp tc = build.inst(IrCmd::LOAD_TAG, build.vmReg(rc));
|
||||
build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER), fallback);
|
||||
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TNUMBER),
|
||||
bcTypes.a == LBC_TYPE_NUMBER ? build.vmExit(pcpos) : getInitializedFallback(build, fallback));
|
||||
}
|
||||
|
||||
IrOp vb = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rb));
|
||||
IrOp vc;
|
||||
if (rc != -1 && rc != rb)
|
||||
{
|
||||
IrOp tc = build.inst(IrCmd::LOAD_TAG, build.vmReg(rc));
|
||||
build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER),
|
||||
bcTypes.b == LBC_TYPE_NUMBER ? build.vmExit(pcpos) : getInitializedFallback(build, fallback));
|
||||
}
|
||||
|
||||
IrOp vb, vc;
|
||||
IrOp result;
|
||||
|
||||
if (opb.kind == IrOpKind::VmConst)
|
||||
{
|
||||
LUAU_ASSERT(build.function.proto);
|
||||
TValue protok = build.function.proto->k[vmConstOp(opb)];
|
||||
|
||||
LUAU_ASSERT(protok.tt == LUA_TNUMBER);
|
||||
|
||||
vb = build.constDouble(protok.value.n);
|
||||
}
|
||||
else
|
||||
{
|
||||
vb = build.inst(IrCmd::LOAD_DOUBLE, opb);
|
||||
}
|
||||
|
||||
if (opc.kind == IrOpKind::VmConst)
|
||||
{
|
||||
LUAU_ASSERT(build.function.proto);
|
||||
@ -405,22 +430,33 @@ static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc,
|
||||
if (ra != rb && ra != rc) // TODO: optimization should handle second check, but we'll test this later
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
||||
|
||||
IrOp next = build.blockAtInst(pcpos + 1);
|
||||
FallbackStreamScope scope(build, fallback, next);
|
||||
if (fallback.kind != IrOpKind::None)
|
||||
{
|
||||
IrOp next = build.blockAtInst(pcpos + 1);
|
||||
FallbackStreamScope scope(build, fallback, next);
|
||||
|
||||
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
|
||||
build.inst(IrCmd::DO_ARITH, build.vmReg(ra), build.vmReg(rb), opc, build.constInt(tm));
|
||||
build.inst(IrCmd::JUMP, next);
|
||||
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
|
||||
build.inst(IrCmd::DO_ARITH, build.vmReg(ra), opb, opc, build.constInt(tm));
|
||||
build.inst(IrCmd::JUMP, next);
|
||||
}
|
||||
}
|
||||
|
||||
void translateInstBinary(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm)
|
||||
{
|
||||
translateInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), build.vmReg(LUAU_INSN_C(*pc)), pcpos, tm);
|
||||
translateInstBinaryNumeric(
|
||||
build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), build.vmReg(LUAU_INSN_B(*pc)), build.vmReg(LUAU_INSN_C(*pc)), pcpos, tm);
|
||||
}
|
||||
|
||||
void translateInstBinaryK(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm)
|
||||
{
|
||||
translateInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, build.vmConst(LUAU_INSN_C(*pc)), pcpos, tm);
|
||||
translateInstBinaryNumeric(
|
||||
build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, build.vmReg(LUAU_INSN_B(*pc)), build.vmConst(LUAU_INSN_C(*pc)), pcpos, tm);
|
||||
}
|
||||
|
||||
void translateInstBinaryRK(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm)
|
||||
{
|
||||
translateInstBinaryNumeric(
|
||||
build, LUAU_INSN_A(*pc), -1, LUAU_INSN_C(*pc), build.vmConst(LUAU_INSN_B(*pc)), build.vmReg(LUAU_INSN_C(*pc)), pcpos, tm);
|
||||
}
|
||||
|
||||
void translateInstNot(IrBuilder& build, const Instruction* pc)
|
||||
@ -439,13 +475,15 @@ void translateInstNot(IrBuilder& build, const Instruction* pc)
|
||||
|
||||
void translateInstMinus(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
IrOp fallback;
|
||||
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
|
||||
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
|
||||
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TNUMBER), fallback);
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TNUMBER),
|
||||
bcTypes.a == LBC_TYPE_NUMBER ? build.vmExit(pcpos) : getInitializedFallback(build, fallback));
|
||||
|
||||
// fast-path: number
|
||||
IrOp vb = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rb));
|
||||
@ -456,23 +494,29 @@ void translateInstMinus(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
if (ra != rb)
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
||||
|
||||
IrOp next = build.blockAtInst(pcpos + 1);
|
||||
FallbackStreamScope scope(build, fallback, next);
|
||||
if (fallback.kind != IrOpKind::None)
|
||||
{
|
||||
IrOp next = build.blockAtInst(pcpos + 1);
|
||||
FallbackStreamScope scope(build, fallback, next);
|
||||
|
||||
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
|
||||
build.inst(IrCmd::DO_ARITH, build.vmReg(LUAU_INSN_A(*pc)), build.vmReg(LUAU_INSN_B(*pc)), build.vmReg(LUAU_INSN_B(*pc)), build.constInt(TM_UNM));
|
||||
build.inst(IrCmd::JUMP, next);
|
||||
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
|
||||
build.inst(
|
||||
IrCmd::DO_ARITH, build.vmReg(LUAU_INSN_A(*pc)), build.vmReg(LUAU_INSN_B(*pc)), build.vmReg(LUAU_INSN_B(*pc)), build.constInt(TM_UNM));
|
||||
build.inst(IrCmd::JUMP, next);
|
||||
}
|
||||
}
|
||||
|
||||
void translateInstLength(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
|
||||
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = LUAU_INSN_B(*pc);
|
||||
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
|
||||
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
|
||||
|
||||
// fast-path: table without __len
|
||||
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
|
||||
@ -562,7 +606,7 @@ IrOp translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool
|
||||
|
||||
IrOp builtinArgs = args;
|
||||
|
||||
if (customArgs.kind == IrOpKind::VmConst && (FFlag::LuauImproveInsertIr || bfid != LBF_TABLE_INSERT))
|
||||
if (customArgs.kind == IrOpKind::VmConst)
|
||||
{
|
||||
LUAU_ASSERT(build.function.proto);
|
||||
TValue protok = build.function.proto->k[vmConstOp(customArgs)];
|
||||
@ -633,18 +677,18 @@ static IrOp getLoopStepK(IrBuilder& build, int ra)
|
||||
return build.undef();
|
||||
}
|
||||
|
||||
void beforeInstForNPrep(IrBuilder& build, const Instruction* pc)
|
||||
void beforeInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
|
||||
IrOp stepK = getLoopStepK(build, ra);
|
||||
build.loopStepStack.push_back(stepK);
|
||||
build.numericLoopStack.push_back({stepK, pcpos + 1});
|
||||
}
|
||||
|
||||
void afterInstForNLoop(IrBuilder& build, const Instruction* pc)
|
||||
{
|
||||
LUAU_ASSERT(!build.loopStepStack.empty());
|
||||
build.loopStepStack.pop_back();
|
||||
LUAU_ASSERT(!build.numericLoopStack.empty());
|
||||
build.numericLoopStack.pop_back();
|
||||
}
|
||||
|
||||
void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
@ -654,8 +698,8 @@ void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
IrOp loopStart = build.blockAtInst(pcpos + getOpLength(LuauOpcode(LUAU_INSN_OP(*pc))));
|
||||
IrOp loopExit = build.blockAtInst(getJumpTarget(*pc, pcpos));
|
||||
|
||||
LUAU_ASSERT(!build.loopStepStack.empty());
|
||||
IrOp stepK = build.loopStepStack.back();
|
||||
LUAU_ASSERT(!build.numericLoopStack.empty());
|
||||
IrOp stepK = build.numericLoopStack.back().step;
|
||||
|
||||
// When loop parameters are not numbers, VM tries to perform type coercion from string and raises an exception if that fails
|
||||
// Performing that fallback in native code increases code size and complicates CFG, obscuring the values when they are constant
|
||||
@ -673,35 +717,9 @@ void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
IrOp tagStep = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 1));
|
||||
build.inst(IrCmd::CHECK_TAG, tagStep, build.constTag(LUA_TNUMBER), build.vmExit(pcpos));
|
||||
|
||||
if (FFlag::LuauLowerAltLoopForn)
|
||||
{
|
||||
IrOp step = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1));
|
||||
IrOp step = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1));
|
||||
|
||||
build.inst(IrCmd::JUMP_FORN_LOOP_COND, idx, limit, step, loopStart, loopExit);
|
||||
}
|
||||
else
|
||||
{
|
||||
IrOp direct = build.block(IrBlockKind::Internal);
|
||||
IrOp reverse = build.block(IrBlockKind::Internal);
|
||||
|
||||
IrOp zero = build.constDouble(0.0);
|
||||
IrOp step = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1));
|
||||
|
||||
// step > 0
|
||||
// note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64
|
||||
build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::Greater), direct, reverse);
|
||||
|
||||
// Condition to start the loop: step > 0 ? idx <= limit : limit <= idx
|
||||
// We invert the condition so that loopStart is the fallthrough (false) label
|
||||
|
||||
// step > 0 is false, check limit <= idx
|
||||
build.beginBlock(reverse);
|
||||
build.inst(IrCmd::JUMP_CMP_NUM, limit, idx, build.cond(IrCondition::NotLessEqual), loopExit, loopStart);
|
||||
|
||||
// step > 0 is true, check idx <= limit
|
||||
build.beginBlock(direct);
|
||||
build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::NotLessEqual), loopExit, loopStart);
|
||||
}
|
||||
build.inst(IrCmd::JUMP_FORN_LOOP_COND, idx, limit, step, loopStart, loopExit);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -729,17 +747,32 @@ void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
|
||||
IrOp loopRepeat = build.blockAtInst(getJumpTarget(*pc, pcpos));
|
||||
int repeatJumpTarget = getJumpTarget(*pc, pcpos);
|
||||
IrOp loopRepeat = build.blockAtInst(repeatJumpTarget);
|
||||
IrOp loopExit = build.blockAtInst(pcpos + getOpLength(LuauOpcode(LUAU_INSN_OP(*pc))));
|
||||
|
||||
// normally, the interrupt is placed at the beginning of the loop body by FORNPREP translation
|
||||
// however, there are rare contrived cases where FORNLOOP ends up jumping to itself without an interrupt placed
|
||||
// we detect this by checking if loopRepeat has any instructions (it should normally start with INTERRUPT) and emit a failsafe INTERRUPT if not
|
||||
if (build.function.blockOp(loopRepeat).start == build.function.instructions.size())
|
||||
build.inst(IrCmd::INTERRUPT, build.constUint(pcpos));
|
||||
LUAU_ASSERT(!build.numericLoopStack.empty());
|
||||
IrBuilder::LoopInfo loopInfo = build.numericLoopStack.back();
|
||||
|
||||
LUAU_ASSERT(!build.loopStepStack.empty());
|
||||
IrOp stepK = build.loopStepStack.back();
|
||||
if (FFlag::LuauLoopInterruptFix)
|
||||
{
|
||||
// normally, the interrupt is placed at the beginning of the loop body by FORNPREP translation
|
||||
// however, there are rare cases where FORNLOOP might not jump directly to the first loop instruction
|
||||
// we detect this by checking the starting instruction of the loop body from loop information stack
|
||||
if (repeatJumpTarget != loopInfo.startpc)
|
||||
build.inst(IrCmd::INTERRUPT, build.constUint(pcpos));
|
||||
}
|
||||
else
|
||||
{
|
||||
// normally, the interrupt is placed at the beginning of the loop body by FORNPREP translation
|
||||
// however, there are rare contrived cases where FORNLOOP ends up jumping to itself without an interrupt placed
|
||||
// we detect this by checking if loopRepeat has any instructions (it should normally start with INTERRUPT) and emit a failsafe INTERRUPT if
|
||||
// not
|
||||
if (build.function.blockOp(loopRepeat).start == build.function.instructions.size())
|
||||
build.inst(IrCmd::INTERRUPT, build.constUint(pcpos));
|
||||
}
|
||||
|
||||
IrOp stepK = loopInfo.step;
|
||||
|
||||
IrOp limit = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 0));
|
||||
IrOp step = stepK.kind == IrOpKind::Undef ? build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1)) : stepK;
|
||||
@ -750,31 +783,7 @@ void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
|
||||
if (stepK.kind == IrOpKind::Undef)
|
||||
{
|
||||
if (FFlag::LuauLowerAltLoopForn)
|
||||
{
|
||||
build.inst(IrCmd::JUMP_FORN_LOOP_COND, idx, limit, step, loopRepeat, loopExit);
|
||||
}
|
||||
else
|
||||
{
|
||||
IrOp direct = build.block(IrBlockKind::Internal);
|
||||
IrOp reverse = build.block(IrBlockKind::Internal);
|
||||
|
||||
IrOp zero = build.constDouble(0.0);
|
||||
|
||||
// step > 0
|
||||
// note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64
|
||||
build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::Greater), direct, reverse);
|
||||
|
||||
// Condition to continue the loop: step > 0 ? idx <= limit : limit <= idx
|
||||
|
||||
// step > 0 is false, check limit <= idx
|
||||
build.beginBlock(reverse);
|
||||
build.inst(IrCmd::JUMP_CMP_NUM, limit, idx, build.cond(IrCondition::LessEqual), loopRepeat, loopExit);
|
||||
|
||||
// step > 0 is true, check idx <= limit
|
||||
build.beginBlock(direct);
|
||||
build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::LessEqual), loopRepeat, loopExit);
|
||||
}
|
||||
build.inst(IrCmd::JUMP_FORN_LOOP_COND, idx, limit, step, loopRepeat, loopExit);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -913,9 +922,10 @@ void translateInstGetTableN(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
int c = LUAU_INSN_C(*pc);
|
||||
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
|
||||
|
||||
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
|
||||
|
||||
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
|
||||
|
||||
@ -942,9 +952,10 @@ void translateInstSetTableN(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
int c = LUAU_INSN_C(*pc);
|
||||
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
|
||||
|
||||
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
|
||||
|
||||
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
|
||||
|
||||
@ -974,11 +985,12 @@ void translateInstGetTable(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
int rc = LUAU_INSN_C(*pc);
|
||||
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
|
||||
|
||||
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
|
||||
IrOp tc = build.inst(IrCmd::LOAD_TAG, build.vmReg(rc));
|
||||
build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER), fallback);
|
||||
build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER), bcTypes.b == LBC_TYPE_NUMBER ? build.vmExit(pcpos) : fallback);
|
||||
|
||||
// fast-path: table with a number index
|
||||
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
|
||||
@ -1011,11 +1023,12 @@ void translateInstSetTable(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
int rc = LUAU_INSN_C(*pc);
|
||||
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
|
||||
|
||||
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
|
||||
IrOp tc = build.inst(IrCmd::LOAD_TAG, build.vmReg(rc));
|
||||
build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER), fallback);
|
||||
build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER), bcTypes.b == LBC_TYPE_NUMBER ? build.vmExit(pcpos) : fallback);
|
||||
|
||||
// fast-path: table with a number index
|
||||
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
|
||||
@ -1080,9 +1093,10 @@ void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
uint32_t aux = pc[1];
|
||||
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
|
||||
|
||||
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
|
||||
|
||||
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
|
||||
|
||||
@ -1107,9 +1121,10 @@ void translateInstSetTableKS(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
uint32_t aux = pc[1];
|
||||
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
|
||||
|
||||
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback);
|
||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
|
||||
|
||||
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
|
||||
|
||||
|
@ -35,6 +35,7 @@ void translateInstJumpxEqN(IrBuilder& build, const Instruction* pc, int pcpos);
|
||||
void translateInstJumpxEqS(IrBuilder& build, const Instruction* pc, int pcpos);
|
||||
void translateInstBinary(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm);
|
||||
void translateInstBinaryK(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm);
|
||||
void translateInstBinaryRK(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm);
|
||||
void translateInstNot(IrBuilder& build, const Instruction* pc);
|
||||
void translateInstMinus(IrBuilder& build, const Instruction* pc, int pcpos);
|
||||
void translateInstLength(IrBuilder& build, const Instruction* pc, int pcpos);
|
||||
@ -65,7 +66,7 @@ void translateInstAndX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp
|
||||
void translateInstOrX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp c);
|
||||
void translateInstNewClosure(IrBuilder& build, const Instruction* pc, int pcpos);
|
||||
|
||||
void beforeInstForNPrep(IrBuilder& build, const Instruction* pc);
|
||||
void beforeInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos);
|
||||
void afterInstForNLoop(IrBuilder& build, const Instruction* pc);
|
||||
|
||||
} // namespace CodeGen
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include <array>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@ -15,9 +17,9 @@
|
||||
LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3)
|
||||
LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReuseArrSlots2, false)
|
||||
LUAU_FASTFLAG(LuauLowerAltLoopForn)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodeGenFixByteLower, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauKeepVmapLinear2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReuseBufferChecks, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -194,12 +196,19 @@ struct ConstPropState
|
||||
checkArraySizeCache.clear();
|
||||
}
|
||||
|
||||
void invalidateHeapBufferData()
|
||||
{
|
||||
checkBufferLenCache.clear();
|
||||
}
|
||||
|
||||
void invalidateHeap()
|
||||
{
|
||||
for (int i = 0; i <= maxReg; ++i)
|
||||
invalidateHeap(regs[i]);
|
||||
|
||||
invalidateHeapTableData();
|
||||
|
||||
// Buffer length checks are not invalidated since buffer size is immutable
|
||||
}
|
||||
|
||||
void invalidateHeap(RegisterInfo& reg)
|
||||
@ -408,6 +417,7 @@ struct ConstPropState
|
||||
|
||||
invalidateValuePropagation();
|
||||
invalidateHeapTableData();
|
||||
invalidateHeapBufferData();
|
||||
}
|
||||
|
||||
IrFunction& function;
|
||||
@ -435,6 +445,8 @@ struct ConstPropState
|
||||
|
||||
std::vector<uint32_t> getArrAddrCache;
|
||||
std::vector<uint32_t> checkArraySizeCache; // Additionally, fallback block argument might be different
|
||||
|
||||
std::vector<uint32_t> checkBufferLenCache; // Additionally, fallback block argument might be different
|
||||
};
|
||||
|
||||
static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid, uint32_t firstReturnReg, int nresults)
|
||||
@ -677,7 +689,21 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
if (inst.a.kind == IrOpKind::VmReg || inst.a.kind == IrOpKind::Inst)
|
||||
{
|
||||
if (inst.a.kind == IrOpKind::VmReg)
|
||||
{
|
||||
if (FFlag::LuauReuseBufferChecks && inst.b.kind == IrOpKind::Inst)
|
||||
{
|
||||
if (uint32_t* prevIdx = state.getPreviousVersionedLoadIndex(IrCmd::LOAD_TVALUE, inst.a))
|
||||
{
|
||||
if (*prevIdx == inst.b.index)
|
||||
{
|
||||
kill(function, inst);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.invalidate(inst.a);
|
||||
}
|
||||
|
||||
uint8_t tag = state.tryGetTag(inst.b);
|
||||
IrOp value = state.tryGetValue(inst.b);
|
||||
@ -921,8 +947,65 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
}
|
||||
break;
|
||||
case IrCmd::CHECK_BUFFER_LEN:
|
||||
// TODO: remove duplicate checks and extend earlier check bound when possible
|
||||
{
|
||||
if (!FFlag::LuauReuseBufferChecks)
|
||||
break;
|
||||
|
||||
std::optional<int> bufferOffset = function.asIntOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b));
|
||||
int accessSize = function.intOp(inst.c);
|
||||
LUAU_ASSERT(accessSize > 0);
|
||||
|
||||
if (bufferOffset)
|
||||
{
|
||||
// Negative offsets and offsets overflowing signed integer will jump to fallback, no need to keep the check
|
||||
if (*bufferOffset < 0 || unsigned(*bufferOffset) + unsigned(accessSize) >= unsigned(INT_MAX))
|
||||
{
|
||||
replace(function, block, index, {IrCmd::JUMP, inst.d});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t prevIdx : state.checkBufferLenCache)
|
||||
{
|
||||
IrInst& prev = function.instructions[prevIdx];
|
||||
|
||||
if (prev.a != inst.a || prev.c != inst.c)
|
||||
continue;
|
||||
|
||||
if (prev.b == inst.b)
|
||||
{
|
||||
if (FFlag::DebugLuauAbortingChecks)
|
||||
replace(function, inst.d, build.undef());
|
||||
else
|
||||
kill(function, inst);
|
||||
return; // Break out from both the loop and the switch
|
||||
}
|
||||
else if (inst.b.kind == IrOpKind::Constant && prev.b.kind == IrOpKind::Constant)
|
||||
{
|
||||
// If arguments are different constants, we can check if a larger bound was already tested or if the previous bound can be raised
|
||||
int currBound = function.intOp(inst.b);
|
||||
int prevBound = function.intOp(prev.b);
|
||||
|
||||
// Negative and overflowing constant offsets should already be replaced with unconditional jumps to a fallback
|
||||
LUAU_ASSERT(currBound >= 0);
|
||||
LUAU_ASSERT(prevBound >= 0);
|
||||
|
||||
if (unsigned(currBound) >= unsigned(prevBound))
|
||||
replace(function, prev.b, inst.b);
|
||||
|
||||
if (FFlag::DebugLuauAbortingChecks)
|
||||
replace(function, inst.d, build.undef());
|
||||
else
|
||||
kill(function, inst);
|
||||
|
||||
return; // Break out from both the loop and the switch
|
||||
}
|
||||
}
|
||||
|
||||
if (int(state.checkBufferLenCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
|
||||
state.checkBufferLenCache.push_back(index);
|
||||
break;
|
||||
}
|
||||
case IrCmd::BUFFER_READI8:
|
||||
case IrCmd::BUFFER_READU8:
|
||||
case IrCmd::BUFFER_WRITEI8:
|
||||
@ -969,9 +1052,6 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
case IrCmd::LOAD_ENV:
|
||||
break;
|
||||
case IrCmd::GET_ARR_ADDR:
|
||||
if (!FFlag::LuauReuseArrSlots2)
|
||||
break;
|
||||
|
||||
for (uint32_t prevIdx : state.getArrAddrCache)
|
||||
{
|
||||
const IrInst& prev = function.instructions[prevIdx];
|
||||
@ -1039,9 +1119,6 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
case IrCmd::DUP_TABLE:
|
||||
break;
|
||||
case IrCmd::TRY_NUM_TO_INDEX:
|
||||
if (!FFlag::LuauReuseArrSlots2)
|
||||
break;
|
||||
|
||||
for (uint32_t prevIdx : state.tryNumToIndexCache)
|
||||
{
|
||||
const IrInst& prev = function.instructions[prevIdx];
|
||||
@ -1079,7 +1156,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
std::optional<int> arrayIndex = function.asIntOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b));
|
||||
|
||||
// Negative offsets will jump to fallback, no need to keep the check
|
||||
if (FFlag::LuauReuseArrSlots2 && arrayIndex && *arrayIndex < 0)
|
||||
if (arrayIndex && *arrayIndex < 0)
|
||||
{
|
||||
replace(function, block, index, {IrCmd::JUMP, inst.c});
|
||||
break;
|
||||
@ -1105,9 +1182,6 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
}
|
||||
}
|
||||
|
||||
if (!FFlag::LuauReuseArrSlots2)
|
||||
break;
|
||||
|
||||
for (uint32_t prevIdx : state.checkArraySizeCache)
|
||||
{
|
||||
const IrInst& prev = function.instructions[prevIdx];
|
||||
@ -1220,6 +1294,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
// TODO: this can be relaxed when x64 emitInstSetList becomes aware of register allocator
|
||||
state.invalidateValuePropagation();
|
||||
state.invalidateHeapTableData();
|
||||
state.invalidateHeapBufferData();
|
||||
break;
|
||||
case IrCmd::CALL:
|
||||
state.invalidateRegistersFrom(vmRegOp(inst.a));
|
||||
@ -1236,6 +1311,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
// TODO: this can be relaxed when x64 emitInstForGLoop becomes aware of register allocator
|
||||
state.invalidateValuePropagation();
|
||||
state.invalidateHeapTableData();
|
||||
state.invalidateHeapBufferData();
|
||||
break;
|
||||
case IrCmd::FORGLOOP_FALLBACK:
|
||||
state.invalidateRegistersFrom(vmRegOp(inst.a) + 2); // Rn and Rn+1 are not modified
|
||||
@ -1299,11 +1375,15 @@ static void constPropInBlock(IrBuilder& build, IrBlock& block, ConstPropState& s
|
||||
constPropInInst(state, build, function, block, inst, index);
|
||||
}
|
||||
|
||||
// Value numbering and load/store propagation is not performed between blocks
|
||||
state.invalidateValuePropagation();
|
||||
if (!FFlag::LuauKeepVmapLinear2)
|
||||
{
|
||||
// Value numbering and load/store propagation is not performed between blocks
|
||||
state.invalidateValuePropagation();
|
||||
|
||||
// Same for table slot data propagation
|
||||
state.invalidateHeapTableData();
|
||||
// Same for table and buffer data propagation
|
||||
state.invalidateHeapTableData();
|
||||
state.invalidateHeapBufferData();
|
||||
}
|
||||
}
|
||||
|
||||
static void constPropInBlockChain(IrBuilder& build, std::vector<uint8_t>& visited, IrBlock* block, ConstPropState& state)
|
||||
@ -1323,6 +1403,16 @@ static void constPropInBlockChain(IrBuilder& build, std::vector<uint8_t>& visite
|
||||
|
||||
constPropInBlock(build, *block, state);
|
||||
|
||||
if (FFlag::LuauKeepVmapLinear2)
|
||||
{
|
||||
// Value numbering and load/store propagation is not performed between blocks
|
||||
state.invalidateValuePropagation();
|
||||
|
||||
// Same for table and buffer data propagation
|
||||
state.invalidateHeapTableData();
|
||||
state.invalidateHeapBufferData();
|
||||
}
|
||||
|
||||
// Blocks in a chain are guaranteed to follow each other
|
||||
// We force that by giving all blocks the same sorting key, but consecutive chain keys
|
||||
block->sortkey = startSortkey;
|
||||
@ -1341,7 +1431,7 @@ static void constPropInBlockChain(IrBuilder& build, std::vector<uint8_t>& visite
|
||||
|
||||
if (target.useCount == 1 && !visited[targetIdx] && target.kind != IrBlockKind::Fallback)
|
||||
{
|
||||
if (FFlag::LuauLowerAltLoopForn && getLiveOutValueCount(function, target) != 0)
|
||||
if (getLiveOutValueCount(function, target) != 0)
|
||||
break;
|
||||
|
||||
// Make sure block ordering guarantee is checked at lowering time
|
||||
|
@ -45,6 +45,7 @@
|
||||
// Version 2: Adds Proto::linedefined. Supported until 0.544.
|
||||
// Version 3: Adds FORGPREP/JUMPXEQK* and enhances AUX encoding for FORGLOOP. Removes FORGLOOP_NEXT/INEXT and JUMPIFEQK/JUMPIFNOTEQK. Currently supported.
|
||||
// Version 4: Adds Proto::flags, typeinfo, and floor division opcodes IDIV/IDIVK. Currently supported.
|
||||
// Version 5: Adds SUBRK/DIVRK and vector constants. Currently supported.
|
||||
|
||||
// Bytecode opcode, part of the instruction header
|
||||
enum LuauOpcode
|
||||
@ -70,7 +71,7 @@ enum LuauOpcode
|
||||
// D: value (-32768..32767)
|
||||
LOP_LOADN,
|
||||
|
||||
// LOADK: sets register to an entry from the constant table from the proto (number/string)
|
||||
// LOADK: sets register to an entry from the constant table from the proto (number/vector/string)
|
||||
// A: target register
|
||||
// D: constant table index (0..32767)
|
||||
LOP_LOADK,
|
||||
@ -218,7 +219,7 @@ enum LuauOpcode
|
||||
// ADDK, SUBK, MULK, DIVK, MODK, POWK: compute arithmetic operation between the source register and a constant and put the result into target register
|
||||
// A: target register
|
||||
// B: source register
|
||||
// C: constant table index (0..255)
|
||||
// C: constant table index (0..255); must refer to a number
|
||||
LOP_ADDK,
|
||||
LOP_SUBK,
|
||||
LOP_MULK,
|
||||
@ -347,9 +348,12 @@ enum LuauOpcode
|
||||
// B: source register (for VAL/REF) or upvalue index (for UPVAL/UPREF)
|
||||
LOP_CAPTURE,
|
||||
|
||||
// removed in v3
|
||||
LOP_DEP_JUMPIFEQK,
|
||||
LOP_DEP_JUMPIFNOTEQK,
|
||||
// SUBRK, DIVRK: compute arithmetic operation between the constant and a source register and put the result into target register
|
||||
// A: target register
|
||||
// B: source register
|
||||
// C: constant table index (0..255); must refer to a number
|
||||
LOP_SUBRK,
|
||||
LOP_DIVRK,
|
||||
|
||||
// FASTCALL1: perform a fast call of a built-in function using 1 register argument
|
||||
// A: builtin function id (see LuauBuiltinFunction)
|
||||
@ -426,7 +430,7 @@ enum LuauBytecodeTag
|
||||
{
|
||||
// Bytecode version; runtime supports [MIN, MAX], compiler emits TARGET by default but may emit a higher version when flags are enabled
|
||||
LBC_VERSION_MIN = 3,
|
||||
LBC_VERSION_MAX = 4,
|
||||
LBC_VERSION_MAX = 5,
|
||||
LBC_VERSION_TARGET = 4,
|
||||
// Type encoding version
|
||||
LBC_TYPE_VERSION = 1,
|
||||
@ -438,6 +442,7 @@ enum LuauBytecodeTag
|
||||
LBC_CONSTANT_IMPORT,
|
||||
LBC_CONSTANT_TABLE,
|
||||
LBC_CONSTANT_CLOSURE,
|
||||
LBC_CONSTANT_VECTOR,
|
||||
};
|
||||
|
||||
// Type table tags
|
||||
|
@ -54,6 +54,7 @@ public:
|
||||
int32_t addConstantNil();
|
||||
int32_t addConstantBoolean(bool value);
|
||||
int32_t addConstantNumber(double value);
|
||||
int32_t addConstantVector(float x, float y, float z, float w);
|
||||
int32_t addConstantString(StringRef value);
|
||||
int32_t addImport(uint32_t iid);
|
||||
int32_t addConstantTable(const TableShape& shape);
|
||||
@ -146,6 +147,7 @@ private:
|
||||
Type_Nil,
|
||||
Type_Boolean,
|
||||
Type_Number,
|
||||
Type_Vector,
|
||||
Type_String,
|
||||
Type_Import,
|
||||
Type_Table,
|
||||
@ -157,6 +159,7 @@ private:
|
||||
{
|
||||
bool valueBoolean;
|
||||
double valueNumber;
|
||||
float valueVector[4];
|
||||
unsigned int valueString; // index into string table
|
||||
uint32_t valueImport; // 10-10-10-2 encoded import id
|
||||
uint32_t valueTable; // index into tableShapes[]
|
||||
@ -167,12 +170,14 @@ private:
|
||||
struct ConstantKey
|
||||
{
|
||||
Constant::Type type;
|
||||
// Note: this stores value* from Constant; when type is Number_Double, this stores the same bits as double does but in uint64_t.
|
||||
// Note: this stores value* from Constant; when type is Type_Number, this stores the same bits as double does but in uint64_t.
|
||||
// For Type_Vector, x and y are stored in 'value' and z and w are stored in 'extra'.
|
||||
uint64_t value;
|
||||
uint64_t extra = 0;
|
||||
|
||||
bool operator==(const ConstantKey& key) const
|
||||
{
|
||||
return type == key.type && value == key.value;
|
||||
return type == key.type && value == key.value && extra == key.extra;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
#include <math.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauVectorLiterals, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace Compile
|
||||
@ -32,6 +34,16 @@ static Constant cnum(double v)
|
||||
return res;
|
||||
}
|
||||
|
||||
static Constant cvector(double x, double y, double z, double w)
|
||||
{
|
||||
Constant res = {Constant::Type_Vector};
|
||||
res.valueVector[0] = (float)x;
|
||||
res.valueVector[1] = (float)y;
|
||||
res.valueVector[2] = (float)z;
|
||||
res.valueVector[3] = (float)w;
|
||||
return res;
|
||||
}
|
||||
|
||||
static Constant cstring(const char* v)
|
||||
{
|
||||
Constant res = {Constant::Type_String};
|
||||
@ -55,6 +67,9 @@ static Constant ctype(const Constant& c)
|
||||
case Constant::Type_Number:
|
||||
return cstring("number");
|
||||
|
||||
case Constant::Type_Vector:
|
||||
return cstring("vector");
|
||||
|
||||
case Constant::Type_String:
|
||||
return cstring("string");
|
||||
|
||||
@ -456,6 +471,17 @@ Constant foldBuiltin(int bfid, const Constant* args, size_t count)
|
||||
if (count == 1 && args[0].type == Constant::Type_Number)
|
||||
return cnum(round(args[0].valueNumber));
|
||||
break;
|
||||
|
||||
case LBF_VECTOR:
|
||||
if (FFlag::LuauVectorLiterals && count >= 3 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number &&
|
||||
args[2].type == Constant::Type_Number)
|
||||
{
|
||||
if (count == 3)
|
||||
return cvector(args[0].valueNumber, args[1].valueNumber, args[2].valueNumber, 0.0);
|
||||
else if (count == 4 && args[3].type == Constant::Type_Number)
|
||||
return cvector(args[0].valueNumber, args[1].valueNumber, args[2].valueNumber, args[3].valueNumber);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return cvar();
|
||||
|
@ -7,6 +7,8 @@
|
||||
#include <algorithm>
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauVectorLiterals)
|
||||
LUAU_FASTFLAG(LuauCompileRevK)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -41,6 +43,11 @@ static void writeInt(std::string& ss, int value)
|
||||
ss.append(reinterpret_cast<const char*>(&value), sizeof(value));
|
||||
}
|
||||
|
||||
static void writeFloat(std::string& ss, float value)
|
||||
{
|
||||
ss.append(reinterpret_cast<const char*>(&value), sizeof(value));
|
||||
}
|
||||
|
||||
static void writeDouble(std::string& ss, double value)
|
||||
{
|
||||
ss.append(reinterpret_cast<const char*>(&value), sizeof(value));
|
||||
@ -146,23 +153,43 @@ size_t BytecodeBuilder::StringRefHash::operator()(const StringRef& v) const
|
||||
|
||||
size_t BytecodeBuilder::ConstantKeyHash::operator()(const ConstantKey& key) const
|
||||
{
|
||||
// finalizer from MurmurHash64B
|
||||
const uint32_t m = 0x5bd1e995;
|
||||
if (key.type == Constant::Type_Vector)
|
||||
{
|
||||
uint32_t i[4];
|
||||
static_assert(sizeof(key.value) + sizeof(key.extra) == sizeof(i), "Expecting vector to have four 32-bit components");
|
||||
memcpy(i, &key.value, sizeof(i));
|
||||
|
||||
uint32_t h1 = uint32_t(key.value);
|
||||
uint32_t h2 = uint32_t(key.value >> 32) ^ (key.type * m);
|
||||
// scramble bits to make sure that integer coordinates have entropy in lower bits
|
||||
i[0] ^= i[0] >> 17;
|
||||
i[1] ^= i[1] >> 17;
|
||||
i[2] ^= i[2] >> 17;
|
||||
i[3] ^= i[3] >> 17;
|
||||
|
||||
h1 ^= h2 >> 18;
|
||||
h1 *= m;
|
||||
h2 ^= h1 >> 22;
|
||||
h2 *= m;
|
||||
h1 ^= h2 >> 17;
|
||||
h1 *= m;
|
||||
h2 ^= h1 >> 19;
|
||||
h2 *= m;
|
||||
// Optimized Spatial Hashing for Collision Detection of Deformable Objects
|
||||
uint32_t h = (i[0] * 73856093) ^ (i[1] * 19349663) ^ (i[2] * 83492791) ^ (i[3] * 39916801);
|
||||
|
||||
// ... truncated to 32-bit output (normally hash is equal to (uint64_t(h1) << 32) | h2, but we only really need the lower 32-bit half)
|
||||
return size_t(h2);
|
||||
return size_t(h);
|
||||
}
|
||||
else
|
||||
{
|
||||
// finalizer from MurmurHash64B
|
||||
const uint32_t m = 0x5bd1e995;
|
||||
|
||||
uint32_t h1 = uint32_t(key.value);
|
||||
uint32_t h2 = uint32_t(key.value >> 32) ^ (key.type * m);
|
||||
|
||||
h1 ^= h2 >> 18;
|
||||
h1 *= m;
|
||||
h2 ^= h1 >> 22;
|
||||
h2 *= m;
|
||||
h1 ^= h2 >> 17;
|
||||
h1 *= m;
|
||||
h2 ^= h1 >> 19;
|
||||
h2 *= m;
|
||||
|
||||
// ... truncated to 32-bit output (normally hash is equal to (uint64_t(h1) << 32) | h2, but we only really need the lower 32-bit half)
|
||||
return size_t(h2);
|
||||
}
|
||||
}
|
||||
|
||||
size_t BytecodeBuilder::TableShapeHash::operator()(const TableShape& v) const
|
||||
@ -330,6 +357,25 @@ int32_t BytecodeBuilder::addConstantNumber(double value)
|
||||
return addConstant(k, c);
|
||||
}
|
||||
|
||||
int32_t BytecodeBuilder::addConstantVector(float x, float y, float z, float w)
|
||||
{
|
||||
Constant c = {Constant::Type_Vector};
|
||||
c.valueVector[0] = x;
|
||||
c.valueVector[1] = y;
|
||||
c.valueVector[2] = z;
|
||||
c.valueVector[3] = w;
|
||||
|
||||
ConstantKey k = {Constant::Type_Vector};
|
||||
static_assert(
|
||||
sizeof(k.value) == sizeof(x) + sizeof(y) && sizeof(k.extra) == sizeof(z) + sizeof(w), "Expecting vector to have four 32-bit components");
|
||||
memcpy(&k.value, &x, sizeof(x));
|
||||
memcpy((char*)&k.value + sizeof(x), &y, sizeof(y));
|
||||
memcpy(&k.extra, &z, sizeof(z));
|
||||
memcpy((char*)&k.extra + sizeof(z), &w, sizeof(w));
|
||||
|
||||
return addConstant(k, c);
|
||||
}
|
||||
|
||||
int32_t BytecodeBuilder::addConstantString(StringRef value)
|
||||
{
|
||||
unsigned int index = addStringTableEntry(value);
|
||||
@ -647,6 +693,14 @@ void BytecodeBuilder::writeFunction(std::string& ss, uint32_t id, uint8_t flags)
|
||||
writeDouble(ss, c.valueNumber);
|
||||
break;
|
||||
|
||||
case Constant::Type_Vector:
|
||||
writeByte(ss, LBC_CONSTANT_VECTOR);
|
||||
writeFloat(ss, c.valueVector[0]);
|
||||
writeFloat(ss, c.valueVector[1]);
|
||||
writeFloat(ss, c.valueVector[2]);
|
||||
writeFloat(ss, c.valueVector[3]);
|
||||
break;
|
||||
|
||||
case Constant::Type_String:
|
||||
writeByte(ss, LBC_CONSTANT_STRING);
|
||||
writeVarInt(ss, c.valueString);
|
||||
@ -1071,7 +1125,7 @@ std::string BytecodeBuilder::getError(const std::string& message)
|
||||
uint8_t BytecodeBuilder::getVersion()
|
||||
{
|
||||
// This function usually returns LBC_VERSION_TARGET but may sometimes return a higher number (within LBC_VERSION_MIN/MAX) under fast flags
|
||||
return LBC_VERSION_TARGET;
|
||||
return (FFlag::LuauVectorLiterals || FFlag::LuauCompileRevK) ? 5 : LBC_VERSION_TARGET;
|
||||
}
|
||||
|
||||
uint8_t BytecodeBuilder::getTypeEncodingVersion()
|
||||
@ -1299,6 +1353,13 @@ void BytecodeBuilder::validateInstructions() const
|
||||
VCONST(LUAU_INSN_C(insn), Number);
|
||||
break;
|
||||
|
||||
case LOP_SUBRK:
|
||||
case LOP_DIVRK:
|
||||
VREG(LUAU_INSN_A(insn));
|
||||
VCONST(LUAU_INSN_B(insn), Number);
|
||||
VREG(LUAU_INSN_C(insn));
|
||||
break;
|
||||
|
||||
case LOP_AND:
|
||||
case LOP_OR:
|
||||
VREG(LUAU_INSN_A(insn));
|
||||
@ -1635,6 +1696,13 @@ void BytecodeBuilder::dumpConstant(std::string& result, int k) const
|
||||
case Constant::Type_Number:
|
||||
formatAppend(result, "%.17g", data.valueNumber);
|
||||
break;
|
||||
case Constant::Type_Vector:
|
||||
// 3-vectors is the most common configuration, so truncate to three components if possible
|
||||
if (data.valueVector[3] == 0.0)
|
||||
formatAppend(result, "%.9g, %.9g, %.9g", data.valueVector[0], data.valueVector[1], data.valueVector[2]);
|
||||
else
|
||||
formatAppend(result, "%.9g, %.9g, %.9g, %.9g", data.valueVector[0], data.valueVector[1], data.valueVector[2], data.valueVector[3]);
|
||||
break;
|
||||
case Constant::Type_String:
|
||||
{
|
||||
const StringRef& str = debugStrings[data.valueString - 1];
|
||||
@ -1914,6 +1982,18 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
|
||||
result.append("]\n");
|
||||
break;
|
||||
|
||||
case LOP_SUBRK:
|
||||
formatAppend(result, "SUBRK R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn));
|
||||
dumpConstant(result, LUAU_INSN_B(insn));
|
||||
formatAppend(result, "] R%d\n", LUAU_INSN_C(insn));
|
||||
break;
|
||||
|
||||
case LOP_DIVRK:
|
||||
formatAppend(result, "DIVRK R%d K%d [", LUAU_INSN_A(insn), LUAU_INSN_B(insn));
|
||||
dumpConstant(result, LUAU_INSN_B(insn));
|
||||
formatAppend(result, "] R%d\n", LUAU_INSN_C(insn));
|
||||
break;
|
||||
|
||||
case LOP_AND:
|
||||
formatAppend(result, "AND R%d R%d R%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
break;
|
||||
|
@ -26,8 +26,7 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
|
||||
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
||||
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileSideEffects, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileDeadIf, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileRevK, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -1094,6 +1093,13 @@ struct Compiler
|
||||
return cv && cv->type != Constant::Type_Unknown && !cv->isTruthful();
|
||||
}
|
||||
|
||||
bool isConstantVector(AstExpr* node)
|
||||
{
|
||||
const Constant* cv = constants.find(node);
|
||||
|
||||
return cv && cv->type == Constant::Type_Vector;
|
||||
}
|
||||
|
||||
Constant getConstant(AstExpr* node)
|
||||
{
|
||||
const Constant* cv = constants.find(node);
|
||||
@ -1117,6 +1123,10 @@ struct Compiler
|
||||
std::swap(left, right);
|
||||
}
|
||||
|
||||
// disable fast path for vectors because supporting it would require a new opcode
|
||||
if (operandIsConstant && isConstantVector(right))
|
||||
operandIsConstant = false;
|
||||
|
||||
uint8_t rl = compileExprAuto(left, rs);
|
||||
|
||||
if (isEq && operandIsConstant)
|
||||
@ -1226,6 +1236,10 @@ struct Compiler
|
||||
cid = bytecode.addConstantNumber(c->valueNumber);
|
||||
break;
|
||||
|
||||
case Constant::Type_Vector:
|
||||
cid = bytecode.addConstantVector(c->valueVector[0], c->valueVector[1], c->valueVector[2], c->valueVector[3]);
|
||||
break;
|
||||
|
||||
case Constant::Type_String:
|
||||
cid = bytecode.addConstantString(sref(c->getString()));
|
||||
break;
|
||||
@ -1501,6 +1515,20 @@ struct Compiler
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauCompileRevK && (expr->op == AstExprBinary::Sub || expr->op == AstExprBinary::Div))
|
||||
{
|
||||
int32_t lc = getConstantNumber(expr->left);
|
||||
|
||||
if (lc >= 0 && lc <= 255)
|
||||
{
|
||||
uint8_t rr = compileExprAuto(expr->right, rs);
|
||||
LuauOpcode op = (expr->op == AstExprBinary::Sub) ? LOP_SUBRK : LOP_DIVRK;
|
||||
|
||||
bytecode.emitABC(op, target, uint8_t(lc), uint8_t(rr));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t rl = compileExprAuto(expr->left, rs);
|
||||
uint8_t rr = compileExprAuto(expr->right, rs);
|
||||
|
||||
@ -2052,6 +2080,13 @@ struct Compiler
|
||||
}
|
||||
break;
|
||||
|
||||
case Constant::Type_Vector:
|
||||
{
|
||||
int32_t cid = bytecode.addConstantVector(cv->valueVector[0], cv->valueVector[1], cv->valueVector[2], cv->valueVector[3]);
|
||||
emitLoadK(target, cid);
|
||||
}
|
||||
break;
|
||||
|
||||
case Constant::Type_String:
|
||||
{
|
||||
int32_t cid = bytecode.addConstantString(sref(cv->getString()));
|
||||
@ -2207,16 +2242,13 @@ struct Compiler
|
||||
|
||||
void compileExprSide(AstExpr* node)
|
||||
{
|
||||
if (FFlag::LuauCompileSideEffects)
|
||||
{
|
||||
// Optimization: some expressions never carry side effects so we don't need to emit any code
|
||||
if (node->is<AstExprLocal>() || node->is<AstExprGlobal>() || node->is<AstExprVarargs>() || node->is<AstExprFunction>() || isConstant(node))
|
||||
return;
|
||||
// Optimization: some expressions never carry side effects so we don't need to emit any code
|
||||
if (node->is<AstExprLocal>() || node->is<AstExprGlobal>() || node->is<AstExprVarargs>() || node->is<AstExprFunction>() || isConstant(node))
|
||||
return;
|
||||
|
||||
// note: the remark is omitted for calls as it's fairly noisy due to inlining
|
||||
if (!node->is<AstExprCall>())
|
||||
bytecode.addDebugRemark("expression only compiled for side effects");
|
||||
}
|
||||
// note: the remark is omitted for calls as it's fairly noisy due to inlining
|
||||
if (!node->is<AstExprCall>())
|
||||
bytecode.addDebugRemark("expression only compiled for side effects");
|
||||
|
||||
RegScope rsi(this);
|
||||
compileExprAuto(node, rsi);
|
||||
@ -2506,15 +2538,12 @@ struct Compiler
|
||||
}
|
||||
|
||||
// Optimization: condition is always false but isn't a constant => we only need the else body and condition's side effects
|
||||
if (FFlag::LuauCompileDeadIf)
|
||||
if (AstExprBinary* cand = stat->condition->as<AstExprBinary>(); cand && cand->op == AstExprBinary::And && isConstantFalse(cand->right))
|
||||
{
|
||||
if (AstExprBinary* cand = stat->condition->as<AstExprBinary>(); cand && cand->op == AstExprBinary::And && isConstantFalse(cand->right))
|
||||
{
|
||||
compileExprSide(cand->left);
|
||||
if (stat->elsebody)
|
||||
compileStat(stat->elsebody);
|
||||
return;
|
||||
}
|
||||
compileExprSide(cand->left);
|
||||
if (stat->elsebody)
|
||||
compileStat(stat->elsebody);
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimization: body is a "break" statement with no "else" => we can directly break out of the loop in "then" case
|
||||
|
@ -26,6 +26,10 @@ static bool constantsEqual(const Constant& la, const Constant& ra)
|
||||
case Constant::Type_Number:
|
||||
return ra.type == Constant::Type_Number && la.valueNumber == ra.valueNumber;
|
||||
|
||||
case Constant::Type_Vector:
|
||||
return ra.type == Constant::Type_Vector && la.valueVector[0] == ra.valueVector[0] && la.valueVector[1] == ra.valueVector[1] &&
|
||||
la.valueVector[2] == ra.valueVector[2] && la.valueVector[3] == ra.valueVector[3];
|
||||
|
||||
case Constant::Type_String:
|
||||
return ra.type == Constant::Type_String && la.stringLength == ra.stringLength && memcmp(la.valueString, ra.valueString, la.stringLength) == 0;
|
||||
|
||||
|
@ -16,6 +16,7 @@ struct Constant
|
||||
Type_Nil,
|
||||
Type_Boolean,
|
||||
Type_Number,
|
||||
Type_Vector,
|
||||
Type_String,
|
||||
};
|
||||
|
||||
@ -26,6 +27,7 @@ struct Constant
|
||||
{
|
||||
bool valueBoolean;
|
||||
double valueNumber;
|
||||
float valueVector[4];
|
||||
const char* valueString = nullptr; // length stored in stringLength
|
||||
};
|
||||
|
||||
|
@ -4,8 +4,9 @@
|
||||
#include "Luau/LinterConfig.h"
|
||||
#include "Luau/ParseOptions.h"
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
@ -30,6 +31,9 @@ struct Config
|
||||
bool typeErrors = true;
|
||||
|
||||
std::vector<std::string> globals;
|
||||
|
||||
std::vector<std::string> paths;
|
||||
std::unordered_map<std::string, std::string> aliases;
|
||||
};
|
||||
|
||||
struct ConfigResolver
|
||||
@ -50,6 +54,8 @@ std::optional<std::string> parseModeString(Mode& mode, const std::string& modeSt
|
||||
std::optional<std::string> parseLintRuleString(
|
||||
LintOptions& enabledLints, LintOptions& fatalLints, const std::string& warningName, const std::string& value, bool compat = false);
|
||||
|
||||
bool isValidAlias(const std::string& alias);
|
||||
|
||||
std::optional<std::string> parseConfig(const std::string& contents, Config& config, bool compat = false);
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
#include "Luau/Lexer.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -107,6 +109,42 @@ Error parseLintRuleString(LintOptions& enabledLints, LintOptions& fatalLints, co
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool isValidAlias(const std::string& alias)
|
||||
{
|
||||
if (alias.empty())
|
||||
return false;
|
||||
|
||||
bool aliasIsNotAPath = alias != "." && alias != ".." && alias.find_first_of("\\/") == std::string::npos;
|
||||
|
||||
if (!aliasIsNotAPath)
|
||||
return false;
|
||||
|
||||
for (char ch : alias)
|
||||
{
|
||||
bool isupper = 'A' <= ch && ch <= 'Z';
|
||||
bool islower = 'a' <= ch && ch <= 'z';
|
||||
bool isdigit = '0' <= ch && ch <= '9';
|
||||
if (!isupper && !islower && !isdigit && ch != '-' && ch != '_' && ch != '.')
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Error parseAlias(std::unordered_map<std::string, std::string>& aliases, std::string aliasKey, const std::string& aliasValue)
|
||||
{
|
||||
if (!isValidAlias(aliasKey))
|
||||
return Error{"Invalid alias " + aliasKey};
|
||||
|
||||
std::transform(aliasKey.begin(), aliasKey.end(), aliasKey.begin(), [](unsigned char c) {
|
||||
return ('A' <= c && c <= 'Z') ? (c + ('a' - 'A')) : c;
|
||||
});
|
||||
if (!aliases.count(aliasKey))
|
||||
aliases[std::move(aliasKey)] = aliasValue;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static void next(Lexer& lexer)
|
||||
{
|
||||
lexer.next();
|
||||
@ -253,6 +291,13 @@ Error parseConfig(const std::string& contents, Config& config, bool compat)
|
||||
config.globals.push_back(value);
|
||||
return std::nullopt;
|
||||
}
|
||||
else if (keys.size() == 1 && keys[0] == "paths")
|
||||
{
|
||||
config.paths.push_back(value);
|
||||
return std::nullopt;
|
||||
}
|
||||
else if (keys.size() == 2 && keys[0] == "aliases")
|
||||
return parseAlias(config.aliases, keys[1], value);
|
||||
else if (compat && keys.size() == 2 && keys[0] == "language" && keys[1] == "mode")
|
||||
return parseModeString(config.mode, value, compat);
|
||||
else
|
||||
|
6
Makefile
6
Makefile
@ -38,11 +38,11 @@ ISOCLINE_SOURCES=extern/isocline/src/isocline.c
|
||||
ISOCLINE_OBJECTS=$(ISOCLINE_SOURCES:%=$(BUILD)/%.o)
|
||||
ISOCLINE_TARGET=$(BUILD)/libisocline.a
|
||||
|
||||
TESTS_SOURCES=$(wildcard tests/*.cpp) CLI/FileUtils.cpp CLI/Flags.cpp CLI/Profiler.cpp CLI/Coverage.cpp CLI/Repl.cpp
|
||||
TESTS_SOURCES=$(wildcard tests/*.cpp) CLI/FileUtils.cpp CLI/Flags.cpp CLI/Profiler.cpp CLI/Coverage.cpp CLI/Repl.cpp CLI/Require.cpp
|
||||
TESTS_OBJECTS=$(TESTS_SOURCES:%=$(BUILD)/%.o)
|
||||
TESTS_TARGET=$(BUILD)/luau-tests
|
||||
|
||||
REPL_CLI_SOURCES=CLI/FileUtils.cpp CLI/Flags.cpp CLI/Profiler.cpp CLI/Coverage.cpp CLI/Repl.cpp CLI/ReplEntry.cpp
|
||||
REPL_CLI_SOURCES=CLI/FileUtils.cpp CLI/Flags.cpp CLI/Profiler.cpp CLI/Coverage.cpp CLI/Repl.cpp CLI/ReplEntry.cpp CLI/Require.cpp
|
||||
REPL_CLI_OBJECTS=$(REPL_CLI_SOURCES:%=$(BUILD)/%.o)
|
||||
REPL_CLI_TARGET=$(BUILD)/luau
|
||||
|
||||
@ -219,7 +219,7 @@ luau-tests: $(TESTS_TARGET)
|
||||
|
||||
# executable targets
|
||||
$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(CONFIG_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET)
|
||||
$(REPL_CLI_TARGET): $(REPL_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET)
|
||||
$(REPL_CLI_TARGET): $(REPL_CLI_OBJECTS) $(COMPILER_TARGET) $(CONFIG_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET)
|
||||
$(ANALYZE_CLI_TARGET): $(ANALYZE_CLI_OBJECTS) $(ANALYSIS_TARGET) $(AST_TARGET) $(CONFIG_TARGET)
|
||||
$(COMPILE_CLI_TARGET): $(COMPILE_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET)
|
||||
$(BYTECODE_CLI_TARGET): $(BYTECODE_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET)
|
||||
|
@ -92,6 +92,7 @@ target_sources(Luau.CodeGen PRIVATE
|
||||
CodeGen/include/Luau/UnwindBuilder.h
|
||||
CodeGen/include/Luau/UnwindBuilderDwarf2.h
|
||||
CodeGen/include/Luau/UnwindBuilderWin.h
|
||||
CodeGen/include/Luau/BytecodeAnalysis.h
|
||||
CodeGen/include/Luau/BytecodeSummary.h
|
||||
CodeGen/include/luacodegen.h
|
||||
|
||||
@ -125,6 +126,7 @@ target_sources(Luau.CodeGen PRIVATE
|
||||
CodeGen/src/OptimizeFinalX64.cpp
|
||||
CodeGen/src/UnwindBuilderDwarf2.cpp
|
||||
CodeGen/src/UnwindBuilderWin.cpp
|
||||
CodeGen/src/BytecodeAnalysis.cpp
|
||||
CodeGen/src/BytecodeSummary.cpp
|
||||
|
||||
CodeGen/src/BitUtils.h
|
||||
@ -349,7 +351,8 @@ if(TARGET Luau.Repl.CLI)
|
||||
CLI/Profiler.h
|
||||
CLI/Profiler.cpp
|
||||
CLI/Repl.cpp
|
||||
CLI/ReplEntry.cpp)
|
||||
CLI/ReplEntry.cpp
|
||||
CLI/Require.cpp)
|
||||
endif()
|
||||
|
||||
if(TARGET Luau.Analyze.CLI)
|
||||
@ -455,7 +458,7 @@ if(TARGET Luau.UnitTest)
|
||||
tests/TypeInfer.tables.test.cpp
|
||||
tests/TypeInfer.test.cpp
|
||||
tests/TypeInfer.tryUnify.test.cpp
|
||||
tests/TypeInfer.typePacks.cpp
|
||||
tests/TypeInfer.typePacks.test.cpp
|
||||
tests/TypeInfer.typestates.test.cpp
|
||||
tests/TypeInfer.unionTypes.test.cpp
|
||||
tests/TypeInfer.unknownnever.test.cpp
|
||||
@ -489,10 +492,12 @@ if(TARGET Luau.CLI.Test)
|
||||
CLI/Profiler.h
|
||||
CLI/Profiler.cpp
|
||||
CLI/Repl.cpp
|
||||
CLI/Require.cpp
|
||||
|
||||
tests/RegisterCallbacks.h
|
||||
tests/RegisterCallbacks.cpp
|
||||
tests/Repl.test.cpp
|
||||
tests/RequireByString.test.cpp
|
||||
tests/main.cpp)
|
||||
endif()
|
||||
|
||||
|
@ -101,7 +101,7 @@
|
||||
VM_DISPATCH_OP(LOP_FORGLOOP), VM_DISPATCH_OP(LOP_FORGPREP_INEXT), VM_DISPATCH_OP(LOP_DEP_FORGLOOP_INEXT), VM_DISPATCH_OP(LOP_FORGPREP_NEXT), \
|
||||
VM_DISPATCH_OP(LOP_NATIVECALL), VM_DISPATCH_OP(LOP_GETVARARGS), VM_DISPATCH_OP(LOP_DUPCLOSURE), VM_DISPATCH_OP(LOP_PREPVARARGS), \
|
||||
VM_DISPATCH_OP(LOP_LOADKX), VM_DISPATCH_OP(LOP_JUMPX), VM_DISPATCH_OP(LOP_FASTCALL), VM_DISPATCH_OP(LOP_COVERAGE), \
|
||||
VM_DISPATCH_OP(LOP_CAPTURE), VM_DISPATCH_OP(LOP_DEP_JUMPIFEQK), VM_DISPATCH_OP(LOP_DEP_JUMPIFNOTEQK), VM_DISPATCH_OP(LOP_FASTCALL1), \
|
||||
VM_DISPATCH_OP(LOP_CAPTURE), VM_DISPATCH_OP(LOP_SUBRK), VM_DISPATCH_OP(LOP_DIVRK), VM_DISPATCH_OP(LOP_FASTCALL1), \
|
||||
VM_DISPATCH_OP(LOP_FASTCALL2), VM_DISPATCH_OP(LOP_FASTCALL2K), VM_DISPATCH_OP(LOP_FORGPREP), VM_DISPATCH_OP(LOP_JUMPXEQKNIL), \
|
||||
VM_DISPATCH_OP(LOP_JUMPXEQKB), VM_DISPATCH_OP(LOP_JUMPXEQKN), VM_DISPATCH_OP(LOP_JUMPXEQKS), VM_DISPATCH_OP(LOP_IDIV), \
|
||||
VM_DISPATCH_OP(LOP_IDIVK),
|
||||
@ -521,7 +521,7 @@ reentry:
|
||||
|
||||
if (unsigned(ic) < LUA_VECTOR_SIZE && name[1] == '\0')
|
||||
{
|
||||
const float* v = rb->value.v; // silences ubsan when indexing v[]
|
||||
const float* v = vvalue(rb); // silences ubsan when indexing v[]
|
||||
setnvalue(ra, v[ic]);
|
||||
VM_NEXT();
|
||||
}
|
||||
@ -1464,8 +1464,8 @@ reentry:
|
||||
}
|
||||
else if (ttisvector(rb) && ttisvector(rc))
|
||||
{
|
||||
const float* vb = rb->value.v;
|
||||
const float* vc = rc->value.v;
|
||||
const float* vb = vvalue(rb);
|
||||
const float* vc = vvalue(rc);
|
||||
setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2], vb[3] + vc[3]);
|
||||
VM_NEXT();
|
||||
}
|
||||
@ -1510,8 +1510,8 @@ reentry:
|
||||
}
|
||||
else if (ttisvector(rb) && ttisvector(rc))
|
||||
{
|
||||
const float* vb = rb->value.v;
|
||||
const float* vc = rc->value.v;
|
||||
const float* vb = vvalue(rb);
|
||||
const float* vc = vvalue(rc);
|
||||
setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2], vb[3] - vc[3]);
|
||||
VM_NEXT();
|
||||
}
|
||||
@ -1556,22 +1556,22 @@ reentry:
|
||||
}
|
||||
else if (ttisvector(rb) && ttisnumber(rc))
|
||||
{
|
||||
const float* vb = rb->value.v;
|
||||
const float* vb = vvalue(rb);
|
||||
float vc = cast_to(float, nvalue(rc));
|
||||
setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc, vb[3] * vc);
|
||||
VM_NEXT();
|
||||
}
|
||||
else if (ttisvector(rb) && ttisvector(rc))
|
||||
{
|
||||
const float* vb = rb->value.v;
|
||||
const float* vc = rc->value.v;
|
||||
const float* vb = vvalue(rb);
|
||||
const float* vc = vvalue(rc);
|
||||
setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2], vb[3] * vc[3]);
|
||||
VM_NEXT();
|
||||
}
|
||||
else if (ttisnumber(rb) && ttisvector(rc))
|
||||
{
|
||||
float vb = cast_to(float, nvalue(rb));
|
||||
const float* vc = rc->value.v;
|
||||
const float* vc = vvalue(rc);
|
||||
setvvalue(ra, vb * vc[0], vb * vc[1], vb * vc[2], vb * vc[3]);
|
||||
VM_NEXT();
|
||||
}
|
||||
@ -1617,22 +1617,22 @@ reentry:
|
||||
}
|
||||
else if (ttisvector(rb) && ttisnumber(rc))
|
||||
{
|
||||
const float* vb = rb->value.v;
|
||||
const float* vb = vvalue(rb);
|
||||
float vc = cast_to(float, nvalue(rc));
|
||||
setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc, vb[3] / vc);
|
||||
VM_NEXT();
|
||||
}
|
||||
else if (ttisvector(rb) && ttisvector(rc))
|
||||
{
|
||||
const float* vb = rb->value.v;
|
||||
const float* vc = rc->value.v;
|
||||
const float* vb = vvalue(rb);
|
||||
const float* vc = vvalue(rc);
|
||||
setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2], vb[3] / vc[3]);
|
||||
VM_NEXT();
|
||||
}
|
||||
else if (ttisnumber(rb) && ttisvector(rc))
|
||||
{
|
||||
float vb = cast_to(float, nvalue(rb));
|
||||
const float* vc = rc->value.v;
|
||||
const float* vc = vvalue(rc);
|
||||
setvvalue(ra, vb / vc[0], vb / vc[1], vb / vc[2], vb / vc[3]);
|
||||
VM_NEXT();
|
||||
}
|
||||
@ -1812,7 +1812,7 @@ reentry:
|
||||
}
|
||||
else if (ttisvector(rb))
|
||||
{
|
||||
const float* vb = rb->value.v;
|
||||
const float* vb = vvalue(rb);
|
||||
float vc = cast_to(float, nvalue(kv));
|
||||
setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc, vb[3] * vc);
|
||||
VM_NEXT();
|
||||
@ -1858,9 +1858,9 @@ reentry:
|
||||
}
|
||||
else if (ttisvector(rb))
|
||||
{
|
||||
const float* vb = rb->value.v;
|
||||
float vc = cast_to(float, nvalue(kv));
|
||||
setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc, vb[3] / vc);
|
||||
const float* vb = vvalue(rb);
|
||||
float nc = cast_to(float, nvalue(kv));
|
||||
setvvalue(ra, vb[0] / nc, vb[1] / nc, vb[2] / nc, vb[3] / nc);
|
||||
VM_NEXT();
|
||||
}
|
||||
else
|
||||
@ -2071,7 +2071,7 @@ reentry:
|
||||
}
|
||||
else if (ttisvector(rb))
|
||||
{
|
||||
const float* vb = rb->value.v;
|
||||
const float* vb = vvalue(rb);
|
||||
setvvalue(ra, -vb[0], -vb[1], -vb[2], -vb[3]);
|
||||
VM_NEXT();
|
||||
}
|
||||
@ -2697,16 +2697,53 @@ reentry:
|
||||
LUAU_UNREACHABLE();
|
||||
}
|
||||
|
||||
VM_CASE(LOP_DEP_JUMPIFEQK)
|
||||
VM_CASE(LOP_SUBRK)
|
||||
{
|
||||
LUAU_ASSERT(!"Unsupported deprecated opcode");
|
||||
LUAU_UNREACHABLE();
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
TValue* kv = VM_KV(LUAU_INSN_B(insn));
|
||||
StkId rc = VM_REG(LUAU_INSN_C(insn));
|
||||
|
||||
// fast-path
|
||||
if (ttisnumber(rc))
|
||||
{
|
||||
setnvalue(ra, nvalue(kv) - nvalue(rc));
|
||||
VM_NEXT();
|
||||
}
|
||||
else
|
||||
{
|
||||
// slow-path, may invoke C/Lua via metamethods
|
||||
VM_PROTECT(luaV_doarith(L, ra, kv, rc, TM_SUB));
|
||||
VM_NEXT();
|
||||
}
|
||||
}
|
||||
|
||||
VM_CASE(LOP_DEP_JUMPIFNOTEQK)
|
||||
VM_CASE(LOP_DIVRK)
|
||||
{
|
||||
LUAU_ASSERT(!"Unsupported deprecated opcode");
|
||||
LUAU_UNREACHABLE();
|
||||
Instruction insn = *pc++;
|
||||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
TValue* kv = VM_KV(LUAU_INSN_B(insn));
|
||||
StkId rc = VM_REG(LUAU_INSN_C(insn));
|
||||
|
||||
// fast-path
|
||||
if (LUAU_LIKELY(ttisnumber(rc)))
|
||||
{
|
||||
setnvalue(ra, nvalue(kv) / nvalue(rc));
|
||||
VM_NEXT();
|
||||
}
|
||||
else if (ttisvector(rc))
|
||||
{
|
||||
float nb = cast_to(float, nvalue(kv));
|
||||
const float* vc = vvalue(rc);
|
||||
setvvalue(ra, nb / vc[0], nb / vc[1], nb / vc[2], nb / vc[3]);
|
||||
VM_NEXT();
|
||||
}
|
||||
else
|
||||
{
|
||||
// slow-path, may invoke C/Lua via metamethods
|
||||
VM_PROTECT(luaV_doarith(L, ra, kv, rc, TM_DIV));
|
||||
VM_NEXT();
|
||||
}
|
||||
}
|
||||
|
||||
VM_CASE(LOP_FASTCALL1)
|
||||
|
@ -287,6 +287,17 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
|
||||
break;
|
||||
}
|
||||
|
||||
case LBC_CONSTANT_VECTOR:
|
||||
{
|
||||
float x = read<float>(data, size, offset);
|
||||
float y = read<float>(data, size, offset);
|
||||
float z = read<float>(data, size, offset);
|
||||
float w = read<float>(data, size, offset);
|
||||
(void)w;
|
||||
setvvalue(&p->k[j], x, y, z, w);
|
||||
break;
|
||||
}
|
||||
|
||||
case LBC_CONSTANT_STRING:
|
||||
{
|
||||
TString* v = readString(strings, data, size, offset);
|
||||
|
@ -46,7 +46,7 @@ int luaV_tostring(lua_State* L, StkId obj)
|
||||
const float* luaV_tovector(const TValue* obj)
|
||||
{
|
||||
if (ttisvector(obj))
|
||||
return obj->value.v;
|
||||
return vvalue(obj);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
105
fuzz/CMakeLists.txt
Normal file
105
fuzz/CMakeLists.txt
Normal file
@ -0,0 +1,105 @@
|
||||
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
cmake_minimum_required(VERSION 3.26)
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
cmake_policy(SET CMP0054 NEW)
|
||||
cmake_policy(SET CMP0058 NEW)
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
cmake_policy(SET CMP0077 NEW)
|
||||
cmake_policy(SET CMP0091 NEW)
|
||||
|
||||
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
message(WARNING "Building the Luau fuzzer requires Clang to be used. AppleClang is not sufficient.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64")
|
||||
message(WARNING "Building the Luau fuzzer for ARM64 is currently unsupported.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# protobuf / std integer types vary based on platform; disable sign-compare
|
||||
# warnings for portability.
|
||||
set(FUZZ_COMPILE_OPTIONS ${LUAU_OPTIONS} -fsanitize=address,fuzzer -g2 -Wno-sign-compare)
|
||||
set(FUZZ_LINK_OPTIONS ${LUAU_OPTIONS} -fsanitize=address,fuzzer)
|
||||
|
||||
FetchContent_Declare(
|
||||
ProtobufMutator
|
||||
GIT_REPOSITORY https://github.com/google/libprotobuf-mutator
|
||||
GIT_TAG 212a7be1eb08e7f9c79732d2aab9b2097085d936
|
||||
# libprotobuf-mutator unconditionally configures its examples, but this
|
||||
# doesn't actually work with how we're building Protobuf from source. This
|
||||
# patch disables configuration of the examples.
|
||||
PATCH_COMMAND
|
||||
git apply
|
||||
--reverse
|
||||
--check
|
||||
--ignore-space-change
|
||||
--ignore-whitespace
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libprotobuf-mutator-patch.patch"
|
||||
||
|
||||
git apply
|
||||
--ignore-space-change
|
||||
--ignore-whitespace
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libprotobuf-mutator-patch.patch"
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
Protobuf
|
||||
GIT_REPOSITORY https://github.com/protocolbuffers/protobuf.git
|
||||
# Needs to match the Protobuf version that libprotobuf-mutator is written for, roughly.
|
||||
GIT_TAG v22.3
|
||||
GIT_SHALLOW ON
|
||||
|
||||
# libprotobuf-mutator will need to be able to find this at configuration
|
||||
# time.
|
||||
OVERRIDE_FIND_PACKAGE
|
||||
)
|
||||
|
||||
set(protobuf_BUILD_TESTS OFF)
|
||||
set(protobuf_BUILD_SHARED_LIBS OFF)
|
||||
# libprotobuf-mutator relies on older module support.
|
||||
set(protobuf_MODULE_COMPATIBLE ON)
|
||||
|
||||
find_package(Protobuf CONFIG REQUIRED)
|
||||
|
||||
# libprotobuf-mutator happily ignores CMP0077 because of its minimum version
|
||||
# requirement. To override that, we set the policy default here.
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
||||
set(LIB_PROTO_MUTATOR_TESTING OFF)
|
||||
|
||||
FetchContent_MakeAvailable(ProtobufMutator)
|
||||
|
||||
# This patches around the fact that find_package isn't going to set the right
|
||||
# values for libprotobuf-mutator to link against protobuf libraries.
|
||||
target_link_libraries(protobuf-mutator-libfuzzer protobuf::libprotobuf)
|
||||
target_link_libraries(protobuf-mutator protobuf::libprotobuf)
|
||||
|
||||
set(LUAU_PB_DIR ${CMAKE_CURRENT_BINARY_DIR}/protobuf)
|
||||
set(LUAU_PB_SOURCES ${LUAU_PB_DIR}/luau.pb.cc ${LUAU_PB_DIR}/luau.pb.h)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${LUAU_PB_SOURCES}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${LUAU_PB_DIR}
|
||||
COMMAND $<TARGET_FILE:protobuf::protoc> ${CMAKE_CURRENT_SOURCE_DIR}/luau.proto --proto_path=${CMAKE_CURRENT_SOURCE_DIR} --cpp_out=${LUAU_PB_DIR}
|
||||
DEPENDS protobuf::protoc ${CMAKE_CURRENT_SOURCE_DIR}/luau.proto
|
||||
)
|
||||
|
||||
add_executable(Luau.Fuzz.Proto)
|
||||
target_compile_options(Luau.Fuzz.Proto PRIVATE ${FUZZ_COMPILE_OPTIONS})
|
||||
target_link_options(Luau.Fuzz.Proto PRIVATE ${FUZZ_LINK_OPTIONS})
|
||||
target_compile_features(Luau.Fuzz.Proto PRIVATE cxx_std_17)
|
||||
target_include_directories(Luau.Fuzz.Proto PRIVATE ${LUAU_PB_DIR} ${protobufmutator_SOURCE_DIR})
|
||||
target_sources(Luau.Fuzz.Proto PRIVATE ${LUAU_PB_SOURCES} proto.cpp protoprint.cpp)
|
||||
target_link_libraries(Luau.Fuzz.Proto PRIVATE protobuf::libprotobuf protobuf-mutator-libfuzzer protobuf-mutator Luau.Analysis Luau.Compiler Luau.Ast Luau.Config Luau.VM Luau.CodeGen)
|
||||
set_target_properties(Luau.Fuzz.Proto PROPERTIES CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF OUTPUT_NAME fuzz-proto)
|
||||
|
||||
add_executable(Luau.Fuzz.ProtoTest)
|
||||
target_compile_options(Luau.Fuzz.ProtoTest PRIVATE ${FUZZ_COMPILE_OPTIONS})
|
||||
target_link_options(Luau.Fuzz.ProtoTest PRIVATE ${FUZZ_LINK_OPTIONS})
|
||||
target_compile_features(Luau.Fuzz.ProtoTest PRIVATE cxx_std_17)
|
||||
target_include_directories(Luau.Fuzz.ProtoTest PRIVATE ${LUAU_PB_DIR} ${protobufmutator_SOURCE_DIR})
|
||||
target_sources(Luau.Fuzz.ProtoTest PRIVATE ${LUAU_PB_SOURCES} prototest.cpp protoprint.cpp)
|
||||
target_link_libraries(Luau.Fuzz.ProtoTest PRIVATE protobuf::libprotobuf protobuf-mutator-libfuzzer protobuf-mutator)
|
||||
set_target_properties(Luau.Fuzz.ProtoTest PROPERTIES CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF OUTPUT_NAME fuzz-prototest)
|
12
fuzz/libprotobuf-mutator-patch.patch
Normal file
12
fuzz/libprotobuf-mutator-patch.patch
Normal file
@ -0,0 +1,12 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 4805c82..9f0df5c 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -149,7 +149,6 @@ add_subdirectory(src)
|
||||
|
||||
if (NOT "${LIB_PROTO_MUTATOR_FUZZER_LIBRARIES}" STREQUAL "" OR
|
||||
NOT "${FUZZING_FLAGS}" STREQUAL "")
|
||||
- add_subdirectory(examples EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
install(EXPORT libprotobuf-mutatorTargets FILE libprotobuf-mutatorTargets.cmake
|
@ -11,12 +11,14 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauClipExtraHasEndProps);
|
||||
|
||||
struct JsonEncoderFixture
|
||||
{
|
||||
Allocator allocator;
|
||||
AstNameTable names{allocator};
|
||||
|
||||
ScopedFastFlag sff{"LuauClipExtraHasEndProps", true};
|
||||
ScopedFastFlag sff{FFlag::LuauClipExtraHasEndProps, true};
|
||||
|
||||
ParseResult parse(std::string_view src)
|
||||
{
|
||||
@ -93,7 +95,7 @@ TEST_CASE("basic_escaping")
|
||||
|
||||
TEST_CASE("encode_AstStatBlock")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauClipExtraHasEndProps", true};
|
||||
ScopedFastFlag sff{FFlag::LuauClipExtraHasEndProps, true};
|
||||
|
||||
AstLocal astlocal{AstName{"a_local"}, Location(), nullptr, 0, 0, nullptr};
|
||||
AstLocal* astlocalarray[] = {&astlocal};
|
||||
|
@ -15,6 +15,8 @@
|
||||
|
||||
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
|
||||
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
|
||||
LUAU_FASTFLAG(LuauAutocompleteStringLiteralBounds);
|
||||
LUAU_FASTFLAG(LuauAutocompleteDoEnd);
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
@ -978,7 +980,7 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_end_with_lambda")
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_end_of_do_block")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauAutocompleteDoEnd", true};
|
||||
ScopedFastFlag sff{FFlag::LuauAutocompleteDoEnd, true};
|
||||
|
||||
check("do @1");
|
||||
|
||||
@ -3105,7 +3107,7 @@ TEST_CASE_FIXTURE(ACFixture, "string_singleton_as_table_key")
|
||||
// https://github.com/Roblox/luau/issues/858
|
||||
TEST_CASE_FIXTURE(ACFixture, "string_singleton_in_if_statement")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauAutocompleteStringLiteralBounds", true};
|
||||
ScopedFastFlag sff{FFlag::LuauAutocompleteStringLiteralBounds, true};
|
||||
|
||||
check(R"(
|
||||
--!strict
|
||||
|
@ -15,14 +15,28 @@ namespace Luau
|
||||
std::string rep(const std::string& s, size_t n);
|
||||
}
|
||||
|
||||
LUAU_FASTFLAG(LuauVectorLiterals)
|
||||
LUAU_FASTFLAG(LuauCompileRevK)
|
||||
LUAU_FASTINT(LuauCompileInlineDepth)
|
||||
LUAU_FASTINT(LuauCompileInlineThreshold)
|
||||
LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost)
|
||||
LUAU_FASTINT(LuauCompileLoopUnrollThreshold)
|
||||
LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost)
|
||||
LUAU_FASTINT(LuauRecursionLimit)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
static std::string compileFunction(const char* source, uint32_t id, int optimizationLevel = 1)
|
||||
static std::string compileFunction(const char* source, uint32_t id, int optimizationLevel = 1, bool enableVectors = false)
|
||||
{
|
||||
Luau::BytecodeBuilder bcb;
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
|
||||
Luau::CompileOptions options;
|
||||
options.optimizationLevel = optimizationLevel;
|
||||
if (enableVectors)
|
||||
{
|
||||
options.vectorLib = "Vector3";
|
||||
options.vectorCtor = "new";
|
||||
}
|
||||
Luau::compileOrThrow(bcb, source, options);
|
||||
|
||||
return bcb.dumpFunction(id);
|
||||
@ -1168,6 +1182,8 @@ RETURN R0 1
|
||||
|
||||
TEST_CASE("AndOrChainCodegen")
|
||||
{
|
||||
ScopedFastFlag sff(FFlag::LuauCompileRevK, true);
|
||||
|
||||
const char* source = R"(
|
||||
return
|
||||
(1 - verticalGradientTurbulence < waterLevel + .015 and Enum.Material.Sand)
|
||||
@ -1176,23 +1192,22 @@ TEST_CASE("AndOrChainCodegen")
|
||||
)";
|
||||
|
||||
CHECK_EQ("\n" + compileFunction0(source), R"(
|
||||
LOADN R2 1
|
||||
GETIMPORT R3 1 [verticalGradientTurbulence]
|
||||
SUB R1 R2 R3
|
||||
GETIMPORT R3 4 [waterLevel]
|
||||
ADDK R2 R3 K2 [0.014999999999999999]
|
||||
GETIMPORT R2 2 [verticalGradientTurbulence]
|
||||
SUBRK R1 K0 [1] R2
|
||||
GETIMPORT R3 5 [waterLevel]
|
||||
ADDK R2 R3 K3 [0.014999999999999999]
|
||||
JUMPIFNOTLT R1 R2 L0
|
||||
GETIMPORT R0 8 [Enum.Material.Sand]
|
||||
GETIMPORT R0 9 [Enum.Material.Sand]
|
||||
JUMPIF R0 L2
|
||||
L0: GETIMPORT R1 10 [sandbank]
|
||||
L0: GETIMPORT R1 11 [sandbank]
|
||||
LOADN R2 0
|
||||
JUMPIFNOTLT R2 R1 L1
|
||||
GETIMPORT R1 10 [sandbank]
|
||||
GETIMPORT R1 11 [sandbank]
|
||||
LOADN R2 1
|
||||
JUMPIFNOTLT R1 R2 L1
|
||||
GETIMPORT R0 8 [Enum.Material.Sand]
|
||||
GETIMPORT R0 9 [Enum.Material.Sand]
|
||||
JUMPIF R0 L2
|
||||
L1: GETIMPORT R0 12 [Enum.Material.Sandstone]
|
||||
L1: GETIMPORT R0 13 [Enum.Material.Sandstone]
|
||||
L2: RETURN R0 1
|
||||
)");
|
||||
}
|
||||
@ -1980,7 +1995,7 @@ RETURN R0 0
|
||||
|
||||
TEST_CASE("LoopContinueCorrectlyHandlesImplicitConstantAfterUnroll")
|
||||
{
|
||||
ScopedFastInt sfi("LuauCompileLoopUnrollThreshold", 200);
|
||||
ScopedFastInt sfi(FInt::LuauCompileLoopUnrollThreshold, 200);
|
||||
|
||||
// access to implicit constant that depends on the unrolled loop constant is still invalid even though we can constant-propagate it
|
||||
try
|
||||
@ -2091,6 +2106,8 @@ RETURN R0 0
|
||||
|
||||
TEST_CASE("AndOrOptimizations")
|
||||
{
|
||||
ScopedFastFlag sff(FFlag::LuauCompileRevK, true);
|
||||
|
||||
// the OR/ORK optimization triggers for cutoff since lhs is simple
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
local function advancedRidgedFilter(value, cutoff)
|
||||
@ -2103,17 +2120,15 @@ end
|
||||
R"(
|
||||
ORK R2 R1 K0 [0.5]
|
||||
SUB R0 R0 R2
|
||||
LOADN R4 1
|
||||
LOADN R8 0
|
||||
JUMPIFNOTLT R0 R8 L0
|
||||
MINUS R7 R0
|
||||
JUMPIF R7 L1
|
||||
L0: MOVE R7 R0
|
||||
L1: MULK R6 R7 K1 [1]
|
||||
LOADN R8 1
|
||||
SUB R7 R8 R2
|
||||
DIV R5 R6 R7
|
||||
SUB R3 R4 R5
|
||||
LOADN R7 0
|
||||
JUMPIFNOTLT R0 R7 L0
|
||||
MINUS R6 R0
|
||||
JUMPIF R6 L1
|
||||
L0: MOVE R6 R0
|
||||
L1: MULK R5 R6 K1 [1]
|
||||
SUBRK R6 K1 [1] R2
|
||||
DIV R4 R5 R6
|
||||
SUBRK R3 K1 [1] R4
|
||||
RETURN R3 1
|
||||
)");
|
||||
|
||||
@ -2126,9 +2141,8 @@ end
|
||||
0),
|
||||
R"(
|
||||
LOADB R2 0
|
||||
LOADK R4 K0 [0.5]
|
||||
MULK R5 R1 K1 [0.40000000000000002]
|
||||
SUB R3 R4 R5
|
||||
MULK R4 R1 K1 [0.40000000000000002]
|
||||
SUBRK R3 K0 [0.5] R4
|
||||
JUMPIFNOTLT R3 R0 L1
|
||||
LOADK R4 K0 [0.5]
|
||||
MULK R5 R1 K1 [0.40000000000000002]
|
||||
@ -2148,9 +2162,8 @@ end
|
||||
0),
|
||||
R"(
|
||||
LOADB R2 1
|
||||
LOADK R4 K0 [0.5]
|
||||
MULK R5 R1 K1 [0.40000000000000002]
|
||||
SUB R3 R4 R5
|
||||
MULK R4 R1 K1 [0.40000000000000002]
|
||||
SUBRK R3 K0 [0.5] R4
|
||||
JUMPIFLT R0 R3 L1
|
||||
LOADK R4 K0 [0.5]
|
||||
MULK R5 R1 K1 [0.40000000000000002]
|
||||
@ -2293,9 +2306,9 @@ TEST_CASE("RecursionParse")
|
||||
// The test forcibly pushes the stack limit during compilation; in NoOpt, the stack consumption is much larger so we need to reduce the limit to
|
||||
// not overflow the C stack. When ASAN is enabled, stack consumption increases even more.
|
||||
#if defined(LUAU_ENABLE_ASAN)
|
||||
ScopedFastInt flag("LuauRecursionLimit", 200);
|
||||
ScopedFastInt flag(FInt::LuauRecursionLimit, 200);
|
||||
#elif defined(_NOOPT) || defined(_DEBUG)
|
||||
ScopedFastInt flag("LuauRecursionLimit", 300);
|
||||
ScopedFastInt flag(FInt::LuauRecursionLimit, 300);
|
||||
#endif
|
||||
|
||||
Luau::BytecodeBuilder bcb;
|
||||
@ -4475,6 +4488,41 @@ L0: RETURN R0 -1
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("VectorLiterals")
|
||||
{
|
||||
ScopedFastFlag sff(FFlag::LuauVectorLiterals, true);
|
||||
|
||||
CHECK_EQ("\n" + compileFunction("return Vector3.new(1, 2, 3)", 0, 2, /*enableVectors*/ true), R"(
|
||||
LOADK R0 K0 [1, 2, 3]
|
||||
RETURN R0 1
|
||||
)");
|
||||
|
||||
CHECK_EQ("\n" + compileFunction("print(Vector3.new(1, 2, 3))", 0, 2, /*enableVectors*/ true), R"(
|
||||
GETIMPORT R0 1 [print]
|
||||
LOADK R1 K2 [1, 2, 3]
|
||||
CALL R0 1 0
|
||||
RETURN R0 0
|
||||
)");
|
||||
|
||||
CHECK_EQ("\n" + compileFunction("print(Vector3.new(1, 2, 3, 4))", 0, 2, /*enableVectors*/ true), R"(
|
||||
GETIMPORT R0 1 [print]
|
||||
LOADK R1 K2 [1, 2, 3, 4]
|
||||
CALL R0 1 0
|
||||
RETURN R0 0
|
||||
)");
|
||||
|
||||
CHECK_EQ("\n" + compileFunction("return Vector3.new(0, 0, 0), Vector3.new(-0, 0, 0)", 0, 2, /*enableVectors*/ true), R"(
|
||||
LOADK R0 K0 [0, 0, 0]
|
||||
LOADK R1 K1 [-0, 0, 0]
|
||||
RETURN R0 2
|
||||
)");
|
||||
|
||||
CHECK_EQ("\n" + compileFunction("return type(Vector3.new(0, 0, 0))", 0, 2, /*enableVectors*/ true), R"(
|
||||
LOADK R0 K0 ['vector']
|
||||
RETURN R0 1
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("TypeAssertion")
|
||||
{
|
||||
// validate that type assertions work with the compiler and that the code inside type assertion isn't evaluated
|
||||
@ -4754,8 +4802,8 @@ L1: RETURN R0 0
|
||||
TEST_CASE("LoopUnrollControlFlow")
|
||||
{
|
||||
ScopedFastInt sfis[] = {
|
||||
{"LuauCompileLoopUnrollThreshold", 50},
|
||||
{"LuauCompileLoopUnrollThresholdMaxBoost", 300},
|
||||
{FInt::LuauCompileLoopUnrollThreshold, 50},
|
||||
{FInt::LuauCompileLoopUnrollThresholdMaxBoost, 300},
|
||||
};
|
||||
|
||||
// break jumps to the end
|
||||
@ -4891,8 +4939,8 @@ RETURN R0 0
|
||||
TEST_CASE("LoopUnrollCost")
|
||||
{
|
||||
ScopedFastInt sfis[] = {
|
||||
{"LuauCompileLoopUnrollThreshold", 25},
|
||||
{"LuauCompileLoopUnrollThresholdMaxBoost", 300},
|
||||
{FInt::LuauCompileLoopUnrollThreshold, 25},
|
||||
{FInt::LuauCompileLoopUnrollThresholdMaxBoost, 300},
|
||||
};
|
||||
|
||||
// loops with short body
|
||||
@ -5069,8 +5117,8 @@ L1: RETURN R0 0
|
||||
TEST_CASE("LoopUnrollCostBuiltins")
|
||||
{
|
||||
ScopedFastInt sfis[] = {
|
||||
{"LuauCompileLoopUnrollThreshold", 25},
|
||||
{"LuauCompileLoopUnrollThresholdMaxBoost", 300},
|
||||
{FInt::LuauCompileLoopUnrollThreshold, 25},
|
||||
{FInt::LuauCompileLoopUnrollThresholdMaxBoost, 300},
|
||||
};
|
||||
|
||||
// this loop uses builtins and is close to the cost budget so it's important that we model builtins as cheaper than regular calls
|
||||
@ -5947,9 +5995,9 @@ RETURN R3 1
|
||||
TEST_CASE("InlineThresholds")
|
||||
{
|
||||
ScopedFastInt sfis[] = {
|
||||
{"LuauCompileInlineThreshold", 25},
|
||||
{"LuauCompileInlineThresholdMaxBoost", 300},
|
||||
{"LuauCompileInlineDepth", 2},
|
||||
{FInt::LuauCompileInlineThreshold, 25},
|
||||
{FInt::LuauCompileInlineThresholdMaxBoost, 300},
|
||||
{FInt::LuauCompileInlineDepth, 2},
|
||||
};
|
||||
|
||||
// this function has enormous register pressure (50 regs) so we choose not to inline it
|
||||
@ -7686,8 +7734,6 @@ RETURN R1 1
|
||||
|
||||
TEST_CASE("SideEffects")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileSideEffects", true);
|
||||
|
||||
// we do not evaluate expressions in some cases when we know they can't carry side effects
|
||||
CHECK_EQ("\n" + compileFunction0(R"(
|
||||
local x = 5, print
|
||||
@ -7739,9 +7785,6 @@ RETURN R0 0
|
||||
|
||||
TEST_CASE("IfElimination")
|
||||
{
|
||||
ScopedFastFlag sff1("LuauCompileDeadIf", true);
|
||||
ScopedFastFlag sff2("LuauCompileSideEffects", true);
|
||||
|
||||
// if the left hand side of a condition is constant, it constant folds and we don't emit the branch
|
||||
CHECK_EQ("\n" + compileFunction0("local a = false if a and b then b() end"), R"(
|
||||
RETURN R0 0
|
||||
@ -7807,4 +7850,32 @@ RETURN R0 1
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("ArithRevK")
|
||||
{
|
||||
ScopedFastFlag sff(FFlag::LuauCompileRevK, true);
|
||||
|
||||
// - and / have special optimized form for reverse constants; in the future, + and * will likely get compiled to ADDK/MULK
|
||||
// other operators are not important enough to optimize reverse constant forms for
|
||||
CHECK_EQ("\n" + compileFunction0(R"(
|
||||
local x: number = unknown
|
||||
return 2 + x, 2 - x, 2 * x, 2 / x, 2 % x, 2 // x, 2 ^ x
|
||||
)"),
|
||||
R"(
|
||||
GETIMPORT R0 1 [unknown]
|
||||
LOADN R2 2
|
||||
ADD R1 R2 R0
|
||||
SUBRK R2 K2 [2] R0
|
||||
LOADN R4 2
|
||||
MUL R3 R4 R0
|
||||
DIVRK R4 K2 [2] R0
|
||||
LOADN R6 2
|
||||
MOD R5 R6 R0
|
||||
LOADN R7 2
|
||||
IDIV R6 R7 R0
|
||||
LOADN R8 2
|
||||
POW R7 R8 R0
|
||||
RETURN R1 7
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -26,6 +26,14 @@ extern bool verbose;
|
||||
extern bool codegen;
|
||||
extern int optimizationLevel;
|
||||
|
||||
LUAU_FASTFLAG(LuauBit32Byteswap);
|
||||
LUAU_FASTFLAG(LuauBufferBetterMsg);
|
||||
LUAU_FASTFLAG(LuauBufferDefinitions);
|
||||
LUAU_FASTFLAG(LuauCodeGenFixByteLower);
|
||||
LUAU_FASTFLAG(LuauCompileBufferAnnotation);
|
||||
LUAU_FASTFLAG(LuauLoopInterruptFix);
|
||||
LUAU_DYNAMIC_FASTFLAG(LuauStricterUtf8);
|
||||
|
||||
static lua_CompileOptions defaultOptions()
|
||||
{
|
||||
lua_CompileOptions copts = {};
|
||||
@ -288,7 +296,9 @@ static std::vector<Luau::CodeGen::FunctionBytecodeSummary> analyzeFile(const cha
|
||||
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
|
||||
lua_State* L = globalState.get();
|
||||
|
||||
LUAU_ASSERT(luau_load(L, "source", bytecode.data(), bytecode.size(), 0) == 0);
|
||||
int result = luau_load(L, "source", bytecode.data(), bytecode.size(), 0);
|
||||
REQUIRE(result == 0);
|
||||
|
||||
return Luau::CodeGen::summarizeBytecode(L, -1, nestingLimit);
|
||||
}
|
||||
|
||||
@ -312,8 +322,8 @@ TEST_CASE("Basic")
|
||||
|
||||
TEST_CASE("Buffers")
|
||||
{
|
||||
ScopedFastFlag luauBufferBetterMsg{"LuauBufferBetterMsg", true};
|
||||
ScopedFastFlag luauCodeGenFixByteLower{"LuauCodeGenFixByteLower", true};
|
||||
ScopedFastFlag luauBufferBetterMsg{FFlag::LuauBufferBetterMsg, true};
|
||||
ScopedFastFlag luauCodeGenFixByteLower{FFlag::LuauCodeGenFixByteLower, true};
|
||||
|
||||
runConformance("buffers.lua");
|
||||
}
|
||||
@ -429,13 +439,13 @@ TEST_CASE("GC")
|
||||
|
||||
TEST_CASE("Bitwise")
|
||||
{
|
||||
ScopedFastFlag sffs{"LuauBit32Byteswap", true};
|
||||
ScopedFastFlag sffs{FFlag::LuauBit32Byteswap, true};
|
||||
runConformance("bitwise.lua");
|
||||
}
|
||||
|
||||
TEST_CASE("UTF8")
|
||||
{
|
||||
ScopedFastFlag sff("LuauStricterUtf8", true);
|
||||
ScopedFastFlag sff(DFFlag::LuauStricterUtf8, true);
|
||||
runConformance("utf8.lua");
|
||||
}
|
||||
|
||||
@ -580,7 +590,7 @@ static void populateRTTI(lua_State* L, Luau::TypeId type)
|
||||
|
||||
TEST_CASE("Types")
|
||||
{
|
||||
ScopedFastFlag luauBufferDefinitions{"LuauBufferDefinitions", true};
|
||||
ScopedFastFlag luauBufferDefinitions{FFlag::LuauBufferDefinitions, true};
|
||||
|
||||
runConformance("types.lua", [](lua_State* L) {
|
||||
Luau::NullModuleResolver moduleResolver;
|
||||
@ -1528,6 +1538,8 @@ TEST_CASE("GCDump")
|
||||
|
||||
TEST_CASE("Interrupt")
|
||||
{
|
||||
ScopedFastFlag luauLoopInterruptFix{FFlag::LuauLoopInterruptFix, true};
|
||||
|
||||
lua_CompileOptions copts = defaultOptions();
|
||||
copts.optimizationLevel = 1; // disable loop unrolling to get fixed expected hit results
|
||||
|
||||
@ -1586,12 +1598,12 @@ TEST_CASE("Interrupt")
|
||||
if (gc >= 0)
|
||||
return;
|
||||
|
||||
CHECK(index < 10);
|
||||
if (++index == 10)
|
||||
CHECK(index < 11);
|
||||
if (++index == 11)
|
||||
lua_yield(L, 0);
|
||||
};
|
||||
|
||||
for (int test = 1; test <= 9; ++test)
|
||||
for (int test = 1; test <= 10; ++test)
|
||||
{
|
||||
lua_State* T = lua_newthread(L);
|
||||
|
||||
@ -1601,7 +1613,7 @@ TEST_CASE("Interrupt")
|
||||
index = 0;
|
||||
int status = lua_resume(T, nullptr, 0);
|
||||
CHECK(status == LUA_YIELD);
|
||||
CHECK(index == 10);
|
||||
CHECK(index == 11);
|
||||
|
||||
// abandon the thread
|
||||
lua_pop(L, 1);
|
||||
@ -1913,8 +1925,6 @@ TEST_CASE("SafeEnv")
|
||||
|
||||
TEST_CASE("Native")
|
||||
{
|
||||
ScopedFastFlag luauLowerAltLoopForn{"LuauLowerAltLoopForn", true};
|
||||
|
||||
runConformance("native.lua");
|
||||
}
|
||||
|
||||
@ -1924,7 +1934,7 @@ TEST_CASE("NativeTypeAnnotations")
|
||||
if (!codegen || !luau_codegen_supported())
|
||||
return;
|
||||
|
||||
ScopedFastFlag luauCompileBufferAnnotation{"LuauCompileBufferAnnotation", true};
|
||||
ScopedFastFlag luauCompileBufferAnnotation{FFlag::LuauCompileBufferAnnotation, true};
|
||||
|
||||
lua_CompileOptions copts = defaultOptions();
|
||||
copts.vectorCtor = "vector";
|
||||
|
@ -1,5 +1,8 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "ConstraintGeneratorFixture.h"
|
||||
#include "ScopedFlags.h"
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -7,7 +10,7 @@ namespace Luau
|
||||
ConstraintGeneratorFixture::ConstraintGeneratorFixture()
|
||||
: Fixture()
|
||||
, mainModule(new Module)
|
||||
, forceTheFlag{"DebugLuauDeferredConstraintResolution", true}
|
||||
, forceTheFlag{FFlag::DebugLuauDeferredConstraintResolution, true}
|
||||
{
|
||||
mainModule->name = "MainModule";
|
||||
mainModule->humanReadableName = "MainModule";
|
||||
|
@ -10,10 +10,12 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
struct DataFlowGraphFixture
|
||||
{
|
||||
// Only needed to fix the operator== reflexivity of an empty Symbol.
|
||||
ScopedFastFlag dcr{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag dcr{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
InternalErrorReporter handle;
|
||||
|
||||
|
@ -590,7 +590,7 @@ TEST_CASE_FIXTURE(DifferFixture, "equal_table_cyclic_diamonds_unraveled")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_function_cyclic")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo()
|
||||
@ -611,7 +611,7 @@ TEST_CASE_FIXTURE(DifferFixture, "equal_function_cyclic")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_function_table_cyclic")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo()
|
||||
@ -638,7 +638,7 @@ TEST_CASE_FIXTURE(DifferFixture, "equal_function_table_cyclic")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_table_self_referential_cyclic")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
// ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
// ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo()
|
||||
@ -685,7 +685,7 @@ TEST_CASE_FIXTURE(DifferFixture, "equal_union_cyclic")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_intersection_cyclic")
|
||||
{
|
||||
// Old solver does not correctly refine test types
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo1(x: number)
|
||||
@ -883,7 +883,7 @@ TEST_CASE_FIXTURE(DifferFixture, "intersection_tables_missing_left")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_function")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(x: number)
|
||||
@ -901,7 +901,7 @@ TEST_CASE_FIXTURE(DifferFixture, "equal_function")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_function_inferred_ret_length")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function bar(x: number, y: string)
|
||||
@ -925,7 +925,7 @@ TEST_CASE_FIXTURE(DifferFixture, "equal_function_inferred_ret_length")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_function_inferred_ret_length_2")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function bar(x: number, y: string)
|
||||
@ -946,7 +946,7 @@ TEST_CASE_FIXTURE(DifferFixture, "equal_function_inferred_ret_length_2")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_arg_normal")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(x: number, y: number, z: number)
|
||||
@ -965,7 +965,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_normal")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_arg_normal_2")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(x: number, y: number, z: string)
|
||||
@ -984,7 +984,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_normal_2")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_ret_normal")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(x: number, y: number, z: string)
|
||||
@ -1003,7 +1003,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_ret_normal")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_arg_length")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(x: number, y: number)
|
||||
@ -1022,7 +1022,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_length")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_2")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(x: number, y: string, z: number)
|
||||
@ -1041,7 +1041,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_2")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_none")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo()
|
||||
@ -1060,7 +1060,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_none")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_none_2")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(x: number)
|
||||
@ -1079,7 +1079,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_none_2")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_ret_length")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(x: number, y: number)
|
||||
@ -1098,7 +1098,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_ret_length")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_2")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(x: number, y: string, z: number)
|
||||
@ -1117,7 +1117,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_2")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_none")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(x: number, y: string)
|
||||
@ -1136,7 +1136,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_none")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_none_2")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo()
|
||||
@ -1155,7 +1155,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_none_2")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_normal")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(x: number, y: string, ...: number)
|
||||
@ -1174,7 +1174,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_normal")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_missing")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(x: number, y: string, ...: number)
|
||||
@ -1193,7 +1193,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_missing")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_missing_2")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(x: number, y: string)
|
||||
@ -1212,7 +1212,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_missing_2")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_variadic_oversaturation")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
-- allowed to be oversaturated
|
||||
@ -1231,7 +1231,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_variadic_oversaturation")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "function_variadic_oversaturation_2")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
-- must not be oversaturated
|
||||
@ -1250,7 +1250,7 @@ TEST_CASE_FIXTURE(DifferFixture, "function_variadic_oversaturation_2")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "generic")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(x, y)
|
||||
@ -1269,7 +1269,7 @@ TEST_CASE_FIXTURE(DifferFixture, "generic")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "generic_one_vs_two")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo<X>(x: X, y: X)
|
||||
@ -1288,7 +1288,7 @@ TEST_CASE_FIXTURE(DifferFixture, "generic_one_vs_two")
|
||||
TEST_CASE_FIXTURE(DifferFixture, "generic_three_or_three")
|
||||
{
|
||||
// Old solver does not correctly infer function typepacks
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo<X, Y>(x: X, y: X, z: Y)
|
||||
@ -1329,7 +1329,7 @@ TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "equal_metatable")
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_normal")
|
||||
{
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", false};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local metaFoo = {
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauStacklessTypeClone3);
|
||||
|
||||
TEST_SUITE_BEGIN("ErrorTests");
|
||||
|
||||
TEST_CASE("TypeError_code_should_return_nonzero_code")
|
||||
@ -17,7 +19,7 @@ TEST_CASE("TypeError_code_should_return_nonzero_code")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_names_show_instead_of_tables")
|
||||
{
|
||||
frontend.options.retainFullTypeGraphs = false;
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone3", true};
|
||||
ScopedFastFlag sff{FFlag::LuauStacklessTypeClone3, true};
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local Account = {}
|
||||
|
@ -22,6 +22,7 @@
|
||||
static const char* mainModuleName = "MainModule";
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena);
|
||||
|
||||
extern std::optional<unsigned> randomSeed; // tests/main.cpp
|
||||
|
||||
@ -136,7 +137,7 @@ const Config& TestConfigResolver::getConfig(const ModuleName& name) const
|
||||
}
|
||||
|
||||
Fixture::Fixture(bool freeze, bool prepareAutocomplete)
|
||||
: sff_DebugLuauFreezeArena("DebugLuauFreezeArena", freeze)
|
||||
: sff_DebugLuauFreezeArena(FFlag::DebugLuauFreezeArena, freeze)
|
||||
, frontend(&fileResolver, &configResolver,
|
||||
{/* retainFullTypeGraphs= */ true, /* forAutocomplete */ false, /* runLintChecks */ false, /* randomConstraintResolutionSeed */ randomSeed})
|
||||
, builtinTypes(frontend.builtinTypes)
|
||||
|
@ -22,6 +22,8 @@
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
|
||||
LUAU_FASTFLAG(LuauBufferTypeck);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -99,7 +101,7 @@ struct Fixture
|
||||
|
||||
ScopedFastFlag sff_DebugLuauFreezeArena;
|
||||
|
||||
ScopedFastFlag luauBufferTypeck{"LuauBufferTypeck", true};
|
||||
ScopedFastFlag luauBufferTypeck{FFlag::LuauBufferTypeck, true};
|
||||
|
||||
TestFileResolver fileResolver;
|
||||
TestConfigResolver configResolver;
|
||||
|
@ -13,6 +13,8 @@
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena);
|
||||
LUAU_FASTFLAG(CorrectEarlyReturnInMarkDirty);
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -1112,7 +1114,7 @@ a:b() -- this should error, since A doesn't define a:b()
|
||||
TEST_CASE("no_use_after_free_with_type_fun_instantiation")
|
||||
{
|
||||
// This flag forces this test to crash if there's a UAF in this code.
|
||||
ScopedFastFlag sff_DebugLuauFreezeArena("DebugLuauFreezeArena", true);
|
||||
ScopedFastFlag sff_DebugLuauFreezeArena(FFlag::DebugLuauFreezeArena, true);
|
||||
|
||||
FrontendFixture fix;
|
||||
|
||||
@ -1252,7 +1254,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "parse_only")
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "markdirty_early_return")
|
||||
{
|
||||
ScopedFastFlag fflag("CorrectEarlyReturnInMarkDirty", true);
|
||||
ScopedFastFlag fflag(FFlag::CorrectEarlyReturnInMarkDirty, true);
|
||||
|
||||
constexpr char moduleName[] = "game/Gui/Modules/A";
|
||||
fileResolver.source[moduleName] = R"(
|
||||
|
@ -13,6 +13,8 @@
|
||||
|
||||
using namespace Luau::CodeGen;
|
||||
|
||||
LUAU_FASTFLAG(LuauReuseBufferChecks);
|
||||
|
||||
class IrBuilderFixture
|
||||
{
|
||||
public:
|
||||
@ -2058,8 +2060,6 @@ bb_fallback_1:
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameIndex")
|
||||
{
|
||||
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots2", true};
|
||||
|
||||
IrOp block = build.block(IrBlockKind::Internal);
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
|
||||
@ -2115,8 +2115,6 @@ bb_fallback_1:
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameValue")
|
||||
{
|
||||
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots2", true};
|
||||
|
||||
IrOp block = build.block(IrBlockKind::Internal);
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
|
||||
@ -2180,8 +2178,6 @@ bb_fallback_1:
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksLowerIndex")
|
||||
{
|
||||
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots2", true};
|
||||
|
||||
IrOp block = build.block(IrBlockKind::Internal);
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
|
||||
@ -2236,8 +2232,6 @@ bb_fallback_1:
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksInvalidations")
|
||||
{
|
||||
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots2", true};
|
||||
|
||||
IrOp block = build.block(IrBlockKind::Internal);
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
|
||||
@ -2296,8 +2290,6 @@ bb_fallback_1:
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "ArrayElemChecksNegativeIndex")
|
||||
{
|
||||
ScopedFastFlag luauReuseHashSlots{"LuauReuseArrSlots2", true};
|
||||
|
||||
IrOp block = build.block(IrBlockKind::Internal);
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
|
||||
@ -2343,6 +2335,111 @@ bb_fallback_1:
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateBufferLengthChecks")
|
||||
{
|
||||
ScopedFastFlag luauReuseBufferChecks{FFlag::LuauReuseBufferChecks, true};
|
||||
|
||||
IrOp block = build.block(IrBlockKind::Internal);
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
|
||||
build.beginBlock(block);
|
||||
|
||||
IrOp sourceBuf = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
|
||||
|
||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
|
||||
IrOp buffer1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
|
||||
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer1, build.constInt(12), build.constInt(4), fallback);
|
||||
build.inst(IrCmd::BUFFER_WRITEI32, buffer1, build.constInt(12), build.constInt(32));
|
||||
|
||||
// Now with lower index, should be removed
|
||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
|
||||
IrOp buffer2 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
|
||||
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer2, build.constInt(8), build.constInt(4), fallback);
|
||||
build.inst(IrCmd::BUFFER_WRITEI32, buffer2, build.constInt(8), build.constInt(30));
|
||||
|
||||
// Now with higher index, should raise the initial check bound
|
||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
|
||||
IrOp buffer3 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
|
||||
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer3, build.constInt(16), build.constInt(4), fallback);
|
||||
build.inst(IrCmd::BUFFER_WRITEI32, buffer3, build.constInt(16), build.constInt(60));
|
||||
|
||||
// Now with different access size, should not reuse previous checks (can be improved in the future)
|
||||
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer3, build.constInt(16), build.constInt(2), fallback);
|
||||
build.inst(IrCmd::BUFFER_WRITEI16, buffer3, build.constInt(16), build.constInt(55));
|
||||
|
||||
// Now with same, but unknown index value
|
||||
IrOp index = build.inst(IrCmd::LOAD_INT, build.vmReg(1));
|
||||
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer3, index, build.constInt(2), fallback);
|
||||
build.inst(IrCmd::BUFFER_WRITEI16, buffer3, index, build.constInt(1));
|
||||
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer3, index, build.constInt(2), fallback);
|
||||
build.inst(IrCmd::BUFFER_WRITEI16, buffer3, index, build.constInt(2));
|
||||
|
||||
build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(1));
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%0 = LOAD_TVALUE R0
|
||||
STORE_TVALUE R2, %0
|
||||
%2 = LOAD_POINTER R2
|
||||
CHECK_BUFFER_LEN %2, 16i, 4i, bb_fallback_1
|
||||
BUFFER_WRITEI32 %2, 12i, 32i
|
||||
BUFFER_WRITEI32 %2, 8i, 30i
|
||||
BUFFER_WRITEI32 %2, 16i, 60i
|
||||
CHECK_BUFFER_LEN %2, 16i, 2i, bb_fallback_1
|
||||
BUFFER_WRITEI16 %2, 16i, 55i
|
||||
%15 = LOAD_INT R1
|
||||
CHECK_BUFFER_LEN %2, %15, 2i, bb_fallback_1
|
||||
BUFFER_WRITEI16 %2, %15, 1i
|
||||
BUFFER_WRITEI16 %2, %15, 2i
|
||||
RETURN R1, 1u
|
||||
|
||||
bb_fallback_1:
|
||||
RETURN R0, 1u
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "BufferLenghtChecksNegativeIndex")
|
||||
{
|
||||
ScopedFastFlag luauReuseBufferChecks{FFlag::LuauReuseBufferChecks, true};
|
||||
|
||||
IrOp block = build.block(IrBlockKind::Internal);
|
||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||
|
||||
build.beginBlock(block);
|
||||
|
||||
IrOp sourceBuf = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
|
||||
|
||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
|
||||
IrOp buffer1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
|
||||
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer1, build.constInt(-4), build.constInt(4), fallback);
|
||||
build.inst(IrCmd::BUFFER_WRITEI32, buffer1, build.constInt(-4), build.constInt(32));
|
||||
build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(1));
|
||||
|
||||
build.beginBlock(fallback);
|
||||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
|
||||
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
||||
bb_0:
|
||||
%0 = LOAD_TVALUE R0
|
||||
STORE_TVALUE R2, %0
|
||||
JUMP bb_fallback_1
|
||||
|
||||
bb_fallback_1:
|
||||
RETURN R0, 1u
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
TEST_SUITE_BEGIN("Analysis");
|
||||
|
@ -1829,8 +1829,6 @@ local _ = 0x10000000000000000
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "IntegerParsingDecimalImprecise")
|
||||
{
|
||||
ScopedFastFlag sff("LuauParseImpreciseNumber", true);
|
||||
|
||||
LintResult result = lint(R"(
|
||||
local _ = 10000000000000000000000000000000000000000000000000000000000000000
|
||||
local _ = 10000000000000001
|
||||
@ -1867,8 +1865,6 @@ local _ = -9223372036854775808
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "IntegerParsingHexImprecise")
|
||||
{
|
||||
ScopedFastFlag sff("LuauParseImpreciseNumber", true);
|
||||
|
||||
LintResult result = lint(R"(
|
||||
local _ = 0x1234567812345678
|
||||
|
||||
|
@ -15,6 +15,9 @@ using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauStacklessTypeClone3)
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena);
|
||||
LUAU_FASTINT(LuauTypeCloneIterationLimit);
|
||||
LUAU_FASTINT(LuauTypeCloneRecursionLimit);
|
||||
|
||||
TEST_SUITE_BEGIN("ModuleTests");
|
||||
|
||||
@ -112,7 +115,7 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table")
|
||||
// not, but it's tangental to the core purpose of this test.
|
||||
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, false},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -270,7 +273,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_class")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "clone_free_types")
|
||||
{
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", false};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
TypeArena arena;
|
||||
TypeId freeTy = freshType(NotNull{&arena}, builtinTypes, nullptr);
|
||||
@ -336,8 +339,8 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
|
||||
int limit = 400;
|
||||
#endif
|
||||
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone3", false};
|
||||
ScopedFastInt luauTypeCloneRecursionLimit{"LuauTypeCloneRecursionLimit", limit};
|
||||
ScopedFastFlag sff{FFlag::LuauStacklessTypeClone3, false};
|
||||
ScopedFastInt luauTypeCloneRecursionLimit{FInt::LuauTypeCloneRecursionLimit, limit};
|
||||
|
||||
TypeArena src;
|
||||
|
||||
@ -360,8 +363,8 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "clone_iteration_limit")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone3", true};
|
||||
ScopedFastInt sfi{"LuauTypeCloneIterationLimit", 500};
|
||||
ScopedFastFlag sff{FFlag::LuauStacklessTypeClone3, true};
|
||||
ScopedFastInt sfi{FInt::LuauTypeCloneIterationLimit, 500};
|
||||
|
||||
TypeArena src;
|
||||
|
||||
@ -530,7 +533,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "clone_table_bound_to_table_bound_to_table")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "clone_a_bound_type_to_a_persistent_type")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone3", true};
|
||||
ScopedFastFlag sff{FFlag::LuauStacklessTypeClone3, true};
|
||||
|
||||
TypeArena arena;
|
||||
|
||||
@ -546,7 +549,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "clone_a_bound_type_to_a_persistent_type")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "clone_a_bound_typepack_to_a_persistent_typepack")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauStacklessTypeClone3", true};
|
||||
ScopedFastFlag sff{FFlag::LuauStacklessTypeClone3, true};
|
||||
|
||||
TypeArena arena;
|
||||
|
||||
|
@ -3,33 +3,73 @@
|
||||
|
||||
#include "Fixture.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/IostreamHelpers.h"
|
||||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
#include "ScopedFlags.h"
|
||||
#include "doctest.h"
|
||||
#include <iostream>
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
#define NONSTRICT_REQUIRE_CHECKED_ERR(index, name, result) \
|
||||
LUAU_FASTFLAG(LuauCheckedFunctionSyntax);
|
||||
|
||||
#define NONSTRICT_REQUIRE_ERR_AT_POS(pos, result, idx) \
|
||||
do \
|
||||
{ \
|
||||
REQUIRE(index < result.errors.size()); \
|
||||
auto err##index = get<CheckedFunctionCallError>(result.errors[index]); \
|
||||
REQUIRE(err##index != nullptr); \
|
||||
CHECK_EQ((err##index)->checkedFunctionName, name); \
|
||||
auto pos_ = (pos); \
|
||||
bool foundErr = false; \
|
||||
int index = 0; \
|
||||
for (const auto& err : result.errors) \
|
||||
{ \
|
||||
if (err.location.begin == pos_) \
|
||||
{ \
|
||||
foundErr = true; \
|
||||
break; \
|
||||
} \
|
||||
index++; \
|
||||
} \
|
||||
REQUIRE_MESSAGE(foundErr, "Expected error at " << pos_); \
|
||||
idx = index; \
|
||||
} while (false)
|
||||
|
||||
#define NONSTRICT_REQUIRE_CHECKED_ERR(pos, name, result) \
|
||||
do \
|
||||
{ \
|
||||
int errIndex; \
|
||||
NONSTRICT_REQUIRE_ERR_AT_POS(pos, result, errIndex); \
|
||||
auto err = get<CheckedFunctionCallError>(result.errors[errIndex]); \
|
||||
REQUIRE(err != nullptr); \
|
||||
CHECK_EQ(err->checkedFunctionName, name); \
|
||||
} while (false)
|
||||
|
||||
#define NONSTRICT_REQUIRE_FUNC_DEFINITION_ERR(pos, argname, result) \
|
||||
do \
|
||||
{ \
|
||||
int errIndex; \
|
||||
NONSTRICT_REQUIRE_ERR_AT_POS(pos, result, errIndex); \
|
||||
auto err = get<NonStrictFunctionDefinitionError>(result.errors[errIndex]); \
|
||||
REQUIRE(err != nullptr); \
|
||||
CHECK_EQ(err->argument, argname); \
|
||||
} while (false)
|
||||
|
||||
|
||||
struct NonStrictTypeCheckerFixture : Fixture
|
||||
{
|
||||
|
||||
NonStrictTypeCheckerFixture()
|
||||
{
|
||||
registerHiddenTypes(&frontend);
|
||||
}
|
||||
|
||||
CheckResult checkNonStrict(const std::string& code)
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauCheckedFunctionSyntax", true},
|
||||
{"DebugLuauDeferredConstraintResolution", true},
|
||||
{FFlag::LuauCheckedFunctionSyntax, true},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, true},
|
||||
};
|
||||
LoadDefinitionFileResult res = loadDefinition(definitions);
|
||||
LUAU_ASSERT(res.success);
|
||||
@ -40,19 +80,41 @@ struct NonStrictTypeCheckerFixture : Fixture
|
||||
declare function @checked abs(n: number): number
|
||||
declare function @checked lower(s: string): string
|
||||
declare function cond() : boolean
|
||||
declare function @checked contrived(n : Not<number>) : number
|
||||
)BUILTIN_SRC";
|
||||
};
|
||||
|
||||
|
||||
TEST_SUITE_BEGIN("NonStrictTypeCheckerTest");
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "simple_negation_caching_example")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
local x = 3
|
||||
abs(x)
|
||||
abs(x)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
result = checkNonStrict(R"(
|
||||
local x = 3
|
||||
contrived(x)
|
||||
contrived(x)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 10), "contrived", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 10), "contrived", result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "simple_non_strict_failure")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"BUILTIN_SRC(
|
||||
abs("hi")
|
||||
)BUILTIN_SRC");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(1, 4), "abs", result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "nested_function_calls_constant")
|
||||
@ -63,7 +125,7 @@ abs(lower(x))
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 4), "abs", result);
|
||||
}
|
||||
|
||||
|
||||
@ -79,8 +141,8 @@ end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(1, "lower", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 8), "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(5, 10), "lower", result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_else_warns_nil_branches")
|
||||
@ -95,8 +157,8 @@ end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(1, "lower", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 8), "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(5, 10), "lower", result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_else_doesnt_warn_else_branch")
|
||||
@ -111,7 +173,7 @@ end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 8), "abs", result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_no_else")
|
||||
@ -129,13 +191,13 @@ end
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_no_else_err_in_cond")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
local x : string
|
||||
local x : string = ""
|
||||
if abs(x) then
|
||||
lower(x)
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 7), "abs", result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_else_expr_should_warn")
|
||||
@ -145,8 +207,8 @@ local x : never
|
||||
local y = if cond() then abs(x) else lower(x)
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(1, "lower", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 29), "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 43), "lower", result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_else_expr_doesnt_warn_else_branch")
|
||||
@ -156,21 +218,7 @@ local x : string = "hi"
|
||||
local y = if cond() then abs(x) else lower(x)
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "sequencing_errors")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
function f(x)
|
||||
abs(x)
|
||||
lower(x)
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(0, "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(1, "lower", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 29), "abs", result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "sequencing_if_checked_call")
|
||||
@ -185,10 +233,10 @@ end
|
||||
lower(x)
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(0, "lower", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(7, 6), "lower", result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "sequencing_unrelated_checked_calls")
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_unrelated_checked_calls")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
function h(x, y)
|
||||
@ -200,5 +248,143 @@ end
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_basic_no_errors")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
function f(x)
|
||||
abs(x)
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_basic_errors")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
function f(x : string)
|
||||
abs(x)
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 8), "abs", result);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_failure")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
function f(x)
|
||||
abs(lower(x))
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 8), "abs", result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_sequencing_errors")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
function f(x)
|
||||
abs(x)
|
||||
lower(x)
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 8), "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 10), "lower", result);
|
||||
NONSTRICT_REQUIRE_FUNC_DEFINITION_ERR(Position(1, 11), "x", result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "local_fn_produces_error")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
local x = 5
|
||||
local function y() lower(x) end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 25), "lower", result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "fn_expr_produces_error")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
local x = 5
|
||||
local y = function() lower(x) end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 27), "lower", result);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_if_warns_never")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
function f(x)
|
||||
if cond() then
|
||||
abs(x)
|
||||
else
|
||||
lower(x)
|
||||
end
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 12), "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(5, 14), "lower", result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_if_no_else")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
function f(x)
|
||||
if cond() then
|
||||
abs(x)
|
||||
end
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_if_assignment_errors")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
function f(x)
|
||||
if cond() then
|
||||
x = 5
|
||||
else
|
||||
x = nil
|
||||
end
|
||||
lower(x)
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(7, 10), "lower", result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_if_assignment_no_errors")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
function f(x : string | number)
|
||||
if cond() then
|
||||
x = 5
|
||||
else
|
||||
x = "hi"
|
||||
end
|
||||
abs(x)
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "local_only_one_warning")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
local x = 5
|
||||
lower(x)
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 6), "lower", result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauTransitiveSubtyping);
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
@ -32,7 +33,7 @@ struct IsSubtypeFixture : Fixture
|
||||
bool isConsistentSubtype(TypeId a, TypeId b)
|
||||
{
|
||||
// any test that is testing isConsistentSubtype is testing the old solver exclusively!
|
||||
ScopedFastFlag noDcr{"DebugLuauDeferredConstraintResolution", false};
|
||||
ScopedFastFlag noDcr{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
Location location;
|
||||
ModulePtr module = getMainModule();
|
||||
@ -182,7 +183,7 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "table_with_union_prop")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "table_with_any_prop")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{"LuauTransitiveSubtyping", true},
|
||||
{FFlag::LuauTransitiveSubtyping, true},
|
||||
};
|
||||
|
||||
check(R"(
|
||||
@ -243,7 +244,7 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "union_and_intersection")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "tables")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{"LuauTransitiveSubtyping", true},
|
||||
{FFlag::LuauTransitiveSubtyping, true},
|
||||
};
|
||||
|
||||
check(R"(
|
||||
@ -398,7 +399,7 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "metatable" * doctest::expected_failures{1})
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "any_is_unknown_union_error")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{"LuauTransitiveSubtyping", true},
|
||||
{FFlag::LuauTransitiveSubtyping, true},
|
||||
};
|
||||
|
||||
check(R"(
|
||||
@ -418,7 +419,7 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "any_is_unknown_union_error")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "any_intersect_T_is_T")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{"LuauTransitiveSubtyping", true},
|
||||
{FFlag::LuauTransitiveSubtyping, true},
|
||||
};
|
||||
|
||||
check(R"(
|
||||
@ -440,7 +441,7 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "any_intersect_T_is_T")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "error_suppression")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{"LuauTransitiveSubtyping", true},
|
||||
{FFlag::LuauTransitiveSubtyping, true},
|
||||
};
|
||||
|
||||
check("");
|
||||
|
@ -11,6 +11,13 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauCheckedFunctionSyntax);
|
||||
LUAU_FASTFLAG(LuauLexerLookaheadRemembersBraceType);
|
||||
LUAU_FASTINT(LuauRecursionLimit);
|
||||
LUAU_FASTINT(LuauTypeLengthLimit);
|
||||
LUAU_FASTINT(LuauParseErrorLimit);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
@ -1156,7 +1163,7 @@ until false
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_nesting_based_end_detection_local_function")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, false},
|
||||
};
|
||||
|
||||
try
|
||||
@ -1192,7 +1199,7 @@ end
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_nesting_based_end_detection_failsafe_earlier")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, false},
|
||||
};
|
||||
|
||||
try
|
||||
@ -1317,7 +1324,7 @@ end
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_type_group")
|
||||
{
|
||||
ScopedFastInt sfis{"LuauRecursionLimit", 10};
|
||||
ScopedFastInt sfis{FInt::LuauRecursionLimit, 10};
|
||||
|
||||
matchParseError(
|
||||
"function f(): ((((((((((Fail)))))))))) end", "Exceeded allowed recursion depth; simplify your type annotation to make the code compile");
|
||||
@ -1336,7 +1343,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_type_group")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "can_parse_complex_unions_successfully")
|
||||
{
|
||||
ScopedFastInt sfis[] = {{"LuauRecursionLimit", 10}, {"LuauTypeLengthLimit", 10}};
|
||||
ScopedFastInt sfis[] = {{FInt::LuauRecursionLimit, 10}, {FInt::LuauTypeLengthLimit, 10}};
|
||||
|
||||
parse(R"(
|
||||
local f:
|
||||
@ -1367,7 +1374,7 @@ local f: a? | b? | c? | d? | e? | f? | g? | h?
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_if_statements")
|
||||
{
|
||||
ScopedFastInt sfis{"LuauRecursionLimit", 10};
|
||||
ScopedFastInt sfis{FInt::LuauRecursionLimit, 10};
|
||||
|
||||
matchParseErrorPrefix(
|
||||
"function f() if true then if true then if true then if true then if true then if true then if true then if true then if true "
|
||||
@ -1377,7 +1384,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_if_statements")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_changed_elseif_statements")
|
||||
{
|
||||
ScopedFastInt sfis{"LuauRecursionLimit", 10};
|
||||
ScopedFastInt sfis{FInt::LuauRecursionLimit, 10};
|
||||
|
||||
matchParseErrorPrefix(
|
||||
"function f() if false then elseif false then elseif false then elseif false then elseif false then elseif false then elseif "
|
||||
@ -1387,7 +1394,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_changed_elseif_statements"
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_ifelse_expressions1")
|
||||
{
|
||||
ScopedFastInt sfis{"LuauRecursionLimit", 10};
|
||||
ScopedFastInt sfis{FInt::LuauRecursionLimit, 10};
|
||||
|
||||
matchParseError("function f() return if true then 1 elseif true then 2 elseif true then 3 elseif true then 4 elseif true then 5 elseif true then "
|
||||
"6 elseif true then 7 elseif true then 8 elseif true then 9 elseif true then 10 else 11 end",
|
||||
@ -1396,7 +1403,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_ifelse_expressions1
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_error_with_too_many_nested_ifelse_expressions2")
|
||||
{
|
||||
ScopedFastInt sfis{"LuauRecursionLimit", 10};
|
||||
ScopedFastInt sfis{FInt::LuauRecursionLimit, 10};
|
||||
|
||||
matchParseError(
|
||||
"function f() return if if if if if if if if if if true then false else true then false else true then false else true then false else true "
|
||||
@ -2411,7 +2418,7 @@ local a : { [string] : number, [number] : string, count: number }
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "recovery_error_limit_1")
|
||||
{
|
||||
ScopedFastInt luauParseErrorLimit("LuauParseErrorLimit", 1);
|
||||
ScopedFastInt luauParseErrorLimit(FInt::LuauParseErrorLimit, 1);
|
||||
|
||||
try
|
||||
{
|
||||
@ -2427,7 +2434,7 @@ TEST_CASE_FIXTURE(Fixture, "recovery_error_limit_1")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "recovery_error_limit_2")
|
||||
{
|
||||
ScopedFastInt luauParseErrorLimit("LuauParseErrorLimit", 2);
|
||||
ScopedFastInt luauParseErrorLimit(FInt::LuauParseErrorLimit, 2);
|
||||
|
||||
try
|
||||
{
|
||||
@ -2491,7 +2498,7 @@ TEST_CASE_FIXTURE(Fixture, "recovery_of_parenthesized_expressions")
|
||||
};
|
||||
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, false},
|
||||
};
|
||||
|
||||
checkRecovery("function foo(a, b. c) return a + b end", "function foo(a, b) return a + b end", 1);
|
||||
@ -2725,7 +2732,7 @@ TEST_CASE_FIXTURE(Fixture, "AstName_comparison")
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_type_list_recovery")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, false},
|
||||
};
|
||||
|
||||
try
|
||||
@ -3020,7 +3027,7 @@ TEST_CASE_FIXTURE(Fixture, "do_block_with_no_end")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_with_lookahead_involved")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauLexerLookaheadRemembersBraceType", true};
|
||||
ScopedFastFlag sff{FFlag::LuauLexerLookaheadRemembersBraceType, true};
|
||||
|
||||
ParseResult result = tryParse(R"(
|
||||
local x = `{ {y} }`
|
||||
@ -3031,7 +3038,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_with_lookahead_involved")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_with_lookahead_involved2")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauLexerLookaheadRemembersBraceType", true};
|
||||
ScopedFastFlag sff{FFlag::LuauLexerLookaheadRemembersBraceType, true};
|
||||
|
||||
ParseResult result = tryParse(R"(
|
||||
local x = `{ { y{} } }`
|
||||
@ -3044,7 +3051,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_top_level_checked_fn")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
|
||||
ScopedFastFlag sff{FFlag::LuauCheckedFunctionSyntax, true};
|
||||
|
||||
std::string src = R"BUILTIN_SRC(
|
||||
declare function @checked abs(n: number): number
|
||||
@ -3064,7 +3071,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_declared_table_checked_member")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
|
||||
ScopedFastFlag sff{FFlag::LuauCheckedFunctionSyntax, true};
|
||||
|
||||
const std::string src = R"BUILTIN_SRC(
|
||||
declare math : {
|
||||
@ -3092,7 +3099,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_checked_outside_decl_fails")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
|
||||
ScopedFastFlag sff{FFlag::LuauCheckedFunctionSyntax, true};
|
||||
|
||||
ParseResult pr = tryParse(R"(
|
||||
local @checked = 3
|
||||
@ -3106,7 +3113,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_checked_in_and_out_of_decl_fails")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
|
||||
ScopedFastFlag sff{FFlag::LuauCheckedFunctionSyntax, true};
|
||||
|
||||
auto pr = tryParse(R"(
|
||||
local @checked = 3
|
||||
@ -3122,7 +3129,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_checked_as_function_name_fails")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
|
||||
ScopedFastFlag sff{FFlag::LuauCheckedFunctionSyntax, true};
|
||||
|
||||
auto pr = tryParse(R"(
|
||||
function @checked(x: number) : number
|
||||
@ -3136,7 +3143,7 @@ TEST_CASE_FIXTURE(Fixture, "cannot_use_@_as_variable_name")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
|
||||
ScopedFastFlag sff{FFlag::LuauCheckedFunctionSyntax, true};
|
||||
|
||||
auto pr = tryParse(R"(
|
||||
local @blah = 3
|
||||
|
391
tests/RequireByString.test.cpp
Normal file
391
tests/RequireByString.test.cpp
Normal file
@ -0,0 +1,391 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Common.h"
|
||||
#include "ScopedFlags.h"
|
||||
#include "lua.h"
|
||||
#include "lualib.h"
|
||||
|
||||
#include "Repl.h"
|
||||
#include "FileUtils.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
|
||||
LUAU_FASTFLAG(LuauUpdatedRequireByStringSemantics)
|
||||
|
||||
class ReplWithPathFixture
|
||||
{
|
||||
public:
|
||||
ReplWithPathFixture()
|
||||
: luaState(luaL_newstate(), lua_close)
|
||||
{
|
||||
L = luaState.get();
|
||||
setupState(L);
|
||||
luaL_sandboxthread(L);
|
||||
|
||||
runCode(L, prettyPrintSource);
|
||||
}
|
||||
|
||||
// Returns all of the output captured from the pretty printer
|
||||
std::string getCapturedOutput()
|
||||
{
|
||||
lua_getglobal(L, "capturedoutput");
|
||||
const char* str = lua_tolstring(L, -1, nullptr);
|
||||
std::string result(str);
|
||||
lua_pop(L, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
enum class PathType
|
||||
{
|
||||
Absolute,
|
||||
Relative
|
||||
};
|
||||
|
||||
std::string getLuauDirectory(PathType type)
|
||||
{
|
||||
std::string luauDirRel = ".";
|
||||
std::string luauDirAbs;
|
||||
|
||||
std::optional<std::string> cwd = getCurrentWorkingDirectory();
|
||||
REQUIRE_MESSAGE(cwd, "Error getting Luau path");
|
||||
std::replace((*cwd).begin(), (*cwd).end(), '\\', '/');
|
||||
luauDirAbs = *cwd;
|
||||
|
||||
for (int i = 0; i < 20; ++i)
|
||||
{
|
||||
if (isDirectory(luauDirAbs + "/Luau/tests") || isDirectory(luauDirAbs + "/Client/Luau/tests"))
|
||||
{
|
||||
if (isDirectory(luauDirAbs + "/Client/Luau/tests"))
|
||||
{
|
||||
luauDirRel += "/Client";
|
||||
luauDirAbs += "/Client";
|
||||
}
|
||||
luauDirRel += "/Luau";
|
||||
luauDirAbs += "/Luau";
|
||||
|
||||
if (type == PathType::Relative)
|
||||
return luauDirRel;
|
||||
if (type == PathType::Absolute)
|
||||
return luauDirAbs;
|
||||
}
|
||||
|
||||
luauDirRel += "/..";
|
||||
std::optional<std::string> parentPath = getParentPath(luauDirAbs);
|
||||
REQUIRE_MESSAGE(parentPath, "Error getting Luau path");
|
||||
luauDirAbs = *parentPath;
|
||||
}
|
||||
|
||||
// Could not find the directory
|
||||
REQUIRE_MESSAGE(false, "Error getting Luau path");
|
||||
return {};
|
||||
}
|
||||
|
||||
void runProtectedRequire(const std::string& path)
|
||||
{
|
||||
runCode(L, "return pcall(function() return require(\"" + path + "\") end)");
|
||||
}
|
||||
|
||||
void assertOutputContainsAll(const std::initializer_list<std::string>& list)
|
||||
{
|
||||
const std::string capturedOutput = getCapturedOutput();
|
||||
for (const std::string& elem : list)
|
||||
{
|
||||
CHECK_MESSAGE(capturedOutput.find(elem) != std::string::npos, "Captured output: ", capturedOutput);
|
||||
}
|
||||
}
|
||||
|
||||
lua_State* L;
|
||||
|
||||
private:
|
||||
std::unique_ptr<lua_State, void (*)(lua_State*)> luaState;
|
||||
|
||||
// This is a simplistic and incomplete pretty printer.
|
||||
// It is included here to test that the pretty printer hook is being called.
|
||||
// More elaborate tests to ensure correct output can be added if we introduce
|
||||
// a more feature rich pretty printer.
|
||||
std::string prettyPrintSource = R"(
|
||||
-- Accumulate pretty printer output in `capturedoutput`
|
||||
capturedoutput = ""
|
||||
|
||||
function arraytostring(arr)
|
||||
local strings = {}
|
||||
table.foreachi(arr, function(k,v) table.insert(strings, pptostring(v)) end )
|
||||
return "{" .. table.concat(strings, ", ") .. "}"
|
||||
end
|
||||
|
||||
function pptostring(x)
|
||||
if type(x) == "table" then
|
||||
-- Just assume array-like tables for now.
|
||||
return arraytostring(x)
|
||||
elseif type(x) == "string" then
|
||||
return '"' .. x .. '"'
|
||||
else
|
||||
return tostring(x)
|
||||
end
|
||||
end
|
||||
|
||||
-- Note: Instead of calling print, the pretty printer just stores the output
|
||||
-- in `capturedoutput` so we can check for the correct results.
|
||||
function _PRETTYPRINT(...)
|
||||
local args = table.pack(...)
|
||||
local strings = {}
|
||||
for i=1, args.n do
|
||||
local item = args[i]
|
||||
local str = pptostring(item, customoptions)
|
||||
if i == 1 then
|
||||
capturedoutput = capturedoutput .. str
|
||||
else
|
||||
capturedoutput = capturedoutput .. "\t" .. str
|
||||
end
|
||||
end
|
||||
end
|
||||
)";
|
||||
};
|
||||
|
||||
TEST_SUITE_BEGIN("RequireByStringTests");
|
||||
|
||||
TEST_CASE("PathResolution")
|
||||
{
|
||||
#ifdef _WIN32
|
||||
std::string prefix = "C:/";
|
||||
#else
|
||||
std::string prefix = "/";
|
||||
#endif
|
||||
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
CHECK(resolvePath(prefix + "Users/modules/module.luau", "") == prefix + "Users/modules/module.luau");
|
||||
CHECK(resolvePath(prefix + "Users/modules/module.luau", "a/string/that/should/be/ignored") == prefix + "Users/modules/module.luau");
|
||||
CHECK(resolvePath(prefix + "Users/modules/module.luau", "./a/string/that/should/be/ignored") == prefix + "Users/modules/module.luau");
|
||||
CHECK(resolvePath(prefix + "Users/modules/module.luau", "/a/string/that/should/be/ignored") == prefix + "Users/modules/module.luau");
|
||||
CHECK(resolvePath(prefix + "Users/modules/module.luau", "/Users/modules") == prefix + "Users/modules/module.luau");
|
||||
|
||||
CHECK(resolvePath("../module", "") == "../module");
|
||||
CHECK(resolvePath("../../module", "") == "../../module");
|
||||
CHECK(resolvePath("../module/..", "") == "..");
|
||||
CHECK(resolvePath("../module/../..", "") == "../..");
|
||||
|
||||
CHECK(resolvePath("../dependency", prefix + "Users/modules/module.luau") == prefix + "Users/dependency");
|
||||
CHECK(resolvePath("../dependency/", prefix + "Users/modules/module.luau") == prefix + "Users/dependency");
|
||||
CHECK(resolvePath("../../../../../Users/dependency", prefix + "Users/modules/module.luau") == prefix + "Users/dependency");
|
||||
CHECK(resolvePath("../..", prefix + "Users/modules/module.luau") == prefix);
|
||||
}
|
||||
|
||||
TEST_CASE("PathNormalization")
|
||||
{
|
||||
#ifdef _WIN32
|
||||
std::string prefix = "C:/";
|
||||
#else
|
||||
std::string prefix = "/";
|
||||
#endif
|
||||
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
// Relative path
|
||||
std::optional<std::string> result = normalizePath("../../modules/module");
|
||||
CHECK(result);
|
||||
std::string normalized = *result;
|
||||
std::vector<std::string> variants = {
|
||||
"./.././.././modules/./module/", "placeholder/../../../modules/module", "../placeholder/placeholder2/../../../modules/module"};
|
||||
for (const std::string& variant : variants)
|
||||
{
|
||||
result = normalizePath(variant);
|
||||
CHECK(result);
|
||||
CHECK(normalized == *result);
|
||||
}
|
||||
|
||||
// Absolute path
|
||||
result = normalizePath(prefix + "Users/modules/module");
|
||||
CHECK(result);
|
||||
normalized = *result;
|
||||
variants = {"Users/Users/Users/.././.././modules/./module/", "placeholder/../Users/..//Users/modules/module",
|
||||
"Users/../placeholder/placeholder2/../../Users/modules/module"};
|
||||
for (const std::string& variant : variants)
|
||||
{
|
||||
result = normalizePath(prefix + variant);
|
||||
CHECK(result);
|
||||
CHECK(normalized == *result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireSimpleRelativePath")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/dependency";
|
||||
runProtectedRequire(path);
|
||||
assertOutputContainsAll({"true", "result from dependency"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireRelativeToRequiringFile")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module";
|
||||
runProtectedRequire(path);
|
||||
assertOutputContainsAll({"true", "result from dependency", "required into module"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireLua")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/lua_dependency";
|
||||
runProtectedRequire(path);
|
||||
assertOutputContainsAll({"true", "result from lua_dependency"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireInitLuau")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/luau";
|
||||
runProtectedRequire(path);
|
||||
assertOutputContainsAll({"true", "result from init.luau"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireInitLua")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/lua";
|
||||
runProtectedRequire(path);
|
||||
assertOutputContainsAll({"true", "result from init.lua"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireLuau")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module";
|
||||
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/module";
|
||||
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
lua_getfield(L, -1, (absolutePath + ".luau").c_str());
|
||||
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
|
||||
|
||||
runProtectedRequire(relativePath);
|
||||
|
||||
assertOutputContainsAll({"true", "result from dependency", "required into module"});
|
||||
|
||||
// Check cache for the absolute path as a cache key
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
lua_getfield(L, -1, (absolutePath + ".luau").c_str());
|
||||
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireLua")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/lua_dependency";
|
||||
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/lua_dependency";
|
||||
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
lua_getfield(L, -1, (absolutePath + ".luau").c_str());
|
||||
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
|
||||
|
||||
runProtectedRequire(relativePath);
|
||||
|
||||
assertOutputContainsAll({"true", "result from lua_dependency"});
|
||||
|
||||
// Check cache for the absolute path as a cache key
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
lua_getfield(L, -1, (absolutePath + ".lua").c_str());
|
||||
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireInitLuau")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/luau";
|
||||
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/luau";
|
||||
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
lua_getfield(L, -1, (absolutePath + "/init.luau").c_str());
|
||||
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
|
||||
|
||||
runProtectedRequire(relativePath);
|
||||
|
||||
assertOutputContainsAll({"true", "result from init.luau"});
|
||||
|
||||
// Check cache for the absolute path as a cache key
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
lua_getfield(L, -1, (absolutePath + "/init.luau").c_str());
|
||||
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireInitLua")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/lua";
|
||||
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/lua";
|
||||
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
lua_getfield(L, -1, (absolutePath + "/init.lua").c_str());
|
||||
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
|
||||
|
||||
runProtectedRequire(relativePath);
|
||||
|
||||
assertOutputContainsAll({"true", "result from init.lua"});
|
||||
|
||||
// Check cache for the absolute path as a cache key
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
lua_getfield(L, -1, (absolutePath + "/init.lua").c_str());
|
||||
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "LoadStringRelative")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
runCode(L, "return pcall(function() return loadstring(\"require('a/relative/path')\")() end)");
|
||||
assertOutputContainsAll({"false", "require is not supported in this context"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireAbsolutePath")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
#ifdef _WIN32
|
||||
std::string absolutePath = "C:/an/absolute/path";
|
||||
#else
|
||||
std::string absolutePath = "/an/absolute/path";
|
||||
#endif
|
||||
runProtectedRequire(absolutePath);
|
||||
assertOutputContainsAll({"false", "cannot require an absolute path"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "PathsArrayRelativePath")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/requirer";
|
||||
runProtectedRequire(path);
|
||||
assertOutputContainsAll({"true", "result from library"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "PathsArrayExplicitlyRelativePath")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/fail_requirer";
|
||||
runProtectedRequire(path);
|
||||
assertOutputContainsAll({"false", "error requiring module"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "PathsArrayFromParent")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/global_library_requirer";
|
||||
runProtectedRequire(path);
|
||||
assertOutputContainsAll({"true", "result from global_library"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithAlias")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/alias_requirer";
|
||||
runProtectedRequire(path);
|
||||
assertOutputContainsAll({"true", "result from dependency"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithParentAlias")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUpdatedRequireByStringSemantics, true};
|
||||
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/parent_alias_requirer";
|
||||
runProtectedRequire(path);
|
||||
assertOutputContainsAll({"true", "result from other_dependency"});
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
@ -13,12 +13,17 @@
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
struct LimitFixture : BuiltinsFixture
|
||||
{
|
||||
#if defined(_NOOPT) || defined(_DEBUG)
|
||||
ScopedFastInt LuauTypeInferRecursionLimit{"LuauTypeInferRecursionLimit", 100};
|
||||
ScopedFastInt LuauTypeInferRecursionLimit{FInt::LuauTypeInferRecursionLimit, 100};
|
||||
#endif
|
||||
};
|
||||
|
||||
@ -36,7 +41,7 @@ TEST_SUITE_BEGIN("RuntimeLimits");
|
||||
TEST_CASE_FIXTURE(LimitFixture, "typescript_port_of_Result_type")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, false},
|
||||
};
|
||||
|
||||
constexpr const char* src = R"LUA(
|
||||
|
@ -13,18 +13,11 @@ private:
|
||||
T oldValue = T();
|
||||
|
||||
public:
|
||||
ScopedFValue(const char* name, T newValue)
|
||||
ScopedFValue(Luau::FValue<T>& fvalue, T newValue)
|
||||
{
|
||||
for (Luau::FValue<T>* v = Luau::FValue<T>::list; v; v = v->next)
|
||||
if (strcmp(v->name, name) == 0)
|
||||
{
|
||||
value = v;
|
||||
oldValue = v->value;
|
||||
v->value = newValue;
|
||||
break;
|
||||
}
|
||||
|
||||
LUAU_ASSERT(value);
|
||||
value = &fvalue;
|
||||
oldValue = fvalue.value;
|
||||
fvalue.value = newValue;
|
||||
}
|
||||
|
||||
ScopedFValue(const ScopedFValue&) = delete;
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
@ -59,7 +61,7 @@ struct SimplifyFixture : Fixture
|
||||
TypeId anotherChildClassTy = nullptr;
|
||||
TypeId unrelatedClassTy = nullptr;
|
||||
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
SimplifyFixture()
|
||||
{
|
||||
|
@ -24,6 +24,8 @@ std::ostream& operator<<(std::ostream& lhs, const SubtypingVariance& variance)
|
||||
{
|
||||
case SubtypingVariance::Covariant:
|
||||
return lhs << "covariant";
|
||||
case SubtypingVariance::Contravariant:
|
||||
return lhs << "contravariant";
|
||||
case SubtypingVariance::Invariant:
|
||||
return lhs << "invariant";
|
||||
case SubtypingVariance::Invalid:
|
||||
@ -643,12 +645,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> () <!: <T>(T) -> ()")
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "<T>() -> (T, T) <!: () -> (string, number)")
|
||||
{
|
||||
TypeId nothingToTwoTs = arena.addType(FunctionType{
|
||||
{genericT},
|
||||
{},
|
||||
builtinTypes->emptyTypePack,
|
||||
arena.addTypePack({genericT, genericT})
|
||||
});
|
||||
TypeId nothingToTwoTs = arena.addType(FunctionType{{genericT}, {}, builtinTypes->emptyTypePack, arena.addTypePack({genericT, genericT})});
|
||||
|
||||
TypeId nothingToStringAndNumber = fn({}, {builtinTypes->stringType, builtinTypes->numberType});
|
||||
|
||||
@ -790,7 +787,7 @@ TEST_IS_NOT_SUBTYPE(negate(builtinTypes->neverType), builtinTypes->stringType);
|
||||
TEST_IS_SUBTYPE(negate(builtinTypes->unknownType), builtinTypes->stringType);
|
||||
TEST_IS_NOT_SUBTYPE(negate(builtinTypes->anyType), builtinTypes->stringType);
|
||||
TEST_IS_SUBTYPE(negate(meet(builtinTypes->neverType, builtinTypes->unknownType)), builtinTypes->stringType);
|
||||
TEST_IS_NOT_SUBTYPE(negate(join(builtinTypes->neverType, builtinTypes->unknownType)), builtinTypes->stringType);
|
||||
TEST_IS_SUBTYPE(negate(join(builtinTypes->neverType, builtinTypes->unknownType)), builtinTypes->stringType);
|
||||
|
||||
// Negated supertypes: never/unknown/any/error
|
||||
TEST_IS_SUBTYPE(builtinTypes->stringType, negate(builtinTypes->neverType));
|
||||
@ -812,7 +809,7 @@ TEST_IS_SUBTYPE(builtinTypes->booleanType, negate(meet(builtinTypes->stringType,
|
||||
TEST_IS_SUBTYPE(builtinTypes->trueType, negate(meet(builtinTypes->booleanType, builtinTypes->numberType)));
|
||||
TEST_IS_SUBTYPE(rootClass, negate(meet(builtinTypes->classType, childClass)));
|
||||
TEST_IS_SUBTYPE(childClass, negate(meet(builtinTypes->classType, builtinTypes->numberType)));
|
||||
TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(meet(builtinTypes->classType, builtinTypes->numberType)));
|
||||
TEST_IS_SUBTYPE(builtinTypes->unknownType, negate(meet(builtinTypes->classType, builtinTypes->numberType)));
|
||||
TEST_IS_NOT_SUBTYPE(str("foo"), negate(meet(builtinTypes->stringType, negate(str("bar")))));
|
||||
|
||||
// Negated supertypes: tables and metatables
|
||||
@ -1041,6 +1038,16 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(string | number) & (\"a\" | true) <: { lower
|
||||
CHECK_IS_SUBTYPE(base, tableWithLower);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "number <: ~~number")
|
||||
{
|
||||
CHECK_IS_SUBTYPE(builtinTypes->numberType, negate(negate(builtinTypes->numberType)));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "~~number <: number")
|
||||
{
|
||||
CHECK_IS_SUBTYPE(negate(negate(builtinTypes->numberType)), builtinTypes->numberType);
|
||||
}
|
||||
|
||||
/*
|
||||
* Within the scope to which a generic belongs, that generic ought to be treated
|
||||
* as its bounds.
|
||||
@ -1075,11 +1082,15 @@ TEST_IS_NOT_SUBTYPE(tbl({}), idx(builtinTypes->numberType, builtinTypes->numberT
|
||||
TEST_IS_NOT_SUBTYPE(tbl({{"X", builtinTypes->numberType}}), idx(builtinTypes->numberType, builtinTypes->numberType));
|
||||
TEST_IS_NOT_SUBTYPE(idx(builtinTypes->numberType, builtinTypes->numberType), tbl({{"X", builtinTypes->numberType}}));
|
||||
|
||||
TEST_IS_NOT_SUBTYPE(idx(join(builtinTypes->numberType, builtinTypes->stringType), builtinTypes->numberType), idx(builtinTypes->numberType, builtinTypes->numberType));
|
||||
TEST_IS_NOT_SUBTYPE(idx(builtinTypes->numberType, builtinTypes->numberType), idx(join(builtinTypes->numberType, builtinTypes->stringType), builtinTypes->numberType));
|
||||
TEST_IS_NOT_SUBTYPE(
|
||||
idx(join(builtinTypes->numberType, builtinTypes->stringType), builtinTypes->numberType), idx(builtinTypes->numberType, builtinTypes->numberType));
|
||||
TEST_IS_NOT_SUBTYPE(
|
||||
idx(builtinTypes->numberType, builtinTypes->numberType), idx(join(builtinTypes->numberType, builtinTypes->stringType), builtinTypes->numberType));
|
||||
|
||||
TEST_IS_NOT_SUBTYPE(idx(builtinTypes->numberType, join(builtinTypes->stringType, builtinTypes->numberType)), idx(builtinTypes->numberType, builtinTypes->numberType));
|
||||
TEST_IS_NOT_SUBTYPE(idx(builtinTypes->numberType, builtinTypes->numberType), idx(builtinTypes->numberType, join(builtinTypes->stringType, builtinTypes->numberType)));
|
||||
TEST_IS_NOT_SUBTYPE(
|
||||
idx(builtinTypes->numberType, join(builtinTypes->stringType, builtinTypes->numberType)), idx(builtinTypes->numberType, builtinTypes->numberType));
|
||||
TEST_IS_NOT_SUBTYPE(
|
||||
idx(builtinTypes->numberType, builtinTypes->numberType), idx(builtinTypes->numberType, join(builtinTypes->stringType, builtinTypes->numberType)));
|
||||
|
||||
TEST_IS_NOT_SUBTYPE(tbl({{"X", builtinTypes->numberType}}), idx(builtinTypes->stringType, builtinTypes->numberType));
|
||||
TEST_IS_SUBTYPE(idx(builtinTypes->stringType, builtinTypes->numberType), tbl({{"X", builtinTypes->numberType}}));
|
||||
@ -1176,6 +1187,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "fn_arguments")
|
||||
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
||||
/* subPath */ TypePath::PathBuilder().args().index(0).build(),
|
||||
/* superPath */ TypePath::PathBuilder().args().index(0).build(),
|
||||
/* variance */ SubtypingVariance::Contravariant,
|
||||
}});
|
||||
}
|
||||
|
||||
@ -1189,6 +1201,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "fn_arguments_tail")
|
||||
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
||||
/* subPath */ TypePath::PathBuilder().args().tail().variadic().build(),
|
||||
/* superPath */ TypePath::PathBuilder().args().tail().variadic().build(),
|
||||
/* variance */ SubtypingVariance::Contravariant,
|
||||
}});
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
TEST_SUITE_BEGIN("SymbolTests");
|
||||
|
||||
TEST_CASE("equality_and_hashing_of_globals")
|
||||
@ -66,7 +68,7 @@ TEST_CASE("equality_and_hashing_of_locals")
|
||||
|
||||
TEST_CASE("equality_of_empty_symbols")
|
||||
{
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
std::string s1 = "name";
|
||||
std::string s2 = "name";
|
||||
|
@ -289,7 +289,7 @@ n3 [label="TableType 3"];
|
||||
TEST_CASE_FIXTURE(Fixture, "free")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, false},
|
||||
};
|
||||
|
||||
Type type{TypeVariant{FreeType{TypeLevel{0, 0}}}};
|
||||
@ -305,7 +305,7 @@ n1 [label="FreeType 1"];
|
||||
TEST_CASE_FIXTURE(Fixture, "free_with_constraints")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", true},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, true},
|
||||
};
|
||||
|
||||
Type type{TypeVariant{FreeType{nullptr, builtinTypes->numberType, builtinTypes->optionalNumberType}}};
|
||||
|
@ -12,6 +12,8 @@ using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauCheckedFunctionSyntax);
|
||||
LUAU_FASTFLAG(DebugLuauSharedSelf);
|
||||
|
||||
TEST_SUITE_BEGIN("ToString");
|
||||
|
||||
@ -236,7 +238,7 @@ TEST_CASE_FIXTURE(Fixture, "functions_are_always_parenthesized_in_unions_or_inte
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "simple_intersections_printed_on_one_line")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauToStringSimpleCompositeTypesSingleLine", true};
|
||||
ScopedFastFlag sff{FFlag::LuauToStringSimpleCompositeTypesSingleLine, true};
|
||||
CheckResult result = check(R"(
|
||||
local a: string & number
|
||||
)");
|
||||
@ -249,7 +251,7 @@ TEST_CASE_FIXTURE(Fixture, "simple_intersections_printed_on_one_line")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "complex_intersections_printed_on_multiple_lines")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauToStringSimpleCompositeTypesSingleLine", true};
|
||||
ScopedFastFlag sff{FFlag::LuauToStringSimpleCompositeTypesSingleLine, true};
|
||||
CheckResult result = check(R"(
|
||||
local a: string & number & boolean
|
||||
)");
|
||||
@ -268,7 +270,7 @@ TEST_CASE_FIXTURE(Fixture, "complex_intersections_printed_on_multiple_lines")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_always_printed_on_multiple_lines")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauToStringSimpleCompositeTypesSingleLine", true};
|
||||
ScopedFastFlag sff{FFlag::LuauToStringSimpleCompositeTypesSingleLine, true};
|
||||
CheckResult result = check(R"(
|
||||
local a: ((string) -> string) & ((number) -> number)
|
||||
)");
|
||||
@ -285,7 +287,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_always_printed_on_multiple_line
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "simple_unions_printed_on_one_line")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauToStringSimpleCompositeTypesSingleLine", true};
|
||||
ScopedFastFlag sff{FFlag::LuauToStringSimpleCompositeTypesSingleLine, true};
|
||||
CheckResult result = check(R"(
|
||||
local a: number | boolean
|
||||
)");
|
||||
@ -298,7 +300,7 @@ TEST_CASE_FIXTURE(Fixture, "simple_unions_printed_on_one_line")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "complex_unions_printed_on_multiple_lines")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauToStringSimpleCompositeTypesSingleLine", true};
|
||||
ScopedFastFlag sff{FFlag::LuauToStringSimpleCompositeTypesSingleLine, true};
|
||||
CheckResult result = check(R"(
|
||||
local a: string | number | boolean
|
||||
)");
|
||||
@ -565,7 +567,7 @@ TEST_CASE_FIXTURE(Fixture, "toStringDetailed")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "toStringDetailed2")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauSharedSelf", true},
|
||||
{FFlag::DebugLuauSharedSelf, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -865,7 +867,7 @@ TEST_CASE_FIXTURE(Fixture, "pick_distinct_names_for_mixed_explicit_and_implicit_
|
||||
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_include_self_param")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"DebugLuauSharedSelf", true},
|
||||
{FFlag::DebugLuauSharedSelf, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -887,7 +889,7 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_include_self_param")
|
||||
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_self_param")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"DebugLuauSharedSelf", true},
|
||||
{FFlag::DebugLuauSharedSelf, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -965,8 +967,8 @@ Type 'string' could not be converted into 'number' in an invariant context)";
|
||||
TEST_CASE_FIXTURE(Fixture, "checked_fn_toString")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauCheckedFunctionSyntax", true},
|
||||
{"DebugLuauDeferredConstraintResolution", true},
|
||||
{FFlag::LuauCheckedFunctionSyntax, true},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, true},
|
||||
};
|
||||
|
||||
auto _result = loadDefinition(R"(
|
||||
|
@ -12,6 +12,8 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
struct TxnLogFixture
|
||||
{
|
||||
TxnLog log{/*useScopes*/ true};
|
||||
@ -33,7 +35,7 @@ TEST_SUITE_BEGIN("TxnLog");
|
||||
|
||||
TEST_CASE_FIXTURE(TxnLogFixture, "colliding_union_incoming_type_has_greater_scope")
|
||||
{
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
log.replace(c, BoundType{a});
|
||||
log2.replace(a, BoundType{c});
|
||||
@ -66,7 +68,7 @@ TEST_CASE_FIXTURE(TxnLogFixture, "colliding_union_incoming_type_has_greater_scop
|
||||
|
||||
TEST_CASE_FIXTURE(TxnLogFixture, "colliding_union_incoming_type_has_lesser_scope")
|
||||
{
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
log.replace(a, BoundType{c});
|
||||
log2.replace(c, BoundType{a});
|
||||
@ -99,7 +101,7 @@ TEST_CASE_FIXTURE(TxnLogFixture, "colliding_union_incoming_type_has_lesser_scope
|
||||
|
||||
TEST_CASE_FIXTURE(TxnLogFixture, "colliding_coincident_logs_do_not_create_degenerate_unions")
|
||||
{
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
log.replace(a, BoundType{b});
|
||||
log2.replace(a, BoundType{b});
|
||||
|
@ -8,6 +8,7 @@
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(DebugLuauSharedSelf);
|
||||
|
||||
TEST_SUITE_BEGIN("TypeAliases");
|
||||
|
||||
@ -188,7 +189,7 @@ TEST_CASE_FIXTURE(Fixture, "mutually_recursive_aliases")
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_aliases")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", true},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, true},
|
||||
};
|
||||
CheckResult result = check(R"(
|
||||
type T<a> = { v: a }
|
||||
@ -206,7 +207,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases")
|
||||
TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", true},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -334,7 +335,7 @@ TEST_CASE_FIXTURE(Fixture, "stringify_type_alias_of_recursive_template_table_typ
|
||||
TEST_CASE_FIXTURE(Fixture, "cli_38393_recursive_intersection_oom")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", false},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, false},
|
||||
}; // FIXME
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -820,7 +821,7 @@ TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_uni
|
||||
TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_unification_with_any_2")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauSharedSelf", true},
|
||||
{FFlag::DebugLuauSharedSelf, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
@ -77,7 +78,7 @@ TEST_CASE_FIXTURE(Fixture, "assignment_cannot_transform_a_table_property_type")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "assignments_to_unannotated_parameters_can_transform_the_type")
|
||||
{
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(x)
|
||||
@ -93,7 +94,7 @@ TEST_CASE_FIXTURE(Fixture, "assignments_to_unannotated_parameters_can_transform_
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "assignments_to_annotated_parameters_are_checked")
|
||||
{
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(x: string)
|
||||
@ -768,7 +769,7 @@ int AssertionCatcher::tripped;
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag")
|
||||
{
|
||||
ScopedFastFlag sffs{"DebugLuauMagicTypes", true};
|
||||
ScopedFastFlag sffs{FFlag::DebugLuauMagicTypes, true};
|
||||
|
||||
AssertionCatcher ac;
|
||||
|
||||
@ -782,7 +783,7 @@ TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag_handler")
|
||||
{
|
||||
ScopedFastFlag sffs{"DebugLuauMagicTypes", true};
|
||||
ScopedFastFlag sffs{FFlag::DebugLuauMagicTypes, true};
|
||||
|
||||
bool caught = false;
|
||||
|
||||
@ -800,7 +801,7 @@ TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag_handler
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "luau_ice_is_not_special_without_the_flag")
|
||||
{
|
||||
ScopedFastFlag sffs{"DebugLuauMagicTypes", false};
|
||||
ScopedFastFlag sffs{FFlag::DebugLuauMagicTypes, false};
|
||||
|
||||
// We only care that this does not throw
|
||||
check(R"(
|
||||
@ -816,7 +817,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_print_is_magic_if_the_flag_is_set")
|
||||
output.push_back(s);
|
||||
});
|
||||
|
||||
ScopedFastFlag sffs{"DebugLuauMagicTypes", true};
|
||||
ScopedFastFlag sffs{FFlag::DebugLuauMagicTypes, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a: _luau_print<typeof(math.abs)>
|
||||
@ -829,7 +830,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_print_is_magic_if_the_flag_is_set")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "luau_print_is_not_special_without_the_flag")
|
||||
{
|
||||
ScopedFastFlag sffs{"DebugLuauMagicTypes", false};
|
||||
ScopedFastFlag sffs{FFlag::DebugLuauMagicTypes, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a: _luau_print<number>
|
||||
@ -840,7 +841,7 @@ TEST_CASE_FIXTURE(Fixture, "luau_print_is_not_special_without_the_flag")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "luau_print_incomplete")
|
||||
{
|
||||
ScopedFastFlag sffs{"DebugLuauMagicTypes", true};
|
||||
ScopedFastFlag sffs{FFlag::DebugLuauMagicTypes, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a: _luau_print
|
||||
|
@ -9,6 +9,7 @@
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
|
||||
|
||||
TEST_SUITE_BEGIN("BuiltinTests");
|
||||
|
||||
@ -133,7 +134,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_predicate")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauAlwaysCommitInferencesOfFunctionCalls", true},
|
||||
{FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -6,11 +6,14 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauTinyControlFlowAnalysis);
|
||||
LUAU_FASTFLAG(LuauLoopControlFlowAnalysis);
|
||||
|
||||
TEST_SUITE_BEGIN("ControlFlowAnalysis");
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
@ -28,10 +31,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}})
|
||||
@ -51,10 +51,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}})
|
||||
@ -74,7 +71,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_return")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?, y: string?)
|
||||
@ -96,10 +93,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_return")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_break")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
@ -124,10 +118,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_break")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_continue")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
@ -152,10 +143,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_continue")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_break")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
@ -180,10 +168,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_break")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_continue")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
@ -208,7 +193,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_continue")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_rand_return_elif_not_y_return")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?, y: string?)
|
||||
@ -232,10 +217,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_rand_return_elif_not_y_
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_break")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
@ -262,10 +244,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_br
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_not_y_continue")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
@ -292,7 +271,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_no
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_rand_return_elif_not_y_fallthrough")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?, y: string?)
|
||||
@ -316,10 +295,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_rand_return_elif_no
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_fallthrough")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
@ -346,10 +322,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_fa
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_not_y_fallthrough")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
@ -376,7 +349,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_no
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_not_z_return")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?, y: string?, z: string?)
|
||||
@ -402,10 +375,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_fallthrough_elif_not_z_break")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
|
||||
@ -435,10 +405,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_fallthrough_elif_n
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_fallthrough_elif_not_z_continue")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
|
||||
@ -468,10 +435,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_fallthrough_eli
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_throw_elif_not_z_fallthrough")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
|
||||
@ -501,10 +465,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_throw_elif_not_
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_not_z_break")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
|
||||
@ -534,7 +495,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "do_if_not_x_return")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
@ -554,10 +515,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_if_not_x_return")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_break")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}})
|
||||
@ -579,10 +537,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_break")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_continue")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}})
|
||||
@ -604,7 +559,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_continue")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_isnt_guaranteed_to_run_first")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
@ -627,7 +582,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_isnt_guaranteed
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_is_guaranteed_to_run_first")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
@ -650,7 +605,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_is_guaranteed_t
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_is_guaranteed_to_run_first_2")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
@ -673,7 +628,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_is_guaranteed_t
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_then_error")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
@ -691,7 +646,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_then_error")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_then_assert_false")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
@ -709,7 +664,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_then_assert_false")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_if_not_y_return")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?, y: string?)
|
||||
@ -733,10 +688,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_if_not_y_return")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_break")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
@ -763,10 +715,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_break")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_continue")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
@ -793,10 +742,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_continue")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_throw")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
@ -823,10 +769,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_throw")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_continue")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
@ -853,7 +796,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_continue")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
@ -876,10 +819,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_breaking")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}})
|
||||
@ -904,10 +844,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_breaking")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_continuing")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}})
|
||||
@ -932,7 +869,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_continuing")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_scope")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
// In CG, we walk the block to prototype aliases. We then visit the block in-order, which will resolve the prototype to a real type.
|
||||
// That second walk assumes that the name occurs in the same `Scope` that the prototype walk had. If we arbitrarily change scope midway
|
||||
@ -958,10 +895,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_scope_breaking")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}})
|
||||
@ -986,10 +920,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_scope_continuing")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}})
|
||||
@ -1014,7 +945,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Ok<T> = { tag: "ok", value: T }
|
||||
@ -1049,10 +980,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_breaking")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Ok<T> = { tag: "ok", value: T }
|
||||
@ -1085,10 +1013,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_breaking")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_continuing")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauTinyControlFlowAnalysis", true},
|
||||
{"LuauLoopControlFlowAnalysis", true}
|
||||
};
|
||||
ScopedFastFlag flags[] = {{FFlag::LuauTinyControlFlowAnalysis, true}, {FFlag::LuauLoopControlFlowAnalysis, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Ok<T> = { tag: "ok", value: T }
|
||||
@ -1121,7 +1046,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_continuing")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "do_assert_x")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTinyControlFlowAnalysis", true};
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
|
@ -13,6 +13,7 @@ using namespace Luau;
|
||||
using std::nullopt;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferClasses");
|
||||
|
||||
@ -368,7 +369,7 @@ b.X = 2 -- real Vector2.X is also read-only
|
||||
TEST_CASE_FIXTURE(ClassFixture, "detailed_class_unification_error")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauAlwaysCommitInferencesOfFunctionCalls", true},
|
||||
{FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true},
|
||||
};
|
||||
CheckResult result = check(R"(
|
||||
local function foo(v)
|
||||
|
@ -7,6 +7,8 @@
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauDefinitionFileSetModuleName)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
TEST_SUITE_BEGIN("DefinitionTests");
|
||||
@ -441,4 +443,27 @@ TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
|
||||
REQUIRE(result.success);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "definition_file_has_source_module_name_set")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauDefinitionFileSetModuleName, true};
|
||||
|
||||
LoadDefinitionFileResult result = loadDefinition(R"(
|
||||
declare class Foo
|
||||
end
|
||||
)");
|
||||
|
||||
REQUIRE(result.success);
|
||||
|
||||
CHECK_EQ(result.sourceModule.name, "@test");
|
||||
CHECK_EQ(result.sourceModule.humanReadableName, "@test");
|
||||
|
||||
std::optional<TypeFun> fooTy = frontend.globals.globalScope->lookupType("Foo");
|
||||
REQUIRE(fooTy);
|
||||
|
||||
const ClassType* ctv = get<ClassType>(fooTy->type);
|
||||
|
||||
REQUIRE(ctv);
|
||||
CHECK_EQ(ctv->definitionModuleName, "@test");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -16,6 +16,8 @@ using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
|
||||
LUAU_FASTINT(LuauTarjanChildLimit);
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferFunctions");
|
||||
|
||||
@ -1911,7 +1913,7 @@ end
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization")
|
||||
{
|
||||
ScopedFastInt sfi{"LuauTarjanChildLimit", 2};
|
||||
ScopedFastInt sfi{FInt::LuauTarjanChildLimit, 2};
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
@ -1995,7 +1997,7 @@ TEST_CASE_FIXTURE(Fixture, "function_exprs_are_generalized_at_signature_scope_no
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauAlwaysCommitInferencesOfFunctionCalls", true},
|
||||
{FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -2050,7 +2052,7 @@ Table type '{ x: number }' not compatible with type 'vec2' because the former is
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauAlwaysCommitInferencesOfFunctionCalls", true};
|
||||
ScopedFastFlag sff{FFlag::LuauAlwaysCommitInferencesOfFunctionCalls, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f<a>(x: a, y: a): a
|
||||
@ -2103,7 +2105,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_packs_are_not_variadic")
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function apply<a, b..., c...>(f: (a, b...) -> c..., x: a)
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(DebugLuauSharedSelf);
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
@ -274,7 +275,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_nested_generic_function")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "infer_generic_methods")
|
||||
{
|
||||
ScopedFastFlag sff{"DebugLuauSharedSelf", true};
|
||||
ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x = {}
|
||||
@ -1260,7 +1261,7 @@ end
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "higher_rank_polymorphism_should_not_accept_instantiated_arguments")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{"LuauInstantiateInSubtyping", true},
|
||||
{FFlag::LuauInstantiateInSubtyping, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user