Sync to upstream/release/663 (#1699)

Hey folks, another week means another Luau release! This one features a
number of bug fixes in the New Type Solver including improvements to
user-defined type functions and a bunch of work to untangle some of the
outstanding issues we've been seeing with constraint solving not
completing in real world use. We're also continuing to make progress on
crashes and other problems that affect the stability of fragment
autocomplete, as we work towards delivering consistent, low-latency
autocomplete for any editor environment.

## New Type Solver

- Fix a bug in user-defined type functions where `print` would
incorrectly insert `\1` a number of times.
- Fix a bug where attempting to refine an optional generic with a type
test will cause a false positive type error (fixes #1666)
- Fix a bug where the `refine` type family would not skip over
`*no-refine*` discriminants (partial resolution for #1424)
- Fix a constraint solving bug where recursive function calls would
consistently produce cyclic constraints leading to incomplete or
inaccurate type inference.
- Implement `readparent` and `writeparent` for class types in
user-defined type functions, replacing the incorrectly included `parent`
method.
- Add initial groundwork (under a debug flag) for eager free type
generalization, moving us towards further improvements to constraint
solving incomplete errors.

## Fragment Autocomplete

- Ease up some assertions to improve stability of mixed-mode use of the
two type solvers (i.e. using Fragment Autocomplete on a type graph
originally produced by the old type solver)
- Resolve a bug with type compatibility checks causing internal compiler
errors in autocomplete.

## Lexer and Parser

- Improve the accuracy of the roundtrippable AST parsing mode by
correctly placing closing parentheses on type groupings.
- Add a getter for `offset` in the Lexer by @aduermael in #1688
- Add a second entry point to the parser to parse an expression,
`parseExpr`

## Internal Contributors

Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: James McNellis <jmcnellis@roblox.com>
Co-authored-by: Talha Pathan <tpathan@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com>
Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com>
Co-authored-by: Menarul Alam <malam@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Vighnesh <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
ariel 2025-02-28 14:42:30 -08:00 committed by GitHub
parent 6a21dba682
commit 640ebbc0a5
Signed by: DevComp
GPG key ID: B5690EEEBB952194
62 changed files with 927 additions and 1069 deletions

View file

@ -96,6 +96,9 @@ struct ConstraintGenerator
// will enqueue them during solving. // will enqueue them during solving.
std::vector<ConstraintPtr> unqueuedConstraints; std::vector<ConstraintPtr> unqueuedConstraints;
// Map a function's signature scope back to its signature type.
DenseHashMap<Scope*, TypeId> scopeToFunction{nullptr};
// The private scope of type aliases for which the type parameters belong to. // The private scope of type aliases for which the type parameters belong to.
DenseHashMap<const AstStatTypeAlias*, ScopePtr> astTypeAliasDefiningScopes{nullptr}; DenseHashMap<const AstStatTypeAlias*, ScopePtr> astTypeAliasDefiningScopes{nullptr};

View file

@ -88,6 +88,7 @@ struct ConstraintSolver
NotNull<TypeFunctionRuntime> typeFunctionRuntime; NotNull<TypeFunctionRuntime> typeFunctionRuntime;
// The entire set of constraints that the solver is trying to resolve. // The entire set of constraints that the solver is trying to resolve.
std::vector<NotNull<Constraint>> constraints; std::vector<NotNull<Constraint>> constraints;
NotNull<DenseHashMap<Scope*, TypeId>> scopeToFunction;
NotNull<Scope> rootScope; NotNull<Scope> rootScope;
ModuleName currentModuleName; ModuleName currentModuleName;
@ -119,6 +120,7 @@ struct ConstraintSolver
DenseHashMap<TypeId, size_t> unresolvedConstraints{{}}; DenseHashMap<TypeId, size_t> unresolvedConstraints{{}};
std::unordered_map<NotNull<const Constraint>, DenseHashSet<TypeId>> maybeMutatedFreeTypes; std::unordered_map<NotNull<const Constraint>, DenseHashSet<TypeId>> maybeMutatedFreeTypes;
std::unordered_map<TypeId, DenseHashSet<const Constraint*>> mutatedFreeTypeToConstraint;
// Irreducible/uninhabited type functions or type pack functions. // Irreducible/uninhabited type functions or type pack functions.
DenseHashSet<const void*> uninhabitedTypeFunctions{{}}; DenseHashSet<const void*> uninhabitedTypeFunctions{{}};
@ -144,6 +146,7 @@ struct ConstraintSolver
NotNull<TypeFunctionRuntime> typeFunctionRuntime, NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<Scope> rootScope, NotNull<Scope> rootScope,
std::vector<NotNull<Constraint>> constraints, std::vector<NotNull<Constraint>> constraints,
NotNull<DenseHashMap<Scope*, TypeId>> scopeToFunction,
ModuleName moduleName, ModuleName moduleName,
NotNull<ModuleResolver> moduleResolver, NotNull<ModuleResolver> moduleResolver,
std::vector<RequireCycle> requireCycles, std::vector<RequireCycle> requireCycles,
@ -171,6 +174,8 @@ struct ConstraintSolver
bool isDone() const; bool isDone() const;
private: private:
void generalizeOneType(TypeId ty);
/** /**
* Bind a type variable to another type. * Bind a type variable to another type.
* *

View file

@ -82,5 +82,65 @@ FragmentAutocompleteResult fragmentAutocomplete(
std::optional<Position> fragmentEndPosition = std::nullopt std::optional<Position> fragmentEndPosition = std::nullopt
); );
enum class FragmentAutocompleteStatus
{
Success,
FragmentTypeCheckFail,
InternalIce
};
struct FragmentAutocompleteStatusResult
{
FragmentAutocompleteStatus status;
std::optional<FragmentAutocompleteResult> result;
};
struct FragmentContext
{
std::string_view newSrc;
const ParseResult& newAstRoot;
std::optional<FrontendOptions> opts;
std::optional<Position> DEPRECATED_fragmentEndPosition;
};
/**
* @brief Attempts to compute autocomplete suggestions from the fragment context.
*
* This function computes autocomplete suggestions using outdated frontend typechecking data
* by patching the fragment context of the new script source content.
*
* @param frontend The Luau Frontend data structure, which may contain outdated typechecking data.
*
* @param moduleName The name of the target module, specifying which script the caller wants to request autocomplete for.
*
* @param cursorPosition The position in the script where the caller wants to trigger autocomplete.
*
* @param context The fragment context that this API will use to patch the outdated typechecking data.
*
* @param stringCompletionCB A callback function that provides autocomplete suggestions for string contexts.
*
* @return
* The status indicating whether `fragmentAutocomplete` ran successfully or failed, along with the reason for failure.
* Also includes autocomplete suggestions if the status is successful.
*
* @usage
* FragmentAutocompleteStatusResult acStatusResult;
* if (shouldFragmentAC)
* acStatusResult = Luau::tryFragmentAutocomplete(...);
*
* if (acStatusResult.status != Successful)
* {
* frontend.check(moduleName, options);
* acStatusResult.acResult = Luau::autocomplete(...);
* }
* return convertResultWithContext(acStatusResult.acResult);
*/
FragmentAutocompleteStatusResult tryFragmentAutocomplete(
Frontend& frontend,
const ModuleName& moduleName,
Position cursorPosition,
FragmentContext context,
StringCompletionCallback stringCompletionCB
);
} // namespace Luau } // namespace Luau

View file

@ -6,6 +6,8 @@
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include <vector>
namespace Luau namespace Luau
{ {

View file

@ -65,11 +65,10 @@ T* getMutable(PendingTypePack* pending)
// Log of what TypeIds we are rebinding, to be committed later. // Log of what TypeIds we are rebinding, to be committed later.
struct TxnLog struct TxnLog
{ {
explicit TxnLog(bool useScopes = false) explicit TxnLog()
: typeVarChanges(nullptr) : typeVarChanges(nullptr)
, typePackChanges(nullptr) , typePackChanges(nullptr)
, ownedSeen() , ownedSeen()
, useScopes(useScopes)
, sharedSeen(&ownedSeen) , sharedSeen(&ownedSeen)
{ {
} }

View file

@ -216,7 +216,11 @@ struct TypeFunctionClassType
std::optional<TypeFunctionTypeId> metatable; // metaclass? std::optional<TypeFunctionTypeId> metatable; // metaclass?
std::optional<TypeFunctionTypeId> parent; // this was mistaken, and we should actually be keeping separate read/write types here.
std::optional<TypeFunctionTypeId> parent_DEPRECATED;
std::optional<TypeFunctionTypeId> readParent;
std::optional<TypeFunctionTypeId> writeParent;
TypeId classTy; TypeId classTy;

View file

@ -93,10 +93,6 @@ struct Unifier
Unifier(NotNull<Normalizer> normalizer, NotNull<Scope> scope, const Location& location, Variance variance, TxnLog* parentLog = nullptr); Unifier(NotNull<Normalizer> normalizer, NotNull<Scope> scope, const Location& location, Variance variance, TxnLog* parentLog = nullptr);
// Configure the Unifier to test for scope subsumption via embedded Scope
// pointers rather than TypeLevels.
void enableNewSolver();
// Test whether the two type vars unify. Never commits the result. // Test whether the two type vars unify. Never commits the result.
ErrorVec canUnify(TypeId subTy, TypeId superTy); ErrorVec canUnify(TypeId subTy, TypeId superTy);
ErrorVec canUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false); ErrorVec canUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
@ -169,7 +165,6 @@ private:
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name); std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name);
TxnLog combineLogsIntoIntersection(std::vector<TxnLog> logs);
TxnLog combineLogsIntoUnion(std::vector<TxnLog> logs); TxnLog combineLogsIntoUnion(std::vector<TxnLog> logs);
public: public:
@ -195,11 +190,6 @@ private:
// Available after regular type pack unification errors // Available after regular type pack unification errors
std::optional<int> firstPackErrorPos; std::optional<int> firstPackErrorPos;
// If true, we do a bunch of small things differently to work better with
// the new type inference engine. Most notably, we use the Scope hierarchy
// directly rather than using TypeLevels.
bool useNewSolver = false;
}; };
void promoteTypeLevels(TxnLog& log, const TypeArena* arena, TypeLevel minLevel, Scope* outerScope, bool useScope, TypePackId tp); void promoteTypeLevels(TxnLog& log, const TypeArena* arena, TypeLevel minLevel, Scope* outerScope, bool useScope, TypePackId tp);

View file

@ -2,6 +2,7 @@
#include "Luau/Autocomplete.h" #include "Luau/Autocomplete.h"
#include "Luau/AstQuery.h" #include "Luau/AstQuery.h"
#include "Luau/TimeTrace.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/Frontend.h" #include "Luau/Frontend.h"
@ -15,6 +16,9 @@ namespace Luau
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback) AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback)
{ {
LUAU_TIMETRACE_SCOPE("Luau::autocomplete", "Autocomplete");
LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str());
const SourceModule* sourceModule = frontend.getSourceModule(moduleName); const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
if (!sourceModule) if (!sourceModule)
return {}; return {};

View file

@ -10,6 +10,7 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/FileResolver.h" #include "Luau/FileResolver.h"
#include "Luau/Frontend.h" #include "Luau/Frontend.h"
#include "Luau/TimeTrace.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/Subtyping.h" #include "Luau/Subtyping.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
@ -24,7 +25,8 @@ LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete) LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUseLimits)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility)
static const std::unordered_set<std::string> kStatementStartingKeywords = static const std::unordered_set<std::string> kStatementStartingKeywords =
{"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; {"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
@ -146,44 +148,91 @@ static std::optional<TypeId> findExpectedTypeAt(const Module& module, AstNode* n
return *it; return *it;
} }
static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull<Scope> scope, TypeArena* typeArena, NotNull<BuiltinTypes> builtinTypes) static bool checkTypeMatch(
const Module& module,
TypeId subTy,
TypeId superTy,
NotNull<Scope> scope,
TypeArena* typeArena,
NotNull<BuiltinTypes> builtinTypes
)
{ {
InternalErrorReporter iceReporter; InternalErrorReporter iceReporter;
UnifierSharedState unifierState(&iceReporter); UnifierSharedState unifierState(&iceReporter);
SimplifierPtr simplifier = newSimplifier(NotNull{typeArena}, builtinTypes); SimplifierPtr simplifier = newSimplifier(NotNull{typeArena}, builtinTypes);
Normalizer normalizer{typeArena, builtinTypes, NotNull{&unifierState}}; Normalizer normalizer{typeArena, builtinTypes, NotNull{&unifierState}};
if (FFlag::LuauAutocompleteUsesModuleForTypeCompatibility)
if (FFlag::LuauSolverV2)
{ {
TypeCheckLimits limits; if (module.checkedInNewSolver)
TypeFunctionRuntime typeFunctionRuntime{ {
NotNull{&iceReporter}, NotNull{&limits} TypeCheckLimits limits;
}; // TODO: maybe subtyping checks should not invoke user-defined type function runtime TypeFunctionRuntime typeFunctionRuntime{
NotNull{&iceReporter}, NotNull{&limits}
}; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit; unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit; unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
Subtyping subtyping{ Subtyping subtyping{
builtinTypes, NotNull{typeArena}, NotNull{simplifier.get()}, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&iceReporter} builtinTypes,
}; NotNull{typeArena},
NotNull{simplifier.get()},
NotNull{&normalizer},
NotNull{&typeFunctionRuntime},
NotNull{&iceReporter}
};
return subtyping.isSubtype(subTy, superTy, scope).isSubtype; return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
}
else
{
Unifier unifier(NotNull<Normalizer>{&normalizer}, scope, Location(), Variance::Covariant);
// Cost of normalization can be too high for autocomplete response time requirements
unifier.normalize = false;
unifier.checkInhabited = false;
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
return unifier.canUnify(subTy, superTy).empty();
}
} }
else else
{ {
Unifier unifier(NotNull<Normalizer>{&normalizer}, scope, Location(), Variance::Covariant); if (FFlag::LuauSolverV2)
// Cost of normalization can be too high for autocomplete response time requirements
unifier.normalize = false;
unifier.checkInhabited = false;
if (FFlag::LuauAutocompleteUseLimits)
{ {
TypeCheckLimits limits;
TypeFunctionRuntime typeFunctionRuntime{
NotNull{&iceReporter}, NotNull{&limits}
}; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit; unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit; unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
}
return unifier.canUnify(subTy, superTy).empty(); Subtyping subtyping{
builtinTypes,
NotNull{typeArena},
NotNull{simplifier.get()},
NotNull{&normalizer},
NotNull{&typeFunctionRuntime},
NotNull{&iceReporter}
};
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
}
else
{
Unifier unifier(NotNull<Normalizer>{&normalizer}, scope, Location(), Variance::Covariant);
// Cost of normalization can be too high for autocomplete response time requirements
unifier.normalize = false;
unifier.checkInhabited = false;
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
return unifier.canUnify(subTy, superTy).empty();
}
} }
} }
@ -209,10 +258,10 @@ static TypeCorrectKind checkTypeCorrectKind(
TypeId expectedType = follow(*typeAtPosition); TypeId expectedType = follow(*typeAtPosition);
auto checkFunctionType = [typeArena, builtinTypes, moduleScope, &expectedType](const FunctionType* ftv) auto checkFunctionType = [typeArena, builtinTypes, moduleScope, &expectedType, &module](const FunctionType* ftv)
{ {
if (std::optional<TypeId> firstRetTy = first(ftv->retTypes)) if (std::optional<TypeId> firstRetTy = first(ftv->retTypes))
return checkTypeMatch(*firstRetTy, expectedType, moduleScope, typeArena, builtinTypes); return checkTypeMatch(module, *firstRetTy, expectedType, moduleScope, typeArena, builtinTypes);
return false; return false;
}; };
@ -235,7 +284,7 @@ static TypeCorrectKind checkTypeCorrectKind(
} }
} }
return checkTypeMatch(ty, expectedType, moduleScope, typeArena, builtinTypes) ? TypeCorrectKind::Correct : TypeCorrectKind::None; return checkTypeMatch(module, ty, expectedType, moduleScope, typeArena, builtinTypes) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
} }
enum class PropIndexType enum class PropIndexType
@ -286,7 +335,7 @@ static void autocompleteProps(
// When called with '.', but declared with 'self', it is considered invalid if first argument is compatible // When called with '.', but declared with 'self', it is considered invalid if first argument is compatible
if (std::optional<TypeId> firstArgTy = first(ftv->argTypes)) if (std::optional<TypeId> firstArgTy = first(ftv->argTypes))
{ {
if (checkTypeMatch(rootTy, *firstArgTy, NotNull{module.getModuleScope().get()}, typeArena, builtinTypes)) if (checkTypeMatch(module, rootTy, *firstArgTy, NotNull{module.getModuleScope().get()}, typeArena, builtinTypes))
return calledWithSelf; return calledWithSelf;
} }
@ -1714,6 +1763,7 @@ AutocompleteResult autocomplete_(
StringCompletionCallback callback StringCompletionCallback callback
) )
{ {
LUAU_TIMETRACE_SCOPE("Luau::autocomplete_", "AutocompleteCore");
AstNode* node = ancestry.back(); AstNode* node = ancestry.back();
AstExprConstantNil dummy{Location{}}; AstExprConstantNil dummy{Location{}};

View file

@ -3,6 +3,8 @@
#include "Luau/Constraint.h" #include "Luau/Constraint.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
namespace Luau namespace Luau
{ {
@ -111,6 +113,11 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
{ {
rci.traverse(fchc->argsPack); rci.traverse(fchc->argsPack);
} }
else if (auto fcc = get<FunctionCallConstraint>(*this); fcc && FFlag::DebugLuauGreedyGeneralization)
{
rci.traverse(fcc->fn);
rci.traverse(fcc->argsPack);
}
else if (auto ptc = get<PrimitiveTypeConstraint>(*this)) else if (auto ptc = get<PrimitiveTypeConstraint>(*this))
{ {
rci.traverse(ptc->freeType); rci.traverse(ptc->freeType);
@ -118,7 +125,8 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
else if (auto hpc = get<HasPropConstraint>(*this)) else if (auto hpc = get<HasPropConstraint>(*this))
{ {
rci.traverse(hpc->resultType); rci.traverse(hpc->resultType);
// `HasPropConstraints` should not mutate `subjectType`. if (FFlag::DebugLuauGreedyGeneralization)
rci.traverse(hpc->subjectType);
} }
else if (auto hic = get<HasIndexerConstraint>(*this)) else if (auto hic = get<HasIndexerConstraint>(*this))
{ {

View file

@ -32,11 +32,11 @@ LUAU_FASTINT(LuauCheckRecursionLimit)
LUAU_FASTFLAG(DebugLuauLogSolverToJson) LUAU_FASTFLAG(DebugLuauLogSolverToJson)
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(LuauNewSolverPrePopulateClasses)
LUAU_FASTFLAGVARIABLE(LuauNewSolverPopulateTableLocations)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauDeferBidirectionalInferenceForTableAssignment) LUAU_FASTFLAGVARIABLE(LuauDeferBidirectionalInferenceForTableAssignment)
LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments) LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments)
@ -785,9 +785,6 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
} }
else if (auto classDeclaration = stat->as<AstStatDeclareClass>()) else if (auto classDeclaration = stat->as<AstStatDeclareClass>())
{ {
if (!FFlag::LuauNewSolverPrePopulateClasses)
continue;
if (scope->exportedTypeBindings.count(classDeclaration->name.value)) if (scope->exportedTypeBindings.count(classDeclaration->name.value))
{ {
auto it = classDefinitionLocations.find(classDeclaration->name.value); auto it = classDefinitionLocations.find(classDeclaration->name.value);
@ -1384,6 +1381,28 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location); FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
bool sigFullyDefined = !hasFreeType(sig.signature); bool sigFullyDefined = !hasFreeType(sig.signature);
DefId def = dfg->getDef(function->name);
if (FFlag::LuauUngeneralizedTypesForRecursiveFunctions)
{
if (AstExprLocal* localName = function->name->as<AstExprLocal>())
{
sig.bodyScope->bindings[localName->local] = Binding{sig.signature, localName->location};
sig.bodyScope->lvalueTypes[def] = sig.signature;
sig.bodyScope->rvalueRefinements[def] = sig.signature;
}
else if (AstExprGlobal* globalName = function->name->as<AstExprGlobal>())
{
sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location};
sig.bodyScope->lvalueTypes[def] = sig.signature;
sig.bodyScope->rvalueRefinements[def] = sig.signature;
}
else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>())
{
sig.bodyScope->rvalueRefinements[def] = sig.signature;
}
}
checkFunctionBody(sig.bodyScope, function->func); checkFunctionBody(sig.bodyScope, function->func);
Checkpoint end = checkpoint(this); Checkpoint end = checkpoint(this);
@ -1417,7 +1436,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
); );
} }
DefId def = dfg->getDef(function->name);
std::optional<TypeId> existingFunctionTy = follow(lookup(scope, function->name->location, def)); std::optional<TypeId> existingFunctionTy = follow(lookup(scope, function->name->location, def));
if (AstExprLocal* localName = function->name->as<AstExprLocal>()) if (AstExprLocal* localName = function->name->as<AstExprLocal>())
@ -1691,7 +1709,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
{ {
// If a class with the same name was already defined, we skip over // If a class with the same name was already defined, we skip over
auto bindingIt = scope->exportedTypeBindings.find(declaredClass->name.value); auto bindingIt = scope->exportedTypeBindings.find(declaredClass->name.value);
if (FFlag::LuauNewSolverPrePopulateClasses && bindingIt == scope->exportedTypeBindings.end()) if (bindingIt == scope->exportedTypeBindings.end())
return ControlFlow::None; return ControlFlow::None;
std::optional<TypeId> superTy = std::make_optional(builtinTypes->classType); std::optional<TypeId> superTy = std::make_optional(builtinTypes->classType);
@ -1708,10 +1726,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
// We don't have generic classes, so this assertion _should_ never be hit. // We don't have generic classes, so this assertion _should_ never be hit.
LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0); LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
if (FFlag::LuauNewSolverPrePopulateClasses) superTy = follow(lookupType->type);
superTy = follow(lookupType->type);
else
superTy = lookupType->type;
if (!get<ClassType>(follow(*superTy))) if (!get<ClassType>(follow(*superTy)))
{ {
@ -1734,14 +1749,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
ctv->metatable = metaTy; ctv->metatable = metaTy;
TypeId classBindTy = bindingIt->second.type;
if (FFlag::LuauNewSolverPrePopulateClasses) emplaceType<BoundType>(asMutable(classBindTy), classTy);
{
TypeId classBindTy = bindingIt->second.type;
emplaceType<BoundType>(asMutable(classBindTy), classTy);
}
else
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
if (declaredClass->indexer) if (declaredClass->indexer)
{ {
@ -2930,10 +2939,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
ttv->state = TableState::Unsealed; ttv->state = TableState::Unsealed;
ttv->definitionModuleName = module->name; ttv->definitionModuleName = module->name;
if (FFlag::LuauNewSolverPopulateTableLocations) ttv->definitionLocation = expr->location;
{
ttv->definitionLocation = expr->location;
}
ttv->scope = scope.get(); ttv->scope = scope.get();
interiorTypes.back().push_back(ty); interiorTypes.back().push_back(ty);
@ -3230,6 +3236,9 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
if (expectedType && get<FreeType>(*expectedType)) if (expectedType && get<FreeType>(*expectedType))
bindFreeType(*expectedType, actualFunctionType); bindFreeType(*expectedType, actualFunctionType);
if (FFlag::DebugLuauGreedyGeneralization)
scopeToFunction[signatureScope.get()] = actualFunctionType;
return { return {
/* signature */ actualFunctionType, /* signature */ actualFunctionType,
/* signatureScope */ signatureScope, /* signatureScope */ signatureScope,
@ -3392,11 +3401,8 @@ TypeId ConstraintGenerator::resolveTableType(const ScopePtr& scope, AstType* ty,
TypeId tableTy = arena->addType(TableType{props, indexer, scope->level, scope.get(), TableState::Sealed}); TypeId tableTy = arena->addType(TableType{props, indexer, scope->level, scope.get(), TableState::Sealed});
TableType* ttv = getMutable<TableType>(tableTy); TableType* ttv = getMutable<TableType>(tableTy);
if (FFlag::LuauNewSolverPopulateTableLocations) ttv->definitionModuleName = module->name;
{ ttv->definitionLocation = tab->location;
ttv->definitionModuleName = module->name;
ttv->definitionLocation = tab->location;
}
return tableTy; return tableTy;
} }

View file

@ -27,17 +27,16 @@
#include <algorithm> #include <algorithm>
#include <utility> #include <utility>
LUAU_FASTFLAGVARIABLE(DebugLuauAssertOnForcedConstraint)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies)
LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings) LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings)
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500) LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauNewSolverPopulateTableLocations)
LUAU_FASTFLAGVARIABLE(LuauAllowNilAssignmentToIndexer)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauAlwaysFillInFunctionCallDiscriminantTypes)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope) LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAGVARIABLE(LuauPrecalculateMutatedFreeTypes2) LUAU_FASTFLAGVARIABLE(LuauPrecalculateMutatedFreeTypes2)
LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization)
namespace Luau namespace Luau
{ {
@ -328,6 +327,7 @@ ConstraintSolver::ConstraintSolver(
NotNull<TypeFunctionRuntime> typeFunctionRuntime, NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<Scope> rootScope, NotNull<Scope> rootScope,
std::vector<NotNull<Constraint>> constraints, std::vector<NotNull<Constraint>> constraints,
NotNull<DenseHashMap<Scope*, TypeId>> scopeToFunction,
ModuleName moduleName, ModuleName moduleName,
NotNull<ModuleResolver> moduleResolver, NotNull<ModuleResolver> moduleResolver,
std::vector<RequireCycle> requireCycles, std::vector<RequireCycle> requireCycles,
@ -341,6 +341,7 @@ ConstraintSolver::ConstraintSolver(
, simplifier(simplifier) , simplifier(simplifier)
, typeFunctionRuntime(typeFunctionRuntime) , typeFunctionRuntime(typeFunctionRuntime)
, constraints(std::move(constraints)) , constraints(std::move(constraints))
, scopeToFunction(scopeToFunction)
, rootScope(rootScope) , rootScope(rootScope)
, currentModuleName(std::move(moduleName)) , currentModuleName(std::move(moduleName))
, dfg(dfg) , dfg(dfg)
@ -362,6 +363,12 @@ ConstraintSolver::ConstraintSolver(
{ {
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
refCount += 1; refCount += 1;
if (FFlag::DebugLuauGreedyGeneralization)
{
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, DenseHashSet<const Constraint*>{nullptr});
it->second.insert(c.get());
}
} }
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint); maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
} }
@ -453,6 +460,9 @@ void ConstraintSolver::run()
snapshot = logger->prepareStepSnapshot(rootScope, c, force, unsolvedConstraints); snapshot = logger->prepareStepSnapshot(rootScope, c, force, unsolvedConstraints);
} }
if (FFlag::DebugLuauAssertOnForcedConstraint)
LUAU_ASSERT(!force);
bool success = tryDispatch(c, force); bool success = tryDispatch(c, force);
progress |= success; progress |= success;
@ -467,12 +477,21 @@ void ConstraintSolver::run()
const auto maybeMutated = maybeMutatedFreeTypes.find(c); const auto maybeMutated = maybeMutatedFreeTypes.find(c);
if (maybeMutated != maybeMutatedFreeTypes.end()) if (maybeMutated != maybeMutatedFreeTypes.end())
{ {
DenseHashSet<TypeId> seen{nullptr};
for (auto ty : maybeMutated->second) for (auto ty : maybeMutated->second)
{ {
// There is a high chance that this type has been rebound // There is a high chance that this type has been rebound
// across blocked types, rebound free types, pending // across blocked types, rebound free types, pending
// expansion types, etc, so we need to follow it. // expansion types, etc, so we need to follow it.
ty = follow(ty); ty = follow(ty);
if (FFlag::DebugLuauGreedyGeneralization)
{
if (seen.contains(ty))
continue;
seen.insert(ty);
}
size_t& refCount = unresolvedConstraints[ty]; size_t& refCount = unresolvedConstraints[ty];
if (refCount > 0) if (refCount > 0)
refCount -= 1; refCount -= 1;
@ -484,6 +503,9 @@ void ConstraintSolver::run()
// refcount to be 1 or 0. // refcount to be 1 or 0.
if (refCount <= 1) if (refCount <= 1)
unblock(ty, Location{}); unblock(ty, Location{});
if (FFlag::DebugLuauGreedyGeneralization && refCount == 0)
generalizeOneType(ty);
} }
} }
} }
@ -600,16 +622,154 @@ bool ConstraintSolver::isDone() const
return unsolvedConstraints.empty(); return unsolvedConstraints.empty();
} }
namespace struct TypeSearcher : TypeVisitor
{ {
enum struct Polarity: uint8_t
{
None = 0b00,
Positive = 0b01,
Negative = 0b10,
Mixed = 0b11,
};
struct TypeAndLocation TypeId needle;
{ Polarity current = Polarity::Positive;
TypeId typeId;
Location location; Polarity result = Polarity::None;
explicit TypeSearcher(TypeId needle)
: TypeSearcher(needle, Polarity::Positive)
{}
explicit TypeSearcher(TypeId needle, Polarity initialPolarity)
: needle(needle)
, current(initialPolarity)
{}
bool visit(TypeId ty) override
{
if (ty == needle)
result = Polarity(int(result) | int(current));
return true;
}
void flip()
{
switch (current)
{
case Polarity::Positive:
current = Polarity::Negative;
break;
case Polarity::Negative:
current = Polarity::Positive;
break;
default:
break;
}
}
bool visit(TypeId ty, const FunctionType& ft) override
{
flip();
traverse(ft.argTypes);
flip();
traverse(ft.retTypes);
return false;
}
// bool visit(TypeId ty, const TableType& tt) override
// {
// }
bool visit(TypeId ty, const ClassType&) override
{
return false;
}
}; };
} // namespace void ConstraintSolver::generalizeOneType(TypeId ty)
{
ty = follow(ty);
const FreeType* freeTy = get<FreeType>(ty);
std::string saveme = toString(ty, opts);
// Some constraints (like prim) will also replace a free type with something
// concrete. If so, our work is already done.
if (!freeTy)
return;
NotNull<Scope> tyScope{freeTy->scope};
// TODO: If freeTy occurs within the enclosing function's type, we need to
// check to see whether this type should instead be generic.
TypeId newBound = follow(freeTy->upperBound);
TypeId* functionTyPtr = nullptr;
while (true)
{
functionTyPtr = scopeToFunction->find(tyScope);
if (functionTyPtr || !tyScope->parent)
break;
else if (tyScope->parent)
tyScope = NotNull{tyScope->parent.get()};
else
break;
}
if (ty == newBound)
ty = builtinTypes->unknownType;
if (!functionTyPtr)
{
asMutable(ty)->reassign(Type{BoundType{follow(freeTy->upperBound)}});
}
else
{
const TypeId functionTy = follow(*functionTyPtr);
FunctionType* const function = getMutable<FunctionType>(functionTy);
LUAU_ASSERT(function);
TypeSearcher ts{ty};
ts.traverse(functionTy);
const TypeId upperBound = follow(freeTy->upperBound);
const TypeId lowerBound = follow(freeTy->lowerBound);
switch (ts.result)
{
case TypeSearcher::Polarity::None:
asMutable(ty)->reassign(Type{BoundType{upperBound}});
break;
case TypeSearcher::Polarity::Negative:
case TypeSearcher::Polarity::Mixed:
if (get<UnknownType>(upperBound))
{
asMutable(ty)->reassign(Type{GenericType{tyScope}});
function->generics.emplace_back(ty);
}
else
asMutable(ty)->reassign(Type{BoundType{upperBound}});
break;
case TypeSearcher::Polarity::Positive:
if (get<UnknownType>(lowerBound))
{
asMutable(ty)->reassign(Type{GenericType{tyScope}});
function->generics.emplace_back(ty);
}
else
asMutable(ty)->reassign(Type{BoundType{lowerBound}});
break;
}
}
}
void ConstraintSolver::bind(NotNull<const Constraint> constraint, TypeId ty, TypeId boundTo) void ConstraintSolver::bind(NotNull<const Constraint> constraint, TypeId ty, TypeId boundTo)
{ {
@ -1170,12 +1330,9 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
target = follow(instantiated); target = follow(instantiated);
} }
if (FFlag::LuauNewSolverPopulateTableLocations) // This is a new type - redefine the location.
{ ttv->definitionLocation = constraint->location;
// This is a new type - redefine the location. ttv->definitionModuleName = currentModuleName;
ttv->definitionLocation = constraint->location;
ttv->definitionModuleName = currentModuleName;
}
ttv->instantiatedTypeParams = typeArguments; ttv->instantiatedTypeParams = typeArguments;
ttv->instantiatedTypePackParams = packArguments; ttv->instantiatedTypePackParams = packArguments;
@ -1222,8 +1379,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
{ {
emplaceTypePack<BoundTypePack>(asMutable(c.result), builtinTypes->anyTypePack); emplaceTypePack<BoundTypePack>(asMutable(c.result), builtinTypes->anyTypePack);
unblock(c.result, constraint->location); unblock(c.result, constraint->location);
if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes) fillInDiscriminantTypes(constraint, c.discriminantTypes);
fillInDiscriminantTypes(constraint, c.discriminantTypes);
return true; return true;
} }
@ -1231,16 +1387,14 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
if (get<ErrorType>(fn)) if (get<ErrorType>(fn))
{ {
bind(constraint, c.result, builtinTypes->errorRecoveryTypePack()); bind(constraint, c.result, builtinTypes->errorRecoveryTypePack());
if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes) fillInDiscriminantTypes(constraint, c.discriminantTypes);
fillInDiscriminantTypes(constraint, c.discriminantTypes);
return true; return true;
} }
if (get<NeverType>(fn)) if (get<NeverType>(fn))
{ {
bind(constraint, c.result, builtinTypes->neverTypePack); bind(constraint, c.result, builtinTypes->neverTypePack);
if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes) fillInDiscriminantTypes(constraint, c.discriminantTypes);
fillInDiscriminantTypes(constraint, c.discriminantTypes);
return true; return true;
} }
@ -1321,30 +1475,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
emplace<FreeTypePack>(constraint, c.result, constraint->scope); emplace<FreeTypePack>(constraint, c.result, constraint->scope);
} }
if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes) fillInDiscriminantTypes(constraint, c.discriminantTypes);
{
fillInDiscriminantTypes(constraint, c.discriminantTypes);
}
else
{
// NOTE: This is the body of the `fillInDiscriminantTypes` helper.
for (std::optional<TypeId> ty : c.discriminantTypes)
{
if (!ty)
continue;
// If the discriminant type has been transmuted, we need to unblock them.
if (!isBlocked(*ty))
{
unblock(*ty, constraint->location);
continue;
}
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
}
}
OverloadResolver resolver{ OverloadResolver resolver{
builtinTypes, builtinTypes,
@ -1912,7 +2043,7 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
bind( bind(
constraint, constraint,
c.propType, c.propType,
isIndex && FFlag::LuauAllowNilAssignmentToIndexer ? arena->addType(UnionType{{propTy, builtinTypes->nilType}}) : propTy isIndex ? arena->addType(UnionType{{propTy, builtinTypes->nilType}}) : propTy
); );
unify(constraint, rhsType, propTy); unify(constraint, rhsType, propTy);
return true; return true;
@ -2010,8 +2141,7 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
bind( bind(
constraint, constraint,
c.propType, c.propType,
FFlag::LuauAllowNilAssignmentToIndexer ? arena->addType(UnionType{{lhsTable->indexer->indexResultType, builtinTypes->nilType}}) arena->addType(UnionType{{lhsTable->indexer->indexResultType, builtinTypes->nilType}})
: lhsTable->indexer->indexResultType
); );
return true; return true;
} }
@ -2064,8 +2194,7 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
bind( bind(
constraint, constraint,
c.propType, c.propType,
FFlag::LuauAllowNilAssignmentToIndexer ? arena->addType(UnionType{{lhsClass->indexer->indexResultType, builtinTypes->nilType}}) arena->addType(UnionType{{lhsClass->indexer->indexResultType, builtinTypes->nilType}})
: lhsClass->indexer->indexResultType
); );
return true; return true;
} }
@ -3113,6 +3242,24 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target)
auto [targetRefs, _] = unresolvedConstraints.try_insert(target, 0); auto [targetRefs, _] = unresolvedConstraints.try_insert(target, 0);
targetRefs += count; targetRefs += count;
// Any constraint that might have mutated source may now mutate target
if (FFlag::DebugLuauGreedyGeneralization)
{
auto it = mutatedFreeTypeToConstraint.find(source);
if (it != mutatedFreeTypeToConstraint.end())
{
auto [it2, fresh] = mutatedFreeTypeToConstraint.try_emplace(target, DenseHashSet<const Constraint*>{nullptr});
for (const Constraint* constraint : it->second)
{
it2->second.insert(constraint);
auto [it3, fresh2] = maybeMutatedFreeTypes.try_emplace(NotNull{constraint}, DenseHashSet<TypeId>{nullptr});
it3->second.insert(target);
}
}
}
} }
std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope, TypeId type, bool avoidSealingTables) std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope, TypeId type, bool avoidSealingTables)

View file

@ -1,8 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauBufferBitMethods2)
LUAU_FASTFLAG(LuauVector2Constructor)
LUAU_FASTFLAGVARIABLE(LuauDebugInfoDefn) LUAU_FASTFLAGVARIABLE(LuauDebugInfoDefn)
namespace Luau namespace Luau
@ -239,37 +237,6 @@ declare utf8: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionBufferSrc_DEPRECATED = R"BUILTIN_SRC(
--- Buffer API
declare buffer: {
create: @checked (size: number) -> buffer,
fromstring: @checked (str: string) -> buffer,
tostring: @checked (b: buffer) -> string,
len: @checked (b: buffer) -> number,
copy: @checked (target: buffer, targetOffset: number, source: buffer, sourceOffset: number?, count: number?) -> (),
fill: @checked (b: buffer, offset: number, value: number, count: number?) -> (),
readi8: @checked (b: buffer, offset: number) -> number,
readu8: @checked (b: buffer, offset: number) -> number,
readi16: @checked (b: buffer, offset: number) -> number,
readu16: @checked (b: buffer, offset: number) -> number,
readi32: @checked (b: buffer, offset: number) -> number,
readu32: @checked (b: buffer, offset: number) -> number,
readf32: @checked (b: buffer, offset: number) -> number,
readf64: @checked (b: buffer, offset: number) -> number,
writei8: @checked (b: buffer, offset: number, value: number) -> (),
writeu8: @checked (b: buffer, offset: number, value: number) -> (),
writei16: @checked (b: buffer, offset: number, value: number) -> (),
writeu16: @checked (b: buffer, offset: number, value: number) -> (),
writei32: @checked (b: buffer, offset: number, value: number) -> (),
writeu32: @checked (b: buffer, offset: number, value: number) -> (),
writef32: @checked (b: buffer, offset: number, value: number) -> (),
writef64: @checked (b: buffer, offset: number, value: number) -> (),
readstring: @checked (b: buffer, offset: number, count: number) -> string,
writestring: @checked (b: buffer, offset: number, value: string, count: number?) -> (),
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionBufferSrc = R"BUILTIN_SRC( static const std::string kBuiltinDefinitionBufferSrc = R"BUILTIN_SRC(
--- Buffer API --- Buffer API
declare buffer: { declare buffer: {
@ -303,36 +270,6 @@ declare buffer: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionVectorSrc_NoVector2Ctor_DEPRECATED = R"BUILTIN_SRC(
-- While vector would have been better represented as a built-in primitive type, type solver class handling covers most of the properties
declare class vector
x: number
y: number
z: number
end
declare vector: {
create: @checked (x: number, y: number, z: number) -> vector,
magnitude: @checked (vec: vector) -> number,
normalize: @checked (vec: vector) -> vector,
cross: @checked (vec1: vector, vec2: vector) -> vector,
dot: @checked (vec1: vector, vec2: vector) -> number,
angle: @checked (vec1: vector, vec2: vector, axis: vector?) -> number,
floor: @checked (vec: vector) -> vector,
ceil: @checked (vec: vector) -> vector,
abs: @checked (vec: vector) -> vector,
sign: @checked (vec: vector) -> vector,
clamp: @checked (vec: vector, min: vector, max: vector) -> vector,
max: @checked (vector, ...vector) -> vector,
min: @checked (vector, ...vector) -> vector,
zero: vector,
one: vector,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionVectorSrc = R"BUILTIN_SRC( static const std::string kBuiltinDefinitionVectorSrc = R"BUILTIN_SRC(
-- While vector would have been better represented as a built-in primitive type, type solver class handling covers most of the properties -- While vector would have been better represented as a built-in primitive type, type solver class handling covers most of the properties
@ -374,13 +311,8 @@ std::string getBuiltinDefinitionSource()
result += kBuiltinDefinitionTableSrc; result += kBuiltinDefinitionTableSrc;
result += FFlag::LuauDebugInfoDefn ? kBuiltinDefinitionDebugSrc : kBuiltinDefinitionDebugSrc_DEPRECATED; result += FFlag::LuauDebugInfoDefn ? kBuiltinDefinitionDebugSrc : kBuiltinDefinitionDebugSrc_DEPRECATED;
result += kBuiltinDefinitionUtf8Src; result += kBuiltinDefinitionUtf8Src;
result += kBuiltinDefinitionBufferSrc;
result += FFlag::LuauBufferBitMethods2 ? kBuiltinDefinitionBufferSrc : kBuiltinDefinitionBufferSrc_DEPRECATED; result += kBuiltinDefinitionVectorSrc;
if (FFlag::LuauVector2Constructor)
result += kBuiltinDefinitionVectorSrc;
else
result += kBuiltinDefinitionVectorSrc_NoVector2Ctor_DEPRECATED;
return result; return result;
} }

View file

@ -26,7 +26,6 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit); LUAU_FASTINT(LuauTypeInferRecursionLimit);
LUAU_FASTINT(LuauTypeInferIterationLimit); LUAU_FASTINT(LuauTypeInferIterationLimit);
LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauAllowFragmentParsing);
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete) LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteBugfixes) LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteBugfixes)
@ -98,6 +97,7 @@ void cloneAndSquashScopes(
Scope* destScope Scope* destScope
) )
{ {
LUAU_TIMETRACE_SCOPE("Luau::cloneAndSquashScopes", "FragmentAutocomplete");
std::vector<const Scope*> scopes; std::vector<const Scope*> scopes;
for (const Scope* current = staleScope; current; current = current->parent.get()) for (const Scope* current = staleScope; current; current = current->parent.get())
{ {
@ -364,6 +364,7 @@ std::optional<FragmentParseResult> parseFragment(
ModulePtr cloneModule(CloneState& cloneState, const ModulePtr& source, std::unique_ptr<Allocator> alloc) ModulePtr cloneModule(CloneState& cloneState, const ModulePtr& source, std::unique_ptr<Allocator> alloc)
{ {
LUAU_TIMETRACE_SCOPE("Luau::cloneModule", "FragmentAutocomplete");
freeze(source->internalTypes); freeze(source->internalTypes);
freeze(source->interfaceTypes); freeze(source->interfaceTypes);
ModulePtr incremental = std::make_shared<Module>(); ModulePtr incremental = std::make_shared<Module>();
@ -445,6 +446,8 @@ FragmentTypeCheckResult typecheckFragment_(
const FrontendOptions& opts const FrontendOptions& opts
) )
{ {
LUAU_TIMETRACE_SCOPE("Luau::typecheckFragment_", "FragmentAutocomplete");
freeze(stale->internalTypes); freeze(stale->internalTypes);
freeze(stale->interfaceTypes); freeze(stale->interfaceTypes);
CloneState cloneState{frontend.builtinTypes}; CloneState cloneState{frontend.builtinTypes};
@ -533,6 +536,7 @@ FragmentTypeCheckResult typecheckFragment_(
NotNull{&typeFunctionRuntime}, NotNull{&typeFunctionRuntime},
NotNull(cg.rootScope), NotNull(cg.rootScope),
borrowConstraints(cg.constraints), borrowConstraints(cg.constraints),
NotNull{&cg.scopeToFunction},
incrementalModule->name, incrementalModule->name,
NotNull{&resolver}, NotNull{&resolver},
{}, {},
@ -573,6 +577,8 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
std::optional<Position> fragmentEndPosition std::optional<Position> fragmentEndPosition
) )
{ {
LUAU_TIMETRACE_SCOPE("Luau::typecheckFragment", "FragmentAutocomplete");
LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str());
if (FFlag::LuauBetterReverseDependencyTracking) if (FFlag::LuauBetterReverseDependencyTracking)
{ {
@ -621,6 +627,35 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
return {FragmentTypeCheckStatus::Success, result}; return {FragmentTypeCheckStatus::Success, result};
} }
FragmentAutocompleteStatusResult tryFragmentAutocomplete(
Frontend& frontend,
const ModuleName& moduleName,
Position cursorPosition,
FragmentContext context,
StringCompletionCallback stringCompletionCB
)
{
// TODO: we should calculate fragmentEnd position here, by using context.newAstRoot and cursorPosition
try
{
Luau::FragmentAutocompleteResult fragmentAutocomplete = Luau::fragmentAutocomplete(
frontend,
context.newSrc,
moduleName,
cursorPosition,
context.opts,
std::move(stringCompletionCB),
context.DEPRECATED_fragmentEndPosition
);
return {FragmentAutocompleteStatus::Success, std::move(fragmentAutocomplete)};
}
catch (const Luau::InternalCompilerError& e)
{
if (FFlag::LogFragmentsFromAutocomplete)
logLuau(e.what());
return {FragmentAutocompleteStatus::InternalIce, std::nullopt};
}
}
FragmentAutocompleteResult fragmentAutocomplete( FragmentAutocompleteResult fragmentAutocomplete(
Frontend& frontend, Frontend& frontend,
@ -632,8 +667,9 @@ FragmentAutocompleteResult fragmentAutocomplete(
std::optional<Position> fragmentEndPosition std::optional<Position> fragmentEndPosition
) )
{ {
LUAU_ASSERT(FFlag::LuauAllowFragmentParsing);
LUAU_ASSERT(FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete); LUAU_ASSERT(FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
LUAU_TIMETRACE_SCOPE("Luau::fragmentAutocomplete", "FragmentAutocomplete");
LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str());
const SourceModule* sourceModule = frontend.getSourceModule(moduleName); const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
if (!sourceModule) if (!sourceModule)

View file

@ -1481,6 +1481,7 @@ ModulePtr check(
NotNull{&typeFunctionRuntime}, NotNull{&typeFunctionRuntime},
NotNull(cg.rootScope), NotNull(cg.rootScope),
borrowConstraints(cg.constraints), borrowConstraints(cg.constraints),
NotNull{&cg.scopeToFunction},
result->name, result->name,
moduleResolver, moduleResolver,
requireCycles, requireCycles,

View file

@ -19,7 +19,6 @@
#include <iostream> #include <iostream>
#include <iterator> #include <iterator>
LUAU_FASTFLAGVARIABLE(LuauCountSelfCallsNonstrict)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements) LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals)
@ -628,17 +627,6 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstExprCall* call) NonStrictContext visit(AstExprCall* call)
{ {
if (FFlag::LuauCountSelfCallsNonstrict)
return visitCall(call);
else
return visitCall_DEPRECATED(call);
}
// rename this to `visit` when `FFlag::LuauCountSelfCallsNonstrict` is removed, and clean up above `visit`.
NonStrictContext visitCall(AstExprCall* call)
{
LUAU_ASSERT(FFlag::LuauCountSelfCallsNonstrict);
NonStrictContext fresh{}; NonStrictContext fresh{};
TypeId* originalCallTy = module->astOriginalCallTypes.find(call->func); TypeId* originalCallTy = module->astOriginalCallTypes.find(call->func);
if (!originalCallTy) if (!originalCallTy)
@ -747,109 +735,6 @@ struct NonStrictTypeChecker
return fresh; return fresh;
} }
// Remove with `FFlag::LuauCountSelfCallsNonstrict` clean up.
NonStrictContext visitCall_DEPRECATED(AstExprCall* call)
{
LUAU_ASSERT(!FFlag::LuauCountSelfCallsNonstrict);
NonStrictContext fresh{};
TypeId* originalCallTy = module->astOriginalCallTypes.find(call->func);
if (!originalCallTy)
return fresh;
TypeId fnTy = *originalCallTy;
if (auto fn = get<FunctionType>(follow(fnTy)))
{
if (fn->isCheckedFunction)
{
// We know fn is a checked function, which means it looks like:
// (S1, ... SN) -> T &
// (~S1, unknown^N-1) -> error &
// (unknown, ~S2, unknown^N-2) -> error
// ...
// ...
// (unknown^N-1, ~S_N) -> error
std::vector<TypeId> argTypes;
argTypes.reserve(call->args.size);
// Pad out the arg types array with the types you would expect to see
TypePackIterator curr = begin(fn->argTypes);
TypePackIterator fin = end(fn->argTypes);
while (curr != fin)
{
argTypes.push_back(*curr);
++curr;
}
if (auto argTail = curr.tail())
{
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*argTail)))
{
while (argTypes.size() < call->args.size)
{
argTypes.push_back(vtp->ty);
}
}
}
std::string functionName = getFunctionNameAsString(*call->func).value_or("");
if (call->args.size > argTypes.size())
{
// We are passing more arguments than we expect, so we should error
reportError(CheckedFunctionIncorrectArgs{functionName, argTypes.size(), call->args.size}, call->location);
return fresh;
}
for (size_t i = 0; i < call->args.size; i++)
{
// For example, if the arg is "hi"
// The actual arg type is string
// The expected arg type is number
// The type of the argument in the overload is ~number
// We will compare arg and ~number
AstExpr* arg = call->args.data[i];
TypeId expectedArgType = argTypes[i];
std::shared_ptr<const NormalizedType> norm = normalizer.normalize(expectedArgType);
DefId def = dfg->getDef(arg);
TypeId runTimeErrorTy;
// If we're dealing with any, negating any will cause all subtype tests to fail
// However, when someone calls this function, they're going to want to be able to pass it anything,
// for that reason, we manually inject never into the context so that the runtime test will always pass.
if (!norm)
reportError(NormalizationTooComplex{}, arg->location);
if (norm && get<AnyType>(norm->tops))
runTimeErrorTy = builtinTypes->neverType;
else
runTimeErrorTy = getOrCreateNegation(expectedArgType);
fresh.addContext(def, runTimeErrorTy);
}
// Populate the context and now iterate through each of the arguments to the call to find out if we satisfy the types
for (size_t i = 0; i < call->args.size; i++)
{
AstExpr* arg = call->args.data[i];
if (auto runTimeFailureType = willRunTimeError(arg, fresh))
reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, functionName, i}, arg->location);
}
if (call->args.size < argTypes.size())
{
// We are passing fewer arguments than we expect
// so we need to ensure that the rest of the args are optional.
bool remainingArgsOptional = true;
for (size_t i = call->args.size; i < argTypes.size(); i++)
remainingArgsOptional = remainingArgsOptional && isOptional(argTypes[i]);
if (!remainingArgsOptional)
{
reportError(CheckedFunctionIncorrectArgs{functionName, argTypes.size(), call->args.size}, call->location);
return fresh;
}
}
}
}
return fresh;
}
NonStrictContext visit(AstExprIndexName* indexName, ValueContext context) NonStrictContext visit(AstExprIndexName* indexName, ValueContext context)
{ {
if (FFlag::LuauNonStrictVisitorImprovements) if (FFlag::LuauNonStrictVisitorImprovements)

View file

@ -12,7 +12,7 @@
LUAU_FASTFLAG(LuauStoreCSTData) LUAU_FASTFLAG(LuauStoreCSTData)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon) LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAG(LuauAstTypeGroup) LUAU_FASTFLAG(LuauAstTypeGroup2)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation) LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
namespace namespace
@ -330,7 +330,7 @@ struct Printer_DEPRECATED
else if (typeCount == 1) else if (typeCount == 1)
{ {
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>()); bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
if (FFlag::LuauAstTypeGroup ? shouldParenthesize : unconditionallyParenthesize) if (FFlag::LuauAstTypeGroup2 ? shouldParenthesize : unconditionallyParenthesize)
writer.symbol("("); writer.symbol("(");
// Only variadic tail // Only variadic tail
@ -343,7 +343,7 @@ struct Printer_DEPRECATED
visualizeTypeAnnotation(*list.types.data[0]); visualizeTypeAnnotation(*list.types.data[0]);
} }
if (FFlag::LuauAstTypeGroup ? shouldParenthesize : unconditionallyParenthesize) if (FFlag::LuauAstTypeGroup2 ? shouldParenthesize : unconditionallyParenthesize)
writer.symbol(")"); writer.symbol(")");
} }
else else
@ -1349,7 +1349,7 @@ struct Printer
else if (typeCount == 1) else if (typeCount == 1)
{ {
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>()); bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
if (FFlag::LuauAstTypeGroup ? shouldParenthesize : unconditionallyParenthesize) if (FFlag::LuauAstTypeGroup2 ? shouldParenthesize : unconditionallyParenthesize)
writer.symbol("("); writer.symbol("(");
// Only variadic tail // Only variadic tail
@ -1362,7 +1362,7 @@ struct Printer
visualizeTypeAnnotation(*list.types.data[0]); visualizeTypeAnnotation(*list.types.data[0]);
} }
if (FFlag::LuauAstTypeGroup ? shouldParenthesize : unconditionallyParenthesize) if (FFlag::LuauAstTypeGroup2 ? shouldParenthesize : unconditionallyParenthesize)
writer.symbol(")"); writer.symbol(")");
} }
else else
@ -2599,6 +2599,7 @@ struct Printer
{ {
writer.symbol("("); writer.symbol("(");
visualizeTypeAnnotation(*a->type); visualizeTypeAnnotation(*a->type);
advance(Position{a->location.end.line, a->location.end.column - 1});
writer.symbol(")"); writer.symbol(")");
} }
else if (const auto& a = typeAnnotation.as<AstTypeSingletonBool>()) else if (const auto& a = typeAnnotation.as<AstTypeSingletonBool>())

View file

@ -29,7 +29,6 @@
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAGVARIABLE(LuauTableKeysAreRValues)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
namespace Luau namespace Luau
@ -1848,16 +1847,8 @@ void TypeChecker2::visit(AstExprTable* expr)
{ {
for (const AstExprTable::Item& item : expr->items) for (const AstExprTable::Item& item : expr->items)
{ {
if (FFlag::LuauTableKeysAreRValues) if (item.key)
{ visit(item.key, ValueContext::RValue);
if (item.key)
visit(item.key, ValueContext::RValue);
}
else
{
if (item.key)
visit(item.key, ValueContext::LValue);
}
visit(item.value, ValueContext::RValue); visit(item.value, ValueContext::RValue);
} }
} }

View file

@ -51,6 +51,8 @@ LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions)
LUAU_FASTFLAGVARIABLE(LuauClipNestedAndRecursiveUnion) LUAU_FASTFLAGVARIABLE(LuauClipNestedAndRecursiveUnion)
LUAU_FASTFLAGVARIABLE(LuauDoNotGeneralizeInTypeFunctions) LUAU_FASTFLAGVARIABLE(LuauDoNotGeneralizeInTypeFunctions)
LUAU_FASTFLAGVARIABLE(LuauPreventReentrantTypeFunctionReduction) LUAU_FASTFLAGVARIABLE(LuauPreventReentrantTypeFunctionReduction)
LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil)
LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement)
namespace Luau namespace Luau
{ {
@ -2005,17 +2007,9 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
} }
else else
{ {
/* HACK: Refinements sometimes produce a type T & ~any under the assumption if (FFlag::LuauSkipNoRefineDuringRefinement)
* that ~any is the same as any. This is so so weird, but refinements needs if (get<NoRefineType>(discriminant))
* some way to say "I may refine this, but I'm not sure." return {target, {}};
*
* It does this by refining on a blocked type and deferring the decision
* until it is unblocked.
*
* Refinements also get negated, so we wind up with types like T & ~*blocked*
*
* We need to treat T & ~any as T in this case.
*/
if (auto nt = get<NegationType>(discriminant)) if (auto nt = get<NegationType>(discriminant))
{ {
if (get<NoRefineType>(follow(nt->ty))) if (get<NoRefineType>(follow(nt->ty)))
@ -2292,8 +2286,20 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
continue; continue;
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty); SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty);
if (!result.blockedTypes.empty())
return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}}; if (FFlag::LuauIntersectNotNil)
{
for (TypeId blockedType : result.blockedTypes)
{
if (!get<GenericType>(blockedType))
return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
}
}
else
{
if (!result.blockedTypes.empty())
return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
}
resultTy = result.result; resultTy = result.result;
} }

View file

@ -18,6 +18,7 @@ LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_FASTFLAGVARIABLE(LuauTypeFunSingletonEquality) LUAU_FASTFLAGVARIABLE(LuauTypeFunSingletonEquality)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypeofReturnsType) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypeofReturnsType)
LUAU_FASTFLAGVARIABLE(LuauTypeFunPrintFix) LUAU_FASTFLAGVARIABLE(LuauTypeFunPrintFix)
LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents)
namespace Luau namespace Luau
{ {
@ -1110,7 +1111,7 @@ static int getFunctionGenerics(lua_State* L)
// Luau: `self:parent() -> type` // Luau: `self:parent() -> type`
// Returns the parent of a class type // Returns the parent of a class type
static int getClassParent(lua_State* L) static int getClassParent_DEPRECATED(lua_State* L)
{ {
int argumentCount = lua_gettop(L); int argumentCount = lua_gettop(L);
if (argumentCount != 1) if (argumentCount != 1)
@ -1122,10 +1123,54 @@ static int getClassParent(lua_State* L)
luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str()); luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str());
// If the parent does not exist, we should return nil // If the parent does not exist, we should return nil
if (!tfct->parent) if (!tfct->parent_DEPRECATED)
lua_pushnil(L); lua_pushnil(L);
else else
allocTypeUserData(L, (*tfct->parent)->type); allocTypeUserData(L, (*tfct->parent_DEPRECATED)->type);
return 1;
}
// Luau: `self:readparent() -> type`
// Returns the read type of the class' parent
static int getReadParent(lua_State* L)
{
int argumentCount = lua_gettop(L);
if (argumentCount != 1)
luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount);
TypeFunctionTypeId self = getTypeUserData(L, 1);
auto tfct = get<TypeFunctionClassType>(self);
if (!tfct)
luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str());
// If the parent does not exist, we should return nil
if (!tfct->readParent)
lua_pushnil(L);
else
allocTypeUserData(L, (*tfct->readParent)->type);
return 1;
}
//
// Luau: `self:writeparent() -> type`
// Returns the write type of the class' parent
static int getWriteParent(lua_State* L)
{
int argumentCount = lua_gettop(L);
if (argumentCount != 1)
luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount);
TypeFunctionTypeId self = getTypeUserData(L, 1);
auto tfct = get<TypeFunctionClassType>(self);
if (!tfct)
luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str());
// If the parent does not exist, we should return nil
if (!tfct->writeParent)
lua_pushnil(L);
else
allocTypeUserData(L, (*tfct->writeParent)->type);
return 1; return 1;
} }
@ -1553,7 +1598,7 @@ void registerTypeUserData(lua_State* L)
{"components", getComponents}, {"components", getComponents},
// Class type methods // Class type methods
{"parent", getClassParent}, {FFlag::LuauTypeFunReadWriteParents ? "readparent" : "parent", FFlag::LuauTypeFunReadWriteParents ? getReadParent : getClassParent_DEPRECATED},
// Function type methods (cont.) // Function type methods (cont.)
{"setgenerics", setFunctionGenerics}, {"setgenerics", setFunctionGenerics},
@ -1563,6 +1608,9 @@ void registerTypeUserData(lua_State* L)
{"name", getGenericName}, {"name", getGenericName},
{"ispack", getGenericIsPack}, {"ispack", getGenericIsPack},
// move this under Class type methods when removing FFlagLuauTypeFunReadWriteParents
{FFlag::LuauTypeFunReadWriteParents ? "writeparent" : nullptr, FFlag::LuauTypeFunReadWriteParents ? getWriteParent : nullptr},
{nullptr, nullptr} {nullptr, nullptr}
}; };

View file

@ -20,6 +20,7 @@
// 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) LUAU_FASTFLAG(LuauTypeFunFixHydratedClasses)
LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
namespace Luau namespace Luau
{ {
@ -212,13 +213,13 @@ private:
{ {
// Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the // 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 // original class
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, ty}); target = typeFunctionRuntime->typeArena.allocate(TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty});
} }
else else
{ {
state->classesSerialized_DEPRECATED[c->name] = ty; state->classesSerialized_DEPRECATED[c->name] = ty;
target = typeFunctionRuntime->typeArena.allocate( target = typeFunctionRuntime->typeArena.allocate(
TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, /* classTy */ nullptr, c->name} TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, /* classTy */ nullptr, c->name}
); );
} }
} }
@ -441,7 +442,20 @@ private:
c2->metatable = shallowSerialize(*c1->metatable); c2->metatable = shallowSerialize(*c1->metatable);
if (c1->parent) if (c1->parent)
c2->parent = shallowSerialize(*c1->parent); {
TypeFunctionTypeId parent = shallowSerialize(*c1->parent);
if (FFlag::LuauTypeFunReadWriteParents)
{
// we don't yet have read/write parents in the type inference engine.
c2->readParent = parent;
c2->writeParent = parent;
}
else
{
c2->parent_DEPRECATED = parent;
}
}
} }
void serializeChildren(const GenericType* g1, TypeFunctionGenericType* g2) void serializeChildren(const GenericType* g1, TypeFunctionGenericType* g2)

View file

@ -14,7 +14,7 @@
#include <type_traits> #include <type_traits>
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAGVARIABLE(LuauDisableNewSolverAssertsInMixedMode);
// Maximum number of steps to follow when traversing a path. May not always // Maximum number of steps to follow when traversing a path. May not always
// equate to the number of components in a path, depending on the traversal // equate to the number of components in a path, depending on the traversal
// logic. // logic.
@ -156,14 +156,16 @@ Path PathBuilder::build()
PathBuilder& PathBuilder::readProp(std::string name) PathBuilder& PathBuilder::readProp(std::string name)
{ {
LUAU_ASSERT(FFlag::LuauSolverV2); if (!FFlag::LuauDisableNewSolverAssertsInMixedMode)
LUAU_ASSERT(FFlag::LuauSolverV2);
components.push_back(Property{std::move(name), true}); components.push_back(Property{std::move(name), true});
return *this; return *this;
} }
PathBuilder& PathBuilder::writeProp(std::string name) PathBuilder& PathBuilder::writeProp(std::string name)
{ {
LUAU_ASSERT(FFlag::LuauSolverV2); if (!FFlag::LuauDisableNewSolverAssertsInMixedMode)
LUAU_ASSERT(FFlag::LuauSolverV2);
components.push_back(Property{std::move(name), false}); components.push_back(Property{std::move(name), false});
return *this; return *this;
} }

View file

@ -14,7 +14,7 @@ LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete); LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete);
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope); LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope);
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
namespace Luau namespace Luau
{ {
@ -550,7 +550,10 @@ std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMa
void trackInteriorFreeType(Scope* scope, TypeId ty) void trackInteriorFreeType(Scope* scope, TypeId ty)
{ {
LUAU_ASSERT(FFlag::LuauSolverV2 && FFlag::LuauTrackInteriorFreeTypesOnScope); if (FFlag::LuauDisableNewSolverAssertsInMixedMode)
LUAU_ASSERT(FFlag::LuauTrackInteriorFreeTypesOnScope);
else
LUAU_ASSERT(FFlag::LuauSolverV2 && FFlag::LuauTrackInteriorFreeTypesOnScope);
for (; scope; scope = scope->parent.get()) for (; scope; scope = scope->parent.get())
{ {
if (scope->interiorFreeTypes) if (scope->interiorFreeTypes)

View file

@ -33,38 +33,20 @@ struct PromoteTypeLevels final : TypeOnceVisitor
const TypeArena* typeArena = nullptr; const TypeArena* typeArena = nullptr;
TypeLevel minLevel; TypeLevel minLevel;
Scope* outerScope = nullptr; PromoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel)
bool useScopes;
PromoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, Scope* outerScope, bool useScopes)
: log(log) : log(log)
, typeArena(typeArena) , typeArena(typeArena)
, minLevel(minLevel) , minLevel(minLevel)
, outerScope(outerScope)
, useScopes(useScopes)
{ {
} }
template<typename TID, typename T> template<typename TID, typename T>
void promote(TID ty, T* t) void promote(TID ty, T* t)
{ {
if (useScopes && !t)
return;
LUAU_ASSERT(t); LUAU_ASSERT(t);
if (useScopes) if (minLevel.subsumesStrict(t->level))
{ log.changeLevel(ty, minLevel);
if (subsumesStrict(outerScope, t->scope))
log.changeScope(ty, NotNull{outerScope});
}
else
{
if (minLevel.subsumesStrict(t->level))
{
log.changeLevel(ty, minLevel);
}
}
} }
bool visit(TypeId ty) override bool visit(TypeId ty) override
@ -141,23 +123,23 @@ struct PromoteTypeLevels final : TypeOnceVisitor
} }
}; };
static void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, Scope* outerScope, bool useScopes, TypeId ty) static void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypeId ty)
{ {
// Type levels of types from other modules are already global, so we don't need to promote anything inside // Type levels of types from other modules are already global, so we don't need to promote anything inside
if (ty->owningArena != typeArena) if (ty->owningArena != typeArena)
return; return;
PromoteTypeLevels ptl{log, typeArena, minLevel, outerScope, useScopes}; PromoteTypeLevels ptl{log, typeArena, minLevel};
ptl.traverse(ty); ptl.traverse(ty);
} }
void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, Scope* outerScope, bool useScopes, TypePackId tp) void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypePackId tp)
{ {
// Type levels of types from other modules are already global, so we don't need to promote anything inside // Type levels of types from other modules are already global, so we don't need to promote anything inside
if (tp->owningArena != typeArena) if (tp->owningArena != typeArena)
return; return;
PromoteTypeLevels ptl{log, typeArena, minLevel, outerScope, useScopes}; PromoteTypeLevels ptl{log, typeArena, minLevel};
ptl.traverse(tp); ptl.traverse(tp);
} }
@ -370,12 +352,9 @@ static std::optional<std::pair<Luau::Name, const SingletonType*>> getTableMatchT
} }
template<typename TY_A, typename TY_B> template<typename TY_A, typename TY_B>
static bool subsumes(bool useScopes, TY_A* left, TY_B* right) static bool subsumes(TY_A* left, TY_B* right)
{ {
if (useScopes) return left->level.subsumes(right->level);
return subsumes(left->scope, right->scope);
else
return left->level.subsumes(right->level);
} }
TypeMismatch::Context Unifier::mismatchContext() TypeMismatch::Context Unifier::mismatchContext()
@ -464,7 +443,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
auto superFree = log.getMutable<FreeType>(superTy); auto superFree = log.getMutable<FreeType>(superTy);
auto subFree = log.getMutable<FreeType>(subTy); auto subFree = log.getMutable<FreeType>(subTy);
if (superFree && subFree && subsumes(useNewSolver, superFree, subFree)) if (superFree && subFree && subsumes(superFree, subFree))
{ {
if (!occursCheck(subTy, superTy, /* reversed = */ false)) if (!occursCheck(subTy, superTy, /* reversed = */ false))
log.replace(subTy, BoundType(superTy)); log.replace(subTy, BoundType(superTy));
@ -475,7 +454,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
{ {
if (!occursCheck(superTy, subTy, /* reversed = */ true)) if (!occursCheck(superTy, subTy, /* reversed = */ true))
{ {
if (subsumes(useNewSolver, superFree, subFree)) if (subsumes(superFree, subFree))
{ {
log.changeLevel(subTy, superFree->level); log.changeLevel(subTy, superFree->level);
} }
@ -489,7 +468,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
{ {
// Unification can't change the level of a generic. // Unification can't change the level of a generic.
auto subGeneric = log.getMutable<GenericType>(subTy); auto subGeneric = log.getMutable<GenericType>(subTy);
if (subGeneric && !subsumes(useNewSolver, subGeneric, superFree)) if (subGeneric && !subsumes(subGeneric, superFree))
{ {
// TODO: a more informative error message? CLI-39912 // TODO: a more informative error message? CLI-39912
reportError(location, GenericError{"Generic subtype escaping scope"}); reportError(location, GenericError{"Generic subtype escaping scope"});
@ -498,7 +477,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
if (!occursCheck(superTy, subTy, /* reversed = */ true)) if (!occursCheck(superTy, subTy, /* reversed = */ true))
{ {
promoteTypeLevels(log, types, superFree->level, superFree->scope, useNewSolver, subTy); promoteTypeLevels(log, types, superFree->level, subTy);
Widen widen{types, builtinTypes}; Widen widen{types, builtinTypes};
log.replace(superTy, BoundType(widen(subTy))); log.replace(superTy, BoundType(widen(subTy)));
@ -515,7 +494,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
// Unification can't change the level of a generic. // Unification can't change the level of a generic.
auto superGeneric = log.getMutable<GenericType>(superTy); auto superGeneric = log.getMutable<GenericType>(superTy);
if (superGeneric && !subsumes(useNewSolver, superGeneric, subFree)) if (superGeneric && !subsumes(superGeneric, subFree))
{ {
// TODO: a more informative error message? CLI-39912 // TODO: a more informative error message? CLI-39912
reportError(location, GenericError{"Generic supertype escaping scope"}); reportError(location, GenericError{"Generic supertype escaping scope"});
@ -524,7 +503,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
if (!occursCheck(subTy, superTy, /* reversed = */ false)) if (!occursCheck(subTy, superTy, /* reversed = */ false))
{ {
promoteTypeLevels(log, types, subFree->level, subFree->scope, useNewSolver, superTy); promoteTypeLevels(log, types, subFree->level, superTy);
log.replace(subTy, BoundType(superTy)); log.replace(subTy, BoundType(superTy));
} }
@ -536,7 +515,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
auto superGeneric = log.getMutable<GenericType>(superTy); auto superGeneric = log.getMutable<GenericType>(superTy);
auto subGeneric = log.getMutable<GenericType>(subTy); auto subGeneric = log.getMutable<GenericType>(subTy);
if (superGeneric && subGeneric && subsumes(useNewSolver, superGeneric, subGeneric)) if (superGeneric && subGeneric && subsumes(superGeneric, subGeneric))
{ {
if (!occursCheck(subTy, superTy, /* reversed = */ false)) if (!occursCheck(subTy, superTy, /* reversed = */ false))
log.replace(subTy, BoundType(superTy)); log.replace(subTy, BoundType(superTy));
@ -753,9 +732,6 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionType* subUnion, Typ
std::unique_ptr<Unifier> innerState = makeChildUnifier(); std::unique_ptr<Unifier> innerState = makeChildUnifier();
innerState->tryUnify_(type, superTy); innerState->tryUnify_(type, superTy);
if (useNewSolver)
logs.push_back(std::move(innerState->log));
if (auto e = hasUnificationTooComplex(innerState->errors)) if (auto e = hasUnificationTooComplex(innerState->errors))
unificationTooComplex = e; unificationTooComplex = e;
else if (innerState->failure) else if (innerState->failure)
@ -870,13 +846,8 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
if (!innerState->failure) if (!innerState->failure)
{ {
found = true; found = true;
if (useNewSolver) log.concat(std::move(innerState->log));
logs.push_back(std::move(innerState->log)); break;
else
{
log.concat(std::move(innerState->log));
break;
}
} }
else if (innerState->errors.empty()) else if (innerState->errors.empty())
{ {
@ -895,9 +866,6 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
} }
} }
if (useNewSolver)
log.concatAsUnion(combineLogsIntoUnion(std::move(logs)), NotNull{types});
if (unificationTooComplex) if (unificationTooComplex)
{ {
reportError(*unificationTooComplex); reportError(*unificationTooComplex);
@ -975,16 +943,10 @@ void Unifier::tryUnifyTypeWithIntersection(TypeId subTy, TypeId superTy, const I
firstFailedOption = {innerState->errors.front()}; firstFailedOption = {innerState->errors.front()};
} }
if (useNewSolver) log.concat(std::move(innerState->log));
logs.push_back(std::move(innerState->log));
else
log.concat(std::move(innerState->log));
failure |= innerState->failure; failure |= innerState->failure;
} }
if (useNewSolver)
log.concat(combineLogsIntoIntersection(std::move(logs)));
if (unificationTooComplex) if (unificationTooComplex)
reportError(*unificationTooComplex); reportError(*unificationTooComplex);
else if (firstFailedOption) else if (firstFailedOption)
@ -1032,28 +994,6 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType*
} }
} }
if (useNewSolver && normalize)
{
// Sometimes a negation type is inside one of the types, e.g. { p: number } & { p: ~number }.
NegationTypeFinder finder;
finder.traverse(subTy);
if (finder.found)
{
// It is possible that A & B <: T even though A </: T and B </: T
// for example (string?) & ~nil <: string.
// We deal with this by type normalization.
std::shared_ptr<const NormalizedType> subNorm = normalizer->normalize(subTy);
std::shared_ptr<const NormalizedType> superNorm = normalizer->normalize(superTy);
if (subNorm && superNorm)
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the intersection parts are compatible");
else
reportError(location, NormalizationTooComplex{});
return;
}
}
std::vector<TxnLog> logs; std::vector<TxnLog> logs;
for (size_t i = 0; i < uv->parts.size(); ++i) for (size_t i = 0; i < uv->parts.size(); ++i)
@ -1070,7 +1010,7 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType*
{ {
found = true; found = true;
errorsSuppressed = innerState->failure; errorsSuppressed = innerState->failure;
if (useNewSolver || innerState->failure) if (innerState->failure)
logs.push_back(std::move(innerState->log)); logs.push_back(std::move(innerState->log));
else else
{ {
@ -1085,9 +1025,7 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType*
} }
} }
if (useNewSolver) if (errorsSuppressed)
log.concat(combineLogsIntoIntersection(std::move(logs)));
else if (errorsSuppressed)
log.concat(std::move(logs.front())); log.concat(std::move(logs.front()));
if (unificationTooComplex) if (unificationTooComplex)
@ -1201,24 +1139,6 @@ void Unifier::tryUnifyNormalizedTypes(
} }
} }
if (useNewSolver)
{
for (TypeId superTable : superNorm.tables)
{
std::unique_ptr<Unifier> innerState = makeChildUnifier();
innerState->tryUnify(subClass, superTable);
if (innerState->errors.empty())
{
found = true;
log.concat(std::move(innerState->log));
break;
}
else if (auto e = hasUnificationTooComplex(innerState->errors))
return reportError(*e);
}
}
if (!found) if (!found)
{ {
return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()}); return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()});
@ -1503,12 +1423,6 @@ struct WeirdIter
} }
}; };
void Unifier::enableNewSolver()
{
useNewSolver = true;
log.useScopes = true;
}
ErrorVec Unifier::canUnify(TypeId subTy, TypeId superTy) ErrorVec Unifier::canUnify(TypeId subTy, TypeId superTy)
{ {
std::unique_ptr<Unifier> s = makeChildUnifier(); std::unique_ptr<Unifier> s = makeChildUnifier();
@ -1588,8 +1502,6 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
if (!occursCheck(superTp, subTp, /* reversed = */ true)) if (!occursCheck(superTp, subTp, /* reversed = */ true))
{ {
Widen widen{types, builtinTypes}; Widen widen{types, builtinTypes};
if (useNewSolver)
promoteTypeLevels(log, types, superFree->level, superFree->scope, /*useScopes*/ true, subTp);
log.replace(superTp, Unifiable::Bound<TypePackId>(widen(subTp))); log.replace(superTp, Unifiable::Bound<TypePackId>(widen(subTp)));
} }
} }
@ -1597,8 +1509,6 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
{ {
if (!occursCheck(subTp, superTp, /* reversed = */ false)) if (!occursCheck(subTp, superTp, /* reversed = */ false))
{ {
if (useNewSolver)
promoteTypeLevels(log, types, subFree->level, subFree->scope, /*useScopes*/ true, superTp);
log.replace(subTp, Unifiable::Bound<TypePackId>(superTp)); log.replace(subTp, Unifiable::Bound<TypePackId>(superTp));
} }
} }
@ -1688,74 +1598,28 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
// If both are at the end, we're done // If both are at the end, we're done
if (!superIter.good() && !subIter.good()) if (!superIter.good() && !subIter.good())
{ {
if (useNewSolver) const bool lFreeTail = superTpv->tail && log.getMutable<FreeTypePack>(log.follow(*superTpv->tail)) != nullptr;
const bool rFreeTail = subTpv->tail && log.getMutable<FreeTypePack>(log.follow(*subTpv->tail)) != nullptr;
if (lFreeTail && rFreeTail)
{ {
if (subIter.tail() && superIter.tail()) tryUnify_(*subTpv->tail, *superTpv->tail);
tryUnify_(*subIter.tail(), *superIter.tail());
else if (subIter.tail())
{
const TypePackId subTail = log.follow(*subIter.tail());
if (log.get<FreeTypePack>(subTail))
tryUnify_(subTail, emptyTp);
else if (log.get<GenericTypePack>(subTail))
reportError(location, TypePackMismatch{subTail, emptyTp});
else if (log.get<VariadicTypePack>(subTail) || log.get<ErrorTypePack>(subTail))
{
// Nothing. This is ok.
}
else
{
ice("Unexpected subtype tail pack " + toString(subTail), location);
}
}
else if (superIter.tail())
{
const TypePackId superTail = log.follow(*superIter.tail());
if (log.get<FreeTypePack>(superTail))
tryUnify_(emptyTp, superTail);
else if (log.get<GenericTypePack>(superTail))
reportError(location, TypePackMismatch{emptyTp, superTail});
else if (log.get<VariadicTypePack>(superTail) || log.get<ErrorTypePack>(superTail))
{
// Nothing. This is ok.
}
else
{
ice("Unexpected supertype tail pack " + toString(superTail), location);
}
}
else
{
// Nothing. This is ok.
}
} }
else else if (lFreeTail)
{ {
const bool lFreeTail = superTpv->tail && log.getMutable<FreeTypePack>(log.follow(*superTpv->tail)) != nullptr; tryUnify_(emptyTp, *superTpv->tail);
const bool rFreeTail = subTpv->tail && log.getMutable<FreeTypePack>(log.follow(*subTpv->tail)) != nullptr; }
if (lFreeTail && rFreeTail) else if (rFreeTail)
{ {
tryUnify_(emptyTp, *subTpv->tail);
}
else if (subTpv->tail && superTpv->tail)
{
if (log.getMutable<VariadicTypePack>(superIter.packId))
tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index));
else if (log.getMutable<VariadicTypePack>(subIter.packId))
tryUnifyVariadics(superIter.packId, subIter.packId, true, int(superIter.index));
else
tryUnify_(*subTpv->tail, *superTpv->tail); tryUnify_(*subTpv->tail, *superTpv->tail);
}
else if (lFreeTail)
{
tryUnify_(emptyTp, *superTpv->tail);
}
else if (rFreeTail)
{
tryUnify_(emptyTp, *subTpv->tail);
}
else if (subTpv->tail && superTpv->tail)
{
if (log.getMutable<VariadicTypePack>(superIter.packId))
tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index));
else if (log.getMutable<VariadicTypePack>(subIter.packId))
tryUnifyVariadics(superIter.packId, subIter.packId, true, int(superIter.index));
else
tryUnify_(*subTpv->tail, *superTpv->tail);
}
} }
break; break;
@ -2212,7 +2076,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
variance = Invariant; variance = Invariant;
std::unique_ptr<Unifier> innerState = makeChildUnifier(); std::unique_ptr<Unifier> innerState = makeChildUnifier();
if (useNewSolver || FFlag::LuauFixIndexerSubtypingOrdering) if (FFlag::LuauFixIndexerSubtypingOrdering)
innerState->tryUnify_(prop.type(), superTable->indexer->indexResultType); innerState->tryUnify_(prop.type(), superTable->indexer->indexResultType);
else else
{ {
@ -2497,49 +2361,8 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed)
{ {
case TableState::Free: case TableState::Free:
{ {
if (useNewSolver) tryUnify_(subTy, superMetatable->table);
{ log.bindTable(subTy, superTy);
std::unique_ptr<Unifier> innerState = makeChildUnifier();
bool missingProperty = false;
for (const auto& [propName, prop] : subTable->props)
{
if (std::optional<TypeId> mtPropTy = findTablePropertyRespectingMeta(superTy, propName))
{
innerState->tryUnify(prop.type(), *mtPropTy);
}
else
{
reportError(mismatchError);
missingProperty = true;
break;
}
}
if (const TableType* superTable = log.get<TableType>(log.follow(superMetatable->table)))
{
// TODO: Unify indexers.
}
if (auto e = hasUnificationTooComplex(innerState->errors))
reportError(*e);
else if (!innerState->errors.empty())
reportError(TypeError{
location,
TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy, "", innerState->errors.front(), mismatchContext()}
});
else if (!missingProperty)
{
log.concat(std::move(innerState->log));
log.bindTable(subTy, superTy);
failure |= innerState->failure;
}
}
else
{
tryUnify_(subTy, superMetatable->table);
log.bindTable(subTy, superTy);
}
break; break;
} }
@ -2865,18 +2688,9 @@ std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, N
return Luau::findTablePropertyRespectingMeta(builtinTypes, errors, lhsType, name, location); return Luau::findTablePropertyRespectingMeta(builtinTypes, errors, lhsType, name, location);
} }
TxnLog Unifier::combineLogsIntoIntersection(std::vector<TxnLog> logs)
{
LUAU_ASSERT(useNewSolver);
TxnLog result(useNewSolver);
for (TxnLog& log : logs)
result.concatAsIntersections(std::move(log), NotNull{types});
return result;
}
TxnLog Unifier::combineLogsIntoUnion(std::vector<TxnLog> logs) TxnLog Unifier::combineLogsIntoUnion(std::vector<TxnLog> logs)
{ {
TxnLog result(useNewSolver); TxnLog result;
for (TxnLog& log : logs) for (TxnLog& log : logs)
result.concatAsUnion(std::move(log), NotNull{types}); result.concatAsUnion(std::move(log), NotNull{types});
return result; return result;
@ -3021,9 +2835,6 @@ std::unique_ptr<Unifier> Unifier::makeChildUnifier()
u->normalize = normalize; u->normalize = normalize;
u->checkInhabited = checkInhabited; u->checkInhabited = checkInhabited;
if (useNewSolver)
u->enableNewSolver();
return u; return u;
} }

View file

@ -71,6 +71,19 @@ struct ParseResult
CstNodeMap cstNodeMap{nullptr}; CstNodeMap cstNodeMap{nullptr};
}; };
struct ParseExprResult
{
AstExpr* expr;
size_t lines = 0;
std::vector<HotComment> hotcomments;
std::vector<ParseError> errors;
std::vector<Comment> commentLocations;
CstNodeMap cstNodeMap{nullptr};
};
static constexpr const char* kParseNameError = "%error-id%"; static constexpr const char* kParseNameError = "%error-id%";
} // namespace Luau } // namespace Luau

View file

@ -63,6 +63,14 @@ public:
ParseOptions options = ParseOptions() ParseOptions options = ParseOptions()
); );
static ParseExprResult parseExpr(
const char* buffer,
std::size_t bufferSize,
AstNameTable& names,
Allocator& allocator,
ParseOptions options = ParseOptions()
);
private: private:
struct Name; struct Name;
struct Binding; struct Binding;

View file

@ -18,7 +18,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
// flag so that we don't break production games by reverting syntax changes. // flag so that we don't break production games by reverting syntax changes.
// See docs/SyntaxChanges.md for an explanation. // See docs/SyntaxChanges.md for an explanation.
LUAU_FASTFLAGVARIABLE(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauAllowFragmentParsing)
LUAU_FASTFLAGVARIABLE(LuauAllowComplexTypesInGenericParams) LUAU_FASTFLAGVARIABLE(LuauAllowComplexTypesInGenericParams)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForTableTypes) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForTableTypes)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForClassNames) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForClassNames)
@ -26,7 +25,7 @@ LUAU_FASTFLAGVARIABLE(LuauFixFunctionNameStartPosition)
LUAU_FASTFLAGVARIABLE(LuauExtendStatEndPosWithSemicolon) LUAU_FASTFLAGVARIABLE(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAGVARIABLE(LuauStoreCSTData) LUAU_FASTFLAGVARIABLE(LuauStoreCSTData)
LUAU_FASTFLAGVARIABLE(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAGVARIABLE(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup) LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup2)
LUAU_FASTFLAGVARIABLE(ParserNoErrorLimit) LUAU_FASTFLAGVARIABLE(ParserNoErrorLimit)
LUAU_FASTFLAGVARIABLE(LuauFixDoBlockEndLocation) LUAU_FASTFLAGVARIABLE(LuauFixDoBlockEndLocation)
@ -182,6 +181,28 @@ ParseResult Parser::parse(const char* buffer, size_t bufferSize, AstNameTable& n
} }
} }
ParseExprResult Parser::parseExpr(const char* buffer, size_t bufferSize, AstNameTable& names, Allocator& allocator, ParseOptions options)
{
LUAU_TIMETRACE_SCOPE("Parser::parse", "Parser");
Parser p(buffer, bufferSize, names, allocator, options);
try
{
AstExpr* expr = p.parseExpr();
size_t lines = p.lexer.current().location.end.line + (bufferSize > 0 && buffer[bufferSize - 1] != '\n');
return ParseExprResult{expr, lines, std::move(p.hotcomments), std::move(p.parseErrors), std::move(p.commentLocations), std::move(p.cstNodeMap)};
}
catch (ParseError& err)
{
// when catching a fatal error, append it to the list of non-fatal errors and return
p.parseErrors.push_back(err);
return ParseExprResult{nullptr, 0, {}, p.parseErrors, {}, std::move(p.cstNodeMap)};
}
}
Parser::Parser(const char* buffer, size_t bufferSize, AstNameTable& names, Allocator& allocator, const ParseOptions& options) Parser::Parser(const char* buffer, size_t bufferSize, AstNameTable& names, Allocator& allocator, const ParseOptions& options)
: options(options) : options(options)
, lexer(buffer, bufferSize, names, options.parseFragment ? options.parseFragment->resumePosition : Position(0, 0)) , lexer(buffer, bufferSize, names, options.parseFragment ? options.parseFragment->resumePosition : Position(0, 0))
@ -197,18 +218,9 @@ Parser::Parser(const char* buffer, size_t bufferSize, AstNameTable& names, Alloc
functionStack.reserve(8); functionStack.reserve(8);
functionStack.push_back(top); functionStack.push_back(top);
if (FFlag::LuauAllowFragmentParsing) nameSelf = names.getOrAdd("self");
{ nameNumber = names.getOrAdd("number");
nameSelf = names.getOrAdd("self"); nameError = names.getOrAdd(kParseNameError);
nameNumber = names.getOrAdd("number");
nameError = names.getOrAdd(kParseNameError);
}
else
{
nameSelf = names.addStatic("self");
nameNumber = names.addStatic("number");
nameError = names.addStatic(kParseNameError);
}
nameNil = names.getOrAdd("nil"); // nil is a reserved keyword nameNil = names.getOrAdd("nil"); // nil is a reserved keyword
matchRecoveryStopOnToken.assign(Lexeme::Type::Reserved_END, 0); matchRecoveryStopOnToken.assign(Lexeme::Type::Reserved_END, 0);
@ -231,13 +243,10 @@ Parser::Parser(const char* buffer, size_t bufferSize, AstNameTable& names, Alloc
scratchLocal.reserve(16); scratchLocal.reserve(16);
scratchBinding.reserve(16); scratchBinding.reserve(16);
if (FFlag::LuauAllowFragmentParsing) if (options.parseFragment)
{ {
if (options.parseFragment) localMap = options.parseFragment->localMap;
{ localStack = options.parseFragment->localStack;
localMap = options.parseFragment->localMap;
localStack = options.parseFragment->localStack;
}
} }
} }
@ -1723,7 +1732,7 @@ std::pair<Location, AstTypeList> Parser::parseReturnType()
if (lexer.current().type != Lexeme::SkinnyArrow && resultNames.empty()) if (lexer.current().type != Lexeme::SkinnyArrow && resultNames.empty())
{ {
// If it turns out that it's just '(A)', it's possible that there are unions/intersections to follow, so fold over it. // If it turns out that it's just '(A)', it's possible that there are unions/intersections to follow, so fold over it.
if (FFlag::LuauAstTypeGroup) if (FFlag::LuauAstTypeGroup2)
{ {
if (result.size() == 1 && varargAnnotation == nullptr) if (result.size() == 1 && varargAnnotation == nullptr)
{ {
@ -2034,6 +2043,7 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray<AstAttr*>
if (lexer.current().type != ')') if (lexer.current().type != ')')
varargAnnotation = parseTypeList(params, names); varargAnnotation = parseTypeList(params, names);
Location closeArgsLocation = lexer.current().location;
expectMatchAndConsume(')', parameterStart, true); expectMatchAndConsume(')', parameterStart, true);
matchRecoveryStopOnToken[Lexeme::SkinnyArrow]--; matchRecoveryStopOnToken[Lexeme::SkinnyArrow]--;
@ -2052,8 +2062,8 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray<AstAttr*>
return {{}, allocator.alloc<AstTypePackExplicit>(begin.location, AstTypeList{paramTypes, nullptr})}; return {{}, allocator.alloc<AstTypePackExplicit>(begin.location, AstTypeList{paramTypes, nullptr})};
else else
{ {
if (FFlag::LuauAstTypeGroup) if (FFlag::LuauAstTypeGroup2)
return {allocator.alloc<AstTypeGroup>(Location(parameterStart.location, params[0]->location), params[0]), {}}; return {allocator.alloc<AstTypeGroup>(Location(parameterStart.location, closeArgsLocation), params[0]), {}};
else else
return {params[0], {}}; return {params[0], {}};
} }
@ -3562,7 +3572,7 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams(Position* openingPosition, TempV
// the next lexeme is one that follows a type // the next lexeme is one that follows a type
// (&, |, ?), then assume that this was actually a // (&, |, ?), then assume that this was actually a
// parenthesized type. // parenthesized type.
if (FFlag::LuauAstTypeGroup) if (FFlag::LuauAstTypeGroup2)
{ {
auto parenthesizedType = explicitTypePack->typeList.types.data[0]; auto parenthesizedType = explicitTypePack->typeList.types.data[0];
parameters.push_back( parameters.push_back(

View file

@ -791,8 +791,6 @@ int replMain(int argc, char** argv)
{ {
Luau::assertHandler() = assertionHandler; Luau::assertHandler() = assertionHandler;
setLuauFlagsDefault();
#ifdef _WIN32 #ifdef _WIN32
SetConsoleOutputCP(CP_UTF8); SetConsoleOutputCP(CP_UTF8);
#endif #endif

View file

@ -1,7 +1,10 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Repl.h" #include "Luau/Repl.h"
#include "Luau/Flags.h"
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
setLuauFlagsDefault();
return replMain(argc, argv); return replMain(argc, argv);
} }

View file

@ -44,7 +44,6 @@
LUAU_FASTFLAGVARIABLE(DebugCodegenNoOpt) LUAU_FASTFLAGVARIABLE(DebugCodegenNoOpt)
LUAU_FASTFLAGVARIABLE(DebugCodegenOptSize) LUAU_FASTFLAGVARIABLE(DebugCodegenOptSize)
LUAU_FASTFLAGVARIABLE(DebugCodegenSkipNumbering) LUAU_FASTFLAGVARIABLE(DebugCodegenSkipNumbering)
LUAU_FASTFLAGVARIABLE(CodegenWiderLoweringStats)
// Per-module IR instruction count limit // Per-module IR instruction count limit
LUAU_FASTINTVARIABLE(CodegenHeuristicsInstructionLimit, 1'048'576) // 1 M LUAU_FASTINTVARIABLE(CodegenHeuristicsInstructionLimit, 1'048'576) // 1 M

View file

@ -25,7 +25,6 @@
LUAU_FASTFLAG(DebugCodegenNoOpt) LUAU_FASTFLAG(DebugCodegenNoOpt)
LUAU_FASTFLAG(DebugCodegenOptSize) LUAU_FASTFLAG(DebugCodegenOptSize)
LUAU_FASTFLAG(DebugCodegenSkipNumbering) LUAU_FASTFLAG(DebugCodegenSkipNumbering)
LUAU_FASTFLAG(CodegenWiderLoweringStats)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_FASTINT(CodegenHeuristicsBlockLimit) LUAU_FASTINT(CodegenHeuristicsBlockLimit)
LUAU_FASTINT(CodegenHeuristicsBlockInstructionLimit) LUAU_FASTINT(CodegenHeuristicsBlockInstructionLimit)
@ -300,8 +299,7 @@ inline bool lowerFunction(
CodeGenCompilationResult& codeGenCompilationResult CodeGenCompilationResult& codeGenCompilationResult
) )
{ {
if (FFlag::CodegenWiderLoweringStats) ir.function.stats = stats;
ir.function.stats = stats;
killUnusedBlocks(ir.function); killUnusedBlocks(ir.function);

View file

@ -13,7 +13,6 @@
#include "lgc.h" #include "lgc.h"
LUAU_FASTFLAG(LuauVectorLibNativeDot) LUAU_FASTFLAG(LuauVectorLibNativeDot)
LUAU_FASTFLAG(LuauCodeGenLerp)
namespace Luau namespace Luau
{ {
@ -706,7 +705,6 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
} }
case IrCmd::SELECT_NUM: case IrCmd::SELECT_NUM:
{ {
LUAU_ASSERT(FFlag::LuauCodeGenLerp);
inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a, inst.b, inst.c, inst.d}); inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a, inst.b, inst.c, inst.d});
RegisterA64 temp1 = tempDouble(inst.a); RegisterA64 temp1 = tempDouble(inst.a);

View file

@ -17,7 +17,6 @@
#include "lgc.h" #include "lgc.h"
LUAU_FASTFLAG(LuauVectorLibNativeDot) LUAU_FASTFLAG(LuauVectorLibNativeDot)
LUAU_FASTFLAG(LuauCodeGenLerp)
namespace Luau namespace Luau
{ {
@ -625,7 +624,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
} }
case IrCmd::SELECT_NUM: case IrCmd::SELECT_NUM:
{ {
LUAU_ASSERT(FFlag::LuauCodeGenLerp);
inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.c, inst.d}); // can't reuse b if a is a memory operand inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.c, inst.d}); // can't reuse b if a is a memory operand
ScopedRegX64 tmp{regs, SizeX64::xmmword}; ScopedRegX64 tmp{regs, SizeX64::xmmword};

View file

@ -13,9 +13,7 @@
static const int kMinMaxUnrolledParams = 5; static const int kMinMaxUnrolledParams = 5;
static const int kBit32BinaryOpUnrolledParams = 5; static const int kBit32BinaryOpUnrolledParams = 5;
LUAU_FASTFLAGVARIABLE(LuauVectorLibNativeCodegen);
LUAU_FASTFLAGVARIABLE(LuauVectorLibNativeDot); LUAU_FASTFLAGVARIABLE(LuauVectorLibNativeDot);
LUAU_FASTFLAGVARIABLE(LuauCodeGenLerp);
namespace Luau namespace Luau
{ {
@ -297,8 +295,6 @@ static BuiltinImplResult translateBuiltinMathLerp(
int pcpos int pcpos
) )
{ {
LUAU_ASSERT(FFlag::LuauCodeGenLerp);
if (nparams < 3 || nresults > 1) if (nparams < 3 || nresults > 1)
return {BuiltinImplType::None, -1}; return {BuiltinImplType::None, -1};
@ -936,8 +932,6 @@ static BuiltinImplResult translateBuiltinVectorMagnitude(
int pcpos int pcpos
) )
{ {
LUAU_ASSERT(FFlag::LuauVectorLibNativeCodegen);
IrOp arg1 = build.vmReg(arg); IrOp arg1 = build.vmReg(arg);
if (nparams != 1 || nresults > 1 || arg1.kind == IrOpKind::Constant) if (nparams != 1 || nresults > 1 || arg1.kind == IrOpKind::Constant)
@ -985,8 +979,6 @@ static BuiltinImplResult translateBuiltinVectorNormalize(
int pcpos int pcpos
) )
{ {
LUAU_ASSERT(FFlag::LuauVectorLibNativeCodegen);
IrOp arg1 = build.vmReg(arg); IrOp arg1 = build.vmReg(arg);
if (nparams != 1 || nresults > 1 || arg1.kind == IrOpKind::Constant) if (nparams != 1 || nresults > 1 || arg1.kind == IrOpKind::Constant)
@ -1037,8 +1029,6 @@ static BuiltinImplResult translateBuiltinVectorNormalize(
static BuiltinImplResult translateBuiltinVectorCross(IrBuilder& build, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, int pcpos) static BuiltinImplResult translateBuiltinVectorCross(IrBuilder& build, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, int pcpos)
{ {
LUAU_ASSERT(FFlag::LuauVectorLibNativeCodegen);
IrOp arg1 = build.vmReg(arg); IrOp arg1 = build.vmReg(arg);
if (nparams != 2 || nresults > 1 || arg1.kind == IrOpKind::Constant || args.kind == IrOpKind::Constant) if (nparams != 2 || nresults > 1 || arg1.kind == IrOpKind::Constant || args.kind == IrOpKind::Constant)
@ -1076,8 +1066,6 @@ static BuiltinImplResult translateBuiltinVectorCross(IrBuilder& build, int npara
static BuiltinImplResult translateBuiltinVectorDot(IrBuilder& build, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, int pcpos) static BuiltinImplResult translateBuiltinVectorDot(IrBuilder& build, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, int pcpos)
{ {
LUAU_ASSERT(FFlag::LuauVectorLibNativeCodegen);
IrOp arg1 = build.vmReg(arg); IrOp arg1 = build.vmReg(arg);
if (nparams != 2 || nresults > 1 || arg1.kind == IrOpKind::Constant || args.kind == IrOpKind::Constant) if (nparams != 2 || nresults > 1 || arg1.kind == IrOpKind::Constant || args.kind == IrOpKind::Constant)
@ -1130,8 +1118,6 @@ static BuiltinImplResult translateBuiltinVectorMap1(
int pcpos int pcpos
) )
{ {
LUAU_ASSERT(FFlag::LuauVectorLibNativeCodegen);
IrOp arg1 = build.vmReg(arg); IrOp arg1 = build.vmReg(arg);
if (nparams != 1 || nresults > 1 || arg1.kind == IrOpKind::Constant) if (nparams != 1 || nresults > 1 || arg1.kind == IrOpKind::Constant)
@ -1165,8 +1151,6 @@ static BuiltinImplResult translateBuiltinVectorClamp(
int pcpos int pcpos
) )
{ {
LUAU_ASSERT(FFlag::LuauVectorLibNativeCodegen);
IrOp arg1 = build.vmReg(arg); IrOp arg1 = build.vmReg(arg);
if (nparams != 3 || nresults > 1 || arg1.kind == IrOpKind::Constant || args.kind == IrOpKind::Constant || arg3.kind == IrOpKind::Constant) if (nparams != 3 || nresults > 1 || arg1.kind == IrOpKind::Constant || args.kind == IrOpKind::Constant || arg3.kind == IrOpKind::Constant)
@ -1231,8 +1215,6 @@ static BuiltinImplResult translateBuiltinVectorMap2(
int pcpos int pcpos
) )
{ {
LUAU_ASSERT(FFlag::LuauVectorLibNativeCodegen);
IrOp arg1 = build.vmReg(arg); IrOp arg1 = build.vmReg(arg);
if (nparams != 2 || nresults > 1 || arg1.kind == IrOpKind::Constant || args.kind == IrOpKind::Constant) if (nparams != 2 || nresults > 1 || arg1.kind == IrOpKind::Constant || args.kind == IrOpKind::Constant)
@ -1273,8 +1255,6 @@ BuiltinImplResult translateBuiltin(
int pcpos int pcpos
) )
{ {
BuiltinImplResult noneResult = {BuiltinImplType::None, -1};
// Builtins are not allowed to handle variadic arguments // Builtins are not allowed to handle variadic arguments
if (nparams == LUA_MULTRET) if (nparams == LUA_MULTRET)
return {BuiltinImplType::None, -1}; return {BuiltinImplType::None, -1};
@ -1396,36 +1376,29 @@ BuiltinImplResult translateBuiltin(
case LBF_BUFFER_WRITEF64: case LBF_BUFFER_WRITEF64:
return translateBuiltinBufferWrite(build, nparams, ra, arg, args, arg3, nresults, pcpos, IrCmd::BUFFER_WRITEF64, 8, IrCmd::NOP); return translateBuiltinBufferWrite(build, nparams, ra, arg, args, arg3, nresults, pcpos, IrCmd::BUFFER_WRITEF64, 8, IrCmd::NOP);
case LBF_VECTOR_MAGNITUDE: case LBF_VECTOR_MAGNITUDE:
return FFlag::LuauVectorLibNativeCodegen ? translateBuiltinVectorMagnitude(build, nparams, ra, arg, args, arg3, nresults, pcpos) : noneResult; return translateBuiltinVectorMagnitude(build, nparams, ra, arg, args, arg3, nresults, pcpos);
case LBF_VECTOR_NORMALIZE: case LBF_VECTOR_NORMALIZE:
return FFlag::LuauVectorLibNativeCodegen ? translateBuiltinVectorNormalize(build, nparams, ra, arg, args, arg3, nresults, pcpos) : noneResult; return translateBuiltinVectorNormalize(build, nparams, ra, arg, args, arg3, nresults, pcpos);
case LBF_VECTOR_CROSS: case LBF_VECTOR_CROSS:
return FFlag::LuauVectorLibNativeCodegen ? translateBuiltinVectorCross(build, nparams, ra, arg, args, arg3, nresults, pcpos) : noneResult; return translateBuiltinVectorCross(build, nparams, ra, arg, args, arg3, nresults, pcpos);
case LBF_VECTOR_DOT: case LBF_VECTOR_DOT:
return FFlag::LuauVectorLibNativeCodegen ? translateBuiltinVectorDot(build, nparams, ra, arg, args, arg3, nresults, pcpos) : noneResult; return translateBuiltinVectorDot(build, nparams, ra, arg, args, arg3, nresults, pcpos);
case LBF_VECTOR_FLOOR: case LBF_VECTOR_FLOOR:
return FFlag::LuauVectorLibNativeCodegen ? translateBuiltinVectorMap1(build, IrCmd::FLOOR_NUM, nparams, ra, arg, args, arg3, nresults, pcpos) return translateBuiltinVectorMap1(build, IrCmd::FLOOR_NUM, nparams, ra, arg, args, arg3, nresults, pcpos);
: noneResult;
case LBF_VECTOR_CEIL: case LBF_VECTOR_CEIL:
return FFlag::LuauVectorLibNativeCodegen ? translateBuiltinVectorMap1(build, IrCmd::CEIL_NUM, nparams, ra, arg, args, arg3, nresults, pcpos) return translateBuiltinVectorMap1(build, IrCmd::CEIL_NUM, nparams, ra, arg, args, arg3, nresults, pcpos);
: noneResult;
case LBF_VECTOR_ABS: case LBF_VECTOR_ABS:
return FFlag::LuauVectorLibNativeCodegen ? translateBuiltinVectorMap1(build, IrCmd::ABS_NUM, nparams, ra, arg, args, arg3, nresults, pcpos) return translateBuiltinVectorMap1(build, IrCmd::ABS_NUM, nparams, ra, arg, args, arg3, nresults, pcpos);
: noneResult;
case LBF_VECTOR_SIGN: case LBF_VECTOR_SIGN:
return FFlag::LuauVectorLibNativeCodegen ? translateBuiltinVectorMap1(build, IrCmd::SIGN_NUM, nparams, ra, arg, args, arg3, nresults, pcpos) return translateBuiltinVectorMap1(build, IrCmd::SIGN_NUM, nparams, ra, arg, args, arg3, nresults, pcpos);
: noneResult;
case LBF_VECTOR_CLAMP: case LBF_VECTOR_CLAMP:
return FFlag::LuauVectorLibNativeCodegen ? translateBuiltinVectorClamp(build, nparams, ra, arg, args, arg3, nresults, fallback, pcpos) return translateBuiltinVectorClamp(build, nparams, ra, arg, args, arg3, nresults, fallback, pcpos);
: noneResult;
case LBF_VECTOR_MIN: case LBF_VECTOR_MIN:
return FFlag::LuauVectorLibNativeCodegen ? translateBuiltinVectorMap2(build, IrCmd::MIN_NUM, nparams, ra, arg, args, arg3, nresults, pcpos) return translateBuiltinVectorMap2(build, IrCmd::MIN_NUM, nparams, ra, arg, args, arg3, nresults, pcpos);
: noneResult;
case LBF_VECTOR_MAX: case LBF_VECTOR_MAX:
return FFlag::LuauVectorLibNativeCodegen ? translateBuiltinVectorMap2(build, IrCmd::MAX_NUM, nparams, ra, arg, args, arg3, nresults, pcpos) return translateBuiltinVectorMap2(build, IrCmd::MAX_NUM, nparams, ra, arg, args, arg3, nresults, pcpos);
: noneResult;
case LBF_MATH_LERP: case LBF_MATH_LERP:
return FFlag::LuauCodeGenLerp ? translateBuiltinMathLerp(build, nparams, ra, arg, args, arg3, nresults, fallback, pcpos) : noneResult; return translateBuiltinMathLerp(build, nparams, ra, arg, args, arg3, nresults, fallback, pcpos);
default: default:
return {BuiltinImplType::None, -1}; return {BuiltinImplType::None, -1};
} }

View file

@ -17,7 +17,6 @@
#include <math.h> #include <math.h>
LUAU_FASTFLAG(LuauVectorLibNativeDot); LUAU_FASTFLAG(LuauVectorLibNativeDot);
LUAU_FASTFLAG(LuauCodeGenLerp);
namespace Luau namespace Luau
{ {
@ -663,7 +662,6 @@ void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint3
} }
break; break;
case IrCmd::SELECT_NUM: case IrCmd::SELECT_NUM:
LUAU_ASSERT(FFlag::LuauCodeGenLerp);
if (inst.c.kind == IrOpKind::Constant && inst.d.kind == IrOpKind::Constant) if (inst.c.kind == IrOpKind::Constant && inst.d.kind == IrOpKind::Constant)
{ {
double c = function.doubleOp(inst.c); double c = function.doubleOp(inst.c);

View file

@ -22,7 +22,6 @@ LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64)
LUAU_FASTINTVARIABLE(LuauCodeGenLiveSlotReuseLimit, 8) LUAU_FASTINTVARIABLE(LuauCodeGenLiveSlotReuseLimit, 8)
LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks) LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks)
LUAU_FASTFLAG(LuauVectorLibNativeDot) LUAU_FASTFLAG(LuauVectorLibNativeDot)
LUAU_FASTFLAGVARIABLE(LuauCodeGenLimitLiveSlotReuse)
namespace Luau namespace Luau
{ {
@ -200,11 +199,7 @@ struct ConstPropState
// Same goes for table array elements as well // Same goes for table array elements as well
void invalidateHeapTableData() void invalidateHeapTableData()
{ {
if (FFlag::LuauCodeGenLimitLiveSlotReuse) getSlotNodeCache.clear();
getSlotNodeCache.clear();
else
getSlotNodeCache_DEPRECATED.clear();
checkSlotMatchCache.clear(); checkSlotMatchCache.clear();
getArrAddrCache.clear(); getArrAddrCache.clear();
@ -428,8 +423,6 @@ struct ConstPropState
// Note that this pressure is approximate, as some values that might have been live at one point could have been marked dead later // Note that this pressure is approximate, as some values that might have been live at one point could have been marked dead later
int getMaxInternalOverlap(std::vector<NumberedInstruction>& set, size_t slot) int getMaxInternalOverlap(std::vector<NumberedInstruction>& set, size_t slot)
{ {
CODEGEN_ASSERT(FFlag::LuauCodeGenLimitLiveSlotReuse);
// Start with one live range for the slot we want to reuse // Start with one live range for the slot we want to reuse
int curr = 1; int curr = 1;
@ -487,9 +480,7 @@ struct ConstPropState
regs[i] = RegisterInfo(); regs[i] = RegisterInfo();
maxReg = 0; maxReg = 0;
instPos = 0u;
if (FFlag::LuauCodeGenLimitLiveSlotReuse)
instPos = 0u;
inSafeEnv = false; inSafeEnv = false;
checkedGc = false; checkedGc = false;
@ -526,7 +517,6 @@ struct ConstPropState
// Heap changes might affect table state // Heap changes might affect table state
std::vector<NumberedInstruction> getSlotNodeCache; // Additionally, pcpos argument might be different std::vector<NumberedInstruction> getSlotNodeCache; // Additionally, pcpos argument might be different
std::vector<uint32_t> getSlotNodeCache_DEPRECATED; // Additionally, pcpos argument might be different
std::vector<uint32_t> checkSlotMatchCache; // Additionally, fallback block argument might be different std::vector<uint32_t> checkSlotMatchCache; // Additionally, fallback block argument might be different
std::vector<uint32_t> getArrAddrCache; std::vector<uint32_t> getArrAddrCache;
@ -651,8 +641,7 @@ static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid
static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& function, IrBlock& block, IrInst& inst, uint32_t index) static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& function, IrBlock& block, IrInst& inst, uint32_t index)
{ {
if (FFlag::LuauCodeGenLimitLiveSlotReuse) state.instPos++;
state.instPos++;
switch (inst.cmd) switch (inst.cmd)
{ {
@ -1260,49 +1249,30 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
state.getArrAddrCache.push_back(index); state.getArrAddrCache.push_back(index);
break; break;
case IrCmd::GET_SLOT_NODE_ADDR: case IrCmd::GET_SLOT_NODE_ADDR:
if (FFlag::LuauCodeGenLimitLiveSlotReuse) for (size_t i = 0; i < state.getSlotNodeCache.size(); i++)
{ {
for (size_t i = 0; i < state.getSlotNodeCache.size(); i++) auto&& [prevIdx, num, lastNum] = state.getSlotNodeCache[i];
const IrInst& prev = function.instructions[prevIdx];
if (prev.a == inst.a && prev.c == inst.c)
{ {
auto&& [prevIdx, num, lastNum] = state.getSlotNodeCache[i]; // Check if this reuse will increase the overall register pressure over the limit
int limit = FInt::LuauCodeGenLiveSlotReuseLimit;
const IrInst& prev = function.instructions[prevIdx]; if (int(state.getSlotNodeCache.size()) > limit && state.getMaxInternalOverlap(state.getSlotNodeCache, i) > limit)
return;
if (prev.a == inst.a && prev.c == inst.c) // Update live range of the value from the optimization standpoint
{ lastNum = state.instPos;
// Check if this reuse will increase the overall register pressure over the limit
int limit = FInt::LuauCodeGenLiveSlotReuseLimit;
if (int(state.getSlotNodeCache.size()) > limit && state.getMaxInternalOverlap(state.getSlotNodeCache, i) > limit) substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx});
return; return; // Break out from both the loop and the switch
// Update live range of the value from the optimization standpoint
lastNum = state.instPos;
substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx});
return; // Break out from both the loop and the switch
}
} }
if (int(state.getSlotNodeCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
state.getSlotNodeCache.push_back({index, state.instPos, state.instPos});
} }
else
{
for (uint32_t prevIdx : state.getSlotNodeCache_DEPRECATED)
{
const IrInst& prev = function.instructions[prevIdx];
if (prev.a == inst.a && prev.c == inst.c) if (int(state.getSlotNodeCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
{ state.getSlotNodeCache.push_back({index, state.instPos, state.instPos});
substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx});
return; // Break out from both the loop and the switch
}
}
if (int(state.getSlotNodeCache_DEPRECATED.size()) < FInt::LuauCodeGenReuseSlotLimit)
state.getSlotNodeCache_DEPRECATED.push_back(index);
}
break; break;
case IrCmd::GET_HASH_NODE_ADDR: case IrCmd::GET_HASH_NODE_ADDR:
case IrCmd::GET_CLOSURE_UPVAL_ADDR: case IrCmd::GET_CLOSURE_UPVAL_ADDR:

View file

@ -5,9 +5,6 @@
#include <math.h> #include <math.h>
LUAU_FASTFLAGVARIABLE(LuauVector2Constants)
LUAU_FASTFLAG(LuauCompileMathLerp)
namespace Luau namespace Luau
{ {
namespace Compile namespace Compile
@ -476,7 +473,7 @@ Constant foldBuiltin(int bfid, const Constant* args, size_t count)
case LBF_VECTOR: case LBF_VECTOR:
if (count >= 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number) if (count >= 2 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number)
{ {
if (count == 2 && FFlag::LuauVector2Constants) if (count == 2)
return cvector(args[0].valueNumber, args[1].valueNumber, 0.0, 0.0); return cvector(args[0].valueNumber, args[1].valueNumber, 0.0, 0.0);
else if (count == 3 && args[2].type == Constant::Type_Number) else if (count == 3 && args[2].type == Constant::Type_Number)
return cvector(args[0].valueNumber, args[1].valueNumber, args[2].valueNumber, 0.0); return cvector(args[0].valueNumber, args[1].valueNumber, args[2].valueNumber, 0.0);
@ -486,8 +483,7 @@ Constant foldBuiltin(int bfid, const Constant* args, size_t count)
break; break;
case LBF_MATH_LERP: case LBF_MATH_LERP:
if (FFlag::LuauCompileMathLerp && count == 3 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number && if (count == 3 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number && args[2].type == Constant::Type_Number)
args[2].type == Constant::Type_Number)
{ {
double a = args[0].valueNumber; double a = args[0].valueNumber;
double b = args[1].valueNumber; double b = args[1].valueNumber;

View file

@ -7,8 +7,6 @@
#include <array> #include <array>
LUAU_FASTFLAGVARIABLE(LuauCompileMathLerp)
namespace Luau namespace Luau
{ {
namespace Compile namespace Compile
@ -139,7 +137,7 @@ static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& op
return LBF_MATH_SIGN; return LBF_MATH_SIGN;
if (builtin.method == "round") if (builtin.method == "round")
return LBF_MATH_ROUND; return LBF_MATH_ROUND;
if (FFlag::LuauCompileMathLerp && builtin.method == "lerp") if (builtin.method == "lerp")
return LBF_MATH_LERP; return LBF_MATH_LERP;
} }
@ -556,7 +554,6 @@ BuiltinInfo getBuiltinInfo(int bfid)
return {-1, 1}; // variadic return {-1, 1}; // variadic
case LBF_MATH_LERP: case LBF_MATH_LERP:
LUAU_ASSERT(FFlag::LuauCompileMathLerp);
return {3, 1, BuiltinInfo::Flag_NoneSafe}; return {3, 1, BuiltinInfo::Flag_NoneSafe};
} }

View file

@ -10,8 +10,6 @@
#include <string.h> #include <string.h>
LUAU_FASTFLAGVARIABLE(LuauBufferBitMethods2)
// while C API returns 'size_t' for binary compatibility in case of future extensions, // while C API returns 'size_t' for binary compatibility in case of future extensions,
// in the current implementation, length and offset are limited to 31 bits // in the current implementation, length and offset are limited to 31 bits
// because offset is limited to an integer, a single 64bit comparison can be used and will not overflow // because offset is limited to an integer, a single 64bit comparison can be used and will not overflow
@ -330,34 +328,6 @@ static int buffer_writebits(lua_State* L)
return 0; return 0;
} }
static const luaL_Reg bufferlib_DEPRECATED[] = {
{"create", buffer_create},
{"fromstring", buffer_fromstring},
{"tostring", buffer_tostring},
{"readi8", buffer_readinteger<int8_t>},
{"readu8", buffer_readinteger<uint8_t>},
{"readi16", buffer_readinteger<int16_t>},
{"readu16", buffer_readinteger<uint16_t>},
{"readi32", buffer_readinteger<int32_t>},
{"readu32", buffer_readinteger<uint32_t>},
{"readf32", buffer_readfp<float, uint32_t>},
{"readf64", buffer_readfp<double, uint64_t>},
{"writei8", buffer_writeinteger<int8_t>},
{"writeu8", buffer_writeinteger<uint8_t>},
{"writei16", buffer_writeinteger<int16_t>},
{"writeu16", buffer_writeinteger<uint16_t>},
{"writei32", buffer_writeinteger<int32_t>},
{"writeu32", buffer_writeinteger<uint32_t>},
{"writef32", buffer_writefp<float, uint32_t>},
{"writef64", buffer_writefp<double, uint64_t>},
{"readstring", buffer_readstring},
{"writestring", buffer_writestring},
{"len", buffer_len},
{"copy", buffer_copy},
{"fill", buffer_fill},
{NULL, NULL},
};
static const luaL_Reg bufferlib[] = { static const luaL_Reg bufferlib[] = {
{"create", buffer_create}, {"create", buffer_create},
{"fromstring", buffer_fromstring}, {"fromstring", buffer_fromstring},
@ -390,7 +360,7 @@ static const luaL_Reg bufferlib[] = {
int luaopen_buffer(lua_State* L) int luaopen_buffer(lua_State* L)
{ {
luaL_register(L, LUA_BUFFERLIBNAME, FFlag::LuauBufferBitMethods2 ? bufferlib : bufferlib_DEPRECATED); luaL_register(L, LUA_BUFFERLIBNAME, bufferlib);
return 1; return 1;
} }

View file

@ -25,8 +25,6 @@
#endif #endif
#endif #endif
LUAU_FASTFLAG(LuauVector2Constructor)
// luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM // luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM
// The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack. // The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack.
// If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path // If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path
@ -1057,60 +1055,33 @@ static int luauF_tunpack(lua_State* L, StkId res, TValue* arg0, int nresults, St
static int luauF_vector(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) static int luauF_vector(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
{ {
if (FFlag::LuauVector2Constructor) if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args))
{ {
if (nparams >= 2 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args)) float x = (float)nvalue(arg0);
{ float y = (float)nvalue(args);
float x = (float)nvalue(arg0); float z = 0.0f;
float y = (float)nvalue(args);
float z = 0.0f;
if (nparams >= 3) if (nparams >= 3)
{ {
if (!ttisnumber(args + 1)) if (!ttisnumber(args + 1))
return -1; return -1;
z = (float)nvalue(args + 1); z = (float)nvalue(args + 1);
} }
#if LUA_VECTOR_SIZE == 4 #if LUA_VECTOR_SIZE == 4
float w = 0.0f; float w = 0.0f;
if (nparams >= 4) if (nparams >= 4)
{
if (!ttisnumber(args + 2))
return -1;
w = (float)nvalue(args + 2);
}
setvvalue(res, x, y, z, w);
#else
setvvalue(res, x, y, z, 0.0f);
#endif
return 1;
}
}
else
{
if (nparams >= 3 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1))
{ {
double x = nvalue(arg0); if (!ttisnumber(args + 2))
double y = nvalue(args); return -1;
double z = nvalue(args + 1); w = (float)nvalue(args + 2);
}
#if LUA_VECTOR_SIZE == 4 setvvalue(res, x, y, z, w);
double w = 0.0;
if (nparams >= 4)
{
if (!ttisnumber(args + 2))
return -1;
w = nvalue(args + 2);
}
setvvalue(res, float(x), float(y), float(z), float(w));
#else #else
setvvalue(res, float(x), float(y), float(z), 0.0f); setvvalue(res, x, y, z, 0.0f);
#endif #endif
return 1; return 1;
}
} }
return -1; return -1;

View file

@ -7,8 +7,6 @@
#include <math.h> #include <math.h>
#include <time.h> #include <time.h>
LUAU_FASTFLAGVARIABLE(LuauMathLerp)
#undef PI #undef PI
#define PI (3.14159265358979323846) #define PI (3.14159265358979323846)
#define RADIANS_PER_DEGREE (PI / 180.0) #define RADIANS_PER_DEGREE (PI / 180.0)
@ -463,6 +461,7 @@ static const luaL_Reg mathlib[] = {
{"sign", math_sign}, {"sign", math_sign},
{"round", math_round}, {"round", math_round},
{"map", math_map}, {"map", math_map},
{"lerp", math_lerp},
{NULL, NULL}, {NULL, NULL},
}; };
@ -483,11 +482,5 @@ int luaopen_math(lua_State* L)
lua_pushnumber(L, HUGE_VAL); lua_pushnumber(L, HUGE_VAL);
lua_setfield(L, -2, "huge"); lua_setfield(L, -2, "huge");
if (FFlag::LuauMathLerp)
{
lua_pushcfunction(L, math_lerp, "lerp");
lua_setfield(L, -2, "lerp");
}
return 1; return 1;
} }

View file

@ -6,8 +6,6 @@
#include <math.h> #include <math.h>
LUAU_FASTFLAGVARIABLE(LuauVector2Constructor)
static int vector_create(lua_State* L) static int vector_create(lua_State* L)
{ {
// checking argument count to avoid accepting 'nil' as a valid value // checking argument count to avoid accepting 'nil' as a valid value
@ -15,7 +13,7 @@ static int vector_create(lua_State* L)
double x = luaL_checknumber(L, 1); double x = luaL_checknumber(L, 1);
double y = luaL_checknumber(L, 2); double y = luaL_checknumber(L, 2);
double z = FFlag::LuauVector2Constructor ? (count >= 3 ? luaL_checknumber(L, 3) : 0.0) : luaL_checknumber(L, 3); double z = count >= 3 ? luaL_checknumber(L, 3) : 0.0;
#if LUA_VECTOR_SIZE == 4 #if LUA_VECTOR_SIZE == 4
double w = count >= 4 ? luaL_checknumber(L, 4) : 0.0; double w = count >= 4 ? luaL_checknumber(L, 4) : 0.0;

View file

@ -19,10 +19,10 @@ LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(StudioReportLuauAny2) LUAU_FASTFLAG(StudioReportLuauAny2)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAG(LuauAlwaysFillInFunctionCallDiscriminantTypes)
LUAU_FASTFLAG(LuauStoreCSTData) LUAU_FASTFLAG(LuauStoreCSTData)
LUAU_FASTFLAG(LuauAstTypeGroup) LUAU_FASTFLAG(LuauAstTypeGroup2)
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment) LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
LUAU_FASTFLAG(LuauSkipNoRefineDuringRefinement)
struct ATSFixture : BuiltinsFixture struct ATSFixture : BuiltinsFixture
@ -76,7 +76,7 @@ export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))
LUAU_ASSERT(module->ats.typeInfo.size() == 1); LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias); LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
if (FFlag::LuauStoreCSTData && FFlag::LuauAstTypeGroup) if (FFlag::LuauStoreCSTData && FFlag::LuauAstTypeGroup2)
{ {
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0& (<t0...>( true | any)->(''))"); LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0& (<t0...>( true | any)->(''))");
} }
@ -84,7 +84,7 @@ export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))
{ {
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0 &(<t0...>( true | any)->(''))"); LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0 &(<t0...>( true | any)->(''))");
} }
else if (FFlag::LuauAstTypeGroup) else if (FFlag::LuauAstTypeGroup2)
{ {
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0& (<t0 ...>(true | any)->(''))"); LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0& (<t0 ...>(true | any)->(''))");
} }
@ -429,7 +429,6 @@ TEST_CASE_FIXTURE(ATSFixture, "CannotExtendTable")
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::StudioReportLuauAny2, true}, {FFlag::StudioReportLuauAny2, true},
{FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes, true},
}; };
fileResolver.source["game/Gui/Modules/A"] = R"( fileResolver.source["game/Gui/Modules/A"] = R"(
@ -449,7 +448,14 @@ end
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
LUAU_ASSERT(module->ats.typeInfo.size() == 0); if (FFlag::LuauSkipNoRefineDuringRefinement)
{
REQUIRE_EQ(module->ats.typeInfo.size(), 1);
CHECK_EQ(module->ats.typeInfo[0].code, Pattern::Assign);
CHECK_EQ(module->ats.typeInfo[0].node, "descendant.CollisionGroup = CAR_COLLISION_GROUP");
}
else
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
} }
TEST_CASE_FIXTURE(ATSFixture, "unknown_symbol") TEST_CASE_FIXTURE(ATSFixture, "unknown_symbol")
@ -522,7 +528,6 @@ TEST_CASE_FIXTURE(ATSFixture, "racing_collision_2")
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::StudioReportLuauAny2, true}, {FFlag::StudioReportLuauAny2, true},
{FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes, true},
}; };
fileResolver.source["game/Gui/Modules/A"] = R"( fileResolver.source["game/Gui/Modules/A"] = R"(
@ -590,7 +595,10 @@ initialize()
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
LUAU_ASSERT(module->ats.typeInfo.size() == 11); if (FFlag::LuauSkipNoRefineDuringRefinement)
CHECK_EQ(module->ats.typeInfo.size(), 12);
else
LUAU_ASSERT(module->ats.typeInfo.size() == 11);
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg); LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
if (FFlag::LuauStoreCSTData) if (FFlag::LuauStoreCSTData)
{ {

View file

@ -11,7 +11,7 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauAstTypeGroup) LUAU_FASTFLAG(LuauAstTypeGroup2)
struct JsonEncoderFixture struct JsonEncoderFixture
{ {
@ -473,10 +473,10 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation")
{ {
AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())"); AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())");
if (FFlag::LuauAstTypeGroup) if (FFlag::LuauAstTypeGroup2)
{ {
std::string_view expected = 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})"; R"({"type":"AstStatTypeAlias","location":"0,0 - 0,56","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,56","types":[{"type":"AstTypeGroup","location":"0,9 - 0,37","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,56","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

@ -23,7 +23,6 @@ LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost)
LUAU_FASTINT(LuauCompileLoopUnrollThreshold) LUAU_FASTINT(LuauCompileLoopUnrollThreshold)
LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost) LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost)
LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTFLAG(LuauVector2Constants)
using namespace Luau; using namespace Luau;
@ -5093,8 +5092,6 @@ L0: RETURN R3 -1
TEST_CASE("VectorConstants") TEST_CASE("VectorConstants")
{ {
ScopedFastFlag luauVector2Constants{FFlag::LuauVector2Constants, true};
CHECK_EQ("\n" + compileFunction("return vector.create(1, 2)", 0, 2, 0), R"( CHECK_EQ("\n" + compileFunction("return vector.create(1, 2)", 0, 2, 0), R"(
LOADK R0 K0 [1, 2, 0] LOADK R0 K0 [1, 2, 0]
RETURN R0 1 RETURN R0 1

View file

@ -32,15 +32,10 @@ void luaC_fullgc(lua_State* L);
void luaC_validate(lua_State* L); void luaC_validate(lua_State* L);
LUAU_FASTFLAG(LuauLibWhereErrorAutoreserve) LUAU_FASTFLAG(LuauLibWhereErrorAutoreserve)
LUAU_FASTFLAG(LuauMathLerp)
LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_DYNAMIC_FASTFLAG(LuauStackLimit) LUAU_DYNAMIC_FASTFLAG(LuauStackLimit)
LUAU_FASTFLAG(LuauVectorLibNativeCodegen)
LUAU_FASTFLAG(LuauVectorLibNativeDot) LUAU_FASTFLAG(LuauVectorLibNativeDot)
LUAU_FASTFLAG(LuauVector2Constructor)
LUAU_FASTFLAG(LuauBufferBitMethods2)
LUAU_FASTFLAG(LuauCodeGenLimitLiveSlotReuse)
LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC) LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC)
static lua_CompileOptions defaultOptions() static lua_CompileOptions defaultOptions()
@ -655,15 +650,11 @@ TEST_CASE("Basic")
TEST_CASE("Buffers") TEST_CASE("Buffers")
{ {
ScopedFastFlag luauBufferBitMethods{FFlag::LuauBufferBitMethods2, true};
runConformance("buffers.luau"); runConformance("buffers.luau");
} }
TEST_CASE("Math") TEST_CASE("Math")
{ {
ScopedFastFlag LuauMathLerp{FFlag::LuauMathLerp, true};
runConformance("math.luau"); runConformance("math.luau");
} }
@ -896,9 +887,7 @@ TEST_CASE("Vector")
TEST_CASE("VectorLibrary") TEST_CASE("VectorLibrary")
{ {
ScopedFastFlag luauVectorLibNativeCodegen{FFlag::LuauVectorLibNativeCodegen, true};
ScopedFastFlag luauVectorLibNativeDot{FFlag::LuauVectorLibNativeDot, true}; ScopedFastFlag luauVectorLibNativeDot{FFlag::LuauVectorLibNativeDot, true};
ScopedFastFlag luauVector2Constructor{FFlag::LuauVector2Constructor, true};
lua_CompileOptions copts = defaultOptions(); lua_CompileOptions copts = defaultOptions();
@ -989,9 +978,6 @@ static void populateRTTI(lua_State* L, Luau::TypeId type)
TEST_CASE("Types") TEST_CASE("Types")
{ {
ScopedFastFlag luauVector2Constructor{FFlag::LuauVector2Constructor, true};
ScopedFastFlag luauMathLerp{FFlag::LuauMathLerp, true};
runConformance( runConformance(
"types.luau", "types.luau",
[](lua_State* L) [](lua_State* L)
@ -2622,8 +2608,6 @@ TEST_CASE("SafeEnv")
TEST_CASE("Native") TEST_CASE("Native")
{ {
ScopedFastFlag luauCodeGenLimitLiveSlotReuse{FFlag::LuauCodeGenLimitLiveSlotReuse, true};
// This tests requires code to run natively, otherwise all 'is_native' checks will fail // This tests requires code to run natively, otherwise all 'is_native' checks will fail
if (!codegen || !luau_codegen_supported()) if (!codegen || !luau_codegen_supported())
return; return;

View file

@ -53,6 +53,7 @@ void ConstraintGeneratorFixture::solve(const std::string& code)
NotNull{&typeFunctionRuntime}, NotNull{&typeFunctionRuntime},
NotNull{rootScope}, NotNull{rootScope},
constraints, constraints,
NotNull{&cg->scopeToFunction},
"MainModule", "MainModule",
NotNull(&moduleResolver), NotNull(&moduleResolver),
{}, {},

View file

@ -25,7 +25,6 @@
static const char* mainModuleName = "MainModule"; static const char* mainModuleName = "MainModule";
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauVector2Constructor)
LUAU_FASTFLAG(DebugLuauLogSolverToJsonFile) LUAU_FASTFLAG(DebugLuauLogSolverToJsonFile)
LUAU_FASTFLAGVARIABLE(DebugLuauForceAllNewSolverTests); LUAU_FASTFLAGVARIABLE(DebugLuauForceAllNewSolverTests);
@ -593,8 +592,6 @@ LoadDefinitionFileResult Fixture::loadDefinition(const std::string& source, bool
BuiltinsFixture::BuiltinsFixture(bool prepareAutocomplete) BuiltinsFixture::BuiltinsFixture(bool prepareAutocomplete)
: Fixture(prepareAutocomplete) : Fixture(prepareAutocomplete)
{ {
ScopedFastFlag luauVector2Constructor{FFlag::LuauVector2Constructor, true};
Luau::unfreeze(frontend.globals.globalTypes); Luau::unfreeze(frontend.globals.globalTypes);
Luau::unfreeze(frontend.globalsForAutocomplete.globalTypes); Luau::unfreeze(frontend.globalsForAutocomplete.globalTypes);

View file

@ -10,6 +10,7 @@
#include "Luau/Frontend.h" #include "Luau/Frontend.h"
#include "Luau/AutocompleteTypes.h" #include "Luau/AutocompleteTypes.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "ScopedFlags.h"
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
@ -21,7 +22,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauAllowFragmentParsing);
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete) LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAG(LuauSymbolEquality); LUAU_FASTFLAG(LuauSymbolEquality);
LUAU_FASTFLAG(LuauStoreSolverTypeOnModule); LUAU_FASTFLAG(LuauStoreSolverTypeOnModule);
@ -35,6 +35,7 @@ LUAU_FASTFLAG(LuauMixedModeDefFinderTraversesTypeOf)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking) LUAU_FASTFLAG(LuauBetterReverseDependencyTracking)
LUAU_FASTFLAG(LuauAutocompleteUsesModuleForTypeCompatibility)
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ClassType*> ptr, std::optional<std::string> contents) static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ClassType*> ptr, std::optional<std::string> contents)
{ {
@ -64,8 +65,7 @@ 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[7] = { ScopedFastFlag sffs[6] = {
{FFlag::LuauAllowFragmentParsing, true},
{FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete, true}, {FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete, true},
{FFlag::LuauStoreSolverTypeOnModule, true}, {FFlag::LuauStoreSolverTypeOnModule, true},
{FFlag::LuauSymbolEquality, true}, {FFlag::LuauSymbolEquality, true},
@ -859,17 +859,16 @@ return module)";
{ {
ScopedFastFlag sff{FFlag::LuauSolverV2, false}; ScopedFastFlag sff{FFlag::LuauSolverV2, false};
ScopedFastFlag sff2{FFlag::LuauCloneIncrementalModule, true};
ScopedFastFlag sff3{FFlag::LuauFreeTypesMustHaveBounds, true};
checkAndExamine(source, "module", "{ }"); checkAndExamine(source, "module", "{ }");
// [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment fragmentACAndCheck(updated1, Position{1, 17}, "module", "{ }", "{ a: (%error-id%: unknown) -> () }");
// early return since the following checking will fail, which it shouldn't! fragmentACAndCheck(updated2, Position{1, 18}, "module", "{ }", "{ ab: (%error-id%: unknown) -> () }");
// fragmentACAndCheck(updated1, Position{1, 17}, "module", "{ }", "{ a: (%error-id%: unknown) -> () }");
// fragmentACAndCheck(updated2, Position{1, 18}, "module", "{ }", "{ ab: (%error-id%: unknown) -> () }");
} }
{ {
ScopedFastFlag sff{FFlag::LuauSolverV2, true}; ScopedFastFlag sff{FFlag::LuauSolverV2, true};
checkAndExamine(source, "module", "{ }"); checkAndExamine(source, "module", "{ }");
// [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment // [TODO] CLI-140762 Fragment autocomplete still doesn't return correct result when LuauSolverV2 is on
// early return since the following checking will fail, which it shouldn't!
return; return;
fragmentACAndCheck(updated1, Position{1, 17}, "module", "{ }", "{ a: (%error-id%: unknown) -> () }"); fragmentACAndCheck(updated1, Position{1, 17}, "module", "{ }", "{ a: (%error-id%: unknown) -> () }");
fragmentACAndCheck(updated2, Position{1, 18}, "module", "{ }", "{ ab: (%error-id%: unknown) -> () }"); fragmentACAndCheck(updated2, Position{1, 18}, "module", "{ }", "{ ab: (%error-id%: unknown) -> () }");
@ -2092,5 +2091,15 @@ return module
autocompleteFragmentInBothSolvers(source, updated, Position{1, 18}, [](FragmentAutocompleteResult& result) {}); autocompleteFragmentInBothSolvers(source, updated, Position{1, 18}, [](FragmentAutocompleteResult& result) {});
} }
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "ice_caused_by_mixed_mode_use")
{
ScopedFastFlag sff{FFlag::LuauAutocompleteUsesModuleForTypeCompatibility, true};
const std::string source = "--[[\n\tPackage link auto-generated by Rotriever\n]]\nlocal PackageIndex = script.Parent._Index\n\nlocal Package = "
"require(PackageIndex[\"ReactOtter\"][\"ReactOtter\"])\n\nexport type Goal = Package.Goal\nexport type SpringOptions "
"= Package.SpringOptions\n\n\nreturn Pa";
autocompleteFragmentInBothSolvers(source, source, Position{11,9}, [](auto& _){
});
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -15,8 +15,6 @@
#include "doctest.h" #include "doctest.h"
#include <iostream> #include <iostream>
LUAU_FASTFLAG(LuauCountSelfCallsNonstrict)
LUAU_FASTFLAG(LuauVector2Constructor)
LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals) LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAG(LuauNonStrictVisitorImprovements) LUAU_FASTFLAG(LuauNonStrictVisitorImprovements)
@ -619,9 +617,6 @@ buffer.readi8(b, 0)
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "nonstrict_method_calls") TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "nonstrict_method_calls")
{ {
ScopedFastFlag luauCountSelfCallsNonstrict{FFlag::LuauCountSelfCallsNonstrict, true};
ScopedFastFlag luauVector2Constructor{FFlag::LuauVector2Constructor, true};
Luau::unfreeze(frontend.globals.globalTypes); Luau::unfreeze(frontend.globals.globalTypes);
Luau::unfreeze(frontend.globalsForAutocomplete.globalTypes); Luau::unfreeze(frontend.globalsForAutocomplete.globalTypes);

View file

@ -22,7 +22,7 @@ LUAU_FASTFLAG(LuauErrorRecoveryForClassNames)
LUAU_FASTFLAG(LuauFixFunctionNameStartPosition) LUAU_FASTFLAG(LuauFixFunctionNameStartPosition)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon) LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAG(LuauAstTypeGroup) LUAU_FASTFLAG(LuauAstTypeGroup2)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation) LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
namespace namespace
@ -372,7 +372,7 @@ TEST_CASE_FIXTURE(Fixture, "return_type_is_an_intersection_type_if_led_with_one_
AstTypeIntersection* returnAnnotation = annotation->returnTypes.types.data[0]->as<AstTypeIntersection>(); AstTypeIntersection* returnAnnotation = annotation->returnTypes.types.data[0]->as<AstTypeIntersection>();
REQUIRE(returnAnnotation != nullptr); REQUIRE(returnAnnotation != nullptr);
if (FFlag::LuauAstTypeGroup) if (FFlag::LuauAstTypeGroup2)
CHECK(returnAnnotation->types.data[0]->as<AstTypeGroup>()); CHECK(returnAnnotation->types.data[0]->as<AstTypeGroup>());
else else
CHECK(returnAnnotation->types.data[0]->as<AstTypeReference>()); CHECK(returnAnnotation->types.data[0]->as<AstTypeReference>());
@ -2451,7 +2451,7 @@ TEST_CASE_FIXTURE(Fixture, "leading_union_intersection_with_single_type_preserve
TEST_CASE_FIXTURE(Fixture, "parse_simple_ast_type_group") TEST_CASE_FIXTURE(Fixture, "parse_simple_ast_type_group")
{ {
ScopedFastFlag _{FFlag::LuauAstTypeGroup, true}; ScopedFastFlag _{FFlag::LuauAstTypeGroup2, true};
AstStatBlock* stat = parse(R"( AstStatBlock* stat = parse(R"(
type Foo = (string) type Foo = (string)
@ -2469,7 +2469,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_simple_ast_type_group")
TEST_CASE_FIXTURE(Fixture, "parse_nested_ast_type_group") TEST_CASE_FIXTURE(Fixture, "parse_nested_ast_type_group")
{ {
ScopedFastFlag _{FFlag::LuauAstTypeGroup, true}; ScopedFastFlag _{FFlag::LuauAstTypeGroup2, true};
AstStatBlock* stat = parse(R"( AstStatBlock* stat = parse(R"(
type Foo = ((string)) type Foo = ((string))
@ -2490,7 +2490,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_nested_ast_type_group")
TEST_CASE_FIXTURE(Fixture, "parse_return_type_ast_type_group") TEST_CASE_FIXTURE(Fixture, "parse_return_type_ast_type_group")
{ {
ScopedFastFlag _{FFlag::LuauAstTypeGroup, true}; ScopedFastFlag _{FFlag::LuauAstTypeGroup2, true};
AstStatBlock* stat = parse(R"( AstStatBlock* stat = parse(R"(
type Foo = () -> (string) type Foo = () -> (string)
@ -3813,7 +3813,7 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type")
auto unionTy = paramTy.type->as<AstTypeUnion>(); auto unionTy = paramTy.type->as<AstTypeUnion>();
LUAU_ASSERT(unionTy); LUAU_ASSERT(unionTy);
CHECK_EQ(unionTy->types.size, 2); CHECK_EQ(unionTy->types.size, 2);
if (FFlag::LuauAstTypeGroup) if (FFlag::LuauAstTypeGroup2)
{ {
auto groupTy = unionTy->types.data[0]->as<AstTypeGroup>(); // (() -> ()) auto groupTy = unionTy->types.data[0]->as<AstTypeGroup>(); // (() -> ())
REQUIRE(groupTy); REQUIRE(groupTy);

View file

@ -14,7 +14,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauStoreCSTData) LUAU_FASTFLAG(LuauStoreCSTData)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon) LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAG(LuauAstTypeGroup); LUAU_FASTFLAG(LuauAstTypeGroup2);
LUAU_FASTFLAG(LexerFixInterpStringStart) LUAU_FASTFLAG(LexerFixInterpStringStart)
TEST_SUITE_BEGIN("TranspilerTests"); TEST_SUITE_BEGIN("TranspilerTests");
@ -1175,7 +1175,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested_3")
{ {
std::string code = "local a: nil | (string & number)"; std::string code = "local a: nil | (string & number)";
if (FFlag::LuauAstTypeGroup) if (FFlag::LuauAstTypeGroup2)
CHECK_EQ("local a: (string & number)?", transpile(code, {}, true).code); CHECK_EQ("local a: (string & number)?", transpile(code, {}, true).code);
else else
CHECK_EQ("local a: ( string & number)?", transpile(code, {}, true).code); CHECK_EQ("local a: ( string & number)?", transpile(code, {}, true).code);
@ -1732,4 +1732,24 @@ TEST_CASE("transpile_type_table_preserve_property_definition_style")
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
} }
TEST_CASE("transpile_types_preserve_parentheses_style")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData, true},
{FFlag::LuauAstTypeGroup2, true},
};
std::string code = R"( type Foo = number )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo = (number) )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo = ((number)) )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"( type Foo = ( (number) ) )";
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -16,8 +16,8 @@ LUAU_FASTFLAG(LuauSolverV2)
struct TxnLogFixture struct TxnLogFixture
{ {
TxnLog log{/*useScopes*/ true}; TxnLog log;
TxnLog log2{/*useScopes*/ true}; TxnLog log2;
TypeArena arena; TypeArena arena;
BuiltinTypes builtinTypes; BuiltinTypes builtinTypes;
@ -33,39 +33,6 @@ struct TxnLogFixture
TEST_SUITE_BEGIN("TxnLog"); TEST_SUITE_BEGIN("TxnLog");
TEST_CASE_FIXTURE(TxnLogFixture, "colliding_union_incoming_type_has_greater_scope")
{
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
log.replace(c, BoundType{a});
log2.replace(a, BoundType{c});
CHECK(nullptr != log.pending(c));
log.concatAsUnion(std::move(log2), NotNull{&arena});
// 'a has greater scope than 'c, so we expect the incoming binding of 'a to
// be discarded.
CHECK(nullptr == log.pending(a));
const PendingType* pt = log.pending(c);
REQUIRE(pt != nullptr);
CHECK(!pt->dead);
const BoundType* bt = get_if<BoundType>(&pt->pending.ty);
CHECK(a == bt->boundTo);
log.commit();
REQUIRE(get<FreeType>(a));
const BoundType* bound = get<BoundType>(c);
REQUIRE(bound);
CHECK(a == bound->boundTo);
}
TEST_CASE_FIXTURE(TxnLogFixture, "colliding_union_incoming_type_has_lesser_scope") TEST_CASE_FIXTURE(TxnLogFixture, "colliding_union_incoming_type_has_lesser_scope")
{ {
ScopedFastFlag sff{FFlag::LuauSolverV2, true}; ScopedFastFlag sff{FFlag::LuauSolverV2, true};

View file

@ -12,6 +12,7 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTypeFunSingletonEquality) LUAU_FASTFLAG(LuauTypeFunSingletonEquality)
LUAU_FASTFLAG(LuauUserTypeFunTypeofReturnsType) LUAU_FASTFLAG(LuauUserTypeFunTypeofReturnsType)
LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
LUAU_FASTFLAG(LuauTypeFunPrintFix) LUAU_FASTFLAG(LuauTypeFunPrintFix)
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
@ -1960,4 +1961,25 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_print_tab_char_fix")
CHECK_EQ("1\t2", toString(result.errors[0])); CHECK_EQ("1\t2", toString(result.errors[0]));
} }
TEST_CASE_FIXTURE(ClassFixture, "udtf_class_parent_ops")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag readWriteParents{FFlag::LuauTypeFunReadWriteParents, true};
CheckResult result = check(R"(
type function readparentof(arg)
return arg:readparent()
end
type function writeparentof(arg)
return arg:writeparent()
end
local function ok1(idx: readparentof<ChildClass>): BaseClass return idx end
local function ok2(idx: writeparentof<ChildClass>): BaseClass return idx end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -9,7 +9,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauNewSolverPrePopulateClasses)
LUAU_FASTFLAG(LuauClipNestedAndRecursiveUnion) LUAU_FASTFLAG(LuauClipNestedAndRecursiveUnion)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauPreventReentrantTypeFunctionReduction) LUAU_FASTFLAG(LuauPreventReentrantTypeFunctionReduction)
@ -497,7 +496,6 @@ TEST_CASE_FIXTURE(Fixture, "class_definition_indexer")
TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes") TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
{ {
ScopedFastFlag _{FFlag::LuauNewSolverPrePopulateClasses, true};
loadDefinition(R"( loadDefinition(R"(
declare class Channel declare class Channel
Messages: { Message } Messages: { Message }

View file

@ -16,11 +16,14 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauSubtypingFixTailPack) LUAU_FASTFLAG(LuauSubtypingFixTailPack)
LUAU_FASTFLAG(LuauUngeneralizedTypesForRecursiveFunctions)
TEST_SUITE_BEGIN("TypeInferFunctions"); TEST_SUITE_BEGIN("TypeInferFunctions");
@ -3041,4 +3044,48 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "coroutine_wrap_result_call")
// New solver still reports an error in this case, but the main goal of the test is to not crash // New solver still reports an error in this case, but the main goal of the test is to not crash
} }
TEST_CASE_FIXTURE(Fixture, "recursive_function_calls_should_not_use_the_generalized_type")
{
ScopedFastFlag crashOnForce{FFlag::DebugLuauAssertOnForcedConstraint, true};
ScopedFastFlag sff{FFlag::LuauUngeneralizedTypesForRecursiveFunctions, true};
CheckResult result = check(R"(
--!strict
function random()
return true -- chosen by fair coin toss
end
local f
f = 5
function f()
if random() then f() end
end
)");
if (FFlag::LuauSolverV2)
LUAU_REQUIRE_NO_ERRORS(result);
else
LUAU_REQUIRE_ERRORS(result); // errors without typestate, obviously
}
TEST_CASE_FIXTURE(Fixture, "recursive_function_calls_should_not_use_the_generalized_type_2")
{
ScopedFastFlag crashOnForce{FFlag::DebugLuauAssertOnForcedConstraint, true};
CheckResult result = check(R"(
--!strict
function random()
return true -- chosen by fair coin toss
end
local function f()
if random() then f() end
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -12,7 +12,6 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNewSolverPopulateTableLocations)
using namespace Luau; using namespace Luau;
@ -464,13 +463,10 @@ local b: B.T = a
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
if (FFlag::LuauNewSolverPopulateTableLocations) CHECK(
CHECK( toString(result.errors.at(0)) ==
toString(result.errors.at(0)) == "Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'; at [read \"x\"], number is not exactly string"
"Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'; at [read \"x\"], number is not exactly string" );
);
else
CHECK(toString(result.errors.at(0)) == "Type 'T' could not be converted into 'T'; at [read \"x\"], number is not exactly string");
} }
else else
{ {
@ -513,13 +509,10 @@ local b: B.T = a
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
if (FFlag::LuauNewSolverPopulateTableLocations) CHECK(
CHECK( toString(result.errors.at(0)) ==
toString(result.errors.at(0)) == "Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'; at [read \"x\"], number is not exactly string"
"Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'; at [read \"x\"], number is not exactly string" );
);
else
CHECK(toString(result.errors.at(0)) == "Type 'T' could not be converted into 'T'; at [read \"x\"], number is not exactly string");
} }
else else
{ {

View file

@ -557,7 +557,7 @@ TEST_CASE_FIXTURE(Fixture, "dcr_can_partially_dispatch_a_constraint")
TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together") TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
{ {
DOES_NOT_PASS_NEW_SOLVER_GUARD(); ScopedFastFlag sff{FFlag::LuauSolverV2, false};
TypeArena arena; TypeArena arena;
TypeId nilType = builtinTypes->nilType; TypeId nilType = builtinTypes->nilType;
@ -575,9 +575,6 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
Unifier u{NotNull{&normalizer}, NotNull{scope.get()}, Location{}, Variance::Covariant}; Unifier u{NotNull{&normalizer}, NotNull{scope.get()}, Location{}, Variance::Covariant};
if (FFlag::LuauSolverV2)
u.enableNewSolver();
u.tryUnify(option1, option2); u.tryUnify(option1, option2);
CHECK(!u.failure); CHECK(!u.failure);
@ -987,7 +984,7 @@ TEST_CASE_FIXTURE(Fixture, "floating_generics_should_not_be_allowed")
TEST_CASE_FIXTURE(Fixture, "free_options_can_be_unified_together") TEST_CASE_FIXTURE(Fixture, "free_options_can_be_unified_together")
{ {
DOES_NOT_PASS_NEW_SOLVER_GUARD(); ScopedFastFlag sff{FFlag::LuauSolverV2, false};
TypeArena arena; TypeArena arena;
TypeId nilType = builtinTypes->nilType; TypeId nilType = builtinTypes->nilType;
@ -1005,9 +1002,6 @@ TEST_CASE_FIXTURE(Fixture, "free_options_can_be_unified_together")
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
Unifier u{NotNull{&normalizer}, NotNull{scope.get()}, Location{}, Variance::Covariant}; Unifier u{NotNull{&normalizer}, NotNull{scope.get()}, Location{}, Variance::Covariant};
if (FFlag::LuauSolverV2)
u.enableNewSolver();
u.tryUnify(option1, option2); u.tryUnify(option1, option2);
CHECK(!u.failure); CHECK(!u.failure);

View file

@ -10,6 +10,8 @@
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauGeneralizationRemoveRecursiveUpperBound2) LUAU_FASTFLAG(LuauGeneralizationRemoveRecursiveUpperBound2)
LUAU_FASTFLAG(LuauIntersectNotNil)
LUAU_FASTFLAG(LuauSkipNoRefineDuringRefinement)
using namespace Luau; using namespace Luau;
@ -2459,4 +2461,69 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi
CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({4, 24}))); CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({4, 24})));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "nonnil_refinement_on_generic")
{
ScopedFastFlag sff{FFlag::LuauIntersectNotNil, true};
CheckResult result = check(R"(
local function printOptional<T>(item: T?, printer: (T) -> string): string
if item ~= nil then
return printer(item)
else
return ""
end
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauSolverV2)
CHECK_EQ("T & ~nil", toString(requireTypeAtPosition({3, 31})));
else
CHECK_EQ("T", toString(requireTypeAtPosition({3, 31})));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "truthy_refinement_on_generic")
{
ScopedFastFlag sff{FFlag::LuauIntersectNotNil, true};
CheckResult result = check(R"(
local function printOptional<T>(item: T?, printer: (T) -> string): string
if item then
return printer(item)
else
return ""
end
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauSolverV2)
CHECK_EQ("T & ~(false?)", toString(requireTypeAtPosition({3, 31})));
else
CHECK_EQ("T", toString(requireTypeAtPosition({3, 31})));
}
TEST_CASE_FIXTURE(Fixture, "truthy_call_of_function_with_table_value_as_argument_should_not_refine_value_as_never")
{
ScopedFastFlag sff{FFlag::LuauSkipNoRefineDuringRefinement, true};
CheckResult result = check(R"(
type Item = {}
local function predicate(value: Item): boolean
return true
end
local function checkValue(value: Item)
if predicate(value) then
local _ = value
end
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("Item", toString(requireTypeAtPosition({8, 27})));
CHECK_EQ("Item", toString(requireTypeAtPosition({9, 28})));
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -18,8 +18,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
LUAU_FASTFLAG(LuauTableKeysAreRValues)
LUAU_FASTFLAG(LuauAllowNilAssignmentToIndexer)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAG(LuauTrackInteriorFreeTablesOnScope) LUAU_FASTFLAG(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAG(LuauDontInPlaceMutateTableType) LUAU_FASTFLAG(LuauDontInPlaceMutateTableType)
@ -1932,7 +1930,7 @@ TEST_CASE_FIXTURE(Fixture, "type_mismatch_on_massive_table_is_cut_short")
TEST_CASE_FIXTURE(Fixture, "ok_to_set_nil_even_on_non_lvalue_base_expr") TEST_CASE_FIXTURE(Fixture, "ok_to_set_nil_even_on_non_lvalue_base_expr")
{ {
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauAllowNilAssignmentToIndexer, true}}; ScopedFastFlag _{FFlag::LuauSolverV2, true};
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
local function f(): { [string]: number } local function f(): { [string]: number }
@ -1991,9 +1989,6 @@ TEST_CASE_FIXTURE(Fixture, "ok_to_set_nil_even_on_non_lvalue_base_expr")
TEST_CASE_FIXTURE(Fixture, "ok_to_set_nil_on_generic_map") TEST_CASE_FIXTURE(Fixture, "ok_to_set_nil_on_generic_map")
{ {
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauAllowNilAssignmentToIndexer, true}};
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
type MyMap<K, V> = { [K]: V } type MyMap<K, V> = { [K]: V }
function set<K, V>(m: MyMap<K, V>, k: K, v: V) function set<K, V>(m: MyMap<K, V>, k: K, v: V)
@ -2010,8 +2005,7 @@ TEST_CASE_FIXTURE(Fixture, "ok_to_set_nil_on_generic_map")
TEST_CASE_FIXTURE(Fixture, "key_setting_inference_given_nil_upper_bound") TEST_CASE_FIXTURE(Fixture, "key_setting_inference_given_nil_upper_bound")
{ {
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauAllowNilAssignmentToIndexer, true}}; ScopedFastFlag _{FFlag::LuauSolverV2, true};
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
local function setkey_object(t: { [string]: number }, v) local function setkey_object(t: { [string]: number }, v)
t.foo = v t.foo = v
@ -2942,16 +2936,12 @@ TEST_CASE_FIXTURE(Fixture, "table_length")
TEST_CASE_FIXTURE(Fixture, "nil_assign_doesnt_hit_indexer") TEST_CASE_FIXTURE(Fixture, "nil_assign_doesnt_hit_indexer")
{ {
// CLI-100076 - Assigning a table key to `nil` in the presence of an indexer should always be permitted LUAU_REQUIRE_NO_ERRORS(check("local a = {} a[0] = 7 a[0] = nil"));
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check("local a = {} a[0] = 7 a[0] = nil");
LUAU_REQUIRE_ERROR_COUNT(0, result);
} }
TEST_CASE_FIXTURE(Fixture, "wrong_assign_does_hit_indexer") TEST_CASE_FIXTURE(Fixture, "wrong_assign_does_hit_indexer")
{ {
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauAllowNilAssignmentToIndexer, true}}; ScopedFastFlag _{FFlag::LuauSolverV2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
local a = {} local a = {}
@ -5055,7 +5045,6 @@ TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager")
TEST_CASE_FIXTURE(BuiltinsFixture, "read_only_property_reads") TEST_CASE_FIXTURE(BuiltinsFixture, "read_only_property_reads")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag sff{FFlag::LuauTableKeysAreRValues, true};
// none of the `t.id` accesses here should error // none of the `t.id` accesses here should error
auto result = check(R"( auto result = check(R"(

View file

@ -23,7 +23,7 @@ LUAU_FASTINT(LuauCheckRecursionLimit)
LUAU_FASTINT(LuauNormalizeCacheLimit) LUAU_FASTINT(LuauNormalizeCacheLimit)
LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauAstTypeGroup) LUAU_FASTFLAG(LuauAstTypeGroup2)
LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals) LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAG(LuauInferLocalTypesInMultipleAssignments) LUAU_FASTFLAG(LuauInferLocalTypesInMultipleAssignments)
@ -1201,10 +1201,13 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
CHECK(3 == result.errors.size()); CHECK(3 == result.errors.size());
CHECK(Location{{2, 22}, {2, 41}} == result.errors[0].location); if (FFlag::LuauAstTypeGroup2)
CHECK(Location{{2, 22}, {2, 42}} == result.errors[0].location);
else
CHECK(Location{{2, 22}, {2, 41}} == result.errors[0].location);
CHECK(Location{{3, 22}, {3, 42}} == result.errors[1].location); CHECK(Location{{3, 22}, {3, 42}} == result.errors[1].location);
if (FFlag::LuauAstTypeGroup) if (FFlag::LuauAstTypeGroup2)
CHECK(Location{{3, 22}, {3, 40}} == result.errors[2].location); CHECK(Location{{3, 22}, {3, 41}} == result.errors[2].location);
else else
CHECK(Location{{3, 23}, {3, 40}} == result.errors[2].location); CHECK(Location{{3, 23}, {3, 40}} == result.errors[2].location);
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[0])); CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[0]));

View file

@ -341,43 +341,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "txnlog_preserves_pack_owner")
CHECK_EQ(a->owningArena, &arena); CHECK_EQ(a->owningArena, &arena);
} }
TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table")
{
TableType::Props freeProps{
{"foo", {builtinTypes->numberType}},
};
TypeId free = arena.addType(TableType{freeProps, std::nullopt, TypeLevel{}, TableState::Free});
TableType::Props indexProps{
{"foo", {builtinTypes->stringType}},
};
TypeId index = arena.addType(TableType{indexProps, std::nullopt, TypeLevel{}, TableState::Sealed});
TableType::Props mtProps{
{"__index", {index}},
};
TypeId mt = arena.addType(TableType{mtProps, std::nullopt, TypeLevel{}, TableState::Sealed});
TypeId target = arena.addType(TableType{TableState::Unsealed, TypeLevel{}});
TypeId metatable = arena.addType(MetatableType{target, mt});
state.enableNewSolver();
state.tryUnify(metatable, free);
state.log.commit();
REQUIRE_EQ(state.errors.size(), 1);
const std::string expected = R"(Type
'{ @metatable {| __index: {| foo: string |} |}, { } }'
could not be converted into
'{- foo: number -}'
caused by:
Type 'number' could not be converted into 'string')";
CHECK_EQ(expected, toString(state.errors[0]));
}
TEST_CASE_FIXTURE(TryUnifyFixture, "fuzz_tail_unification_issue") TEST_CASE_FIXTURE(TryUnifyFixture, "fuzz_tail_unification_issue")
{ {
TypePackVar variadicAny{VariadicTypePack{builtinTypes->anyType}}; TypePackVar variadicAny{VariadicTypePack{builtinTypes->anyType}};
@ -403,101 +366,6 @@ local l0:(any)&(typeof(_)),l0:(any)|(any) = _,_
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERRORS(result);
} }
static TypeId createTheType(TypeArena& arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope, TypeId freeTy)
{
/*
({|
render: (
(('a) -> ()) | {| current: 'a |}
) -> nil
|}) -> ()
*/
TypePackId emptyPack = arena.addTypePack({});
return arena.addType(FunctionType{
arena.addTypePack({arena.addType(TableType{
TableType::Props{
{{"render",
Property(arena.addType(FunctionType{
arena.addTypePack({arena.addType(UnionType{
{arena.addType(FunctionType{arena.addTypePack({freeTy}), emptyPack}),
arena.addType(TableType{TableType::Props{{"current", {freeTy}}}, std::nullopt, TypeLevel{}, scope, TableState::Sealed})}
})}),
arena.addTypePack({builtinTypes->nilType})
}))}}
},
std::nullopt,
TypeLevel{},
scope,
TableState::Sealed
})}),
emptyPack
});
};
// See CLI-71190
TEST_CASE_FIXTURE(TryUnifyFixture, "unifying_two_unions_under_dcr_does_not_create_a_BoundType_cycle")
{
const std::shared_ptr<Scope> scope = globalScope;
const std::shared_ptr<Scope> nestedScope = std::make_shared<Scope>(scope);
const TypeId outerType = arena.freshType(builtinTypes, scope.get());
const TypeId outerType2 = arena.freshType(builtinTypes, scope.get());
const TypeId innerType = arena.freshType(builtinTypes, nestedScope.get());
state.enableNewSolver();
SUBCASE("equal_scopes")
{
TypeId one = createTheType(arena, builtinTypes, scope.get(), outerType);
TypeId two = createTheType(arena, builtinTypes, scope.get(), outerType2);
state.tryUnify(one, two);
state.log.commit();
ToStringOptions opts;
CHECK(follow(outerType) == follow(outerType2));
}
SUBCASE("outer_scope_is_subtype")
{
TypeId one = createTheType(arena, builtinTypes, scope.get(), outerType);
TypeId two = createTheType(arena, builtinTypes, scope.get(), innerType);
state.tryUnify(one, two);
state.log.commit();
ToStringOptions opts;
CHECK(follow(outerType) == follow(innerType));
// The scope of outerType exceeds that of innerType. The latter should be bound to the former.
const BoundType* bt = get_if<BoundType>(&innerType->ty);
REQUIRE(bt);
CHECK(bt->boundTo == outerType);
}
SUBCASE("outer_scope_is_supertype")
{
TypeId one = createTheType(arena, builtinTypes, scope.get(), innerType);
TypeId two = createTheType(arena, builtinTypes, scope.get(), outerType);
state.tryUnify(one, two);
state.log.commit();
ToStringOptions opts;
CHECK(follow(outerType) == follow(innerType));
// The scope of outerType exceeds that of innerType. The latter should be bound to the former.
const BoundType* bt = get_if<BoundType>(&innerType->ty);
REQUIRE(bt);
CHECK(bt->boundTo == outerType);
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "table_unification_full_restart_recursion") TEST_CASE_FIXTURE(BuiltinsFixture, "table_unification_full_restart_recursion")
{ {
ScopedFastFlag luauUnifierRecursionOnRestart{FFlag::LuauUnifierRecursionOnRestart, true}; ScopedFastFlag luauUnifierRecursionOnRestart{FFlag::LuauUnifierRecursionOnRestart, true};