Sync to upstream/release/662 (#1681)

## What's new

This update brings improvements to the new type solver, roundtrippable
AST parsing mode and closes multiple issues reported in this repository.

* `require` dependency tracing for non-string requires now supports `()`
groups in expressions and types as well as an ability to type annotate a
value with a `typeof` of a different module path
* Fixed rare misaligned memory access in Compiler/Typechecker on 32 bit
platforms (Closes #1572)

## New Solver

* Fixed crash/UB in subtyping of type packs (Closes #1449)
* Fixed incorrect type errors when calling `debug.info` (Closes #1534
and Resolves #966)
* Fixed incorrect boolean and string equality comparison result in
user-defined type functions (Closes #1623)
* Fixed incorrect class types being produced in user-defined type
functions when multiple classes share the same name (Closes #1639)
* Improved bidirectional typechecking for table literals containing
elements that have not been solved yet (Closes #1641)

## Roundtrippable AST

* Added source information for `AstStatTypeAlias`
* Fixed an issue with `AstTypeGroup` node (added in #1643) producing
invalid AST json. Contained type is now named 'inner' instead of 'type'
* Fixed end location of the `do ... end` statement

---

Internal Contributors:

Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Talha Pathan <tpathan@roblox.com>
Co-authored-by: Varun Saini <vsaini@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
vegorov-rbx 2025-02-21 10:24:12 -08:00 committed by GitHub
parent 86bf4ae42d
commit c2e72666d9
Signed by: DevComp
GPG key ID: B5690EEEBB952194
51 changed files with 1048 additions and 291 deletions

View file

@ -109,6 +109,21 @@ struct FunctionCheckConstraint
NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes; 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 // prim FreeType ExpectedType PrimitiveType
// //
// FreeType is bounded below by the singleton type and above by PrimitiveType // FreeType is bounded below by the singleton type and above by PrimitiveType
@ -273,7 +288,8 @@ using ConstraintV = Variant<
UnpackConstraint, UnpackConstraint,
ReduceConstraint, ReduceConstraint,
ReducePackConstraint, ReducePackConstraint,
EqualityConstraint>; EqualityConstraint,
TableCheckConstraint>;
struct Constraint struct Constraint
{ {

View file

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

View file

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

View file

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

View file

@ -310,7 +310,8 @@ struct MagicFunctionTypeCheckContext
struct MagicFunction 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 // 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 // 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/Common.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/TypeFwd.h"
#include <optional> #include <optional>
#include <string> #include <string>
@ -217,7 +218,9 @@ struct TypeFunctionClassType
std::optional<TypeFunctionTypeId> parent; std::optional<TypeFunctionTypeId> parent;
std::string name; TypeId classTy;
std::string name_DEPRECATED;
}; };
struct TypeFunctionGenericType 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 // 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 // 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 // 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 // List of errors that occur during serialization/deserialization
// At every iteration of serialization/deserialzation, if this list.size() != 0, we halt the process // 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) TypeFunctionRuntimeBuilderState(NotNull<TypeFunctionContext> ctx)
: ctx(ctx) : ctx(ctx)
, classesSerialized({})
, errors({})
{ {
} }
}; };

View file

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

View file

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

View file

@ -20,7 +20,6 @@
#include <utility> #include <utility>
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(AutocompleteRequirePathSuggestions2)
LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
@ -1521,12 +1520,9 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(
{ {
for (const std::string& tag : funcType->tags) 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)) if (std::optional<AutocompleteEntryMap> ret = callback(tag, getMethodContainingClass(module, candidate->func), candidateString))
{ {

View file

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

View file

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

View file

@ -36,6 +36,7 @@ LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAGVARIABLE(LuauNewSolverPrePopulateClasses) LUAU_FASTFLAGVARIABLE(LuauNewSolverPrePopulateClasses)
LUAU_FASTFLAGVARIABLE(LuauNewSolverPopulateTableLocations) LUAU_FASTFLAGVARIABLE(LuauNewSolverPopulateTableLocations)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauDeferBidirectionalInferenceForTableAssignment)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments) LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments)
@ -2998,30 +2999,46 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
if (expectedType) if (expectedType)
{ {
Unifier2 unifier{arena, builtinTypes, NotNull{scope.get()}, ice}; if (FFlag::LuauDeferBidirectionalInferenceForTableAssignment)
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( addConstraint(
NotNull{&module->astTypes}, scope,
NotNull{&module->astExpectedTypes}, expr->location,
builtinTypes, TableCheckConstraint{
arena, *expectedType,
NotNull{&unifier}, ty,
*expectedType, expr,
ty, NotNull{&module->astTypes},
expr, NotNull{&module->astExpectedTypes},
toBlock }
); );
// The visitor we ran prior should ensure that there are no }
// blocked types that we would encounter while matching on else
// this expression. {
LUAU_ASSERT(toBlock.empty()); 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_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauAlwaysFillInFunctionCallDiscriminantTypes) LUAU_FASTFLAGVARIABLE(LuauAlwaysFillInFunctionCallDiscriminantTypes)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope) LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAGVARIABLE(LuauPrecalculateMutatedFreeTypes) LUAU_FASTFLAGVARIABLE(LuauPrecalculateMutatedFreeTypes2)
namespace Luau namespace Luau
{ {
@ -355,13 +355,27 @@ ConstraintSolver::ConstraintSolver(
{ {
unsolvedConstraints.emplace_back(c); unsolvedConstraints.emplace_back(c);
// initialize the reference counts for the free types in this constraint. if (FFlag::LuauPrecalculateMutatedFreeTypes2)
for (auto ty : c->getMaybeMutatedFreeTypes())
{ {
// increment the reference count for `ty` auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); for (auto ty : maybeMutatedTypesPerConstraint)
refCount += 1; {
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) for (NotNull<const Constraint> dep : c->dependencies)
{ {
@ -439,10 +453,6 @@ void ConstraintSolver::run()
snapshot = logger->prepareStepSnapshot(rootScope, c, force, unsolvedConstraints); 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); bool success = tryDispatch(c, force);
progress |= success; progress |= success;
@ -452,23 +462,29 @@ void ConstraintSolver::run()
unblock(c); unblock(c);
unsolvedConstraints.erase(unsolvedConstraints.begin() + ptrdiff_t(i)); unsolvedConstraints.erase(unsolvedConstraints.begin() + ptrdiff_t(i));
if (FFlag::LuauPrecalculateMutatedFreeTypes) if (FFlag::LuauPrecalculateMutatedFreeTypes2)
{ {
for (auto ty : c->getMaybeMutatedFreeTypes()) const auto maybeMutated = maybeMutatedFreeTypes.find(c);
mutatedFreeTypes->insert(ty); if (maybeMutated != maybeMutatedFreeTypes.end())
for (auto ty : *mutatedFreeTypes)
{ {
size_t& refCount = unresolvedConstraints[ty]; for (auto ty : maybeMutated->second)
if (refCount > 0) {
refCount -= 1; // 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 // We have two constraints that are designed to wait for the
// refCount on a free type to be equal to 1: the // refCount on a free type to be equal to 1: the
// PrimitiveTypeConstraint and ReduceConstraint. We // PrimitiveTypeConstraint and ReduceConstraint. We
// therefore wake any constraint waiting for a free type's // therefore wake any constraint waiting for a free type's
// refcount to be 1 or 0. // refcount to be 1 or 0.
if (refCount <= 1) if (refCount <= 1)
unblock(ty, Location{}); unblock(ty, Location{});
}
} }
} }
else else
@ -668,6 +684,8 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
success = tryDispatch(*fcc, constraint); success = tryDispatch(*fcc, constraint);
else if (auto fcc = get<FunctionCheckConstraint>(*constraint)) else if (auto fcc = get<FunctionCheckConstraint>(*constraint))
success = tryDispatch(*fcc, constraint); success = tryDispatch(*fcc, constraint);
else if (auto tcc = get<TableCheckConstraint>(*constraint))
success = tryDispatch(*tcc, constraint);
else if (auto fcc = get<PrimitiveTypeConstraint>(*constraint)) else if (auto fcc = get<PrimitiveTypeConstraint>(*constraint))
success = tryDispatch(*fcc, constraint); success = tryDispatch(*fcc, constraint);
else if (auto hpc = get<HasPropConstraint>(*constraint)) else if (auto hpc = get<HasPropConstraint>(*constraint))
@ -1170,10 +1188,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
return true; return true;
} }
void ConstraintSolver::fillInDiscriminantTypes( void ConstraintSolver::fillInDiscriminantTypes(NotNull<const Constraint> constraint, const std::vector<std::optional<TypeId>>& discriminantTypes)
NotNull<const Constraint> constraint,
const std::vector<std::optional<TypeId>>& discriminantTypes
)
{ {
for (std::optional<TypeId> ty : discriminantTypes) for (std::optional<TypeId> ty : discriminantTypes)
{ {
@ -1521,6 +1536,28 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
return true; 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) 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; 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(LuauBufferBitMethods2)
LUAU_FASTFLAG(LuauVector2Constructor) LUAU_FASTFLAG(LuauVector2Constructor)
LUAU_FASTFLAGVARIABLE(LuauDebugInfoDefn)
namespace Luau namespace Luau
{ {
@ -209,6 +210,15 @@ declare table: {
static const std::string kBuiltinDefinitionDebugSrc = R"BUILTIN_SRC( 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: { 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...), 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), traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string),
@ -362,7 +372,7 @@ std::string getBuiltinDefinitionSource()
result += kBuiltinDefinitionOsSrc; result += kBuiltinDefinitionOsSrc;
result += kBuiltinDefinitionCoroutineSrc; result += kBuiltinDefinitionCoroutineSrc;
result += kBuiltinDefinitionTableSrc; result += kBuiltinDefinitionTableSrc;
result += kBuiltinDefinitionDebugSrc; result += FFlag::LuauDebugInfoDefn ? kBuiltinDefinitionDebugSrc : kBuiltinDefinitionDebugSrc_DEPRECATED;
result += kBuiltinDefinitionUtf8Src; result += kBuiltinDefinitionUtf8Src;
result += FFlag::LuauBufferBitMethods2 ? kBuiltinDefinitionBufferSrc : kBuiltinDefinitionBufferSrc_DEPRECATED; result += FFlag::LuauBufferBitMethods2 ? kBuiltinDefinitionBufferSrc : kBuiltinDefinitionBufferSrc_DEPRECATED;

View file

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

View file

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

View file

@ -52,7 +52,6 @@ LUAU_FASTFLAGVARIABLE(LuauBetterReverseDependencyTracking)
LUAU_FASTFLAG(StudioReportLuauAny2) LUAU_FASTFLAG(StudioReportLuauAny2)
LUAU_FASTFLAGVARIABLE(LuauStoreSolverTypeOnModule) LUAU_FASTFLAGVARIABLE(LuauStoreSolverTypeOnModule)
LUAU_FASTFLAGVARIABLE(LuauReferenceAllocatorInNewSolver)
LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena) LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena)
namespace Luau namespace Luau
@ -1430,11 +1429,8 @@ ModulePtr check(
result->mode = mode; result->mode = mode;
result->internalTypes.owningModule = result.get(); result->internalTypes.owningModule = result.get();
result->interfaceTypes.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; iceHandler->moduleName = sourceModule.name;
@ -1751,7 +1747,7 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(const ModuleName&
if (FFlag::LuauBetterReverseDependencyTracking) 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) for (const auto& [moduleName, _] : sourceNode->requireLocations)
{ {
if (auto depIt = sourceNodes.find(moduleName); depIt != sourceNodes.end()) if (auto depIt = sourceNodes.find(moduleName); depIt != sourceNodes.end())

View file

@ -2,6 +2,8 @@
#include "Luau/Generalization.h" #include "Luau/Generalization.h"
#include "Luau/Common.h"
#include "Luau/DenseHash.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
@ -10,7 +12,7 @@
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete) LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauGeneralizationRemoveRecursiveUpperBound) LUAU_FASTFLAGVARIABLE(LuauGeneralizationRemoveRecursiveUpperBound2)
namespace Luau 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); haystack = follow(haystack);
@ -97,6 +99,10 @@ struct MutatingGeneralizer : TypeOnceVisitor
LUAU_ASSERT(onlyType != haystack); LUAU_ASSERT(onlyType != haystack);
emplaceType<BoundType>(asMutable(haystack), onlyType); emplaceType<BoundType>(asMutable(haystack), onlyType);
} }
else if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound2 && ut->options.empty())
{
emplaceType<BoundType>(asMutable(haystack), builtinTypes->neverType);
}
return; return;
} }
@ -139,6 +145,10 @@ struct MutatingGeneralizer : TypeOnceVisitor
TypeId onlyType = it->parts[0]; TypeId onlyType = it->parts[0];
LUAU_ASSERT(onlyType != needle); LUAU_ASSERT(onlyType != needle);
emplaceType<BoundType>(asMutable(needle), onlyType); emplaceType<BoundType>(asMutable(needle), onlyType);
}
else if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound2 && it->parts.empty())
{
emplaceType<BoundType>(asMutable(needle), builtinTypes->unknownType);
} }
return; return;
@ -233,53 +243,6 @@ struct MutatingGeneralizer : TypeOnceVisitor
else else
{ {
TypeId ub = follow(ft->upperBound); 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) if (FreeType* upperFree = getMutable<FreeType>(ub); upperFree && upperFree->lowerBound == ty)
upperFree->lowerBound = builtinTypes->neverType; upperFree->lowerBound = builtinTypes->neverType;
else else

View file

@ -2296,7 +2296,7 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
// //
// Dog & ~Animal // 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. // being removed from the normalized class type.
emptyIntersectWithNegation = true; emptyIntersectWithNegation = true;
break; break;

View file

@ -4,6 +4,8 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Module.h" #include "Luau/Module.h"
LUAU_FASTFLAGVARIABLE(LuauExtendedSimpleRequire)
namespace Luau namespace Luau
{ {
@ -65,7 +67,7 @@ struct RequireTracer : AstVisitor
return true; return true;
} }
AstExpr* getDependent(AstExpr* node) AstExpr* getDependent_DEPRECATED(AstExpr* node)
{ {
if (AstExprLocal* expr = node->as<AstExprLocal>()) if (AstExprLocal* expr = node->as<AstExprLocal>())
return locals[expr->local]; return locals[expr->local];
@ -78,50 +80,122 @@ struct RequireTracer : AstVisitor
else else
return nullptr; 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() void process()
{ {
ModuleInfo moduleContext{currentModuleName}; ModuleInfo moduleContext{currentModuleName};
// seed worklist with require arguments if (FFlag::LuauExtendedSimpleRequire)
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)
{ {
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 for (AstExprCall* require : requireCalls)
if (result.exprs.contains(expr)) work.push_back(require->args.data[0]);
continue;
std::optional<ModuleInfo> info; // 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(expr))
{ {
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 // resolve all expressions to a module info
if (expr->is<AstExprLocal>()) for (size_t i = work.size(); i > 0; --i)
info = context ? std::optional<ModuleInfo>(*context) : std::nullopt; {
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 else
info = fileResolver->resolveModule(context, expr); {
} info = fileResolver->resolveModule(&moduleContext, expr);
else }
{
info = fileResolver->resolveModule(&moduleContext, expr);
}
if (info) if (info)
result.exprs[expr] = std::move(*info); result.exprs[expr] = std::move(*info);
}
} }
// resolve all requires according to their argument // resolve all requires according to their argument
@ -150,7 +224,8 @@ struct RequireTracer : AstVisitor
ModuleName currentModuleName; ModuleName currentModuleName;
DenseHashMap<AstLocal*, AstExpr*> locals; DenseHashMap<AstLocal*, AstExpr*> locals;
std::vector<AstExpr*> work; std::vector<AstExpr*> work_DEPRECATED;
std::vector<AstNode*> work;
std::vector<AstExprCall*> requireCalls; std::vector<AstExprCall*> requireCalls;
}; };

View file

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

View file

@ -408,7 +408,7 @@ TypeId matchLiteralType(
if (FFlag::LuauDontInPlaceMutateTableType) if (FFlag::LuauDontInPlaceMutateTableType)
{ {
for (const auto& key: keysToDelete) for (const auto& key : keysToDelete)
{ {
const AstArray<char>& s = key->value; const AstArray<char>& s = key->value;
std::string keyStr{s.data, s.data + s.size}; 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>) else if constexpr (std::is_same_v<T, EqualityConstraint>)
return "equality: " + tos(c.resultType) + " ~ " + tos(c.assignmentType); 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 else
static_assert(always_false_v<T>, "Non-exhaustive constraint switch"); static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
}; };

View file

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

View file

@ -489,7 +489,6 @@ static FunctionGraphReductionResult reduceFunctionsInternal(
return std::move(reducer.result); return std::move(reducer.result);
} }
} }
FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext ctx, bool force) FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext ctx, bool force)
@ -744,11 +743,9 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
resetTypeFunctionState(L); 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); std::unique_ptr<TypeFunctionRuntimeBuilderState> runtimeBuilder = std::make_unique<TypeFunctionRuntimeBuilderState>(ctx);
// Push serialized arguments onto the stack
for (auto typeParam : typeParams) for (auto typeParam : typeParams)
{ {
TypeId ty = follow(typeParam); TypeId ty = follow(typeParam);
@ -2839,9 +2836,9 @@ TypeFunctionReductionResult<TypeId> setmetatableTypeFunction(
return {std::nullopt, Reduction::Erroneous, {}, {}}; 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. // 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() || if (targetNorm->hasTops() || targetNorm->hasBooleans() || targetNorm->hasErrors() || targetNorm->hasNils() || targetNorm->hasNumbers() ||
targetNorm->hasNumbers() || targetNorm->hasStrings() || targetNorm->hasThreads() || targetNorm->hasBuffers() || targetNorm->hasStrings() || targetNorm->hasThreads() || targetNorm->hasBuffers() || targetNorm->hasFunctions() || targetNorm->hasTyvars() ||
targetNorm->hasFunctions() || targetNorm->hasTyvars() || targetNorm->hasClasses()) targetNorm->hasClasses())
return {std::nullopt, Reduction::Erroneous, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}};
// if the supposed metatable is not a table, we will fail to reduce. // if the supposed metatable is not a table, we will fail to reduce.
@ -2899,11 +2896,7 @@ TypeFunctionReductionResult<TypeId> setmetatableTypeFunction(
return {result, Reduction::MaybeOk, {}, {}}; return {result, Reduction::MaybeOk, {}, {}};
} }
static TypeFunctionReductionResult<TypeId> getmetatableHelper( static TypeFunctionReductionResult<TypeId> getmetatableHelper(TypeId targetTy, const Location& location, NotNull<TypeFunctionContext> ctx)
TypeId targetTy,
const Location& location,
NotNull<TypeFunctionContext> ctx
)
{ {
targetTy = follow(targetTy); targetTy = follow(targetTy);

View file

@ -13,7 +13,9 @@
#include <set> #include <set>
#include <vector> #include <vector>
LUAU_FASTFLAGVARIABLE(LuauTypeFunFixHydratedClasses)
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit) LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_FASTFLAGVARIABLE(LuauTypeFunSingletonEquality)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypeofReturnsType) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypeofReturnsType)
LUAU_FASTFLAGVARIABLE(LuauTypeFunPrintFix) LUAU_FASTFLAGVARIABLE(LuauTypeFunPrintFix)
@ -1566,7 +1568,7 @@ void registerTypeUserData(lua_State* L)
// Create and register metatable for type userdata // Create and register metatable for type userdata
luaL_newmetatable(L, "type"); luaL_newmetatable(L, "type");
if (FFlag::LuauUserTypeFunTypeofReturnsType) if (FFlag::LuauUserTypeFunTypeofReturnsType)
{ {
lua_pushstring(L, "type"); lua_pushstring(L, "type");
@ -1708,14 +1710,14 @@ bool areEqual(SeenSet& seen, const TypeFunctionSingletonType& lhs, const TypeFun
{ {
const TypeFunctionBooleanSingleton* lp = get<TypeFunctionBooleanSingleton>(&lhs); const TypeFunctionBooleanSingleton* lp = get<TypeFunctionBooleanSingleton>(&lhs);
const TypeFunctionBooleanSingleton* rp = get<TypeFunctionBooleanSingleton>(&lhs); const TypeFunctionBooleanSingleton* rp = get<TypeFunctionBooleanSingleton>(FFlag::LuauTypeFunSingletonEquality ? &rhs : &lhs);
if (lp && rp) if (lp && rp)
return lp->value == rp->value; return lp->value == rp->value;
} }
{ {
const TypeFunctionStringSingleton* lp = get<TypeFunctionStringSingleton>(&lhs); const TypeFunctionStringSingleton* lp = get<TypeFunctionStringSingleton>(&lhs);
const TypeFunctionStringSingleton* rp = get<TypeFunctionStringSingleton>(&lhs); const TypeFunctionStringSingleton* rp = get<TypeFunctionStringSingleton>(FFlag::LuauTypeFunSingletonEquality ? &rhs : &lhs);
if (lp && rp) if (lp && rp)
return lp->value == rp->value; return lp->value == rp->value;
} }
@ -1868,7 +1870,10 @@ bool areEqual(SeenSet& seen, const TypeFunctionClassType& lhs, const TypeFunctio
if (seenSetContains(seen, &lhs, &rhs)) if (seenSetContains(seen, &lhs, &rhs))
return true; 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) 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 // used to control the recursion limit of any operations done by user-defined type functions
// currently, controls serialization, deserialization, and `type.copy` // currently, controls serialization, deserialization, and `type.copy`
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000); LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000);
LUAU_FASTFLAG(LuauTypeFunFixHydratedClasses)
namespace Luau namespace Luau
{ {
@ -207,8 +208,19 @@ private:
} }
else if (auto c = get<ClassType>(ty)) else if (auto c = get<ClassType>(ty))
{ {
state->classesSerialized[c->name] = ty; if (FFlag::LuauTypeFunFixHydratedClasses)
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, c->name}); {
// 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)) else if (auto g = get<GenericType>(ty))
{ {
@ -687,10 +699,17 @@ private:
} }
else if (auto c = get<TypeFunctionClassType>(ty)) else if (auto c = get<TypeFunctionClassType>(ty))
{ {
if (auto result = state->classesSerialized.find(c->name)) if (FFlag::LuauTypeFunFixHydratedClasses)
target = *result; {
target = c->classTy;
}
else 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)) else if (auto g = get<TypeFunctionGenericType>(ty))
{ {

View file

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

View file

@ -270,6 +270,47 @@ public:
Position functionKeywordPosition; 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 class CstTypeReference : public CstNode
{ {
public: public:

View file

@ -144,7 +144,7 @@ private:
AstStat* parseReturn(); AstStat* parseReturn();
// type Name `=' Type // type Name `=' Type
AstStat* parseTypeAlias(const Location& start, bool exported); AstStat* parseTypeAlias(const Location& start, bool exported, Position typeKeywordPosition);
// type function Name ... end // type function Name ... end
AstStat* parseTypeFunction(const Location& start, bool exported); AstStat* parseTypeFunction(const Location& start, bool exported);
@ -294,7 +294,12 @@ private:
Name parseIndexName(const char* context, const Position& previous); Name parseIndexName(const char* context, const Position& previous);
// `<' namelist `>' // `<' 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[, ...] `>' // `<' Type[, ...] `>'
AstArray<AstTypeOrPack> parseTypeParams( AstArray<AstTypeOrPack> parseTypeParams(

View file

@ -111,11 +111,7 @@ CstStatForIn::CstStatForIn(AstArray<Position> varsCommaPositions, AstArray<Posit
{ {
} }
CstStatAssign::CstStatAssign( CstStatAssign::CstStatAssign(AstArray<Position> varsCommaPositions, Position equalsPosition, AstArray<Position> valuesCommaPositions)
AstArray<Position> varsCommaPositions,
Position equalsPosition,
AstArray<Position> valuesCommaPositions
)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, varsCommaPositions(varsCommaPositions) , varsCommaPositions(varsCommaPositions)
, equalsPosition(equalsPosition) , 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( CstTypeReference::CstTypeReference(
std::optional<Position> prefixPointPosition, std::optional<Position> prefixPointPosition,
Position openParametersPosition, Position openParametersPosition,

View file

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

View file

@ -22,10 +22,10 @@
// __register_frame and __deregister_frame are defined in libgcc or libc++ // __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 // (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 // 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 // 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. // and the linker won't look for the strong ones in the library.
#ifndef LUAU_ENABLE_REGISTER_FRAME #ifndef LUAU_ENABLE_REGISTER_FRAME
#define REGISTER_FRAME_WEAK __attribute__((weak)) #define REGISTER_FRAME_WEAK __attribute__((weak))

View file

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

View file

@ -22,6 +22,7 @@ LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAG(LuauAlwaysFillInFunctionCallDiscriminantTypes) LUAU_FASTFLAG(LuauAlwaysFillInFunctionCallDiscriminantTypes)
LUAU_FASTFLAG(LuauStoreCSTData) LUAU_FASTFLAG(LuauStoreCSTData)
LUAU_FASTFLAG(LuauAstTypeGroup) LUAU_FASTFLAG(LuauAstTypeGroup)
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
struct ATSFixture : BuiltinsFixture struct ATSFixture : BuiltinsFixture
@ -693,6 +694,7 @@ TEST_CASE_FIXTURE(ATSFixture, "mutually_recursive_generic")
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::StudioReportLuauAny2, true}, {FFlag::StudioReportLuauAny2, true},
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true}
}; };
fileResolver.source["game/Gui/Modules/A"] = R"( fileResolver.source["game/Gui/Modules/A"] = R"(
@ -705,8 +707,7 @@ TEST_CASE_FIXTURE(ATSFixture, "mutually_recursive_generic")
y.g.i = y y.g.i = y
)"; )";
CheckResult result1 = frontend.check("game/Gui/Modules/A"); LUAU_REQUIRE_NO_ERRORS(frontend.check("game/Gui/Modules/A"));
LUAU_REQUIRE_ERROR_COUNT(2, result1);
ModulePtr module = frontend.moduleResolver.getModule("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) 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); CHECK(toJson(statement) == expected);
} }
else 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' // IndexableNumericKeyClass has a table indexer with a key type of 'number' and a return type of 'number'
addIndexableClass("IndexableNumericKeyClass", numberType, numberType); 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) for (const auto& [name, tf] : globals.globalScope->exportedTypeBindings)
persist(tf.type); persist(tf.type);

View file

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

View file

@ -31,7 +31,6 @@ LUAU_FASTINT(LuauParseErrorLimit)
LUAU_FASTFLAG(LuauCloneIncrementalModule) LUAU_FASTFLAG(LuauCloneIncrementalModule)
LUAU_FASTFLAG(LuauIncrementalAutocompleteBugfixes) LUAU_FASTFLAG(LuauIncrementalAutocompleteBugfixes)
LUAU_FASTFLAG(LuauReferenceAllocatorInNewSolver)
LUAU_FASTFLAG(LuauMixedModeDefFinderTraversesTypeOf) LUAU_FASTFLAG(LuauMixedModeDefFinderTraversesTypeOf)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) 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"); 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::LuauAllowFragmentParsing, true},
{FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete, true}, {FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete, true},
{FFlag::LuauStoreSolverTypeOnModule, true}, {FFlag::LuauStoreSolverTypeOnModule, true},
{FFlag::LuauSymbolEquality, true}, {FFlag::LuauSymbolEquality, true},
{FFlag::LexerResumesFromPosition2, true}, {FFlag::LexerResumesFromPosition2, true},
{FFlag::LuauReferenceAllocatorInNewSolver, true},
{FFlag::LuauIncrementalAutocompleteBugfixes, true}, {FFlag::LuauIncrementalAutocompleteBugfixes, true},
{FFlag::LuauBetterReverseDependencyTracking, 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") TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "inline_autocomplete_picks_the_right_scope")
{ {
const std::string source = R"( const std::string source = R"(

View file

@ -16,7 +16,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(DebugLuauFreezeArena); LUAU_FASTFLAG(DebugLuauFreezeArena);
LUAU_FASTFLAG(DebugLuauMagicTypes); LUAU_FASTFLAG(DebugLuauMagicTypes);
LUAU_FASTFLAG(LuauReferenceAllocatorInNewSolver);
LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena) LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena)
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking); LUAU_FASTFLAG(LuauBetterReverseDependencyTracking);
@ -1528,7 +1527,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "get_required_scripts_dirty")
TEST_CASE_FIXTURE(FrontendFixture, "check_module_references_allocator") TEST_CASE_FIXTURE(FrontendFixture, "check_module_references_allocator")
{ {
ScopedFastFlag sff{FFlag::LuauReferenceAllocatorInNewSolver, true};
fileResolver.source["game/workspace/MyScript"] = R"( fileResolver.source["game/workspace/MyScript"] = R"(
print("Hello World") 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") TEST_CASE_FIXTURE(FrontendFixture, "dfg_data_cleared_on_retain_type_graphs_unset")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauSelectivelyRetainDFGArena, true}};
{FFlag::LuauSolverV2, true},
{FFlag::LuauSelectivelyRetainDFGArena, true}
};
fileResolver.source["game/A"] = R"( fileResolver.source["game/A"] = R"(
local a = 1 local a = 1
local b = 2 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/A"] = "return {hello=5, world=true}";
fileResolver.source["game/Gui/Modules/B"] = "return require(game:GetService('Gui').Modules.A)"; fileResolver.source["game/Gui/Modules/B"] = "return require(game:GetService('Gui').Modules.A)";
FrontendOptions opts; FrontendOptions opts;
opts.forAutocomplete = false; 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") TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_non_strict")
{ {
ScopedFastFlag flags[] = { ScopedFastFlag flags[] = {{FFlag::LuauNonStrictVisitorImprovements, true}, {FFlag::LuauNewNonStrictWarnOnUnknownGlobals, true}};
{FFlag::LuauNonStrictVisitorImprovements, true},
{FFlag::LuauNewNonStrictWarnOnUnknownGlobals, true}
};
CheckResult result = check(Mode::Nonstrict, R"( CheckResult result = check(Mode::Nonstrict, R"(
foo = 5 foo = 5

View file

@ -23,6 +23,7 @@ LUAU_FASTFLAG(LuauFixFunctionNameStartPosition)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon) LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAG(LuauAstTypeGroup) LUAU_FASTFLAG(LuauAstTypeGroup)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
namespace namespace
{ {
@ -2508,6 +2509,40 @@ TEST_CASE_FIXTURE(Fixture, "parse_return_type_ast_type_group")
CHECK(funcType->returnTypes.types.data[0]->is<AstTypeGroup>()); 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_END();
TEST_SUITE_BEGIN("ParseErrorRecovery"); TEST_SUITE_BEGIN("ParseErrorRecovery");
@ -3786,7 +3821,7 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type")
} }
else else
CHECK(unionTy->types.data[0]->is<AstTypeFunction>()); // () -> () 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") TEST_CASE_FIXTURE(Fixture, "complex_union_in_generic_ty")

View file

@ -6,6 +6,8 @@
#include "doctest.h" #include "doctest.h"
LUAU_FASTFLAG(LuauExtendedSimpleRequire)
using namespace Luau; using namespace Luau;
namespace namespace
@ -178,4 +180,59 @@ TEST_CASE_FIXTURE(RequireTracerFixture, "follow_string_indexexpr")
CHECK_EQ("game/Test", result.exprs[local->values.data[0]].name); 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(); TEST_SUITE_END();

View file

@ -317,6 +317,74 @@ TEST_CASE("returns_spaces_around_tokens")
CHECK_EQ(three, transpile(three).code); 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") TEST_CASE("table_literals")
{ {
const std::string code = R"( local t={1, 2, 3, foo='bar', baz=99,[5.5]='five point five', 'end'} )"; 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,10 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauTypeFunFixHydratedClasses)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTypeFunSingletonEquality)
LUAU_FASTFLAG(LuauUserTypeFunTypeofReturnsType) LUAU_FASTFLAG(LuauUserTypeFunTypeofReturnsType)
LUAU_FASTFLAG(LuauTypeFunPrintFix) LUAU_FASTFLAG(LuauTypeFunPrintFix)
@ -489,7 +491,10 @@ local function notok(idx: fail<number>): never return idx end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(4, result); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_serialization_works")
@ -639,6 +644,21 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works")
LUAU_REQUIRE_NO_ERRORS(result); 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") TEST_CASE_FIXTURE(ClassFixture, "udtf_class_methods_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
@ -1868,6 +1888,40 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_eqsat_opaque")
CHECK_EQ("t0<number & string>", toString(simplified->result)); // NOLINT(bugprone-unchecked-optional-access) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "typeof_type_userdata_returns_type")
{ {
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true}; ScopedFastFlag solverV2{FFlag::LuauSolverV2, true};

View file

@ -20,6 +20,7 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauSubtypingFixTailPack)
TEST_SUITE_BEGIN("TypeInferFunctions"); TEST_SUITE_BEGIN("TypeInferFunctions");
@ -3027,4 +3028,17 @@ TEST_CASE_FIXTURE(Fixture, "hidden_variadics_should_not_break_subtyping")
LUAU_REQUIRE_NO_ERRORS(result); 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(); TEST_SUITE_END();

View file

@ -12,6 +12,7 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping); LUAU_FASTFLAG(LuauInstantiateInSubtyping);
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
using namespace Luau; using namespace Luau;
@ -854,6 +855,8 @@ end
TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe") TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe")
{ {
ScopedFastFlag _{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
-- At one point this produced a UAF -- 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 y.a.c = y
)"); )");
LUAU_REQUIRE_ERRORS(result);
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
CHECK( {
toString(result.errors.at(0)) == LUAU_REQUIRE_ERROR_COUNT(1, result);
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>))" 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 else
{ {
LUAU_REQUIRE_ERROR_COUNT(2, result);
const std::string expected = R"(Type 'y' could not be converted into 'T<string>' const std::string expected = R"(Type 'y' could not be converted into 'T<string>'
caused by: caused by:
Property 'a' is not compatible. 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") 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 // `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 // 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. // unreachable.
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
--!strict --!strict

View file

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

View file

@ -25,7 +25,8 @@ LUAU_FASTFLAG(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAG(LuauDontInPlaceMutateTableType) LUAU_FASTFLAG(LuauDontInPlaceMutateTableType)
LUAU_FASTFLAG(LuauAllowNonSharedTableTypesInLiteral) LUAU_FASTFLAG(LuauAllowNonSharedTableTypesInLiteral)
LUAU_FASTFLAG(LuauFollowTableFreeze) LUAU_FASTFLAG(LuauFollowTableFreeze)
LUAU_FASTFLAG(LuauPrecalculateMutatedFreeTypes) LUAU_FASTFLAG(LuauPrecalculateMutatedFreeTypes2)
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
TEST_SUITE_BEGIN("TableTests"); TEST_SUITE_BEGIN("TableTests");
@ -5012,7 +5013,7 @@ TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager")
// bidirectional inference is known to be broken. // bidirectional inference is known to be broken.
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauPrecalculateMutatedFreeTypes, true}, {FFlag::LuauPrecalculateMutatedFreeTypes2, true},
}; };
auto result = check(R"( 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(); TEST_SUITE_END();