Sync to upstream/release/662

This commit is contained in:
Vyacheslav Egorov 2025-02-21 14:39:02 +02:00
parent 587cf132c6
commit 279e15a52f
52 changed files with 1118 additions and 292 deletions

View file

@ -109,6 +109,21 @@ struct FunctionCheckConstraint
NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes;
};
// table_check expectedType exprType
//
// If `expectedType` is a table type and `exprType` is _also_ a table type,
// propogate the member types of `expectedType` into the types of `exprType`.
// This is used to implement bidirectional inference on table assignment.
// Also see: FunctionCheckConstraint.
struct TableCheckConstraint
{
TypeId expectedType;
TypeId exprType;
AstExprTable* table = nullptr;
NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes;
NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes;
};
// prim FreeType ExpectedType PrimitiveType
//
// FreeType is bounded below by the singleton type and above by PrimitiveType
@ -273,7 +288,8 @@ using ConstraintV = Variant<
UnpackConstraint,
ReduceConstraint,
ReducePackConstraint,
EqualityConstraint>;
EqualityConstraint,
TableCheckConstraint>;
struct Constraint
{

View file

@ -118,6 +118,8 @@ struct ConstraintSolver
// A mapping from free types to the number of unresolved constraints that mention them.
DenseHashMap<TypeId, size_t> unresolvedConstraints{{}};
std::unordered_map<NotNull<const Constraint>, DenseHashSet<TypeId>> maybeMutatedFreeTypes;
// Irreducible/uninhabited type functions or type pack functions.
DenseHashSet<const void*> uninhabitedTypeFunctions{{}};
@ -201,6 +203,7 @@ public:
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const TableCheckConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint);
@ -421,10 +424,7 @@ public:
ToStringOptions opts;
void fillInDiscriminantTypes(
NotNull<const Constraint> constraint,
const std::vector<std::optional<TypeId>>& discriminantTypes
);
void fillInDiscriminantTypes(NotNull<const Constraint> constraint, const std::vector<std::optional<TypeId>>& discriminantTypes);
};
void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts);

View file

@ -105,7 +105,7 @@ private:
std::vector<Id> storage;
};
template <typename L>
template<typename L>
using Node = EqSat::Node<L>;
using EType = EqSat::Language<

View file

@ -11,14 +11,12 @@
namespace Luau
{
class AstStat;
class AstExpr;
class AstNode;
class AstStatBlock;
struct AstLocal;
struct RequireTraceResult
{
DenseHashMap<const AstExpr*, ModuleInfo> exprs{nullptr};
DenseHashMap<const AstNode*, ModuleInfo> exprs{nullptr};
std::vector<std::pair<ModuleName, Location>> requireList;
};

View file

@ -310,7 +310,8 @@ struct MagicFunctionTypeCheckContext
struct MagicFunction
{
virtual std::optional<WithPredicate<TypePackId>> handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) = 0;
virtual std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) = 0;
// Callback to allow custom typechecking of builtin function calls whose argument types
// will only be resolved after constraint solving. For example, the arguments to string.format

View file

@ -3,6 +3,7 @@
#include "Luau/Common.h"
#include "Luau/Variant.h"
#include "Luau/TypeFwd.h"
#include <optional>
#include <string>
@ -217,7 +218,9 @@ struct TypeFunctionClassType
std::optional<TypeFunctionTypeId> parent;
std::string name;
TypeId classTy;
std::string name_DEPRECATED;
};
struct TypeFunctionGenericType

View file

@ -32,7 +32,7 @@ struct TypeFunctionRuntimeBuilderState
// Invariant: users can not create a new class types -> any class types that get deserialized must have been an argument to the type function
// Using this invariant, whenever a ClassType is serialized, we can put it into this map
// whenever a ClassType is deserialized, we can use this map to return the corresponding value
DenseHashMap<std::string, TypeId> classesSerialized{{}};
DenseHashMap<std::string, TypeId> classesSerialized_DEPRECATED{{}};
// List of errors that occur during serialization/deserialization
// At every iteration of serialization/deserialzation, if this list.size() != 0, we halt the process
@ -40,8 +40,6 @@ struct TypeFunctionRuntimeBuilderState
TypeFunctionRuntimeBuilderState(NotNull<TypeFunctionContext> ctx)
: ctx(ctx)
, classesSerialized({})
, errors({})
{
}
};

View file

@ -51,7 +51,6 @@ struct UnifierSharedState
UnifierCounters counters;
bool reentrantTypeReduction = false;
};
struct TypeReductionRentrancyGuard final

View file

@ -1168,7 +1168,7 @@ struct AstJsonEncoder : public AstVisitor
"AstTypeGroup",
[&]()
{
write("type", node->type);
write("inner", node->type);
}
);
return false;

View file

@ -20,7 +20,6 @@
#include <utility>
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(AutocompleteRequirePathSuggestions2)
LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
@ -1521,12 +1520,9 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(
{
for (const std::string& tag : funcType->tags)
{
if (FFlag::AutocompleteRequirePathSuggestions2)
if (tag == kRequireTagName && fileResolver)
{
if (tag == kRequireTagName && fileResolver)
{
return convertRequireSuggestionsToAutocompleteEntryMap(fileResolver->getRequireSuggestions(module->name, candidateString));
}
return convertRequireSuggestionsToAutocompleteEntryMap(fileResolver->getRequireSuggestions(module->name, candidateString));
}
if (std::optional<AutocompleteEntryMap> ret = callback(tag, getMethodContainingClass(module, candidate->func), candidateString))
{

View file

@ -30,7 +30,6 @@
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression)
LUAU_FASTFLAG(AutocompleteRequirePathSuggestions2)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauFreezeIgnorePersistent)
@ -41,68 +40,79 @@ namespace Luau
struct MagicSelect final : MagicFunction
{
std::optional<WithPredicate<TypePackId>> handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicSetMetatable final : MagicFunction
{
std::optional<WithPredicate<TypePackId>> handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicAssert final : MagicFunction
{
std::optional<WithPredicate<TypePackId>> handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicPack final : MagicFunction
{
std::optional<WithPredicate<TypePackId>> handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicRequire final : MagicFunction
{
std::optional<WithPredicate<TypePackId>> handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicClone final : MagicFunction
{
std::optional<WithPredicate<TypePackId>> handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicFreeze final : MagicFunction
{
std::optional<WithPredicate<TypePackId>> handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicFormat final : MagicFunction
{
std::optional<WithPredicate<TypePackId>> handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
bool typeCheck(const MagicFunctionTypeCheckContext& ctx) override;
};
struct MagicMatch final : MagicFunction
{
std::optional<WithPredicate<TypePackId>> handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicGmatch final : MagicFunction
{
std::optional<WithPredicate<TypePackId>> handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicFind final : MagicFunction
{
std::optional<WithPredicate<TypePackId>> handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
@ -454,16 +464,9 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
attachMagicFunction(ttv->props["freeze"].type(), std::make_shared<MagicFreeze>());
}
if (FFlag::AutocompleteRequirePathSuggestions2)
{
TypeId requireTy = getGlobalBinding(globals, "require");
attachTag(requireTy, kRequireTagName);
attachMagicFunction(requireTy, std::make_shared<MagicRequire>());
}
else
{
attachMagicFunction(getGlobalBinding(globals, "require"), std::make_shared<MagicRequire>());
}
TypeId requireTy = getGlobalBinding(globals, "require");
attachTag(requireTy, kRequireTagName);
attachMagicFunction(requireTy, std::make_shared<MagicRequire>());
}
static std::vector<TypeId> parseFormatString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size)
@ -637,15 +640,15 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
{
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
{
case ErrorSuppression::Suppress:
break;
case ErrorSuppression::NormalizationFailed:
break;
case ErrorSuppression::DoNotSuppress:
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
case ErrorSuppression::Suppress:
break;
case ErrorSuppression::NormalizationFailed:
break;
case ErrorSuppression::DoNotSuppress:
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
if (!reasonings.suppressed)
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
if (!reasonings.suppressed)
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
}
}
else
@ -1503,7 +1506,8 @@ static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCa
return std::nullopt;
}
std::optional<WithPredicate<TypePackId>> MagicFreeze::handleOldSolver(struct TypeChecker &, const std::shared_ptr<struct Scope> &, const class AstExprCall &, WithPredicate<TypePackId>)
std::optional<WithPredicate<TypePackId>> MagicFreeze::
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>)
{
return std::nullopt;
}

View file

@ -146,6 +146,10 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
{
rci.traverse(rpc->tp);
}
else if (auto tcc = get<TableCheckConstraint>(*this))
{
rci.traverse(tcc->exprType);
}
return types;
}

View file

@ -36,6 +36,7 @@ LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAGVARIABLE(LuauNewSolverPrePopulateClasses)
LUAU_FASTFLAGVARIABLE(LuauNewSolverPopulateTableLocations)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauDeferBidirectionalInferenceForTableAssignment)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments)
@ -2998,30 +2999,46 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
if (expectedType)
{
Unifier2 unifier{arena, builtinTypes, NotNull{scope.get()}, ice};
std::vector<TypeId> toBlock;
// This logic is incomplete as we want to re-run this
// _after_ blocked types have resolved, but this
// allows us to do some bidirectional inference.
toBlock = findBlockedTypesIn(expr, NotNull{&module->astTypes});
if (toBlock.empty())
if (FFlag::LuauDeferBidirectionalInferenceForTableAssignment)
{
matchLiteralType(
NotNull{&module->astTypes},
NotNull{&module->astExpectedTypes},
builtinTypes,
arena,
NotNull{&unifier},
*expectedType,
ty,
expr,
toBlock
addConstraint(
scope,
expr->location,
TableCheckConstraint{
*expectedType,
ty,
expr,
NotNull{&module->astTypes},
NotNull{&module->astExpectedTypes},
}
);
// The visitor we ran prior should ensure that there are no
// blocked types that we would encounter while matching on
// this expression.
LUAU_ASSERT(toBlock.empty());
}
else
{
Unifier2 unifier{arena, builtinTypes, NotNull{scope.get()}, ice};
std::vector<TypeId> toBlock;
// This logic is incomplete as we want to re-run this
// _after_ blocked types have resolved, but this
// allows us to do some bidirectional inference.
toBlock = findBlockedTypesIn(expr, NotNull{&module->astTypes});
if (toBlock.empty())
{
matchLiteralType(
NotNull{&module->astTypes},
NotNull{&module->astExpectedTypes},
builtinTypes,
arena,
NotNull{&unifier},
*expectedType,
ty,
expr,
toBlock
);
// The visitor we ran prior should ensure that there are no
// blocked types that we would encounter while matching on
// this expression.
LUAU_ASSERT(toBlock.empty());
}
}
}

View file

@ -37,7 +37,7 @@ LUAU_FASTFLAGVARIABLE(LuauAllowNilAssignmentToIndexer)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauAlwaysFillInFunctionCallDiscriminantTypes)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAGVARIABLE(LuauPrecalculateMutatedFreeTypes)
LUAU_FASTFLAGVARIABLE(LuauPrecalculateMutatedFreeTypes2)
namespace Luau
{
@ -355,13 +355,27 @@ ConstraintSolver::ConstraintSolver(
{
unsolvedConstraints.emplace_back(c);
// initialize the reference counts for the free types in this constraint.
for (auto ty : c->getMaybeMutatedFreeTypes())
if (FFlag::LuauPrecalculateMutatedFreeTypes2)
{
// increment the reference count for `ty`
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
refCount += 1;
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
for (auto ty : maybeMutatedTypesPerConstraint)
{
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
refCount += 1;
}
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
}
else
{
// initialize the reference counts for the free types in this constraint.
for (auto ty : c->getMaybeMutatedFreeTypes())
{
// increment the reference count for `ty`
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
refCount += 1;
}
}
for (NotNull<const Constraint> dep : c->dependencies)
{
@ -439,10 +453,6 @@ void ConstraintSolver::run()
snapshot = logger->prepareStepSnapshot(rootScope, c, force, unsolvedConstraints);
}
std::optional<DenseHashSet<TypeId>> mutatedFreeTypes = std::nullopt;
if (FFlag::LuauPrecalculateMutatedFreeTypes)
mutatedFreeTypes = c->getMaybeMutatedFreeTypes();
bool success = tryDispatch(c, force);
progress |= success;
@ -452,23 +462,29 @@ void ConstraintSolver::run()
unblock(c);
unsolvedConstraints.erase(unsolvedConstraints.begin() + ptrdiff_t(i));
if (FFlag::LuauPrecalculateMutatedFreeTypes)
if (FFlag::LuauPrecalculateMutatedFreeTypes2)
{
for (auto ty : c->getMaybeMutatedFreeTypes())
mutatedFreeTypes->insert(ty);
for (auto ty : *mutatedFreeTypes)
const auto maybeMutated = maybeMutatedFreeTypes.find(c);
if (maybeMutated != maybeMutatedFreeTypes.end())
{
size_t& refCount = unresolvedConstraints[ty];
if (refCount > 0)
refCount -= 1;
for (auto ty : maybeMutated->second)
{
// There is a high chance that this type has been rebound
// across blocked types, rebound free types, pending
// expansion types, etc, so we need to follow it.
ty = follow(ty);
size_t& refCount = unresolvedConstraints[ty];
if (refCount > 0)
refCount -= 1;
// We have two constraints that are designed to wait for the
// refCount on a free type to be equal to 1: the
// PrimitiveTypeConstraint and ReduceConstraint. We
// therefore wake any constraint waiting for a free type's
// refcount to be 1 or 0.
if (refCount <= 1)
unblock(ty, Location{});
// We have two constraints that are designed to wait for the
// refCount on a free type to be equal to 1: the
// PrimitiveTypeConstraint and ReduceConstraint. We
// therefore wake any constraint waiting for a free type's
// refcount to be 1 or 0.
if (refCount <= 1)
unblock(ty, Location{});
}
}
}
else
@ -668,6 +684,8 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
success = tryDispatch(*fcc, constraint);
else if (auto fcc = get<FunctionCheckConstraint>(*constraint))
success = tryDispatch(*fcc, constraint);
else if (auto tcc = get<TableCheckConstraint>(*constraint))
success = tryDispatch(*tcc, constraint);
else if (auto fcc = get<PrimitiveTypeConstraint>(*constraint))
success = tryDispatch(*fcc, constraint);
else if (auto hpc = get<HasPropConstraint>(*constraint))
@ -1170,10 +1188,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
return true;
}
void ConstraintSolver::fillInDiscriminantTypes(
NotNull<const Constraint> constraint,
const std::vector<std::optional<TypeId>>& discriminantTypes
)
void ConstraintSolver::fillInDiscriminantTypes(NotNull<const Constraint> constraint, const std::vector<std::optional<TypeId>>& discriminantTypes)
{
for (std::optional<TypeId> ty : discriminantTypes)
{
@ -1521,6 +1536,28 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
return true;
}
bool ConstraintSolver::tryDispatch(const TableCheckConstraint& c, NotNull<const Constraint> constraint)
{
// This is expensive as we need to traverse a (potentially large)
// literal up front in order to determine if there are any blocked
// types, otherwise we may run `matchTypeLiteral` multiple times,
// which right now may fail due to being non-idempotent (it
// destructively updates the underlying literal type).
auto blockedTypes = findBlockedTypesIn(c.table, c.astTypes);
for (const auto ty : blockedTypes)
{
block(ty, constraint);
}
if (!blockedTypes.empty())
return false;
Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}};
std::vector<TypeId> toBlock;
(void)matchLiteralType(c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, c.expectedType, c.exprType, c.table, toBlock);
LUAU_ASSERT(toBlock.empty());
return true;
}
bool ConstraintSolver::tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint)
{
std::optional<TypeId> expectedType = c.expectedType ? std::make_optional<TypeId>(follow(*c.expectedType)) : std::nullopt;

View file

@ -3,6 +3,7 @@
LUAU_FASTFLAG(LuauBufferBitMethods2)
LUAU_FASTFLAG(LuauVector2Constructor)
LUAU_FASTFLAGVARIABLE(LuauDebugInfoDefn)
namespace Luau
{
@ -209,6 +210,15 @@ declare table: {
static const std::string kBuiltinDefinitionDebugSrc = R"BUILTIN_SRC(
declare debug: {
info: ((thread: thread, level: number, options: string) -> ...any) & ((level: number, options: string) -> ...any) & (<A..., R1...>(func: (A...) -> R1..., options: string) -> ...any),
traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string),
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionDebugSrc_DEPRECATED = R"BUILTIN_SRC(
declare debug: {
info: (<R...>(thread: thread, level: number, options: string) -> R...) & (<R...>(level: number, options: string) -> R...) & (<A..., R1..., R2...>(func: (A...) -> R1..., options: string) -> R2...),
traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string),
@ -362,7 +372,7 @@ std::string getBuiltinDefinitionSource()
result += kBuiltinDefinitionOsSrc;
result += kBuiltinDefinitionCoroutineSrc;
result += kBuiltinDefinitionTableSrc;
result += kBuiltinDefinitionDebugSrc;
result += FFlag::LuauDebugInfoDefn ? kBuiltinDefinitionDebugSrc : kBuiltinDefinitionDebugSrc_DEPRECATED;
result += kBuiltinDefinitionUtf8Src;
result += FFlag::LuauBufferBitMethods2 ? kBuiltinDefinitionBufferSrc : kBuiltinDefinitionBufferSrc_DEPRECATED;

View file

@ -396,7 +396,8 @@ Id toId(
{
LUAU_ASSERT(tfun->packArguments.empty());
if (tfun->userFuncName) {
if (tfun->userFuncName)
{
// TODO: User defined type functions are pseudo-effectful: error
// reporting is done via the `print` statement, so running a
// UDTF multiple times may end up double erroring. egraphs

View file

@ -30,7 +30,6 @@ LUAU_FASTFLAG(LuauAllowFragmentParsing);
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteBugfixes)
LUAU_FASTFLAG(LuauReferenceAllocatorInNewSolver)
LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf)
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking)
LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule)
@ -596,7 +595,7 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
return {};
}
if (FFlag::LuauIncrementalAutocompleteBugfixes && FFlag::LuauReferenceAllocatorInNewSolver)
if (FFlag::LuauIncrementalAutocompleteBugfixes)
{
if (sourceModule->allocator.get() != module->allocator.get())
{

View file

@ -52,7 +52,6 @@ LUAU_FASTFLAGVARIABLE(LuauBetterReverseDependencyTracking)
LUAU_FASTFLAG(StudioReportLuauAny2)
LUAU_FASTFLAGVARIABLE(LuauStoreSolverTypeOnModule)
LUAU_FASTFLAGVARIABLE(LuauReferenceAllocatorInNewSolver)
LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena)
namespace Luau
@ -1430,11 +1429,8 @@ ModulePtr check(
result->mode = mode;
result->internalTypes.owningModule = result.get();
result->interfaceTypes.owningModule = result.get();
if (FFlag::LuauReferenceAllocatorInNewSolver)
{
result->allocator = sourceModule.allocator;
result->names = sourceModule.names;
}
result->allocator = sourceModule.allocator;
result->names = sourceModule.names;
iceHandler->moduleName = sourceModule.name;
@ -1751,7 +1747,7 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(const ModuleName&
if (FFlag::LuauBetterReverseDependencyTracking)
{
// clear all prior dependents. we will re-add them after parsing the rest of the graph
// clear all prior dependents. we will re-add them after parsing the rest of the graph
for (const auto& [moduleName, _] : sourceNode->requireLocations)
{
if (auto depIt = sourceNodes.find(moduleName); depIt != sourceNodes.end())

View file

@ -2,6 +2,8 @@
#include "Luau/Generalization.h"
#include "Luau/Common.h"
#include "Luau/DenseHash.h"
#include "Luau/Scope.h"
#include "Luau/Type.h"
#include "Luau/ToString.h"
@ -10,7 +12,7 @@
#include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauGeneralizationRemoveRecursiveUpperBound)
LUAU_FASTFLAGVARIABLE(LuauGeneralizationRemoveRecursiveUpperBound2)
namespace Luau
{
@ -50,7 +52,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
{
}
static void replace(DenseHashSet<TypeId>& seen, TypeId haystack, TypeId needle, TypeId replacement)
void replace(DenseHashSet<TypeId>& seen, TypeId haystack, TypeId needle, TypeId replacement)
{
haystack = follow(haystack);
@ -97,6 +99,10 @@ struct MutatingGeneralizer : TypeOnceVisitor
LUAU_ASSERT(onlyType != haystack);
emplaceType<BoundType>(asMutable(haystack), onlyType);
}
else if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound2 && ut->options.empty())
{
emplaceType<BoundType>(asMutable(haystack), builtinTypes->neverType);
}
return;
}
@ -139,6 +145,10 @@ struct MutatingGeneralizer : TypeOnceVisitor
TypeId onlyType = it->parts[0];
LUAU_ASSERT(onlyType != needle);
emplaceType<BoundType>(asMutable(needle), onlyType);
}
else if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound2 && it->parts.empty())
{
emplaceType<BoundType>(asMutable(needle), builtinTypes->unknownType);
}
return;
@ -233,53 +243,6 @@ struct MutatingGeneralizer : TypeOnceVisitor
else
{
TypeId ub = follow(ft->upperBound);
if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound)
{
// If the upper bound is a union type or an intersection type,
// and one of it's members is the free type we're
// generalizing, don't include it in the upper bound. For a
// free type such as:
//
// t1 where t1 = D <: 'a <: (A | B | C | t1)
//
// Naively replacing it with it's upper bound creates:
//
// t1 where t1 = A | B | C | t1
//
// It makes sense to just optimize this and exclude the
// recursive component by semantic subtyping rules.
if (auto itv = get<IntersectionType>(ub))
{
std::vector<TypeId> newIds;
newIds.reserve(itv->parts.size());
for (auto part : itv)
{
if (part != ty)
newIds.push_back(part);
}
if (newIds.size() == 1)
ub = newIds[0];
else if (newIds.size() > 0)
ub = arena->addType(IntersectionType{std::move(newIds)});
}
else if (auto utv = get<UnionType>(ub))
{
std::vector<TypeId> newIds;
newIds.reserve(utv->options.size());
for (auto part : utv)
{
if (part != ty)
newIds.push_back(part);
}
if (newIds.size() == 1)
ub = newIds[0];
else if (newIds.size() > 0)
ub = arena->addType(UnionType{std::move(newIds)});
}
}
if (FreeType* upperFree = getMutable<FreeType>(ub); upperFree && upperFree->lowerBound == ty)
upperFree->lowerBound = builtinTypes->neverType;
else

View file

@ -2296,7 +2296,7 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
//
// Dog & ~Animal
//
// Clearly this intersects to never, so we mark this class as
// Clearly this intersects to never, so we mark this class as
// being removed from the normalized class type.
emptyIntersectWithNegation = true;
break;

View file

@ -4,6 +4,8 @@
#include "Luau/Ast.h"
#include "Luau/Module.h"
LUAU_FASTFLAGVARIABLE(LuauExtendedSimpleRequire)
namespace Luau
{
@ -65,7 +67,7 @@ struct RequireTracer : AstVisitor
return true;
}
AstExpr* getDependent(AstExpr* node)
AstExpr* getDependent_DEPRECATED(AstExpr* node)
{
if (AstExprLocal* expr = node->as<AstExprLocal>())
return locals[expr->local];
@ -78,50 +80,122 @@ struct RequireTracer : AstVisitor
else
return nullptr;
}
AstNode* getDependent(AstNode* node)
{
if (AstExprLocal* expr = node->as<AstExprLocal>())
return locals[expr->local];
else if (AstExprIndexName* expr = node->as<AstExprIndexName>())
return expr->expr;
else if (AstExprIndexExpr* expr = node->as<AstExprIndexExpr>())
return expr->expr;
else if (AstExprCall* expr = node->as<AstExprCall>(); expr && expr->self)
return expr->func->as<AstExprIndexName>()->expr;
else if (AstExprGroup* expr = node->as<AstExprGroup>())
return expr->expr;
else if (AstExprTypeAssertion* expr = node->as<AstExprTypeAssertion>())
return expr->annotation;
else if (AstTypeGroup* expr = node->as<AstTypeGroup>())
return expr->type;
else if (AstTypeTypeof* expr = node->as<AstTypeTypeof>())
return expr->expr;
else
return nullptr;
}
void process()
{
ModuleInfo moduleContext{currentModuleName};
// seed worklist with require arguments
work.reserve(requireCalls.size());
for (AstExprCall* require : requireCalls)
work.push_back(require->args.data[0]);
// push all dependent expressions to the work stack; note that the vector is modified during traversal
for (size_t i = 0; i < work.size(); ++i)
if (AstExpr* dep = getDependent(work[i]))
work.push_back(dep);
// resolve all expressions to a module info
for (size_t i = work.size(); i > 0; --i)
if (FFlag::LuauExtendedSimpleRequire)
{
AstExpr* expr = work[i - 1];
// seed worklist with require arguments
work.reserve(requireCalls.size());
// when multiple expressions depend on the same one we push it to work queue multiple times
if (result.exprs.contains(expr))
continue;
for (AstExprCall* require : requireCalls)
work.push_back(require->args.data[0]);
std::optional<ModuleInfo> info;
if (AstExpr* dep = getDependent(expr))
// push all dependent expressions to the work stack; note that the vector is modified during traversal
for (size_t i = 0; i < work.size(); ++i)
{
const ModuleInfo* context = result.exprs.find(dep);
if (AstNode* dep = getDependent(work[i]))
work.push_back(dep);
}
// locals just inherit their dependent context, no resolution required
if (expr->is<AstExprLocal>())
info = context ? std::optional<ModuleInfo>(*context) : std::nullopt;
// resolve all expressions to a module info
for (size_t i = work.size(); i > 0; --i)
{
AstNode* expr = work[i - 1];
// when multiple expressions depend on the same one we push it to work queue multiple times
if (result.exprs.contains(expr))
continue;
std::optional<ModuleInfo> info;
if (AstNode* dep = getDependent(expr))
{
const ModuleInfo* context = result.exprs.find(dep);
if (context && expr->is<AstExprLocal>())
info = *context; // locals just inherit their dependent context, no resolution required
else if (context && (expr->is<AstExprGroup>() || expr->is<AstTypeGroup>()))
info = *context; // simple group nodes propagate their value
else if (context && (expr->is<AstTypeTypeof>() || expr->is<AstExprTypeAssertion>()))
info = *context; // typeof type annotations will resolve to the typeof content
else if (AstExpr* asExpr = expr->asExpr())
info = fileResolver->resolveModule(context, asExpr);
}
else if (AstExpr* asExpr = expr->asExpr())
{
info = fileResolver->resolveModule(&moduleContext, asExpr);
}
if (info)
result.exprs[expr] = std::move(*info);
}
}
else
{
// seed worklist with require arguments
work_DEPRECATED.reserve(requireCalls.size());
for (AstExprCall* require : requireCalls)
work_DEPRECATED.push_back(require->args.data[0]);
// push all dependent expressions to the work stack; note that the vector is modified during traversal
for (size_t i = 0; i < work_DEPRECATED.size(); ++i)
if (AstExpr* dep = getDependent_DEPRECATED(work_DEPRECATED[i]))
work_DEPRECATED.push_back(dep);
// resolve all expressions to a module info
for (size_t i = work_DEPRECATED.size(); i > 0; --i)
{
AstExpr* expr = work_DEPRECATED[i - 1];
// when multiple expressions depend on the same one we push it to work queue multiple times
if (result.exprs.contains(expr))
continue;
std::optional<ModuleInfo> info;
if (AstExpr* dep = getDependent_DEPRECATED(expr))
{
const ModuleInfo* context = result.exprs.find(dep);
// locals just inherit their dependent context, no resolution required
if (expr->is<AstExprLocal>())
info = context ? std::optional<ModuleInfo>(*context) : std::nullopt;
else
info = fileResolver->resolveModule(context, expr);
}
else
info = fileResolver->resolveModule(context, expr);
}
else
{
info = fileResolver->resolveModule(&moduleContext, expr);
}
{
info = fileResolver->resolveModule(&moduleContext, expr);
}
if (info)
result.exprs[expr] = std::move(*info);
if (info)
result.exprs[expr] = std::move(*info);
}
}
// resolve all requires according to their argument
@ -150,7 +224,8 @@ struct RequireTracer : AstVisitor
ModuleName currentModuleName;
DenseHashMap<AstLocal*, AstExpr*> locals;
std::vector<AstExpr*> work;
std::vector<AstExpr*> work_DEPRECATED;
std::vector<AstNode*> work;
std::vector<AstExprCall*> requireCalls;
};

View file

@ -22,6 +22,7 @@
#include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_FASTFLAGVARIABLE(LuauSubtypingFixTailPack)
namespace Luau
{
@ -858,7 +859,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
else
return SubtypingResult{false}
.withSuperComponent(TypePath::PackField::Tail)
.withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}});
.withError({scope->location, UnexpectedTypePackInSubtyping{FFlag::LuauSubtypingFixTailPack ? *superTail : *subTail}});
}
else
return {false};

View file

@ -408,7 +408,7 @@ TypeId matchLiteralType(
if (FFlag::LuauDontInPlaceMutateTableType)
{
for (const auto& key: keysToDelete)
for (const auto& key : keysToDelete)
{
const AstArray<char>& s = key->value;
std::string keyStr{s.data, s.data + s.size};

View file

@ -1865,6 +1865,8 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
}
else if constexpr (std::is_same_v<T, EqualityConstraint>)
return "equality: " + tos(c.resultType) + " ~ " + tos(c.assignmentType);
else if constexpr (std::is_same_v<T, TableCheckConstraint>)
return "table_check " + tos(c.expectedType) + " :> " + tos(c.exprType);
else
static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
};

View file

@ -13,6 +13,7 @@
LUAU_FASTFLAG(LuauStoreCSTData)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAG(LuauAstTypeGroup)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
namespace
{
@ -117,9 +118,9 @@ struct StringWriter : Writer
index = newlinePos + 1;
}
pos.line += numLines;
pos.line += unsigned(numLines);
if (numLines > 0)
pos.column = unsigned(s.size()) - index;
pos.column = unsigned(s.size()) - unsigned(index);
else
pos.column += unsigned(s.size());
}
@ -666,7 +667,8 @@ struct Printer_DEPRECATED
writer.keyword("do");
for (const auto& s : block->body)
visualize(*s);
writer.advance(block->location.end);
if (!FFlag::LuauFixDoBlockEndLocation)
writer.advance(block->location.end);
writeEnd(program.location);
}
else if (const auto& a = program.as<AstStatIf>())
@ -2036,15 +2038,23 @@ struct Printer
{
if (writeTypes)
{
const auto* cstNode = lookupCstNode<CstStatTypeAlias>(a);
if (a->exported)
writer.keyword("export");
if (cstNode)
advance(cstNode->typeKeywordPosition);
writer.keyword("type");
advance(a->nameLocation.begin);
writer.identifier(a->name.value);
if (a->generics.size > 0 || a->genericPacks.size > 0)
{
if (cstNode)
advance(cstNode->genericsOpenPosition);
writer.symbol("<");
CommaSeparatorInserter comma(writer);
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
for (auto o : a->generics)
{
@ -2055,7 +2065,15 @@ struct Printer
if (o->defaultValue)
{
writer.maybeSpace(o->defaultValue->location.begin, 2);
const auto* genericTypeCstNode = lookupCstNode<CstGenericType>(o);
if (genericTypeCstNode)
{
LUAU_ASSERT(genericTypeCstNode->defaultEqualsPosition.has_value());
advance(*genericTypeCstNode->defaultEqualsPosition);
}
else
writer.maybeSpace(o->defaultValue->location.begin, 2);
writer.symbol("=");
visualizeTypeAnnotation(*o->defaultValue);
}
@ -2065,21 +2083,36 @@ struct Printer
{
comma();
const auto* genericTypePackCstNode = lookupCstNode<CstGenericTypePack>(o);
writer.advance(o->location.begin);
writer.identifier(o->name.value);
if (genericTypePackCstNode)
advance(genericTypePackCstNode->ellipsisPosition);
writer.symbol("...");
if (o->defaultValue)
{
writer.maybeSpace(o->defaultValue->location.begin, 2);
if (cstNode)
{
LUAU_ASSERT(genericTypePackCstNode->defaultEqualsPosition.has_value());
advance(*genericTypePackCstNode->defaultEqualsPosition);
}
else
writer.maybeSpace(o->defaultValue->location.begin, 2);
writer.symbol("=");
visualizeTypePackAnnotation(*o->defaultValue, false);
}
}
if (cstNode)
advance(cstNode->genericsClosePosition);
writer.symbol(">");
}
writer.maybeSpace(a->type->location.begin, 2);
if (cstNode)
advance(cstNode->equalsPosition);
else
writer.maybeSpace(a->type->location.begin, 2);
writer.symbol("=");
visualizeTypeAnnotation(*a->type);
}

View file

@ -489,7 +489,6 @@ static FunctionGraphReductionResult reduceFunctionsInternal(
return std::move(reducer.result);
}
}
FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext ctx, bool force)
@ -744,11 +743,9 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
resetTypeFunctionState(L);
// Push serialized arguments onto the stack
// Since there aren't any new class types being created in type functions, there isn't a deserialization function
// class types. Instead, we can keep this map and return the mapping as the "deserialized value"
std::unique_ptr<TypeFunctionRuntimeBuilderState> runtimeBuilder = std::make_unique<TypeFunctionRuntimeBuilderState>(ctx);
// Push serialized arguments onto the stack
for (auto typeParam : typeParams)
{
TypeId ty = follow(typeParam);
@ -2839,9 +2836,9 @@ TypeFunctionReductionResult<TypeId> setmetatableTypeFunction(
return {std::nullopt, Reduction::Erroneous, {}, {}};
// we're trying to reject any type that has not normalized to a table or a union/intersection of tables.
if (targetNorm->hasTops() || targetNorm->hasBooleans() || targetNorm->hasErrors() || targetNorm->hasNils() ||
targetNorm->hasNumbers() || targetNorm->hasStrings() || targetNorm->hasThreads() || targetNorm->hasBuffers() ||
targetNorm->hasFunctions() || targetNorm->hasTyvars() || targetNorm->hasClasses())
if (targetNorm->hasTops() || targetNorm->hasBooleans() || targetNorm->hasErrors() || targetNorm->hasNils() || targetNorm->hasNumbers() ||
targetNorm->hasStrings() || targetNorm->hasThreads() || targetNorm->hasBuffers() || targetNorm->hasFunctions() || targetNorm->hasTyvars() ||
targetNorm->hasClasses())
return {std::nullopt, Reduction::Erroneous, {}, {}};
// if the supposed metatable is not a table, we will fail to reduce.
@ -2899,11 +2896,7 @@ TypeFunctionReductionResult<TypeId> setmetatableTypeFunction(
return {result, Reduction::MaybeOk, {}, {}};
}
static TypeFunctionReductionResult<TypeId> getmetatableHelper(
TypeId targetTy,
const Location& location,
NotNull<TypeFunctionContext> ctx
)
static TypeFunctionReductionResult<TypeId> getmetatableHelper(TypeId targetTy, const Location& location, NotNull<TypeFunctionContext> ctx)
{
targetTy = follow(targetTy);

View file

@ -13,7 +13,10 @@
#include <set>
#include <vector>
LUAU_FASTFLAGVARIABLE(LuauTypeFunFixHydratedClasses)
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_FASTFLAGVARIABLE(LuauTypeFunSingletonEquality)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypeofReturnsType)
namespace Luau
{
@ -1565,6 +1568,12 @@ void registerTypeUserData(lua_State* L)
// Create and register metatable for type userdata
luaL_newmetatable(L, "type");
if (FFlag::LuauUserTypeFunTypeofReturnsType)
{
lua_pushstring(L, "type");
lua_setfield(L, -2, "__type");
}
// Protect metatable from being changed
lua_pushstring(L, "The metatable is locked");
lua_setfield(L, -2, "__metatable");
@ -1695,14 +1704,14 @@ bool areEqual(SeenSet& seen, const TypeFunctionSingletonType& lhs, const TypeFun
{
const TypeFunctionBooleanSingleton* lp = get<TypeFunctionBooleanSingleton>(&lhs);
const TypeFunctionBooleanSingleton* rp = get<TypeFunctionBooleanSingleton>(&lhs);
const TypeFunctionBooleanSingleton* rp = get<TypeFunctionBooleanSingleton>(FFlag::LuauTypeFunSingletonEquality ? &rhs : &lhs);
if (lp && rp)
return lp->value == rp->value;
}
{
const TypeFunctionStringSingleton* lp = get<TypeFunctionStringSingleton>(&lhs);
const TypeFunctionStringSingleton* rp = get<TypeFunctionStringSingleton>(&lhs);
const TypeFunctionStringSingleton* rp = get<TypeFunctionStringSingleton>(FFlag::LuauTypeFunSingletonEquality ? &rhs : &lhs);
if (lp && rp)
return lp->value == rp->value;
}
@ -1855,7 +1864,10 @@ bool areEqual(SeenSet& seen, const TypeFunctionClassType& lhs, const TypeFunctio
if (seenSetContains(seen, &lhs, &rhs))
return true;
return lhs.name == rhs.name;
if (FFlag::LuauTypeFunFixHydratedClasses)
return lhs.classTy == rhs.classTy;
else
return lhs.name_DEPRECATED == rhs.name_DEPRECATED;
}
bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType& rhs)

View file

@ -19,6 +19,7 @@
// used to control the recursion limit of any operations done by user-defined type functions
// currently, controls serialization, deserialization, and `type.copy`
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000);
LUAU_FASTFLAG(LuauTypeFunFixHydratedClasses)
namespace Luau
{
@ -207,8 +208,19 @@ private:
}
else if (auto c = get<ClassType>(ty))
{
state->classesSerialized[c->name] = ty;
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, c->name});
if (FFlag::LuauTypeFunFixHydratedClasses)
{
// Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the
// original class
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, ty});
}
else
{
state->classesSerialized_DEPRECATED[c->name] = ty;
target = typeFunctionRuntime->typeArena.allocate(
TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, /* classTy */ nullptr, c->name}
);
}
}
else if (auto g = get<GenericType>(ty))
{
@ -687,10 +699,17 @@ private:
}
else if (auto c = get<TypeFunctionClassType>(ty))
{
if (auto result = state->classesSerialized.find(c->name))
target = *result;
if (FFlag::LuauTypeFunFixHydratedClasses)
{
target = c->classTy;
}
else
state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious class type is being deserialized");
{
if (auto result = state->classesSerialized_DEPRECATED.find(c->name_DEPRECATED))
target = *result;
else
state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious class type is being deserialized");
}
}
else if (auto g = get<TypeFunctionGenericType>(ty))
{

View file

@ -38,7 +38,7 @@ private:
{
Page* next;
char data[8192];
alignas(8) char data[8192];
};
Page* root;

View file

@ -270,6 +270,47 @@ public:
Position functionKeywordPosition;
};
class CstGenericType : public CstNode
{
public:
LUAU_CST_RTTI(CstGenericType)
CstGenericType(std::optional<Position> defaultEqualsPosition);
std::optional<Position> defaultEqualsPosition;
};
class CstGenericTypePack : public CstNode
{
public:
LUAU_CST_RTTI(CstGenericTypePack)
CstGenericTypePack(Position ellipsisPosition, std::optional<Position> defaultEqualsPosition);
Position ellipsisPosition;
std::optional<Position> defaultEqualsPosition;
};
class CstStatTypeAlias : public CstNode
{
public:
LUAU_CST_RTTI(CstStatTypeAlias)
CstStatTypeAlias(
Position typeKeywordPosition,
Position genericsOpenPosition,
AstArray<Position> genericsCommaPositions,
Position genericsClosePosition,
Position equalsPosition
);
Position typeKeywordPosition;
Position genericsOpenPosition;
AstArray<Position> genericsCommaPositions;
Position genericsClosePosition;
Position equalsPosition;
};
class CstTypeReference : public CstNode
{
public:

View file

@ -144,7 +144,7 @@ private:
AstStat* parseReturn();
// type Name `=' Type
AstStat* parseTypeAlias(const Location& start, bool exported);
AstStat* parseTypeAlias(const Location& start, bool exported, Position typeKeywordPosition);
// type function Name ... end
AstStat* parseTypeFunction(const Location& start, bool exported);
@ -294,7 +294,12 @@ private:
Name parseIndexName(const char* context, const Position& previous);
// `<' namelist `>'
std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> parseGenericTypeList(bool withDefaultValues);
std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> parseGenericTypeList(
bool withDefaultValues,
Position* openPosition = nullptr,
TempVector<Position>* commaPositions = nullptr,
Position* closePosition = nullptr
);
// `<' Type[, ...] `>'
AstArray<AstTypeOrPack> parseTypeParams(

View file

@ -111,11 +111,7 @@ CstStatForIn::CstStatForIn(AstArray<Position> varsCommaPositions, AstArray<Posit
{
}
CstStatAssign::CstStatAssign(
AstArray<Position> varsCommaPositions,
Position equalsPosition,
AstArray<Position> valuesCommaPositions
)
CstStatAssign::CstStatAssign(AstArray<Position> varsCommaPositions, Position equalsPosition, AstArray<Position> valuesCommaPositions)
: CstNode(CstClassIndex())
, varsCommaPositions(varsCommaPositions)
, equalsPosition(equalsPosition)
@ -135,6 +131,35 @@ CstStatLocalFunction::CstStatLocalFunction(Position functionKeywordPosition)
{
}
CstGenericType::CstGenericType(std::optional<Position> defaultEqualsPosition)
: CstNode(CstClassIndex())
, defaultEqualsPosition(defaultEqualsPosition)
{
}
CstGenericTypePack::CstGenericTypePack(Position ellipsisPosition, std::optional<Position> defaultEqualsPosition)
: CstNode(CstClassIndex())
, ellipsisPosition(ellipsisPosition)
, defaultEqualsPosition(defaultEqualsPosition)
{
}
CstStatTypeAlias::CstStatTypeAlias(
Position typeKeywordPosition,
Position genericsOpenPosition,
AstArray<Position> genericsCommaPositions,
Position genericsClosePosition,
Position equalsPosition
)
: CstNode(CstClassIndex())
, typeKeywordPosition(typeKeywordPosition)
, genericsOpenPosition(genericsOpenPosition)
, genericsCommaPositions(genericsCommaPositions)
, genericsClosePosition(genericsClosePosition)
, equalsPosition(equalsPosition)
{
}
CstTypeReference::CstTypeReference(
std::optional<Position> prefixPointPosition,
Position openParametersPosition,

View file

@ -28,6 +28,7 @@ LUAU_FASTFLAGVARIABLE(LuauStoreCSTData)
LUAU_FASTFLAGVARIABLE(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup)
LUAU_FASTFLAGVARIABLE(ParserNoErrorLimit)
LUAU_FASTFLAGVARIABLE(LuauFixDoBlockEndLocation)
namespace Luau
{
@ -374,12 +375,13 @@ AstStat* Parser::parseStat()
AstName ident = getIdentifier(expr);
if (ident == "type")
return parseTypeAlias(expr->location, /* exported= */ false);
return parseTypeAlias(expr->location, /* exported= */ false, expr->location.begin);
if (ident == "export" && lexer.current().type == Lexeme::Name && AstName(lexer.current().name) == "type")
{
Position typeKeywordPosition = lexer.current().location.begin;
nextLexeme();
return parseTypeAlias(expr->location, /* exported= */ true);
return parseTypeAlias(expr->location, /* exported= */ true, typeKeywordPosition);
}
if (ident == "continue")
@ -534,11 +536,13 @@ AstStat* Parser::parseDo()
body->location.begin = start.begin;
Position endPosition = lexer.current().location.begin;
Location endLocation = lexer.current().location;
body->hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo);
if (FFlag::LuauFixDoBlockEndLocation && body->hasEnd)
body->location.end = endLocation.end;
if (FFlag::LuauStoreCSTData && options.storeCstData)
cstNodeMap[body] = allocator.alloc<CstStatDo>(endPosition);
cstNodeMap[body] = allocator.alloc<CstStatDo>(endLocation.begin);
return body;
}
@ -721,7 +725,12 @@ AstExpr* Parser::parseFunctionName(Location start_DEPRECATED, bool& hasself, Ast
debugname = name.name;
expr = allocator.alloc<AstExprIndexName>(
Location(FFlag::LuauFixFunctionNameStartPosition ? expr->location : start_DEPRECATED, name.location), expr, name.name, name.location, opPosition, '.'
Location(FFlag::LuauFixFunctionNameStartPosition ? expr->location : start_DEPRECATED, name.location),
expr,
name.name,
name.location,
opPosition,
'.'
);
// note: while the parser isn't recursive here, we're generating recursive structures of unbounded depth
@ -742,7 +751,12 @@ AstExpr* Parser::parseFunctionName(Location start_DEPRECATED, bool& hasself, Ast
debugname = name.name;
expr = allocator.alloc<AstExprIndexName>(
Location(FFlag::LuauFixFunctionNameStartPosition ? expr->location : start_DEPRECATED, name.location), expr, name.name, name.location, opPosition, ':'
Location(FFlag::LuauFixFunctionNameStartPosition ? expr->location : start_DEPRECATED, name.location),
expr,
name.name,
name.location,
opPosition,
':'
);
hasself = true;
@ -1007,7 +1021,7 @@ AstStat* Parser::parseReturn()
}
// type Name [`<' varlist `>'] `=' Type
AstStat* Parser::parseTypeAlias(const Location& start, bool exported)
AstStat* Parser::parseTypeAlias(const Location& start, bool exported, Position typeKeywordPosition)
{
// parsing a type function
if (lexer.current().type == Lexeme::ReservedFunction)
@ -1023,13 +1037,34 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported)
if (!name)
name = Name(nameError, lexer.current().location);
auto [generics, genericPacks] = parseGenericTypeList(/* withDefaultValues= */ true);
Position genericsOpenPosition{0, 0};
TempVector<Position> genericsCommaPositions(scratchPosition);
Position genericsClosePosition{0, 0};
auto [generics, genericPacks] = FFlag::LuauStoreCSTData && options.storeCstData
? parseGenericTypeList(
/* withDefaultValues= */ true, &genericsOpenPosition, &genericsCommaPositions, &genericsClosePosition
)
: parseGenericTypeList(/* withDefaultValues= */ true);
Position equalsPosition = lexer.current().location.begin;
expectAndConsume('=', "type alias");
AstType* type = parseType();
return allocator.alloc<AstStatTypeAlias>(Location(start, type->location), name->name, name->location, generics, genericPacks, type, exported);
if (FFlag::LuauStoreCSTData)
{
AstStatTypeAlias* node =
allocator.alloc<AstStatTypeAlias>(Location(start, type->location), name->name, name->location, generics, genericPacks, type, exported);
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstStatTypeAlias>(
typeKeywordPosition, genericsOpenPosition, copy(genericsCommaPositions), genericsClosePosition, equalsPosition
);
return node;
}
else
{
return allocator.alloc<AstStatTypeAlias>(Location(start, type->location), name->name, name->location, generics, genericPacks, type, exported);
}
}
// type function Name `(' arglist `)' `=' funcbody `end'
@ -1733,8 +1768,8 @@ std::pair<CstExprConstantString::QuoteStyle, unsigned int> Parser::extractString
switch (lexer.current().type)
{
case Lexeme::QuotedString:
style = lexer.current().getQuoteStyle() == Lexeme::QuoteStyle::Double ? CstExprConstantString::QuotedDouble
: CstExprConstantString::QuotedSingle;
style =
lexer.current().getQuoteStyle() == Lexeme::QuoteStyle::Double ? CstExprConstantString::QuotedDouble : CstExprConstantString::QuotedSingle;
break;
case Lexeme::InterpStringSimple:
style = CstExprConstantString::QuotedInterp;
@ -3026,9 +3061,7 @@ AstExpr* Parser::parseFunctionArgs(AstExpr* func, bool self)
{
AstExprCall* node = allocator.alloc<AstExprCall>(Location(func->location, end), func, copy(args), self, Location(argStart, argEnd));
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstExprCall>(
matchParen.position, lexer.previousLocation().begin, copy(commaPositions)
);
cstNodeMap[node] = allocator.alloc<CstExprCall>(matchParen.position, lexer.previousLocation().begin, copy(commaPositions));
return node;
}
else
@ -3314,7 +3347,12 @@ Parser::Name Parser::parseIndexName(const char* context, const Position& previou
return Name(nameError, location);
}
std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> Parser::parseGenericTypeList(bool withDefaultValues)
std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> Parser::parseGenericTypeList(
bool withDefaultValues,
Position* openPosition,
TempVector<Position>* commaPositions,
Position* closePosition
)
{
TempVector<AstGenericType*> names{scratchGenericTypes};
TempVector<AstGenericTypePack*> namePacks{scratchGenericTypePacks};
@ -3322,6 +3360,8 @@ std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> Parser::pars
if (lexer.current().type == '<')
{
Lexeme begin = lexer.current();
if (FFlag::LuauStoreCSTData && openPosition)
*openPosition = begin.location.begin;
nextLexeme();
bool seenPack = false;
@ -3335,6 +3375,7 @@ std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> Parser::pars
{
seenPack = true;
Position ellipsisPosition = lexer.current().location.begin;
if (lexer.current().type != Lexeme::Dot3)
report(lexer.current().location, "Generic types come before generic type packs");
else
@ -3343,13 +3384,24 @@ std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> Parser::pars
if (withDefaultValues && lexer.current().type == '=')
{
seenDefault = true;
Position equalsPosition = lexer.current().location.begin;
nextLexeme();
if (shouldParseTypePack(lexer))
{
AstTypePack* typePack = parseTypePack();
namePacks.push_back(allocator.alloc<AstGenericTypePack>(nameLocation, name, typePack));
if (FFlag::LuauStoreCSTData)
{
AstGenericTypePack* node = allocator.alloc<AstGenericTypePack>(nameLocation, name, typePack);
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstGenericTypePack>(ellipsisPosition, equalsPosition);
namePacks.push_back(node);
}
else
{
namePacks.push_back(allocator.alloc<AstGenericTypePack>(nameLocation, name, typePack));
}
}
else
{
@ -3358,7 +3410,17 @@ std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> Parser::pars
if (type)
report(type->location, "Expected type pack after '=', got type");
namePacks.push_back(allocator.alloc<AstGenericTypePack>(nameLocation, name, typePack));
if (FFlag::LuauStoreCSTData)
{
AstGenericTypePack* node = allocator.alloc<AstGenericTypePack>(nameLocation, name, typePack);
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstGenericTypePack>(ellipsisPosition, equalsPosition);
namePacks.push_back(node);
}
else
{
namePacks.push_back(allocator.alloc<AstGenericTypePack>(nameLocation, name, typePack));
}
}
}
else
@ -3366,7 +3428,17 @@ std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> Parser::pars
if (seenDefault)
report(lexer.current().location, "Expected default type pack after type pack name");
namePacks.push_back(allocator.alloc<AstGenericTypePack>(nameLocation, name, nullptr));
if (FFlag::LuauStoreCSTData)
{
AstGenericTypePack* node = allocator.alloc<AstGenericTypePack>(nameLocation, name, nullptr);
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstGenericTypePack>(ellipsisPosition, std::nullopt);
namePacks.push_back(node);
}
else
{
namePacks.push_back(allocator.alloc<AstGenericTypePack>(nameLocation, name, nullptr));
}
}
}
else
@ -3374,23 +3446,46 @@ std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> Parser::pars
if (withDefaultValues && lexer.current().type == '=')
{
seenDefault = true;
Position equalsPosition = lexer.current().location.begin;
nextLexeme();
AstType* defaultType = parseType();
names.push_back(allocator.alloc<AstGenericType>(nameLocation, name, defaultType));
if (FFlag::LuauStoreCSTData)
{
AstGenericType* node = allocator.alloc<AstGenericType>(nameLocation, name, defaultType);
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstGenericType>(equalsPosition);
names.push_back(node);
}
else
{
names.push_back(allocator.alloc<AstGenericType>(nameLocation, name, defaultType));
}
}
else
{
if (seenDefault)
report(lexer.current().location, "Expected default type after type name");
names.push_back(allocator.alloc<AstGenericType>(nameLocation, name, nullptr));
if (FFlag::LuauStoreCSTData)
{
AstGenericType* node = allocator.alloc<AstGenericType>(nameLocation, name, nullptr);
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstGenericType>(std::nullopt);
names.push_back(node);
}
else
{
names.push_back(allocator.alloc<AstGenericType>(nameLocation, name, nullptr));
}
}
}
if (lexer.current().type == ',')
{
if (FFlag::LuauStoreCSTData && commaPositions)
commaPositions->push_back(lexer.current().location.begin);
nextLexeme();
if (lexer.current().type == '>')
@ -3403,6 +3498,8 @@ std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> Parser::pars
break;
}
if (FFlag::LuauStoreCSTData && closePosition)
*closePosition = lexer.current().location.begin;
expectMatchAndConsume('>', begin);
}

View file

@ -22,10 +22,10 @@
// __register_frame and __deregister_frame are defined in libgcc or libc++
// (depending on how it's built). We want to declare them as weak symbols
// so that if they're provided by a shared library, we'll use them, and if
// so that if they're provided by a shared library, we'll use them, and if
// not, we'll disable some c++ exception handling support. However, if they're
// declared as weak and the definitions are linked in a static library
// that's not linked with whole-archive, then the symbols will technically be defined here,
// that's not linked with whole-archive, then the symbols will technically be defined here,
// and the linker won't look for the strong ones in the library.
#ifndef LUAU_ENABLE_REGISTER_FRAME
#define REGISTER_FRAME_WEAK __attribute__((weak))

View file

@ -66,7 +66,7 @@ struct Node
};
};
template <typename L>
template<typename L>
struct NodeIterator
{
private:

43
bench/measure_time.py Normal file
View file

@ -0,0 +1,43 @@
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
import os, sys, time, numpy
try:
import scipy
from scipy import mean, stats
except ModuleNotFoundError:
print("Warning: scipy package is not installed, confidence values will not be available")
stats = None
duration_list = []
DEFAULT_CYCLES_TO_RUN = 100
cycles_to_run = DEFAULT_CYCLES_TO_RUN
try:
cycles_to_run = sys.argv[3] if sys.argv[3] else DEFAULT_CYCLES_TO_RUN
cycles_to_run = int(cycles_to_run)
except IndexError:
pass
except (ValueError, TypeError):
cycles_to_run = DEFAULT_CYCLES_TO_RUN
print("Error: Cycles to run argument must be an integer. Using default value of {}".format(DEFAULT_CYCLES_TO_RUN))
# Numpy complains if we provide a cycle count of less than 3 ~ default to 3 whenever a lower value is provided
cycles_to_run = cycles_to_run if cycles_to_run > 2 else 3
for i in range(1,cycles_to_run):
start = time.perf_counter()
# Run the code you want to measure here
os.system(sys.argv[1])
end = time.perf_counter()
duration_ms = (end - start) * 1000
duration_list.append(duration_ms)
# Stats
mean = numpy.mean(duration_list)
std_err = stats.sem(duration_list)
print("SUCCESS: {} : {:.2f}ms +/- {:.2f}% on luau ".format('duration', mean,std_err))

View file

@ -22,6 +22,7 @@ LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAG(LuauAlwaysFillInFunctionCallDiscriminantTypes)
LUAU_FASTFLAG(LuauStoreCSTData)
LUAU_FASTFLAG(LuauAstTypeGroup)
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
struct ATSFixture : BuiltinsFixture
@ -693,6 +694,7 @@ TEST_CASE_FIXTURE(ATSFixture, "mutually_recursive_generic")
ScopedFastFlag sff[] = {
{FFlag::LuauSolverV2, true},
{FFlag::StudioReportLuauAny2, true},
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true}
};
fileResolver.source["game/Gui/Modules/A"] = R"(
@ -705,8 +707,7 @@ TEST_CASE_FIXTURE(ATSFixture, "mutually_recursive_generic")
y.g.i = y
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_ERROR_COUNT(2, result1);
LUAU_REQUIRE_NO_ERRORS(frontend.check("game/Gui/Modules/A"));
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");

View file

@ -475,7 +475,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation")
if (FFlag::LuauAstTypeGroup)
{
std::string_view expected = R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeGroup","location":"0,9 - 0,36","type":{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeGroup","location":"0,22 - 0,36","type":{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}}]}}},{"type":"AstTypeGroup","location":"0,40 - 0,55","type":{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}}]},"exported":false})";
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeGroup","location":"0,9 - 0,36","inner":{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeGroup","location":"0,22 - 0,36","inner":{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}}]}}},{"type":"AstTypeGroup","location":"0,40 - 0,55","inner":{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}}]},"exported":false})";
CHECK(toJson(statement) == expected);
}
else

View file

@ -132,6 +132,15 @@ ClassFixture::ClassFixture()
// IndexableNumericKeyClass has a table indexer with a key type of 'number' and a return type of 'number'
addIndexableClass("IndexableNumericKeyClass", numberType, numberType);
// Add a confusing derived class which shares the same name internally, but has a unique alias
TypeId duplicateBaseClassInstanceType = arena.addType(ClassType{"BaseClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(duplicateBaseClassInstanceType)->props = {
{"Method", {makeFunction(arena, duplicateBaseClassInstanceType, {}, {stringType})}},
};
addGlobalBinding(globals, "confusingBaseClassInstance", duplicateBaseClassInstanceType, "@test");
for (const auto& [name, tf] : globals.globalScope->exportedTypeBindings)
persist(tf.type);

View file

@ -180,7 +180,7 @@ std::optional<TypeId> linearSearchForBinding(Scope* scope, const char* name);
void registerHiddenTypes(Frontend* frontend);
void createSomeClasses(Frontend* frontend);
template <typename E>
template<typename E>
const E* findError(const CheckResult& result)
{
for (const auto& e : result.errors)

View file

@ -31,7 +31,6 @@ LUAU_FASTINT(LuauParseErrorLimit)
LUAU_FASTFLAG(LuauCloneIncrementalModule)
LUAU_FASTFLAG(LuauIncrementalAutocompleteBugfixes)
LUAU_FASTFLAG(LuauReferenceAllocatorInNewSolver)
LUAU_FASTFLAG(LuauMixedModeDefFinderTraversesTypeOf)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
@ -65,13 +64,12 @@ struct FragmentAutocompleteFixtureImpl : BaseType
{
static_assert(std::is_base_of_v<Fixture, BaseType>, "BaseType must be a descendant of Fixture");
ScopedFastFlag sffs[8] = {
ScopedFastFlag sffs[7] = {
{FFlag::LuauAllowFragmentParsing, true},
{FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete, true},
{FFlag::LuauStoreSolverTypeOnModule, true},
{FFlag::LuauSymbolEquality, true},
{FFlag::LexerResumesFromPosition2, true},
{FFlag::LuauReferenceAllocatorInNewSolver, true},
{FFlag::LuauIncrementalAutocompleteBugfixes, true},
{FFlag::LuauBetterReverseDependencyTracking, true},
};
@ -929,6 +927,148 @@ tbl.abc.
);
}
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "multiple_functions_complex")
{
const std::string text = R"( local function f1(a1)
local l1 = 1;"
g1 = 1;"
end
local function f2(a2)
local l2 = 1;
g2 = 1;
end
)";
autocompleteFragmentInBothSolvers(
text,
text,
Position{0, 0},
[](FragmentAutocompleteResult& fragment)
{
auto strings = fragment.acResults.entryMap;
CHECK(strings.count("f1") == 0);
CHECK(strings.count("a1") == 0);
CHECK(strings.count("l1") == 0);
CHECK(strings.count("g1") != 0);
CHECK(strings.count("f2") == 0);
CHECK(strings.count("a2") == 0);
CHECK(strings.count("l2") == 0);
CHECK(strings.count("g2") != 0);
}
);
autocompleteFragmentInBothSolvers(
text,
text,
Position{0, 22},
[](FragmentAutocompleteResult& fragment)
{
auto strings = fragment.acResults.entryMap;
CHECK(strings.count("f1") != 0);
CHECK(strings.count("a1") != 0);
CHECK(strings.count("l1") == 0);
CHECK(strings.count("g1") != 0);
CHECK(strings.count("f2") == 0);
CHECK(strings.count("a2") == 0);
CHECK(strings.count("l2") == 0);
CHECK(strings.count("g2") != 0);
}
);
autocompleteFragmentInBothSolvers(
text,
text,
Position{1, 17},
[](FragmentAutocompleteResult& fragment)
{
auto strings = fragment.acResults.entryMap;
CHECK(strings.count("f1") != 0);
CHECK(strings.count("a1") != 0);
CHECK(strings.count("l1") != 0);
CHECK(strings.count("g1") != 0);
CHECK(strings.count("f2") == 0);
CHECK(strings.count("a2") == 0);
CHECK(strings.count("l2") == 0);
CHECK(strings.count("g2") != 0);
}
);
autocompleteFragmentInBothSolvers(
text,
text,
Position{2, 11},
[](FragmentAutocompleteResult& fragment)
{
auto strings = fragment.acResults.entryMap;
CHECK(strings.count("f1") != 0);
CHECK(strings.count("a1") != 0);
CHECK(strings.count("l1") != 0);
CHECK(strings.count("g1") != 0);
CHECK(strings.count("f2") == 0);
CHECK(strings.count("a2") == 0);
CHECK(strings.count("l2") == 0);
CHECK(strings.count("g2") != 0);
}
);
autocompleteFragmentInBothSolvers(
text,
text,
Position{4, 0},
[](FragmentAutocompleteResult& fragment)
{
auto strings = fragment.acResults.entryMap;
CHECK(strings.count("f1") != 0);
// FIXME: RIDE-11123: This should be zero counts of `a1`.
CHECK(strings.count("a1") != 0);
CHECK(strings.count("l1") == 0);
CHECK(strings.count("g1") != 0);
CHECK(strings.count("f2") == 0);
CHECK(strings.count("a2") == 0);
CHECK(strings.count("l2") == 0);
CHECK(strings.count("g2") != 0);
}
);
autocompleteFragmentInBothSolvers(
text,
text,
Position{6, 17},
[](FragmentAutocompleteResult& fragment)
{
auto strings = fragment.acResults.entryMap;
CHECK(strings.count("f1") != 0);
CHECK(strings.count("a1") == 0);
CHECK(strings.count("l1") == 0);
CHECK(strings.count("g1") != 0);
CHECK(strings.count("f2") != 0);
CHECK(strings.count("a2") != 0);
CHECK(strings.count("l2") != 0);
CHECK(strings.count("g2") != 0);
}
);
autocompleteFragmentInBothSolvers(
text,
text,
Position{8, 4},
[](FragmentAutocompleteResult& fragment)
{
auto strings = fragment.acResults.entryMap;
CHECK(strings.count("f1") != 0);
CHECK(strings.count("a1") == 0);
CHECK(strings.count("l1") == 0);
CHECK(strings.count("g1") != 0);
CHECK(strings.count("f2") != 0);
// FIXME: RIDE-11123: This should be zero counts of `a2`.
CHECK(strings.count("a2") != 0);
CHECK(strings.count("l2") == 0);
CHECK(strings.count("g2") != 0);
}
);
}
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "inline_autocomplete_picks_the_right_scope")
{
const std::string source = R"(

View file

@ -16,7 +16,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(DebugLuauFreezeArena);
LUAU_FASTFLAG(DebugLuauMagicTypes);
LUAU_FASTFLAG(LuauReferenceAllocatorInNewSolver);
LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena)
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking);
@ -1528,7 +1527,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "get_required_scripts_dirty")
TEST_CASE_FIXTURE(FrontendFixture, "check_module_references_allocator")
{
ScopedFastFlag sff{FFlag::LuauReferenceAllocatorInNewSolver, true};
fileResolver.source["game/workspace/MyScript"] = R"(
print("Hello World")
)";
@ -1546,10 +1544,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "check_module_references_allocator")
TEST_CASE_FIXTURE(FrontendFixture, "dfg_data_cleared_on_retain_type_graphs_unset")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauSelectivelyRetainDFGArena, true}
};
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauSelectivelyRetainDFGArena, true}};
fileResolver.source["game/A"] = R"(
local a = 1
local b = 2
@ -1760,7 +1755,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "test_invalid_dependency_tracking_per_module_
fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}";
fileResolver.source["game/Gui/Modules/B"] = "return require(game:GetService('Gui').Modules.A)";
FrontendOptions opts;
opts.forAutocomplete = false;

View file

@ -641,10 +641,7 @@ TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "nonstrict_method_calls")
TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_non_strict")
{
ScopedFastFlag flags[] = {
{FFlag::LuauNonStrictVisitorImprovements, true},
{FFlag::LuauNewNonStrictWarnOnUnknownGlobals, true}
};
ScopedFastFlag flags[] = {{FFlag::LuauNonStrictVisitorImprovements, true}, {FFlag::LuauNewNonStrictWarnOnUnknownGlobals, true}};
CheckResult result = check(Mode::Nonstrict, R"(
foo = 5

View file

@ -23,6 +23,7 @@ LUAU_FASTFLAG(LuauFixFunctionNameStartPosition)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAG(LuauAstTypeGroup)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
namespace
{
@ -2508,6 +2509,40 @@ TEST_CASE_FIXTURE(Fixture, "parse_return_type_ast_type_group")
CHECK(funcType->returnTypes.types.data[0]->is<AstTypeGroup>());
}
TEST_CASE_FIXTURE(Fixture, "inner_and_outer_scope_of_functions_have_correct_end_position")
{
AstStatBlock* stat = parse(R"(
local function foo()
local x = 1
end
)");
REQUIRE(stat);
REQUIRE_EQ(1, stat->body.size);
auto func = stat->body.data[0]->as<AstStatLocalFunction>();
REQUIRE(func);
CHECK_EQ(func->func->body->location, Location{{1, 28}, {3, 8}});
CHECK_EQ(func->location, Location{{1, 8}, {3, 11}});
}
TEST_CASE_FIXTURE(Fixture, "do_block_end_location_is_after_end_token")
{
ScopedFastFlag _{FFlag::LuauFixDoBlockEndLocation, true};
AstStatBlock* stat = parse(R"(
do
local x = 1
end
)");
REQUIRE(stat);
REQUIRE_EQ(1, stat->body.size);
auto block = stat->body.data[0]->as<AstStatBlock>();
REQUIRE(block);
CHECK_EQ(block->location, Location{{1, 8}, {3, 11}});
}
TEST_SUITE_END();
TEST_SUITE_BEGIN("ParseErrorRecovery");
@ -3786,7 +3821,7 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type")
}
else
CHECK(unionTy->types.data[0]->is<AstTypeFunction>()); // () -> ()
CHECK(unionTy->types.data[1]->is<AstTypeReference>()); // nil
CHECK(unionTy->types.data[1]->is<AstTypeReference>()); // nil
}
TEST_CASE_FIXTURE(Fixture, "complex_union_in_generic_ty")

View file

@ -6,6 +6,8 @@
#include "doctest.h"
LUAU_FASTFLAG(LuauExtendedSimpleRequire)
using namespace Luau;
namespace
@ -178,4 +180,59 @@ TEST_CASE_FIXTURE(RequireTracerFixture, "follow_string_indexexpr")
CHECK_EQ("game/Test", result.exprs[local->values.data[0]].name);
}
TEST_CASE_FIXTURE(RequireTracerFixture, "follow_group")
{
ScopedFastFlag luauExtendedSimpleRequire{FFlag::LuauExtendedSimpleRequire, true};
AstStatBlock* block = parse(R"(
local R = (((game).Test))
require(R)
)");
REQUIRE_EQ(2, block->body.size);
RequireTraceResult result = traceRequires(&fileResolver, block, "ModuleName");
AstStatLocal* local = block->body.data[0]->as<AstStatLocal>();
REQUIRE(local != nullptr);
CHECK_EQ("game/Test", result.exprs[local->values.data[0]].name);
}
TEST_CASE_FIXTURE(RequireTracerFixture, "follow_type_annotation")
{
ScopedFastFlag luauExtendedSimpleRequire{FFlag::LuauExtendedSimpleRequire, true};
AstStatBlock* block = parse(R"(
local R = game.Test :: (typeof(game.Redirect))
require(R)
)");
REQUIRE_EQ(2, block->body.size);
RequireTraceResult result = traceRequires(&fileResolver, block, "ModuleName");
AstStatLocal* local = block->body.data[0]->as<AstStatLocal>();
REQUIRE(local != nullptr);
CHECK_EQ("game/Redirect", result.exprs[local->values.data[0]].name);
}
TEST_CASE_FIXTURE(RequireTracerFixture, "follow_type_annotation_2")
{
ScopedFastFlag luauExtendedSimpleRequire{FFlag::LuauExtendedSimpleRequire, true};
AstStatBlock* block = parse(R"(
local R = game.Test :: (typeof(game.Redirect))
local N = R.Nested
require(N)
)");
REQUIRE_EQ(3, block->body.size);
RequireTraceResult result = traceRequires(&fileResolver, block, "ModuleName");
AstStatLocal* local = block->body.data[1]->as<AstStatLocal>();
REQUIRE(local != nullptr);
CHECK_EQ("game/Redirect/Nested", result.exprs[local->values.data[0]].name);
}
TEST_SUITE_END();

View file

@ -317,6 +317,74 @@ TEST_CASE("returns_spaces_around_tokens")
CHECK_EQ(three, transpile(three).code);
}
TEST_CASE_FIXTURE(Fixture, "type_alias_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( type Foo = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( export type Foo = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( export type Foo = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo<X, Y, Z...> = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo <X, Y, Z...> = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo< X, Y, Z...> = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo<X , Y, Z...> = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo<X, Y, Z...> = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo<X, Y , Z...> = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo<X, Y, Z...> = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo<X, Y, Z ...> = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo<X, Y, Z... > = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_CASE_FIXTURE(Fixture, "type_alias_with_defaults_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( type Foo<X = string, Z... = ...any> = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo<X = string, Z... = ...any> = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo<X = string, Z... = ...any> = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo<X = string, Z... = ...any> = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo<X = string, Z... = ...any> = string )";
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_CASE("table_literals")
{
const std::string code = R"( local t={1, 2, 3, foo='bar', baz=99,[5.5]='five point five', 'end'} )";

View file

@ -7,8 +7,11 @@
using namespace Luau;
LUAU_FASTFLAG(LuauTypeFunFixHydratedClasses)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTypeFunSingletonEquality)
LUAU_FASTFLAG(LuauUserTypeFunTypeofReturnsType)
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
@ -487,7 +490,10 @@ local function notok(idx: fail<number>): never return idx end
)");
LUAU_REQUIRE_ERROR_COUNT(4, result);
CHECK(toString(result.errors[0]) == R"('fail' type function errored at runtime: [string "fail"]:7: type.inner: cannot call inner method on non-negation type: `number` type)");
CHECK(
toString(result.errors[0]) ==
R"('fail' type function errored at runtime: [string "fail"]:7: type.inner: cannot call inner method on non-negation type: `number` type)"
);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_serialization_works")
@ -637,6 +643,21 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works")
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works2")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauTypeFunFixHydratedClasses{FFlag::LuauTypeFunFixHydratedClasses, true};
CheckResult result = check(R"(
type function serialize_class(arg)
return arg
end
local function ok(idx: serialize_class<typeof(confusingBaseClassInstance)>): typeof(confusingBaseClassInstance) return idx end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(ClassFixture, "udtf_class_methods_works")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
@ -1866,4 +1887,56 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_eqsat_opaque")
CHECK_EQ("t0<number & string>", toString(simplified->result)); // NOLINT(bugprone-unchecked-optional-access)
}
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_bool")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauTypeFunSingletonEquality{FFlag::LuauTypeFunSingletonEquality, true};
CheckResult result = check(R"(
type function compare(arg)
return types.singleton(types.singleton(false) == arg)
end
local function ok(idx: compare<false>): true return idx end
local function ok(idx: compare<true>): false return idx end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_string")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauTypeFunSingletonEquality{FFlag::LuauTypeFunSingletonEquality, true};
CheckResult result = check(R"(
type function compare(arg)
return types.singleton(types.singleton("") == arg)
end
local function ok(idx: compare<"">): true return idx end
local function ok(idx: compare<"a">): false return idx end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "typeof_type_userdata_returns_type")
{
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunTypeofReturnsType{FFlag::LuauUserTypeFunTypeofReturnsType, true};
CheckResult result = check(R"(
type function test(t)
print(typeof(t))
return t
end
local _:test<number>
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK(toString(result.errors[0]) == R"(type)");
}
TEST_SUITE_END();

View file

@ -20,6 +20,7 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauSubtypingFixTailPack)
TEST_SUITE_BEGIN("TypeInferFunctions");
@ -3027,4 +3028,17 @@ TEST_CASE_FIXTURE(Fixture, "hidden_variadics_should_not_break_subtyping")
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "coroutine_wrap_result_call")
{
ScopedFastFlag luauSubtypingFixTailPack{FFlag::LuauSubtypingFixTailPack, true};
CheckResult result = check(R"(
function foo(a, b)
coroutine.wrap(a)(b)
end
)");
// New solver still reports an error in this case, but the main goal of the test is to not crash
}
TEST_SUITE_END();

View file

@ -12,6 +12,7 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
using namespace Luau;
@ -854,6 +855,8 @@ end
TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe")
{
ScopedFastFlag _{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true};
CheckResult result = check(R"(
--!strict
-- At one point this produced a UAF
@ -865,15 +868,19 @@ local y: T<string> = { a = { c = nil, d = 5 }, b = 37 }
y.a.c = y
)");
LUAU_REQUIRE_ERRORS(result);
if (FFlag::LuauSolverV2)
CHECK(
toString(result.errors.at(0)) ==
R"(Type '{ a: { c: nil, d: number }, b: number }' could not be converted into 'T<number>'; type { a: { c: nil, d: number }, b: number }[read "a"][read "c"] (nil) is not exactly T<number>[read "a"][read "c"][0] (T<number>))"
);
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
auto mismatch = get<TypeMismatch>(result.errors.at(0));
CHECK(mismatch);
CHECK_EQ(toString(mismatch->givenType), "{ a: { c: T<string>?, d: number }, b: number }");
CHECK_EQ(toString(mismatch->wantedType), "T<string>");
std::string reason = "at [read \"a\"][read \"d\"], number is not exactly string\n\tat [read \"b\"], number is not exactly string";
CHECK_EQ(mismatch->reason, reason);
}
else
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
const std::string expected = R"(Type 'y' could not be converted into 'T<string>'
caused by:
Property 'a' is not compatible.

View file

@ -815,11 +815,11 @@ TEST_CASE_FIXTURE(Fixture, "strict_binary_op_where_lhs_unknown")
TEST_CASE_FIXTURE(BuiltinsFixture, "and_binexps_dont_unify")
{
ScopedFastFlag _{FFlag::LuauDoNotGeneralizeInTypeFunctions, true};
ScopedFastFlag _{FFlag::LuauDoNotGeneralizeInTypeFunctions, true};
// `t` will be inferred to be of type `{ { test: unknown } }` which is
// reasonable, in that it's empty with no bounds on its members. Optimally
// we might emit an error here that the `print(...)` expression is
// we might emit an error here that the `print(...)` expression is
// unreachable.
LUAU_REQUIRE_NO_ERRORS(check(R"(
--!strict

View file

@ -9,7 +9,7 @@
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauGeneralizationRemoveRecursiveUpperBound)
LUAU_FASTFLAG(LuauGeneralizationRemoveRecursiveUpperBound2)
using namespace Luau;
@ -2445,7 +2445,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::DebugLuauEqSatSimplification, true},
{FFlag::LuauGeneralizationRemoveRecursiveUpperBound, true},
{FFlag::LuauGeneralizationRemoveRecursiveUpperBound2, true},
};
LUAU_REQUIRE_NO_ERRORS(check(R"(

View file

@ -25,7 +25,8 @@ LUAU_FASTFLAG(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAG(LuauDontInPlaceMutateTableType)
LUAU_FASTFLAG(LuauAllowNonSharedTableTypesInLiteral)
LUAU_FASTFLAG(LuauFollowTableFreeze)
LUAU_FASTFLAG(LuauPrecalculateMutatedFreeTypes)
LUAU_FASTFLAG(LuauPrecalculateMutatedFreeTypes2)
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
TEST_SUITE_BEGIN("TableTests");
@ -5012,7 +5013,7 @@ TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager")
// bidirectional inference is known to be broken.
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauPrecalculateMutatedFreeTypes, true},
{FFlag::LuauPrecalculateMutatedFreeTypes2, true},
};
auto result = check(R"(
@ -5153,4 +5154,44 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_musnt_assert")
)");
}
TEST_CASE_FIXTURE(Fixture, "optional_property_with_call")
{
ScopedFastFlag _{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true};
LUAU_CHECK_NO_ERRORS(check(R"(
type t = {
key: boolean?,
time: number,
}
local function num(): number
return 0
end
local _: t = {
time = num(),
}
)"));
}
TEST_CASE_FIXTURE(Fixture, "empty_union_container_overflow")
{
LUAU_REQUIRE_NO_ERRORS(check(R"(
--!strict
local CellRenderer = {}
function CellRenderer:init(props)
self._separators = {
unhighlight = function()
local cellKey, prevCellKey = self.props.cellKey, self.props.prevCellKey
self.props.onUpdateSeparators({ cellKey, prevCellKey })
end,
updateProps = function (select, newProps)
local cellKey, prevCellKey = self.props.cellKey, self.props.prevCellKey
self.props.onUpdateSeparators({ if select == 'leading' then prevCellKey else cellKey })
end
}
end
)"));
}
TEST_SUITE_END();