Compare commits

..

No commits in common. "master" and "0.669" have entirely different histories.

177 changed files with 3045 additions and 7442 deletions

View file

@ -57,7 +57,7 @@ struct AutocompleteEntry
// Set if this suggestion matches the type expected in the context
TypeCorrectKind typeCorrect = TypeCorrectKind::None;
std::optional<const ExternType*> containingExternType = std::nullopt;
std::optional<const ClassType*> containingClass = std::nullopt;
std::optional<const Property*> prop = std::nullopt;
std::optional<std::string> documentationSymbol = std::nullopt;
Tags tags;
@ -85,7 +85,7 @@ struct AutocompleteResult
};
using StringCompletionCallback =
std::function<std::optional<AutocompleteEntryMap>(std::string tag, std::optional<const ExternType*> ctx, std::optional<std::string> contents)>;
std::function<std::optional<AutocompleteEntryMap>(std::string tag, std::optional<const ClassType*> ctx, std::optional<std::string> contents)>;
constexpr char kGeneratedAnonymousFunctionEntryName[] = "function (anonymous autofilled)";

View file

@ -3,7 +3,6 @@
#include "Luau/Ast.h"
#include "Luau/Constraint.h"
#include "Luau/ConstraintSet.h"
#include "Luau/ControlFlow.h"
#include "Luau/DataFlowGraph.h"
#include "Luau/EqSatSimplification.h"
@ -92,8 +91,9 @@ struct ConstraintGenerator
// Constraints that go straight to the solver.
std::vector<ConstraintPtr> constraints;
// The set of all free types introduced during constraint generation.
DenseHashSet<TypeId> freeTypes{nullptr};
// Constraints that do not go to the solver right away. Other constraints
// will enqueue them during solving.
std::vector<ConstraintPtr> unqueuedConstraints;
// Map a function's signature scope back to its signature type.
DenseHashMap<Scope*, TypeId> scopeToFunction{nullptr};
@ -151,9 +151,6 @@ struct ConstraintGenerator
std::vector<RequireCycle> requireCycles
);
ConstraintSet run(AstStatBlock* block);
ConstraintSet runOnFragment(const ScopePtr& resumeScope, AstStatBlock* block);
/**
* The entry point to the ConstraintGenerator. This will construct a set
* of scopes, constraints, and free types that can be solved later.
@ -272,7 +269,7 @@ private:
ControlFlow visit(const ScopePtr& scope, AstStatTypeAlias* alias);
ControlFlow visit(const ScopePtr& scope, AstStatTypeFunction* function);
ControlFlow visit(const ScopePtr& scope, AstStatDeclareGlobal* declareGlobal);
ControlFlow visit(const ScopePtr& scope, AstStatDeclareExternType* declareExternType);
ControlFlow visit(const ScopePtr& scope, AstStatDeclareClass* declareClass);
ControlFlow visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction);
ControlFlow visit(const ScopePtr& scope, AstStatError* error);
@ -484,4 +481,9 @@ private:
TypeId simplifyUnion(const ScopePtr& scope, Location location, TypeId left, TypeId right);
};
/** Borrow a vector of pointers from a vector of owning pointers to constraints.
*/
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints);
} // namespace Luau

View file

@ -1,32 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Constraint.h"
#include "Luau/DenseHash.h"
#include "Luau/Error.h"
#include <vector>
namespace Luau
{
struct ConstraintSet
{
NotNull<Scope> rootScope;
std::vector<ConstraintPtr> constraints;
// The set of all free types created during constraint generation
DenseHashSet<TypeId> freeTypes{nullptr};
// Map a function's signature scope back to its signature type. Once we've
// dispatched all of the constraints pertaining to a particular free type,
// we use this mapping to generalize that free type.
DenseHashMap<Scope*, TypeId> scopeToFunction{nullptr};
// It is pretty uncommon for constraint generation to itself produce errors, but it can happen.
std::vector<TypeError> errors;
};
}

View file

@ -3,7 +3,6 @@
#pragma once
#include "Luau/Constraint.h"
#include "Luau/ConstraintSet.h"
#include "Luau/DataFlowGraph.h"
#include "Luau/DenseHash.h"
#include "Luau/EqSatSimplification.h"
@ -88,7 +87,6 @@ struct ConstraintSolver
NotNull<Simplifier> simplifier;
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
// The entire set of constraints that the solver is trying to resolve.
ConstraintSet constraintSet;
std::vector<NotNull<Constraint>> constraints;
NotNull<DenseHashMap<Scope*, TypeId>> scopeToFunction;
NotNull<Scope> rootScope;
@ -142,19 +140,6 @@ struct ConstraintSolver
DenseHashMap<TypeId, const Constraint*> typeFunctionsToFinalize{nullptr};
explicit ConstraintSolver(
NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
ModuleName moduleName,
NotNull<ModuleResolver> moduleResolver,
std::vector<RequireCycle> requireCycles,
DcrLogger* logger,
NotNull<const DataFlowGraph> dfg,
TypeCheckLimits limits,
ConstraintSet constraintSet
);
explicit ConstraintSolver(
NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier,
@ -189,9 +174,6 @@ struct ConstraintSolver
bool isDone() const;
private:
/// A helper that does most of the setup work that is shared between the two constructors.
void initFreeTypeTracking();
void generalizeOneType(TypeId ty);
/**
@ -450,10 +432,6 @@ public:
void fillInDiscriminantTypes(NotNull<const Constraint> constraint, const std::vector<std::optional<TypeId>>& discriminantTypes);
};
/** Borrow a vector of pointers from a vector of owning pointers to constraints.
*/
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints);
void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts);
} // namespace Luau

View file

@ -173,7 +173,7 @@ private:
ControlFlow visit(AstStatTypeFunction* f);
ControlFlow visit(AstStatDeclareGlobal* d);
ControlFlow visit(AstStatDeclareFunction* d);
ControlFlow visit(AstStatDeclareExternType* d);
ControlFlow visit(AstStatDeclareClass* d);
ControlFlow visit(AstStatError* error);
DataFlowResult visitExpr(AstExpr* e);

View file

@ -332,11 +332,11 @@ struct TypePackMismatch
bool operator==(const TypePackMismatch& rhs) const;
};
struct DynamicPropertyLookupOnExternTypesUnsafe
struct DynamicPropertyLookupOnClassesUnsafe
{
TypeId ty;
bool operator==(const DynamicPropertyLookupOnExternTypesUnsafe& rhs) const;
bool operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const;
};
struct UninhabitedTypeFunction
@ -455,13 +455,6 @@ struct UserDefinedTypeFunctionError
bool operator==(const UserDefinedTypeFunctionError& rhs) const;
};
struct ReservedIdentifier
{
std::string name;
bool operator==(const ReservedIdentifier& rhs) const;
};
using TypeErrorData = Variant<
TypeMismatch,
UnknownSymbol,
@ -499,7 +492,7 @@ using TypeErrorData = Variant<
TypesAreUnrelated,
NormalizationTooComplex,
TypePackMismatch,
DynamicPropertyLookupOnExternTypesUnsafe,
DynamicPropertyLookupOnClassesUnsafe,
UninhabitedTypeFunction,
UninhabitedTypePackFunction,
WhereClauseNeeded,
@ -511,8 +504,7 @@ using TypeErrorData = Variant<
UnexpectedTypeInSubtyping,
UnexpectedTypePackInSubtyping,
ExplicitFunctionAnnotationRecommended,
UserDefinedTypeFunctionError,
ReservedIdentifier>;
UserDefinedTypeFunctionError>;
struct TypeErrorSummary
{

View file

@ -20,7 +20,7 @@ struct SourceCode
None,
Module,
Script,
Local_DEPRECATED
Local
};
std::string source;
@ -117,7 +117,8 @@ struct FileResolver
return std::nullopt;
}
std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const;
// Make non-virtual when removing FFlagLuauImproveRequireByStringAutocomplete.
virtual std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const;
std::shared_ptr<RequireSuggester> requireSuggester;
};

View file

@ -16,24 +16,8 @@ struct GeneralizationParams
Polarity polarity = Polarity::None;
};
template<typename TID>
struct GeneralizationResult
{
std::optional<TID> result;
// True if the provided type was replaced with a generic.
bool wasReplacedByGeneric = false;
bool resourceLimitsExceeded = false;
explicit operator bool() const
{
return bool(result);
}
};
// Replace a single free type by its bounds according to the polarity provided.
GeneralizationResult<TypeId> generalizeType(
std::optional<TypeId> generalizeType(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
@ -42,7 +26,7 @@ GeneralizationResult<TypeId> generalizeType(
);
// Generalize one type pack
GeneralizationResult<TypePackId> generalizeTypePack(
std::optional<TypePackId> generalizeTypePack(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
@ -52,31 +36,11 @@ GeneralizationResult<TypePackId> generalizeTypePack(
void sealTable(NotNull<Scope> scope, TypeId ty);
/** Attempt to generalize a type.
*
* If generalizationTarget is set, then only that type will be replaced by its
* bounds. The way this is intended to be used is that ty is some function that
* is not fully generalized, and generalizationTarget is a type within its
* signature. There should be no further constraints that could affect the
* bounds of generalizationTarget.
*
* Returns nullopt if generalization failed due to resources limits.
*/
std::optional<TypeId> generalize(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes,
TypeId ty,
std::optional<TypeId> generalizationTarget = {}
);
void pruneUnnecessaryGenerics(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes,
TypeId ty
);
} // namespace Luau
}

View file

@ -133,9 +133,9 @@ struct GenericTypeFinder : TypeOnceVisitor
return false;
}
bool visit(TypeId ty, const Luau::ExternType&) override
bool visit(TypeId ty, const Luau::ClassType&) override
{
// During function instantiation, extern types are not traversed even if they have generics
// During function instantiation, classes are not traversed even if they have generics
return false;
}
};

View file

@ -181,7 +181,7 @@ struct NormalizedStringType
bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr);
struct NormalizedExternType
struct NormalizedClassType
{
/** Has the following structure:
*
@ -192,7 +192,7 @@ struct NormalizedExternType
*
* Each TypeId is a class type.
*/
std::unordered_map<TypeId, TypeIds> externTypes;
std::unordered_map<TypeId, TypeIds> classes;
/**
* In order to maintain a consistent insertion order, we use this vector to
@ -245,7 +245,7 @@ enum class NormalizationResult
};
// A normalized type is either any, unknown, or one of the form P | T | F | G where
// * P is a union of primitive types (including singletons, extern types and the error type)
// * P is a union of primitive types (including singletons, classes and the error type)
// * T is a union of table types
// * F is a union of an intersection of function types
// * G is a union of generic/free/blocked types, intersected with a normalized type
@ -260,7 +260,7 @@ struct NormalizedType
// This type is either never, boolean type, or a boolean singleton.
TypeId booleans;
NormalizedExternType externTypes;
NormalizedClassType classes;
// The error part of the type.
// This type is either never or the error type.
@ -333,7 +333,7 @@ struct NormalizedType
// Helpers that improve readability of the above (they just say if the component is present)
bool hasTops() const;
bool hasBooleans() const;
bool hasExternTypes() const;
bool hasClasses() const;
bool hasErrors() const;
bool hasNils() const;
bool hasNumbers() const;
@ -391,10 +391,10 @@ public:
void unionTysWithTy(TypeIds& here, TypeId there);
TypeId unionOfTops(TypeId here, TypeId there);
TypeId unionOfBools(TypeId here, TypeId there);
void unionExternTypesWithExternType(TypeIds& heres, TypeId there);
void unionExternTypes(TypeIds& heres, const TypeIds& theres);
void unionExternTypesWithExternType(NormalizedExternType& heres, TypeId there);
void unionExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres);
void unionClassesWithClass(TypeIds& heres, TypeId there);
void unionClasses(TypeIds& heres, const TypeIds& theres);
void unionClassesWithClass(NormalizedClassType& heres, TypeId there);
void unionClasses(NormalizedClassType& heres, const NormalizedClassType& theres);
void unionStrings(NormalizedStringType& here, const NormalizedStringType& there);
std::optional<TypePackId> unionOfTypePacks(TypePackId here, TypePackId there);
std::optional<TypeId> unionOfFunctions(TypeId here, TypeId there);
@ -423,8 +423,8 @@ public:
// ------- Normalizing intersections
TypeId intersectionOfTops(TypeId here, TypeId there);
TypeId intersectionOfBools(TypeId here, TypeId there);
void intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres);
void intersectExternTypesWithExternType(NormalizedExternType& heres, TypeId there);
void intersectClasses(NormalizedClassType& heres, const NormalizedClassType& theres);
void intersectClassesWithClass(NormalizedClassType& heres, TypeId there);
void intersectStrings(NormalizedStringType& here, const NormalizedStringType& there);
std::optional<TypePackId> intersectionOfTypePacks(TypePackId here, TypePackId there);
std::optional<TypeId> intersectionOfTables(TypeId here, TypeId there, SeenTablePropPairs& seenTablePropPairs, Set<TypeId>& seenSet);

View file

@ -16,7 +16,7 @@ struct Scope;
void quantify(TypeId ty, TypeLevel level);
// TODO: This is eerily similar to the pattern that NormalizedExternType
// TODO: This is eerily similar to the pattern that NormalizedClassType
// implements. We could, and perhaps should, merge them together.
template<typename K, typename V>
struct OrderedMap

View file

@ -24,9 +24,6 @@ SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<
SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right);
SimplifyResult simplifyIntersectWithTruthy(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId target);
SimplifyResult simplifyIntersectWithFalsy(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId target);
enum class Relation
{
Disjoint, // No A is a B or vice versa

View file

@ -86,7 +86,6 @@ struct TarjanNode
struct Tarjan
{
Tarjan();
virtual ~Tarjan() = default;
// Vertices (types and type packs) are indexed, using pre-order traversal.
DenseHashMap<TypeId, int> typeToIndex{nullptr};
@ -122,7 +121,7 @@ struct Tarjan
void visitChildren(TypePackId tp, int index);
void visitChild(TypeId ty);
void visitChild(TypePackId tp);
void visitChild(TypePackId ty);
template<typename Ty>
void visitChild(std::optional<Ty> ty)
@ -133,7 +132,7 @@ struct Tarjan
// Visit the root vertex.
TarjanResult visitRoot(TypeId ty);
TarjanResult visitRoot(TypePackId tp);
TarjanResult visitRoot(TypePackId ty);
// Used to reuse the object for a new operation
void clearTarjan(const TxnLog* log);
@ -151,12 +150,26 @@ struct Tarjan
void visitSCC(int index);
// Each subclass can decide to ignore some nodes.
virtual bool ignoreChildren(TypeId ty);
virtual bool ignoreChildren(TypePackId ty);
virtual bool ignoreChildren(TypeId ty)
{
return false;
}
virtual bool ignoreChildren(TypePackId ty)
{
return false;
}
// Some subclasses might ignore children visit, but not other actions like replacing the children
virtual bool ignoreChildrenVisit(TypeId ty);
virtual bool ignoreChildrenVisit(TypePackId ty);
virtual bool ignoreChildrenVisit(TypeId ty)
{
return ignoreChildren(ty);
}
virtual bool ignoreChildrenVisit(TypePackId ty)
{
return ignoreChildren(ty);
}
// Subclasses should say which vertices are dirty,
// and what to do with dirty vertices.
@ -171,7 +184,6 @@ struct Tarjan
struct Substitution : Tarjan
{
protected:
explicit Substitution(TypeArena* arena);
Substitution(const TxnLog* log_, TypeArena* arena);
/*
@ -220,23 +232,28 @@ public:
virtual TypeId clean(TypeId ty) = 0;
virtual TypePackId clean(TypePackId tp) = 0;
protected:
// Helper functions to create new types (used by subclasses)
template<typename T>
TypeId addType(T tv)
TypeId addType(const T& tv)
{
return arena->addType(std::move(tv));
return arena->addType(tv);
}
template<typename T>
TypePackId addTypePack(T tp)
TypePackId addTypePack(const T& tp)
{
return arena->addTypePack(TypePackVar{std::move(tp)});
return arena->addTypePack(TypePackVar{tp});
}
private:
template<typename Ty>
std::optional<Ty> replace(std::optional<Ty> ty);
std::optional<Ty> replace(std::optional<Ty> ty)
{
if (ty)
return replace(*ty);
else
return std::nullopt;
}
};
} // namespace Luau

View file

@ -22,7 +22,7 @@ struct InternalErrorReporter;
class TypeIds;
class Normalizer;
struct NormalizedExternType;
struct NormalizedClassType;
struct NormalizedFunctionType;
struct NormalizedStringType;
struct NormalizedType;
@ -121,7 +121,7 @@ struct SubtypingEnvironment
DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr};
/*
* See the test cyclic_tables_are_assumed_to_be_compatible_with_extern_types for
* See the test cyclic_tables_are_assumed_to_be_compatible_with_classes for
* details.
*
* An empty value is equivalent to a nonexistent key.
@ -229,8 +229,9 @@ private:
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ExternType* subExternType, const ExternType* superExternType, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ExternType* subExternType, TypeId superTy, const TableType* superTable, NotNull<Scope>);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull<Scope> scope);
SubtypingResult
isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable, NotNull<Scope>);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const FunctionType* subFunction,
@ -258,11 +259,11 @@ private:
);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const NormalizedExternType& subExternType,
const NormalizedExternType& superExternType,
const NormalizedClassType& subClass,
const NormalizedClassType& superClass,
NotNull<Scope> scope
);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedExternType& subExternType, const TypeIds& superTables, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables, NotNull<Scope> scope);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const NormalizedStringType& subString,

View file

@ -44,7 +44,6 @@ struct ToStringOptions
bool hideTableKind = false; // If true, all tables will be surrounded with plain '{}'
bool hideNamedFunctionTypeParameters = false; // If true, type parameters of functions will be hidden at top-level.
bool hideFunctionSelfArgument = false; // If true, `self: X` will be omitted from the function signature if the function has self
bool hideTableAliasExpansions = false; // If true, all table aliases will not be expanded
bool useQuestionMarks = true; // If true, use a postfix ? for options, else write them out as unions that include nil.
size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypes
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);

View file

@ -74,6 +74,10 @@ struct FreeType
// This one got promoted to explicit
explicit FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound, Polarity polarity = Polarity::Unknown);
explicit FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId upperBound);
// Old constructors
explicit FreeType(TypeLevel level);
explicit FreeType(Scope* scope);
FreeType(Scope* scope, TypeLevel level);
int index;
TypeLevel level;
@ -287,7 +291,7 @@ struct MagicFunctionCallContext
{
NotNull<struct ConstraintSolver> solver;
NotNull<const Constraint> constraint;
NotNull<const AstExprCall> callSite;
const class AstExprCall* callSite;
TypePackId arguments;
TypePackId result;
};
@ -532,15 +536,15 @@ struct ClassUserData
virtual ~ClassUserData() {}
};
/** The type of an external userdata exposed to Luau.
/** The type of a class.
*
* Extern types behave like tables in many ways, but there are some important differences:
* Classes behave like tables in many ways, but there are some important differences:
*
* The properties of a class are always exactly known.
* Extern types optionally have a parent type.
* Two different extern types that share the same properties are nevertheless distinct and mutually incompatible.
* Classes optionally have a parent class.
* Two different classes that share the same properties are nevertheless distinct and mutually incompatible.
*/
struct ExternType
struct ClassType
{
using Props = TableType::Props;
@ -554,7 +558,7 @@ struct ExternType
std::optional<Location> definitionLocation;
std::optional<TableIndexer> indexer;
ExternType(
ClassType(
Name name,
Props props,
std::optional<TypeId> parent,
@ -575,7 +579,7 @@ struct ExternType
{
}
ExternType(
ClassType(
Name name,
Props props,
std::optional<TypeId> parent,
@ -775,7 +779,7 @@ using TypeVariant = Unifiable::Variant<
FunctionType,
TableType,
MetatableType,
ExternType,
ClassType,
AnyType,
UnionType,
IntersectionType,
@ -986,7 +990,7 @@ public:
const TypeId threadType;
const TypeId bufferType;
const TypeId functionType;
const TypeId externType;
const TypeId classType;
const TypeId tableType;
const TypeId emptyTableType;
const TypeId trueType;
@ -998,7 +1002,6 @@ public:
const TypeId noRefineType;
const TypeId falsyType;
const TypeId truthyType;
const TypeId notNilType;
const TypeId optionalNumberType;
const TypeId optionalStringType;
@ -1019,10 +1022,10 @@ TypeLevel* getMutableLevel(TypeId ty);
std::optional<TypeLevel> getLevel(TypePackId tp);
const Property* lookupExternTypeProp(const ExternType* cls, const Name& name);
const Property* lookupClassProp(const ClassType* cls, const Name& name);
// Whether `cls` is a subclass of `parent`
bool isSubclass(const ExternType* cls, const ExternType* parent);
bool isSubclass(const ClassType* cls, const ClassType* parent);
Type* asMutable(TypeId ty);

View file

@ -37,6 +37,10 @@ struct TypeArena
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope);
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLevel level);
TypeId freshType_DEPRECATED(TypeLevel level);
TypeId freshType_DEPRECATED(Scope* scope);
TypeId freshType_DEPRECATED(Scope* scope, TypeLevel level);
TypePackId freshTypePack(Scope* scope, Polarity polarity = Polarity::Unknown);
TypePackId addTypePack(std::initializer_list<TypeId> types);

View file

@ -11,7 +11,7 @@ namespace Luau
struct TypeRehydrationOptions
{
std::unordered_set<std::string> bannedNames;
bool expandExternTypeProps = false;
bool expandClassProps = false;
};
void attachTypeData(SourceModule& source, Module& result);

View file

@ -160,7 +160,7 @@ private:
void visit(AstTypeList types);
void visit(AstStatDeclareFunction* stat);
void visit(AstStatDeclareGlobal* stat);
void visit(AstStatDeclareExternType* stat);
void visit(AstStatDeclareClass* stat);
void visit(AstStatError* stat);
void visit(AstExpr* expr, ValueContext context);
void visit(AstExprGroup* expr, ValueContext context);

View file

@ -155,9 +155,6 @@ struct TypeFunction
/// The reducer function for the type function.
ReducerFunction<TypeId> reducer;
/// If true, this type function can reduce even if it is parameterized on a generic.
bool canReduceGenerics = false;
};
/// Represents a type function that may be applied to map a series of types and
@ -170,9 +167,6 @@ struct TypePackFunction
/// The reducer function for the type pack function.
ReducerFunction<TypePackId> reducer;
/// If true, this type function can reduce even if it is parameterized on a generic.
bool canReduceGenerics = false;
};
struct FunctionGraphReductionResult

View file

@ -205,7 +205,7 @@ struct TypeFunctionTableType
std::optional<TypeFunctionTypeId> metatable;
};
struct TypeFunctionExternType
struct TypeFunctionClassType
{
using Name = std::string;
using Props = std::map<Name, TypeFunctionProperty>;
@ -222,7 +222,7 @@ struct TypeFunctionExternType
std::optional<TypeFunctionTypeId> readParent;
std::optional<TypeFunctionTypeId> writeParent;
TypeId externTy;
TypeId classTy;
};
struct TypeFunctionGenericType
@ -244,7 +244,7 @@ using TypeFunctionTypeVariant = Luau::Variant<
TypeFunctionNegationType,
TypeFunctionFunctionType,
TypeFunctionTableType,
TypeFunctionExternType,
TypeFunctionClassType,
TypeFunctionGenericType>;
struct TypeFunctionType

View file

@ -29,7 +29,7 @@ struct SingletonType;
struct FunctionType;
struct TableType;
struct MetatableType;
struct ExternType;
struct ClassType;
struct AnyType;
struct UnionType;
struct IntersectionType;

View file

@ -90,11 +90,11 @@ struct TypeChecker
ControlFlow check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatLocalFunction& function);
ControlFlow check(const ScopePtr& scope, const AstStatTypeAlias& typealias);
ControlFlow check(const ScopePtr& scope, const AstStatTypeFunction& typefunction);
ControlFlow check(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType);
ControlFlow check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass);
ControlFlow check(const ScopePtr& scope, const AstStatDeclareFunction& declaredFunction);
void prototype(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel = 0);
void prototype(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType);
void prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass);
ControlFlow checkBlock(const ScopePtr& scope, const AstStatBlock& statement);
ControlFlow checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& statement);
@ -130,7 +130,6 @@ struct TypeChecker
const PredicateVec& predicates = {}
);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType = std::nullopt);
WithPredicate<TypeId> checkExpr_DEPRECATED(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType = std::nullopt);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprError& expr);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType = std::nullopt);
@ -487,7 +486,7 @@ private:
/**
* A set of incorrect class definitions which is used to avoid a second-pass analysis.
*/
DenseHashSet<const AstStatDeclareExternType*> incorrectExternTypeDefinitions{nullptr};
DenseHashSet<const AstStatDeclareClass*> incorrectClassDefinitions{nullptr};
std::vector<std::pair<TypeId, ScopePtr>> deferredQuantification;
};

View file

@ -185,8 +185,6 @@ TypePackIterator begin(TypePackId tp);
TypePackIterator begin(TypePackId tp, const TxnLog* log);
TypePackIterator end(TypePackId tp);
TypePackId getTail(TypePackId tp);
using SeenSet = std::set<std::pair<const void*, const void*>>;
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs);

View file

@ -140,7 +140,7 @@ private:
void tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
void tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyWithExternType(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyNegations(TypeId subTy, TypeId superTy);
TypePackId tryApplyOverloadedFunction(TypeId function, const NormalizedFunctionType& overloads, TypePackId args);

View file

@ -126,7 +126,7 @@ struct GenericTypeVisitor
{
return visit(ty);
}
virtual bool visit(TypeId ty, const ExternType& etv)
virtual bool visit(TypeId ty, const ClassType& ctv)
{
return visit(ty);
}
@ -313,11 +313,11 @@ struct GenericTypeVisitor
traverse(mtv->metatable);
}
}
else if (auto etv = get<ExternType>(ty))
else if (auto ctv = get<ClassType>(ty))
{
if (visit(ty, *etv))
if (visit(ty, *ctv))
{
for (const auto& [name, prop] : etv->props)
for (const auto& [name, prop] : ctv->props)
{
if (FFlag::LuauSolverV2)
{
@ -335,16 +335,16 @@ struct GenericTypeVisitor
traverse(prop.type());
}
if (etv->parent)
traverse(*etv->parent);
if (ctv->parent)
traverse(*ctv->parent);
if (etv->metatable)
traverse(*etv->metatable);
if (ctv->metatable)
traverse(*ctv->metatable);
if (etv->indexer)
if (ctv->indexer)
{
traverse(etv->indexer->indexType);
traverse(etv->indexer->indexResultType);
traverse(ctv->indexer->indexType);
traverse(ctv->indexer->indexResultType);
}
}
}
@ -396,7 +396,7 @@ struct GenericTypeVisitor
traverse(unwrapped);
// Visiting into LazyType that hasn't been unwrapped may necessarily cause infinite expansion, so we don't do that on purpose.
// Asserting also makes no sense, because the type _will_ happen here, most likely as a property of some ExternType
// Asserting also makes no sense, because the type _will_ happen here, most likely as a property of some ClassType
// that doesn't need to be expanded.
}
else if (auto stv = get<SingletonType>(ty))

View file

@ -88,7 +88,7 @@ TypePackId Anyification::clean(TypePackId tp)
bool Anyification::ignoreChildren(TypeId ty)
{
if (get<ExternType>(ty))
if (get<ClassType>(ty))
return true;
return ty->persistent;

View file

@ -31,7 +31,7 @@ bool ApplyTypeFunction::ignoreChildren(TypeId ty)
{
if (get<GenericType>(ty))
return true;
else if (get<ExternType>(ty))
else if (get<ClassType>(ty))
return true;
else
return false;

View file

@ -8,8 +8,6 @@
#include <math.h>
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau
{
@ -433,16 +431,8 @@ struct AstJsonEncoder : public AstVisitor
if (node->self)
PROP(self);
PROP(args);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (node->returnAnnotation)
PROP(returnAnnotation);
}
else
{
if (node->returnAnnotation_DEPRECATED)
write("returnAnnotation", node->returnAnnotation_DEPRECATED);
}
PROP(vararg);
PROP(varargLocation);
if (node->varargAnnotation)
@ -475,26 +465,26 @@ struct AstJsonEncoder : public AstVisitor
writeRaw("}");
}
void write(class AstGenericType* genericType)
void write(const AstGenericType& genericType)
{
writeRaw("{");
bool c = pushComma();
writeType("AstGenericType");
write("name", genericType->name);
if (genericType->defaultValue)
write("luauType", genericType->defaultValue);
write("name", genericType.name);
if (genericType.defaultValue)
write("luauType", genericType.defaultValue);
popComma(c);
writeRaw("}");
}
void write(class AstGenericTypePack* genericTypePack)
void write(const AstGenericTypePack& genericTypePack)
{
writeRaw("{");
bool c = pushComma();
writeType("AstGenericTypePack");
write("name", genericTypePack->name);
if (genericTypePack->defaultValue)
write("luauType", genericTypePack->defaultValue);
write("name", genericTypePack.name);
if (genericTypePack.defaultValue)
write("luauType", genericTypePack.defaultValue);
popComma(c);
writeRaw("}");
}
@ -912,10 +902,7 @@ struct AstJsonEncoder : public AstVisitor
PROP(paramNames);
PROP(vararg);
PROP(varargLocation);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
PROP(retTypes);
else
write("retTypes", node->retTypes_DEPRECATED);
PROP(generics);
PROP(genericPacks);
}
@ -936,7 +923,7 @@ struct AstJsonEncoder : public AstVisitor
);
}
void write(const AstDeclaredExternTypeProperty& prop)
void write(const AstDeclaredClassProp& prop)
{
writeRaw("{");
bool c = pushComma();
@ -949,7 +936,7 @@ struct AstJsonEncoder : public AstVisitor
writeRaw("}");
}
void write(class AstStatDeclareExternType* node)
void write(class AstStatDeclareClass* node)
{
writeNode(
node,
@ -1061,10 +1048,7 @@ struct AstJsonEncoder : public AstVisitor
PROP(genericPacks);
PROP(argTypes);
PROP(argNames);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
PROP(returnTypes);
else
write("returnTypes", node->returnTypes_DEPRECATED);
}
);
}
@ -1445,7 +1429,7 @@ struct AstJsonEncoder : public AstVisitor
return false;
}
bool visit(class AstStatDeclareExternType* node) override
bool visit(class AstStatDeclareClass* node) override
{
write(node);
return false;

View file

@ -574,11 +574,11 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol);
}
}
else if (const ExternType* etv = get<ExternType>(parentTy))
else if (const ClassType* ctv = get<ClassType>(parentTy))
{
while (etv)
while (ctv)
{
if (auto propIt = etv->props.find(indexName->index.value); propIt != etv->props.end())
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
{
if (FFlag::LuauSolverV2)
{
@ -590,7 +590,7 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol
);
}
etv = etv->parent ? Luau::get<Luau::ExternType>(*etv->parent) : nullptr;
ctv = ctv->parent ? Luau::get<Luau::ClassType>(*ctv->parent) : nullptr;
}
}
else if (const PrimitiveType* ptv = get<PrimitiveType>(parentTy); ptv && ptv->metatable)

View file

@ -24,10 +24,13 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames)
LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUnionCopyPreviousSeen)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteMissingFollows)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
static const std::unordered_set<std::string> kStatementStartingKeywords =
{"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
@ -80,8 +83,6 @@ static ParenthesesRecommendation getParenRecommendationForIntersect(const Inters
ParenthesesRecommendation rec = ParenthesesRecommendation::None;
for (Luau::TypeId partId : intersect->parts)
{
if (FFlag::LuauAutocompleteMissingFollows)
partId = follow(partId);
if (auto partFunc = Luau::get<FunctionType>(partId))
{
rec = std::max(rec, getParenRecommendationForFunc(partFunc, nodes));
@ -307,7 +308,7 @@ static void autocompleteProps(
const std::vector<AstNode*>& nodes,
AutocompleteEntryMap& result,
std::unordered_set<TypeId>& seen,
std::optional<const ExternType*> containingExternType = std::nullopt
std::optional<const ClassType*> containingClass = std::nullopt
)
{
rootTy = follow(rootTy);
@ -330,8 +331,8 @@ static void autocompleteProps(
if (calledWithSelf == ftv->hasSelf)
return true;
// Calls on extern types require strict match between how function is declared and how it's called
if (get<ExternType>(rootTy))
// Calls on classes require strict match between how function is declared and how it's called
if (get<ClassType>(rootTy))
return false;
// When called with ':', but declared without 'self', it is invalid if a function has incompatible first argument or no arguments at all
@ -364,7 +365,7 @@ static void autocompleteProps(
return calledWithSelf;
};
auto fillProps = [&](const ExternType::Props& props)
auto fillProps = [&](const ClassType::Props& props)
{
for (const auto& [name, prop] : props)
{
@ -397,7 +398,7 @@ static void autocompleteProps(
prop.deprecated,
isWrongIndexer(type),
typeCorrect,
containingExternType,
containingClass,
&prop,
prop.documentationSymbol,
{},
@ -428,12 +429,12 @@ static void autocompleteProps(
}
};
if (auto cls = get<ExternType>(ty))
if (auto cls = get<ClassType>(ty))
{
containingExternType = containingExternType.value_or(cls);
containingClass = containingClass.value_or(cls);
fillProps(cls->props);
if (cls->parent)
autocompleteProps(module, typeArena, builtinTypes, rootTy, *cls->parent, indexType, nodes, result, seen, containingExternType);
autocompleteProps(module, typeArena, builtinTypes, rootTy, *cls->parent, indexType, nodes, result, seen, containingClass);
}
else if (auto tbl = get<TableType>(ty))
fillProps(tbl->props);
@ -487,7 +488,7 @@ static void autocompleteProps(
// If we don't do this, and we have the misfortune of receiving a
// recursive union like:
//
// t1 where t1 = t1 | ExternType
// t1 where t1 = t1 | Class
//
// Then we are on a one way journey to a stack overflow.
if (FFlag::LuauAutocompleteUnionCopyPreviousSeen)
@ -587,7 +588,7 @@ AutocompleteEntryMap autocompleteProps(
AutocompleteEntryMap autocompleteModuleTypes(const Module& module, const ScopePtr& scopeAtPosition, Position position, std::string_view moduleName)
{
AutocompleteEntryMap result;
ScopePtr startScope = scopeAtPosition;
ScopePtr startScope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position);
for (ScopePtr& scope = startScope; scope; scope = scope->parent)
{
if (auto it = scope->importedTypeBindings.find(std::string(moduleName)); it != scope->importedTypeBindings.end())
@ -699,30 +700,6 @@ static std::optional<TypeId> findTypeElementAt(const AstTypeList& astTypeList, T
return {};
}
static std::optional<TypeId> findTypeElementAt(AstTypePack* astTypePack, TypePackId tp, Position position)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
if (const auto typePack = astTypePack->as<AstTypePackExplicit>())
{
return findTypeElementAt(typePack->typeList, tp, position);
}
else if (const auto variadic = astTypePack->as<AstTypePackVariadic>())
{
if (variadic->location.containsClosed(position))
{
auto [_, tail] = flatten(tp);
if (tail)
{
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*tail)))
return findTypeElementAt(variadic->variadicType, vtp->ty, position);
}
}
}
return {};
}
static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Position position)
{
ty = follow(ty);
@ -743,17 +720,9 @@ static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Posi
if (auto element = findTypeElementAt(type->argTypes, ftv->argTypes, position))
return element;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (auto element = findTypeElementAt(type->returnTypes, ftv->retTypes, position))
return element;
}
else
{
if (auto element = findTypeElementAt(type->returnTypes_DEPRECATED, ftv->retTypes, position))
return element;
}
}
// It's possible to walk through other types like intrsection and unions if we find value in doing that
return {};
@ -761,7 +730,7 @@ static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Posi
std::optional<TypeId> getLocalTypeInScopeAt(const Module& module, const ScopePtr& scopeAtPosition, Position position, AstLocal* local)
{
if (ScopePtr scope = scopeAtPosition)
if (ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position))
{
for (const auto& [name, binding] : scope->bindings)
{
@ -903,7 +872,7 @@ AutocompleteEntryMap autocompleteTypeNames(
{
AutocompleteEntryMap result;
ScopePtr startScope = scopeAtPosition;
ScopePtr startScope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position);
for (ScopePtr scope = startScope; scope; scope = scope->parent)
{
@ -1082,16 +1051,12 @@ AutocompleteEntryMap autocompleteTypeNames(
}
}
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (!node->returnAnnotation)
return result;
if (const auto typePack = node->returnAnnotation->as<AstTypePackExplicit>())
for (size_t i = 0; i < node->returnAnnotation->types.size; i++)
{
for (size_t i = 0; i < typePack->typeList.types.size; i++)
{
AstType* ret = typePack->typeList.types.data[i];
AstType* ret = node->returnAnnotation->types.data[i];
if (ret->location.containsClosed(position))
{
@ -1106,7 +1071,7 @@ AutocompleteEntryMap autocompleteTypeNames(
}
}
if (AstTypePack* retTp = typePack->typeList.tailType)
if (AstTypePack* retTp = node->returnAnnotation->tailType)
{
if (auto variadic = retTp->as<AstTypePackVariadic>())
{
@ -1121,56 +1086,6 @@ AutocompleteEntryMap autocompleteTypeNames(
}
}
}
else if (auto variadic = node->returnAnnotation->as<AstTypePackVariadic>())
{
if (variadic->location.containsClosed(position))
{
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
{
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, ~0u))
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
}
}
}
}
else
{
if (!node->returnAnnotation_DEPRECATED)
return result;
for (size_t i = 0; i < node->returnAnnotation_DEPRECATED->types.size; i++)
{
AstType* ret = node->returnAnnotation_DEPRECATED->types.data[i];
if (ret->location.containsClosed(position))
{
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
{
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, i))
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
}
// TODO: with additional type information, we could suggest inferred return type here
break;
}
}
if (AstTypePack* retTp = node->returnAnnotation_DEPRECATED->tailType)
{
if (auto variadic = retTp->as<AstTypePackVariadic>())
{
if (variadic->location.containsClosed(position))
{
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
{
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, ~0u))
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
}
}
}
}
}
}
return result;
}
@ -1290,7 +1205,7 @@ static AutocompleteEntryMap autocompleteStatement(
)
{
// This is inefficient. :(
ScopePtr scope = scopeAtPosition;
ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position);
AutocompleteEntryMap result;
@ -1468,7 +1383,7 @@ static AutocompleteContext autocompleteExpression(
else
{
// This is inefficient. :(
ScopePtr scope = scopeAtPosition;
ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position);
while (scope)
{
@ -1537,7 +1452,7 @@ static AutocompleteResult autocompleteExpression(
return {result, ancestry, context};
}
static std::optional<const ExternType*> getMethodContainingExternType(const ModulePtr& module, AstExpr* funcExpr)
static std::optional<const ClassType*> getMethodContainingClass(const ModulePtr& module, AstExpr* funcExpr)
{
AstExpr* parentExpr = nullptr;
if (auto indexName = funcExpr->as<AstExprIndexName>())
@ -1561,14 +1476,14 @@ static std::optional<const ExternType*> getMethodContainingExternType(const Modu
Luau::TypeId parentType = Luau::follow(*parentIt);
if (auto parentExternType = Luau::get<ExternType>(parentType))
if (auto parentClass = Luau::get<ClassType>(parentType))
{
return parentExternType;
return parentClass;
}
if (auto parentUnion = Luau::get<UnionType>(parentType))
{
return returnFirstNonnullOptionOfType<ExternType>(parentUnion);
return returnFirstNonnullOptionOfType<ClassType>(parentUnion);
}
return std::nullopt;
@ -1626,7 +1541,10 @@ static std::optional<AutocompleteEntryMap> convertRequireSuggestionsToAutocomple
{
AutocompleteEntry entry = {AutocompleteEntryKind::RequirePath};
entry.insertText = std::move(suggestion.fullPath);
if (FFlag::LuauExposeRequireByStringAutocomplete)
{
entry.tags = std::move(suggestion.tags);
}
result[std::move(suggestion.label)] = std::move(entry);
}
return result;
@ -1687,7 +1605,7 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(
{
return convertRequireSuggestionsToAutocompleteEntryMap(fileResolver->getRequireSuggestions(module->name, candidateString));
}
if (std::optional<AutocompleteEntryMap> ret = callback(tag, getMethodContainingExternType(module, candidate->func), candidateString))
if (std::optional<AutocompleteEntryMap> ret = callback(tag, getMethodContainingClass(module, candidate->func), candidateString))
{
return ret;
}
@ -1705,8 +1623,6 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(
{
for (TypeId part : intersect->parts)
{
if (FFlag::LuauAutocompleteMissingFollows)
part = follow(part);
if (auto candidateFunctionType = Luau::get<FunctionType>(part))
{
if (std::optional<AutocompleteEntryMap> ret = performCallback(candidateFunctionType))
@ -1855,7 +1771,7 @@ static std::optional<AutocompleteEntry> makeAnonymousAutofilled(
if (!type)
return std::nullopt;
const ScopePtr scope = scopeAtPosition;
const ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(*module, position);
if (!scope)
return std::nullopt;

View file

@ -30,11 +30,12 @@
*/
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked)
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
namespace Luau
{
@ -313,8 +314,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
TypeArena& arena = globals.globalTypes;
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauNonReentrantGeneralization2
if (FFlag::LuauNonReentrantGeneralization2)
Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauNonReentrantGeneralization
if (FFlag::LuauNonReentrantGeneralization)
globalScope = globals.globalScope.get();
if (FFlag::LuauSolverV2)
@ -343,7 +344,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
if (auto it = globals.globalScope->exportedTypeBindings.find("vector"); it != globals.globalScope->exportedTypeBindings.end())
{
TypeId vectorTy = it->second.type;
ExternType* vectorCls = getMutable<ExternType>(vectorTy);
ClassType* vectorCls = getMutable<ClassType>(vectorTy);
vectorCls->metatable = arena.addType(TableType{{}, std::nullopt, TypeLevel{}, TableState::Sealed});
TableType* metatableTy = Luau::getMutable<TableType>(vectorCls->metatable);
@ -704,14 +705,6 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
return true;
}
// CLI-150726: The block below effectively constructs a type pack and then type checks it by going parameter-by-parameter.
// This does _not_ handle cases like:
//
// local foo : () -> (...string) = (nil :: any)
// print(string.format("%s %d %s", foo()))
//
// ... which should be disallowed.
std::vector<TypeId> expected = parseFormatString(context.builtinTypes, fmt->value.data, fmt->value.size);
const auto& [params, tail] = flatten(context.arguments);
@ -723,9 +716,7 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
{
TypeId actualTy = params[i + paramOffset];
TypeId expectedTy = expected[i];
Location location = FFlag::LuauFormatUseLastPosition
? context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location
: context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location;
Location location = context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location;
// use subtyping instead here
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);
@ -1538,6 +1529,7 @@ bool MagicClone::infer(const MagicFunctionCallContext& context)
tableType->scope = context.constraint->scope.get();
}
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(context.constraint->scope.get(), resultType);
TypePackId clonedTypePack = arena->addTypePack({resultType});
@ -1549,6 +1541,7 @@ bool MagicClone::infer(const MagicFunctionCallContext& context)
static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCallContext& context)
{
TypeArena* arena = context.solver->arena;
if (FFlag::LuauFollowTableFreeze)
inputType = follow(inputType);
if (auto mt = get<MetatableType>(inputType))
{

View file

@ -355,7 +355,7 @@ private:
t->metatable = shallowClone(t->metatable);
}
void cloneChildren(ExternType* t)
void cloneChildren(ClassType* t)
{
for (auto& [_, p] : t->props)
p = shallowClone(p);

View file

@ -20,7 +20,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor
DenseHashSet<TypeId>* result;
explicit ReferenceCountInitializer(DenseHashSet<TypeId>* result)
ReferenceCountInitializer(DenseHashSet<TypeId>* result)
: result(result)
{
}
@ -43,15 +43,24 @@ struct ReferenceCountInitializer : TypeOnceVisitor
return false;
}
bool visit(TypeId ty, const ExternType&) override
bool visit(TypeId ty, const ClassType&) override
{
// ExternTypes never contain free types.
// ClassTypes never contain free types.
return false;
}
bool visit(TypeId, const TypeFunctionInstanceType&) override
{
return FFlag::DebugLuauGreedyGeneralization;
// We do not consider reference counted types that are inside a type
// function to be part of the reachable reference counted types.
// Otherwise, code can be constructed in just the right way such
// that two type functions both claim to mutate a free type, which
// prevents either type function from trying to generalize it, so
// we potentially get stuck.
//
// The default behavior here is `true` for "visit the child types"
// of this type, hence:
return false;
}
};
@ -121,10 +130,8 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
}
else if (auto hic = get<HasIndexerConstraint>(*this))
{
if (FFlag::DebugLuauGreedyGeneralization)
rci.traverse(hic->subjectType);
rci.traverse(hic->resultType);
// `HasIndexerConstraint` should not mutate `indexType`.
// `HasIndexerConstraint` should not mutate `subjectType` or `indexType`.
}
else if (auto apc = get<AssignPropConstraint>(*this))
{
@ -143,10 +150,6 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
rci.traverse(ty);
// `UnpackConstraint` should not mutate `sourcePack`.
}
else if (auto rpc = get<ReduceConstraint>(*this); FFlag::DebugLuauGreedyGeneralization && rpc)
{
rci.traverse(rpc->ty);
}
else if (auto rpc = get<ReducePackConstraint>(*this))
{
rci.traverse(rpc->tp);

View file

@ -33,12 +33,19 @@
LUAU_FASTINT(LuauCheckRecursionLimit)
LUAU_FASTFLAG(DebugLuauLogSolverToJson)
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAGVARIABLE(LuauPropagateExpectedTypesForCalls)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions)
LUAU_FASTFLAGVARIABLE(LuauGlobalSelfAssignmentCycle)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments)
LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement)
LUAU_FASTFLAGVARIABLE(LuauExtraFollows)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations)
@ -46,12 +53,6 @@ LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAGVARIABLE(LuauCacheInferencePerAstExpr)
LUAU_FASTFLAGVARIABLE(LuauAlwaysResolveAstTypes)
LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAGVARIABLE(LuauNoTypeFunctionsNamedTypeOf)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
namespace Luau
{
@ -156,7 +157,7 @@ struct HasFreeType : TypeOnceVisitor
return true;
}
bool visit(TypeId ty, const ExternType&) override
bool visit(TypeId ty, const ClassType&) override
{
return false;
}
@ -217,32 +218,6 @@ ConstraintGenerator::ConstraintGenerator(
LUAU_ASSERT(module);
}
ConstraintSet ConstraintGenerator::run(AstStatBlock* block)
{
visitModuleRoot(block);
return ConstraintSet{
NotNull{rootScope},
std::move(constraints),
std::move(freeTypes),
std::move(scopeToFunction),
std::move(errors)
};
}
ConstraintSet ConstraintGenerator::runOnFragment(const ScopePtr& resumeScope, AstStatBlock* block)
{
visitFragmentRoot(resumeScope, block);
return ConstraintSet{
NotNull{rootScope},
std::move(constraints),
std::move(freeTypes),
std::move(scopeToFunction),
std::move(errors)
};
}
void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
{
LUAU_TIMETRACE_SCOPE("ConstraintGenerator::visitModuleRoot", "Typechecking");
@ -255,7 +230,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
rootScope->location = block->location;
module->astScopes[block] = NotNull{scope.get()};
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
interiorFreeTypes.emplace_back();
else
DEPRECATED_interiorTypes.emplace_back();
@ -288,16 +263,17 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
GeneralizationConstraint{
result,
moduleFnTy,
std::vector<TypeId>{},
(FFlag::LuauNonReentrantGeneralization || FFlag::LuauTrackInteriorFreeTypesOnScope) ? std::vector<TypeId>{}
: std::move(DEPRECATED_interiorTypes.back())
}
);
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
{
scope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
}
else
else if (FFlag::LuauTrackInteriorFreeTypesOnScope)
scope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back());
getMutable<BlockedType>(result)->setOwner(genConstraint);
@ -311,7 +287,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
}
);
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
interiorFreeTypes.pop_back();
else
DEPRECATED_interiorTypes.pop_back();
@ -343,13 +319,13 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat
// We prepopulate global data in the resumeScope to avoid writing data into the old modules scopes
prepopulateGlobalScopeForFragmentTypecheck(globalScope, resumeScope, block);
// Pre
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
interiorFreeTypes.emplace_back();
else
DEPRECATED_interiorTypes.emplace_back();
visitBlockWithoutChildScope(resumeScope, block);
// Post
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
interiorFreeTypes.pop_back();
else
DEPRECATED_interiorTypes.pop_back();
@ -379,29 +355,29 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat
TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity)
{
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
{
auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity);
interiorFreeTypes.back().types.push_back(ft);
if (FFlag::DebugLuauGreedyGeneralization)
freeTypes.insert(ft);
return ft;
}
else
else if (FFlag::LuauTrackInteriorFreeTypesOnScope)
{
auto ft = Luau::freshType(arena, builtinTypes, scope.get());
DEPRECATED_interiorTypes.back().push_back(ft);
return ft;
}
else
{
return Luau::freshType(arena, builtinTypes, scope.get());
}
}
TypePackId ConstraintGenerator::freshTypePack(const ScopePtr& scope, Polarity polarity)
{
FreeTypePack f{scope.get(), polarity};
TypePackId result = arena->addTypePack(TypePackVar{std::move(f)});
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
interiorFreeTypes.back().typePacks.push_back(result);
return result;
}
@ -602,9 +578,16 @@ void ConstraintGenerator::computeRefinement(
// When the top-level expression is `t[x]`, we want to refine it into `nil`, not `never`.
LUAU_ASSERT(refis->get(proposition->key->def));
if (FFlag::LuauDoNotLeakNilInRefinement)
{
refis->get(proposition->key->def)->shouldAppendNilType =
(sense || !eq) && containsSubscriptedDefinition(proposition->key->def) && !proposition->implicitFromCall;
}
else
{
refis->get(proposition->key->def)->shouldAppendNilType = (sense || !eq) && containsSubscriptedDefinition(proposition->key->def);
}
}
}
namespace
@ -650,7 +633,7 @@ struct FindSimplificationBlockers : TypeOnceVisitor
return false;
}
bool visit(TypeId, const ExternType&) override
bool visit(TypeId, const ClassType&) override
{
return false;
}
@ -875,7 +858,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
aliasDefinitionLocations[function->name.value] = function->location;
}
else if (auto classDeclaration = stat->as<AstStatDeclareExternType>())
else if (auto classDeclaration = stat->as<AstStatDeclareClass>())
{
if (scope->exportedTypeBindings.count(classDeclaration->name.value))
{
@ -1069,7 +1052,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStat* stat)
return visit(scope, s);
else if (auto s = stat->as<AstStatDeclareFunction>())
return visit(scope, s);
else if (auto s = stat->as<AstStatDeclareExternType>())
else if (auto s = stat->as<AstStatDeclareClass>())
return visit(scope, s);
else if (auto s = stat->as<AstStatError>())
return visit(scope, s);
@ -1137,6 +1120,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat
TypePackId rvaluePack = checkPack(scope, statLocal->values, expectedTypes).tp;
Checkpoint end = checkpoint(this);
if (FFlag::LuauInferLocalTypesInMultipleAssignments)
{
std::vector<TypeId> deferredTypes;
auto [head, tail] = flatten(rvaluePack);
@ -1192,6 +1177,64 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat
for (TypeId t : deferredTypes)
getMutable<BlockedType>(t)->setOwner(uc);
}
}
else
{
if (hasAnnotation)
{
for (size_t i = 0; i < statLocal->vars.size; ++i)
{
LUAU_ASSERT(get<BlockedType>(assignees[i]));
TypeIds* localDomain = localTypes.find(assignees[i]);
LUAU_ASSERT(localDomain);
localDomain->insert(annotatedTypes[i]);
}
TypePackId annotatedPack = arena->addTypePack(std::move(annotatedTypes));
addConstraint(scope, statLocal->location, PackSubtypeConstraint{rvaluePack, annotatedPack});
}
else
{
std::vector<TypeId> valueTypes;
valueTypes.reserve(statLocal->vars.size);
auto [head, tail] = flatten(rvaluePack);
if (head.size() >= statLocal->vars.size)
{
for (size_t i = 0; i < statLocal->vars.size; ++i)
valueTypes.push_back(head[i]);
}
else
{
for (size_t i = 0; i < statLocal->vars.size; ++i)
valueTypes.push_back(arena->addType(BlockedType{}));
auto uc = addConstraint(scope, statLocal->location, UnpackConstraint{valueTypes, rvaluePack});
forEachConstraint(
start,
end,
this,
[&uc](const ConstraintPtr& runBefore)
{
uc->dependencies.push_back(NotNull{runBefore.get()});
}
);
for (TypeId t : valueTypes)
getMutable<BlockedType>(t)->setOwner(uc);
}
for (size_t i = 0; i < statLocal->vars.size; ++i)
{
LUAU_ASSERT(get<BlockedType>(assignees[i]));
TypeIds* localDomain = localTypes.find(assignees[i]);
LUAU_ASSERT(localDomain);
localDomain->insert(valueTypes[i]);
}
}
}
if (statLocal->vars.size == 1 && statLocal->values.size == 1 && firstValueType && scope.get() == rootScope && !hasAnnotation)
{
@ -1290,8 +1333,11 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI
for (AstLocal* var : forIn->vars)
{
TypeId assignee = arena->addType(BlockedType{});
variableTypes.push_back(assignee);
TypeId loopVar = arena->addType(BlockedType{});
variableTypes.push_back(loopVar);
localTypes[loopVar].insert(assignee);
if (var->annotation)
{
@ -1310,23 +1356,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI
loopScope, getLocation(forIn->values), IterableConstraint{iterator, variableTypes, forIn->values.data[0], &module->astForInNextTypes}
);
if (FFlag::LuauAddCallConstraintForIterableFunctions)
{
// Add an intersection ReduceConstraint for the key variable to denote that it can't be nil
AstLocal* keyVar = *forIn->vars.begin();
const DefId keyDef = dfg->getDef(keyVar);
const TypeId loopVar = loopScope->lvalueTypes[keyDef];
const TypeId intersectionTy =
createTypeFunctionInstance(builtinTypeFunctions().intersectFunc, {loopVar, builtinTypes->notNilType}, {}, loopScope, keyVar->location);
loopScope->bindings[keyVar] = Binding{intersectionTy, keyVar->location};
loopScope->lvalueTypes[keyDef] = intersectionTy;
auto c = addConstraint(loopScope, keyVar->location, ReduceConstraint{intersectionTy});
c->dependencies.push_back(iterable);
}
for (TypeId var : variableTypes)
{
auto bt = getMutable<BlockedType>(var);
@ -1409,7 +1438,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->name->location};
bool sigFullyDefined = FFlag::DebugLuauGreedyGeneralization ? false : !hasFreeType(sig.signature);
bool sigFullyDefined = !hasFreeType(sig.signature);
if (sigFullyDefined)
emplaceType<BoundType>(asMutable(functionType), sig.signature);
@ -1471,7 +1500,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
Checkpoint start = checkpoint(this);
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
bool sigFullyDefined = FFlag::DebugLuauGreedyGeneralization ? false : !hasFreeType(sig.signature);
bool sigFullyDefined = !hasFreeType(sig.signature);
DefId def = dfg->getDef(function->name);
@ -1718,7 +1747,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias*
if (alias->name == "typeof")
{
reportError(alias->location, ReservedIdentifier{"typeof"});
reportError(alias->location, GenericError{"Type aliases cannot be named typeof"});
return ControlFlow::None;
}
@ -1779,14 +1808,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
if (!FFlag::LuauUserTypeFunTypecheck)
return ControlFlow::None;
if (FFlag::LuauNoTypeFunctionsNamedTypeOf)
{
if (function->name == "typeof")
{
reportError(function->location, ReservedIdentifier{"typeof"});
}
}
auto scopePtr = astTypeFunctionEnvironmentScopes.find(function);
LUAU_ASSERT(scopePtr);
@ -1796,7 +1817,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
// Place this function as a child of the non-type function scope
scope->children.push_back(NotNull{sig.signatureScope.get()});
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
interiorFreeTypes.emplace_back();
else
DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{});
@ -1810,20 +1831,20 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
GeneralizationConstraint{
generalizedTy,
sig.signature,
std::vector<TypeId>{},
FFlag::LuauTrackInteriorFreeTypesOnScope ? std::vector<TypeId>{} : std::move(DEPRECATED_interiorTypes.back())
}
);
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
{
sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
}
else
else if (FFlag::LuauTrackInteriorFreeTypesOnScope)
sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back());
getMutable<BlockedType>(generalizedTy)->setOwner(gc);
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
interiorFreeTypes.pop_back();
else
DEPRECATED_interiorTypes.pop_back();
@ -1883,71 +1904,71 @@ static bool isMetamethod(const Name& name)
name == "__idiv";
}
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareExternType* declaredExternType)
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClass* declaredClass)
{
// If a class with the same name was already defined, we skip over
auto bindingIt = scope->exportedTypeBindings.find(declaredExternType->name.value);
auto bindingIt = scope->exportedTypeBindings.find(declaredClass->name.value);
if (bindingIt == scope->exportedTypeBindings.end())
return ControlFlow::None;
std::optional<TypeId> superTy = std::make_optional(builtinTypes->externType);
if (declaredExternType->superName)
std::optional<TypeId> superTy = std::make_optional(builtinTypes->classType);
if (declaredClass->superName)
{
Name superName = Name(declaredExternType->superName->value);
Name superName = Name(declaredClass->superName->value);
std::optional<TypeFun> lookupType = scope->lookupType(superName);
if (!lookupType)
{
reportError(declaredExternType->location, UnknownSymbol{superName, UnknownSymbol::Type});
reportError(declaredClass->location, UnknownSymbol{superName, UnknownSymbol::Type});
return ControlFlow::None;
}
// We don't have generic extern types, 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);
superTy = follow(lookupType->type);
if (!get<ExternType>(follow(*superTy)))
if (!get<ClassType>(follow(*superTy)))
{
reportError(
declaredExternType->location,
GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredExternType->name.value)}
declaredClass->location,
GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredClass->name.value)}
);
return ControlFlow::None;
}
}
Name className(declaredExternType->name.value);
Name className(declaredClass->name.value);
TypeId externTy = arena->addType(ExternType(className, {}, superTy, std::nullopt, {}, {}, module->name, declaredExternType->location));
ExternType* etv = getMutable<ExternType>(externTy);
TypeId classTy = arena->addType(ClassType(className, {}, superTy, std::nullopt, {}, {}, module->name, declaredClass->location));
ClassType* ctv = getMutable<ClassType>(classTy);
TypeId metaTy = arena->addType(TableType{TableState::Sealed, scope->level, scope.get()});
TableType* metatable = getMutable<TableType>(metaTy);
etv->metatable = metaTy;
ctv->metatable = metaTy;
TypeId classBindTy = bindingIt->second.type;
emplaceType<BoundType>(asMutable(classBindTy), externTy);
emplaceType<BoundType>(asMutable(classBindTy), classTy);
if (declaredExternType->indexer)
if (declaredClass->indexer)
{
RecursionCounter counter{&recursionCount};
if (recursionCount >= FInt::LuauCheckRecursionLimit)
{
reportCodeTooComplex(declaredExternType->indexer->location);
reportCodeTooComplex(declaredClass->indexer->location);
}
else
{
etv->indexer = TableIndexer{
resolveType(scope, declaredExternType->indexer->indexType, /* inTypeArguments */ false),
resolveType(scope, declaredExternType->indexer->resultType, /* inTypeArguments */ false),
ctv->indexer = TableIndexer{
resolveType(scope, declaredClass->indexer->indexType, /* inTypeArguments */ false),
resolveType(scope, declaredClass->indexer->resultType, /* inTypeArguments */ false),
};
}
}
for (const AstDeclaredExternTypeProperty& prop : declaredExternType->props)
for (const AstDeclaredClassProp& prop : declaredClass->props)
{
Name propName(prop.name.value);
TypeId propTy = resolveType(scope, prop.ty, /* inTypeArguments */ false);
@ -1961,7 +1982,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareExte
if (FunctionType* ftv = getMutable<FunctionType>(propTy))
{
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
ftv->argTypes = addTypePack({externTy}, ftv->argTypes);
ftv->argTypes = addTypePack({classTy}, ftv->argTypes);
ftv->hasSelf = true;
@ -1976,7 +1997,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareExte
}
}
TableType::Props& props = assignToMetatable ? metatable->props : etv->props;
TableType::Props& props = assignToMetatable ? metatable->props : ctv->props;
if (props.count(propName) == 0)
{
@ -2007,7 +2028,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareExte
}
else
{
reportError(declaredExternType->location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
reportError(declaredClass->location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
}
}
}
@ -2039,8 +2060,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc
funScope = childScope(global, scope);
TypePackId paramPack = resolveTypePack(funScope, global->params, /* inTypeArguments */ false);
TypePackId retPack = FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funScope, global->retTypes, /* inTypeArguments */ false)
: resolveTypePack(funScope, global->retTypes_DEPRECATED, /* inTypeArguments */ false);
TypePackId retPack = resolveTypePack(funScope, global->retTypes, /* inTypeArguments */ false);
FunctionDefinition defn;
@ -2265,7 +2285,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
{
std::vector<TypeId> unpackedTypes;
if (args.size() > 0)
target = follow(args[0]);
target = FFlag::LuauExtraFollows ? follow(args[0]) : args[0];
else
{
target = arena->addType(BlockedType{});
@ -2388,14 +2408,11 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
argEndCheckpoint,
this,
[checkConstraint, callConstraint](const ConstraintPtr& constraint)
{
if (!(FFlag::DebugLuauGreedyGeneralization && get<PrimitiveTypeConstraint>(*constraint)))
{
constraint->dependencies.emplace_back(checkConstraint);
callConstraint->dependencies.emplace_back(constraint.get());
}
}
);
return InferencePack{rets, {refinementArena.variadic(returnRefinements)}};
@ -2489,7 +2506,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin
return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})};
TypeId freeTy = nullptr;
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
{
freeTy = freshType(scope, Polarity::Positive);
FreeType* ft = getMutable<FreeType>(freeTy);
@ -2499,7 +2516,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin
}
else
{
FreeType ft = FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType};
FreeType ft =
FFlag::LuauFreeTypesMustHaveBounds ? FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType} : FreeType{scope.get()};
ft.lowerBound = arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}});
ft.upperBound = builtinTypes->stringType;
freeTy = arena->addType(ft);
@ -2516,7 +2534,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool*
return Inference{singletonType};
TypeId freeTy = nullptr;
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
{
freeTy = freshType(scope, Polarity::Positive);
FreeType* ft = getMutable<FreeType>(freeTy);
@ -2526,7 +2544,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool*
}
else
{
FreeType ft = FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType};
FreeType ft =
FFlag::LuauFreeTypesMustHaveBounds ? FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType} : FreeType{scope.get()};
ft.lowerBound = singletonType;
ft.upperBound = builtinTypes->booleanType;
freeTy = arena->addType(ft);
@ -2677,7 +2696,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
Checkpoint startCheckpoint = checkpoint(this);
FunctionSignature sig = checkFunctionSignature(scope, func, expectedType);
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
interiorFreeTypes.emplace_back();
else
DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{});
@ -2691,11 +2710,12 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
GeneralizationConstraint{
generalizedTy,
sig.signature,
std::vector<TypeId>{},
(FFlag::LuauNonReentrantGeneralization || FFlag::LuauTrackInteriorFreeTypesOnScope) ? std::vector<TypeId>{}
: std::move(DEPRECATED_interiorTypes.back())
}
);
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
{
sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
@ -2705,7 +2725,9 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
}
else
{
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back());
getMutable<BlockedType>(generalizedTy)->setOwner(gc);
DEPRECATED_interiorTypes.pop_back();
}
@ -2993,7 +3015,7 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGenerator::checkBinary(
else if (typeguard->type == "userdata")
{
// For now, we don't really care about being accurate with userdata if the typeguard was using typeof.
discriminantTy = builtinTypes->externType;
discriminantTy = builtinTypes->classType;
}
else if (!typeguard->isTypeof && typeguard->type == "vector")
discriminantTy = builtinTypes->neverType; // TODO: figure out a way to deal with this quirky type
@ -3003,8 +3025,8 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGenerator::checkBinary(
{
TypeId ty = follow(typeFun->type);
// We're only interested in the root type of any extern type.
if (auto etv = get<ExternType>(ty); etv && (etv->parent == builtinTypes->externType || hasTag(ty, kTypeofRootTag)))
// We're only interested in the root class of any classes.
if (auto ctv = get<ClassType>(ty); ctv && (ctv->parent == builtinTypes->classType || hasTag(ty, kTypeofRootTag)))
discriminantTy = ty;
}
@ -3077,7 +3099,7 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprLocal* local
if (ty)
{
TypeIds* localDomain = localTypes.find(*ty);
if (localDomain && !(FFlag::LuauDoNotAddUpvalueTypesToLocalType && local->upvalue))
if (localDomain)
localDomain->insert(rhsType);
}
else
@ -3108,8 +3130,6 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprLocal* local
if (annotatedTy)
addConstraint(scope, local->location, SubtypeConstraint{rhsType, *annotatedTy});
// This is vestigial.
if (!FFlag::LuauDoNotAddUpvalueTypesToLocalType)
if (TypeIds* localDomain = localTypes.find(*ty))
localDomain->insert(rhsType);
}
@ -3122,9 +3142,12 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprGlobal* glob
DefId def = dfg->getDef(global);
rootScope->lvalueTypes[def] = rhsType;
if (FFlag::LuauGlobalSelfAssignmentCycle)
{
// Ignore possible self-assignment, it doesn't create a new constraint
if (annotatedTy == follow(rhsType))
return;
}
// Sketchy: We're specifically looking for BlockedTypes that were
// initially created by ConstraintGenerator::prepopulateGlobalScope.
@ -3187,7 +3210,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
ttv->definitionLocation = expr->location;
ttv->scope = scope.get();
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
interiorFreeTypes.back().types.push_back(ty);
else
DEPRECATED_interiorTypes.back().push_back(ty);
@ -3350,7 +3373,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
if (fn->self)
{
TypeId selfType = freshType(signatureScope, Polarity::Negative);
TypeId selfType = freshType(signatureScope);
argTypes.push_back(selfType);
argNames.emplace_back(FunctionArgument{fn->self->name.value, fn->self->location});
signatureScope->bindings[fn->self] = Binding{selfType, fn->self->location};
@ -3415,36 +3438,12 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
LUAU_ASSERT(nullptr != varargPack);
if (FFlag::DebugLuauGreedyGeneralization)
{
// Some of the types in argTypes will eventually be generics, and some
// will not. The ones that are not generic will be pruned when
// GeneralizationConstraint dispatches.
genericTypes.insert(genericTypes.begin(), argTypes.begin(), argTypes.end());
varargPack = follow(varargPack);
returnType = follow(returnType);
if (varargPack == returnType)
genericTypePacks = {varargPack};
else
genericTypePacks = {varargPack, returnType};
}
// If there is both an annotation and an expected type, the annotation wins.
// Type checking will sort out any discrepancies later.
if (FFlag::LuauStoreReturnTypesAsPackOnAst && fn->returnAnnotation)
if (fn->returnAnnotation)
{
TypePackId annotatedRetType =
resolveTypePack(signatureScope, fn->returnAnnotation, /* inTypeArguments */ false, /* replaceErrorWithFresh*/ true);
// We bind the annotated type directly here so that, when we need to
// generate constraints for return types, we have a guarantee that we
// know the annotated return type already, if one was provided.
LUAU_ASSERT(get<FreeTypePack>(returnType));
emplaceTypePack<BoundTypePack>(asMutable(returnType), annotatedRetType);
}
else if (!FFlag::LuauStoreReturnTypesAsPackOnAst && fn->returnAnnotation_DEPRECATED)
{
TypePackId annotatedRetType =
resolveTypePack(signatureScope, *fn->returnAnnotation_DEPRECATED, /* inTypeArguments */ false, /* replaceErrorWithFresh*/ true);
resolveTypePack(signatureScope, *fn->returnAnnotation, /* inTypeArguments */ false, /* replaceErrorWithFresh*/ true);
// We bind the annotated type directly here so that, when we need to
// generate constraints for return types, we have a guarantee that we
// know the annotated return type already, if one was provided.
@ -3695,16 +3694,8 @@ TypeId ConstraintGenerator::resolveFunctionType(
AstTypePackExplicit tempArgTypes{Location{}, fn->argTypes};
TypePackId argTypes = resolveTypePack_(signatureScope, &tempArgTypes, inTypeArguments, replaceErrorWithFresh);
TypePackId returnTypes;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
returnTypes = resolveTypePack_(signatureScope, fn->returnTypes, inTypeArguments, replaceErrorWithFresh);
}
else
{
AstTypePackExplicit tempRetTypes{Location{}, fn->returnTypes_DEPRECATED};
returnTypes = resolveTypePack_(signatureScope, &tempRetTypes, inTypeArguments, replaceErrorWithFresh);
}
AstTypePackExplicit tempRetTypes{Location{}, fn->returnTypes};
TypePackId returnTypes = resolveTypePack_(signatureScope, &tempRetTypes, inTypeArguments, replaceErrorWithFresh);
// TODO: FunctionType needs a pointer to the scope so that we know
// how to quantify/instantiate it.
@ -3788,9 +3779,13 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo
}
}
else
{
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{
if (unionAnnotation->types.size == 1)
return resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments);
}
std::vector<TypeId> parts;
for (AstType* part : unionAnnotation->types)
{
@ -3818,9 +3813,13 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo
}
}
else
{
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{
if (intersectionAnnotation->types.size == 1)
return resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments);
}
std::vector<TypeId> parts;
for (AstType* part : intersectionAnnotation->types)
{
@ -4044,14 +4043,14 @@ TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location locati
return resultType;
}
struct FragmentTypeCheckGlobalPrepopulator_DEPRECATED : AstVisitor
struct FragmentTypeCheckGlobalPrepopulator : AstVisitor
{
const NotNull<Scope> globalScope;
const NotNull<Scope> currentScope;
const NotNull<const DataFlowGraph> dfg;
const NotNull<TypeArena> arena;
FragmentTypeCheckGlobalPrepopulator_DEPRECATED(
FragmentTypeCheckGlobalPrepopulator(
NotNull<Scope> globalScope,
NotNull<Scope> currentScope,
NotNull<const DataFlowGraph> dfg,
@ -4168,16 +4167,12 @@ struct GlobalPrepopulator : AstVisitor
void ConstraintGenerator::prepopulateGlobalScopeForFragmentTypecheck(const ScopePtr& globalScope, const ScopePtr& resumeScope, AstStatBlock* program)
{
if (!FFlag::LuauGlobalVariableModuleIsolation)
{
FragmentTypeCheckGlobalPrepopulator_DEPRECATED gp{NotNull{globalScope.get()}, NotNull{resumeScope.get()}, dfg, arena};
FragmentTypeCheckGlobalPrepopulator gp{NotNull{globalScope.get()}, NotNull{resumeScope.get()}, dfg, arena};
if (prepareModuleScope)
prepareModuleScope(module->name, resumeScope);
program->visit(&gp);
}
if (FFlag::LuauUserTypeFunTypecheck)
{
@ -4350,4 +4345,15 @@ TypeId ConstraintGenerator::simplifyUnion(const ScopePtr& scope, Location locati
return ::Luau::simplifyUnion(builtinTypes, arena, left, right).result;
}
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints)
{
std::vector<NotNull<Constraint>> result;
result.reserve(constraints.size());
for (const auto& c : constraints)
result.emplace_back(c.get());
return result;
}
} // namespace Luau

View file

@ -11,6 +11,7 @@
#include "Luau/Location.h"
#include "Luau/ModuleResolver.h"
#include "Luau/OverloadResolution.h"
#include "Luau/Quantify.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Simplify.h"
#include "Luau/TableLiteralInference.h"
@ -32,20 +33,20 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies)
LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings)
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock)
LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauSearchForRefineableType)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall)
LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions)
namespace Luau
{
static void dump(ConstraintSolver* cs, ToStringOptions& opts);
size_t HashBlockedConstraintId::operator()(const BlockedConstraintId& bci) const
{
size_t result = 0;
@ -275,6 +276,26 @@ size_t HashInstantiationSignature::operator()(const InstantiationSignature& sign
return hash;
}
void dump(ConstraintSolver* cs, ToStringOptions& opts)
{
printf("constraints:\n");
for (NotNull<const Constraint> c : cs->unsolvedConstraints)
{
auto it = cs->blockedConstraints.find(c);
int blockCount = it == cs->blockedConstraints.end() ? 0 : int(it->second);
printf("\t%d\t%s\n", blockCount, toString(*c, opts).c_str());
if (FFlag::DebugLuauLogSolverIncludeDependencies)
{
for (NotNull<Constraint> dep : c->dependencies)
{
if (std::find(cs->unsolvedConstraints.begin(), cs->unsolvedConstraints.end(), dep) != cs->unsolvedConstraints.end())
printf("\t\t|\t%s\n", toString(*dep, opts).c_str());
}
}
}
}
struct InstantiationQueuer : TypeOnceVisitor
{
ConstraintSolver* solver;
@ -300,44 +321,12 @@ struct InstantiationQueuer : TypeOnceVisitor
return true;
}
bool visit(TypeId ty, const ExternType& etv) override
bool visit(TypeId ty, const ClassType& ctv) override
{
return false;
}
};
ConstraintSolver::ConstraintSolver(
NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
ModuleName moduleName,
NotNull<ModuleResolver> moduleResolver,
std::vector<RequireCycle> requireCycles,
DcrLogger* logger,
NotNull<const DataFlowGraph> dfg,
TypeCheckLimits limits,
ConstraintSet constraintSet_
)
: arena(normalizer->arena)
, builtinTypes(normalizer->builtinTypes)
, normalizer(normalizer)
, simplifier(simplifier)
, typeFunctionRuntime(typeFunctionRuntime)
, constraintSet(std::move(constraintSet_))
, constraints(borrowConstraints(constraintSet.constraints))
, scopeToFunction(&constraintSet.scopeToFunction)
, rootScope(constraintSet.rootScope)
, currentModuleName(std::move(moduleName))
, dfg(dfg)
, moduleResolver(moduleResolver)
, requireCycles(std::move(requireCycles))
, logger(logger)
, limits(std::move(limits))
, opts{/*exhaustive*/ true}
{
initFreeTypeTracking();
}
ConstraintSolver::ConstraintSolver(
NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier,
@ -357,7 +346,6 @@ ConstraintSolver::ConstraintSolver(
, normalizer(normalizer)
, simplifier(simplifier)
, typeFunctionRuntime(typeFunctionRuntime)
, constraintSet{rootScope}
, constraints(std::move(constraints))
, scopeToFunction(scopeToFunction)
, rootScope(rootScope)
@ -367,9 +355,33 @@ ConstraintSolver::ConstraintSolver(
, requireCycles(std::move(requireCycles))
, logger(logger)
, limits(std::move(limits))
, opts{/*exhaustive*/ true}
{
initFreeTypeTracking();
opts.exhaustive = true;
for (NotNull<Constraint> c : this->constraints)
{
unsolvedConstraints.emplace_back(c);
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
for (auto ty : maybeMutatedTypesPerConstraint)
{
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
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);
for (NotNull<const Constraint> dep : c->dependencies)
{
block(dep, c);
}
}
}
void ConstraintSolver::randomize(unsigned seed)
@ -414,18 +426,6 @@ void ConstraintSolver::run()
logger->captureInitialSolverState(rootScope, unsolvedConstraints);
}
// Free types that have no constraints at all can be generalized right away.
if (FFlag::DebugLuauGreedyGeneralization)
{
for (TypeId ty : constraintSet.freeTypes)
{
if (auto it = mutatedFreeTypeToConstraint.find(ty); it == mutatedFreeTypeToConstraint.end() || it->second.empty())
generalizeOneType(ty);
}
}
constraintSet.freeTypes.clear();
auto runSolverPass = [&](bool force)
{
bool progress = false;
@ -655,39 +655,12 @@ struct TypeSearcher : TypeVisitor
// }
bool visit(TypeId ty, const ExternType&) override
bool visit(TypeId ty, const ClassType&) override
{
return false;
}
};
void ConstraintSolver::initFreeTypeTracking()
{
for (NotNull<Constraint> c : this->constraints)
{
unsolvedConstraints.emplace_back(c);
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
for (auto ty : maybeMutatedTypesPerConstraint)
{
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
refCount += 1;
if (FFlag::DebugLuauGreedyGeneralization)
{
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, nullptr);
it->second.insert(c.get());
}
}
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
for (NotNull<const Constraint> dep : c->dependencies)
{
block(dep, c);
}
}
}
void ConstraintSolver::generalizeOneType(TypeId ty)
{
ty = follow(ty);
@ -700,20 +673,73 @@ void ConstraintSolver::generalizeOneType(TypeId ty)
if (!freeTy)
return;
TypeId* functionType = scopeToFunction->find(freeTy->scope);
if (!functionType)
return;
NotNull<Scope> tyScope{freeTy->scope};
std::optional<TypeId> resultTy = generalize(arena, builtinTypes, NotNull{freeTy->scope}, generalizedTypes, *functionType, ty);
// TODO: If freeTy occurs within the enclosing function's type, we need to
// check to see whether this type should instead be generic.
if (FFlag::DebugLuauLogSolver)
TypeId newBound = follow(freeTy->upperBound);
TypeId* functionTyPtr = nullptr;
while (true)
{
printf(
"Eagerly generalized %s (now %s)\n\tin function %s\n",
saveme.c_str(),
toString(ty, opts).c_str(),
toString(resultTy.value_or(*functionType), opts).c_str()
);
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 Polarity::None:
asMutable(ty)->reassign(Type{BoundType{upperBound}});
break;
case Polarity::Negative:
case Polarity::Mixed:
if (get<UnknownType>(upperBound) && ts.count > 1)
{
asMutable(ty)->reassign(Type{GenericType{tyScope}});
function->generics.emplace_back(ty);
}
else
asMutable(ty)->reassign(Type{BoundType{upperBound}});
break;
case Polarity::Positive:
if (get<UnknownType>(lowerBound) && ts.count > 1)
{
asMutable(ty)->reassign(Type{GenericType{tyScope}});
function->generics.emplace_back(ty);
}
else
asMutable(ty)->reassign(Type{BoundType{lowerBound}});
break;
default:
LUAU_ASSERT(!"Unreachable");
}
}
}
@ -729,7 +755,7 @@ void ConstraintSolver::bind(NotNull<const Constraint> constraint, TypeId ty, Typ
constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed
); // FIXME? Is this the right polarity?
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
trackInteriorFreeType(constraint->scope, ty);
return;
@ -864,7 +890,6 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
if (generalizedTy)
{
pruneUnnecessaryGenerics(arena, builtinTypes, constraint->scope, generalizedTypes, *generalizedTy);
if (get<BlockedType>(generalizedType))
bind(constraint, generalizedType, *generalizedTy);
else
@ -885,13 +910,15 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
bind(constraint, c.generalizedType, builtinTypes->errorRecoveryType());
}
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
{
// We check if this member is initialized and then access it, but
// clang-tidy doesn't understand this is safe.
if (constraint->scope->interiorFreeTypes)
{
for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access)
{
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
{
ty = follow(ty);
if (auto freeTy = get<FreeType>(ty))
@ -901,9 +928,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
params.useCount = 1;
params.polarity = freeTy->polarity;
GeneralizationResult<TypeId> res = generalizeType(arena, builtinTypes, constraint->scope, ty, params);
if (res.resourceLimitsExceeded)
reportError(CodeTooComplex{}, constraint->scope->location); // FIXME: We don't have a very good location for this.
generalizeType(arena, builtinTypes, constraint->scope, ty, params);
}
else if (get<TableType>(ty))
sealTable(constraint->scope, ty);
@ -913,7 +938,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
}
}
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
{
if (constraint->scope->interiorFreeTypePacks)
{
@ -932,6 +957,13 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
}
}
}
}
else
{
for (TypeId ty : c.interiorTypes)
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty);
}
return true;
}
@ -1007,11 +1039,15 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
{
TypeId keyTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
TypeId valueTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
{
trackInteriorFreeType(constraint->scope, keyTy);
trackInteriorFreeType(constraint->scope, valueTy);
}
TypeId tableTy =
arena->addType(TableType{TableType::Props{}, TableIndexer{keyTy, valueTy}, TypeLevel{}, constraint->scope, TableState::Free});
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInteriorFreeTablesOnScope)
trackInteriorFreeType(constraint->scope, tableTy);
unify(constraint, nextTy, tableTy);
@ -1336,6 +1372,8 @@ void ConstraintSolver::fillInDiscriminantTypes(NotNull<const Constraint> constra
if (!ty)
continue;
if (FFlag::LuauSearchForRefineableType)
{
if (isBlocked(*ty))
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
@ -1343,7 +1381,20 @@ void ConstraintSolver::fillInDiscriminantTypes(NotNull<const Constraint> constra
// We also need to unconditionally unblock these types, otherwise
// you end up with funky looking "Blocked on *no-refine*."
unblock(*ty, constraint->location);
}
else
{
// 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);
}
}
}
@ -1456,9 +1507,9 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
if (ftv)
{
if (ftv->magic && c.callSite)
if (ftv->magic)
{
usedMagic = ftv->magic->infer(MagicFunctionCallContext{NotNull{this}, constraint, NotNull{c.callSite}, c.argsPack, result});
usedMagic = ftv->magic->infer(MagicFunctionCallContext{NotNull{this}, constraint, c.callSite, c.argsPack, result});
ftv->magic->refine(MagicRefinementContext{constraint->scope, c.callSite, c.discriminantTypes});
}
}
@ -1493,7 +1544,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
{
for (TypeId freeTy : u2.newFreshTypes)
trackInteriorFreeType(constraint->scope, freeTy);
@ -1533,7 +1584,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
// This can potentially contain free types if the return type of
// `inferredTy` is never unified elsewhere.
if (FFlag::LuauTrackInferredFunctionTypeFromCall)
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInferredFunctionTypeFromCall)
trackInteriorFreeType(constraint->scope, inferredTy);
unblock(c.result, constraint->location);
@ -1893,7 +1944,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed};
emplace<FreeType>(constraint, resultType, freeResult);
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
trackInteriorFreeType(constraint->scope, resultType);
tt->indexer = TableIndexer{indexType, resultType};
@ -1902,7 +1953,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
}
else if (auto mt = get<MetatableType>(subjectType))
return tryDispatchHasIndexer(recursionDepth, constraint, mt->table, indexType, resultType, seen);
else if (auto ct = get<ExternType>(subjectType))
else if (auto ct = get<ClassType>(subjectType))
{
if (auto indexer = ct->indexer)
{
@ -2064,9 +2115,9 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
// Important: In every codepath through this function, the type `c.propType`
// must be bound to something, even if it's just the errorType.
if (auto lhsExternType = get<ExternType>(lhsType))
if (auto lhsClass = get<ClassType>(lhsType))
{
const Property* prop = lookupExternTypeProp(lhsExternType, propName);
const Property* prop = lookupClassProp(lhsClass, propName);
if (!prop || !prop->writeTy.has_value())
{
bind(constraint, c.propType, builtinTypes->anyType);
@ -2081,48 +2132,13 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
if (auto lhsFree = getMutable<FreeType>(lhsType))
{
auto lhsFreeUpperBound = follow(lhsFree->upperBound);
if (FFlag::DebugLuauGreedyGeneralization)
{
const auto [blocked, maybeTy, isIndex] = lookupTableProp(constraint, lhsType, propName, ValueContext::LValue);
if (!blocked.empty())
{
for (TypeId t : blocked)
block(t, constraint);
return false;
}
else if (maybeTy)
{
bind(constraint, c.propType, isIndex ? arena->addType(UnionType{{*maybeTy, builtinTypes->nilType}}) : *maybeTy);
unify(constraint, rhsType, *maybeTy);
return true;
}
else
{
TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope});
trackInteriorFreeType(constraint->scope, newUpperBound);
TableType* upperTable = getMutable<TableType>(newUpperBound);
LUAU_ASSERT(upperTable);
upperTable->props[c.propName] = rhsType;
// Food for thought: Could we block if simplification encounters a blocked type?
lhsFree->upperBound = simplifyIntersection(constraint->scope, constraint->location, lhsFreeUpperBound, newUpperBound);
bind(constraint, c.propType, rhsType);
return true;
}
}
else
{
if (get<TableType>(lhsFreeUpperBound) || get<MetatableType>(lhsFreeUpperBound))
lhsType = lhsFreeUpperBound;
else
{
TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope});
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInteriorFreeTablesOnScope)
trackInteriorFreeType(constraint->scope, newUpperBound);
TableType* upperTable = getMutable<TableType>(newUpperBound);
@ -2137,7 +2153,6 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
return true;
}
}
}
// Handle the case that lhsType is a table that already has the property or
// a matching indexer. This also handles unions and intersections.
@ -2287,20 +2302,20 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
return *res;
}
if (auto lhsExternType = get<ExternType>(lhsType))
if (auto lhsClass = get<ClassType>(lhsType))
{
while (true)
{
if (lhsExternType->indexer)
if (lhsClass->indexer)
{
unify(constraint, indexType, lhsExternType->indexer->indexType);
unify(constraint, rhsType, lhsExternType->indexer->indexResultType);
bind(constraint, c.propType, arena->addType(UnionType{{lhsExternType->indexer->indexResultType, builtinTypes->nilType}}));
unify(constraint, indexType, lhsClass->indexer->indexType);
unify(constraint, rhsType, lhsClass->indexer->indexResultType);
bind(constraint, c.propType, arena->addType(UnionType{{lhsClass->indexer->indexResultType, builtinTypes->nilType}}));
return true;
}
if (lhsExternType->parent)
lhsExternType = get<ExternType>(lhsExternType->parent);
if (lhsClass->parent)
lhsClass = get<ClassType>(lhsClass->parent);
else
break;
}
@ -2327,7 +2342,7 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
parts.insert(rhsType);
}
}
else if (auto cls = get<ExternType>(follow(t)))
else if (auto cls = get<ClassType>(follow(t)))
{
while (true)
{
@ -2339,7 +2354,7 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
}
if (cls->parent)
cls = get<ExternType>(cls->parent);
cls = get<ClassType>(cls->parent);
else
break;
}
@ -2390,6 +2405,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
// constitute any meaningful constraint, so we replace it
// with a free type.
TypeId f = freshType(arena, builtinTypes, constraint->scope, Polarity::Positive); // FIXME? Is this the right polarity?
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(constraint->scope, f);
shiftReferences(resultTy, f);
emplaceType<BoundType>(asMutable(resultTy), f);
@ -2533,8 +2549,11 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
{
TypeId keyTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
TypeId valueTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
{
trackInteriorFreeType(constraint->scope, keyTy);
trackInteriorFreeType(constraint->scope, valueTy);
}
TypeId tableTy = arena->addType(TableType{TableState::Sealed, {}, constraint->scope});
getMutable<TableType>(tableTy)->indexer = TableIndexer{keyTy, valueTy};
@ -2684,31 +2703,12 @@ bool ConstraintSolver::tryDispatchIterableFunction(TypeId nextTy, TypeId tableTy
const FunctionType* nextFn = get<FunctionType>(nextTy);
// If this does not hold, we should've never called `tryDispatchIterableFunction` in the first place.
LUAU_ASSERT(nextFn);
const TypePackId nextRetPack = nextFn->retTypes;
// the type of the `nextAstFragment` is the `nextTy`.
(*c.astForInNextTypes)[c.nextAstFragment] = nextTy;
if (FFlag::LuauAddCallConstraintForIterableFunctions)
{
// Construct a FunctionCallConstraint, to help us learn about the type of the loop variables being assigned to in this iterable
TypePackId tableTyPack = arena->addTypePack({tableTy});
TypePackId variablesPack = arena->addTypePack(BlockedTypePack{});
auto callConstraint = pushConstraint(constraint->scope, constraint->location, FunctionCallConstraint{nextTy, tableTyPack, variablesPack});
getMutable<BlockedTypePack>(variablesPack)->owner = callConstraint.get();
auto unpackConstraint = unpackAndAssign(c.variables, variablesPack, constraint);
inheritBlocks(constraint, callConstraint);
inheritBlocks(unpackConstraint, callConstraint);
}
else
{
const TypePackId nextRetPack = nextFn->retTypes;
TypePackIterator it = begin(nextRetPack);
auto it = begin(nextRetPack);
std::vector<TypeId> modifiedNextRetHead;
// The first value is never nil in the context of the loop, even if it's nil
@ -2730,7 +2730,6 @@ bool ConstraintSolver::tryDispatchIterableFunction(TypeId nextTy, TypeId tableTy
auto unpackConstraint = unpackAndAssign(c.variables, modifiedNextRetPack, constraint);
inheritBlocks(constraint, unpackConstraint);
}
return true;
}
@ -2811,6 +2810,7 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
if (ttv->state == TableState::Free)
{
TypeId result = freshType(arena, builtinTypes, ttv->scope, Polarity::Mixed);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(ttv->scope, result);
switch (context)
{
@ -2885,9 +2885,9 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
else if (get<MetatableType>(mtt))
return lookupTableProp(constraint, mtt, propName, context, inConditional, suppressSimplification, seen);
}
else if (auto ct = get<ExternType>(subjectType))
else if (auto ct = get<ClassType>(subjectType))
{
if (auto p = lookupExternTypeProp(ct, propName))
if (auto p = lookupClassProp(ct, propName))
return {{}, context == ValueContext::RValue ? p->readTy : p->writeTy};
if (ct->indexer)
{
@ -2909,33 +2909,23 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
{
const TypeId upperBound = follow(ft->upperBound);
if (FFlag::DebugLuauGreedyGeneralization)
{
if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
{
TablePropLookupResult res = lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen);
// If the upper bound is a table that already has the property, we don't need to extend its bounds.
if (res.propType || get<PrimitiveType>(upperBound))
return res;
}
}
else
{
if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
return lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen);
}
// TODO: The upper bound could be an intersection that contains suitable tables or extern types.
// TODO: The upper bound could be an intersection that contains suitable tables or classes.
NotNull<Scope> scope{ft->scope};
const TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, scope});
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInteriorFreeTablesOnScope)
trackInteriorFreeType(constraint->scope, newUpperBound);
TableType* tt = getMutable<TableType>(newUpperBound);
LUAU_ASSERT(tt);
TypeId propType = freshType(arena, builtinTypes, scope, Polarity::Mixed);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(scope, propType);
switch (context)
@ -3155,7 +3145,7 @@ struct Blocker : TypeOnceVisitor
return false;
}
bool visit(TypeId ty, const ExternType&) override
bool visit(TypeId ty, const ClassType&) override
{
return false;
}
@ -3531,35 +3521,4 @@ LUAU_NOINLINE void ConstraintSolver::throwUserCancelError() const
template bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TypeId subTy, TypeId superTy);
template bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TypePackId subTy, TypePackId superTy);
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints)
{
std::vector<NotNull<Constraint>> result;
result.reserve(constraints.size());
for (const auto& c : constraints)
result.emplace_back(c.get());
return result;
}
void dump(ConstraintSolver* cs, ToStringOptions& opts)
{
printf("constraints:\n");
for (NotNull<const Constraint> c : cs->unsolvedConstraints)
{
auto it = cs->blockedConstraints.find(c);
int blockCount = it == cs->blockedConstraints.end() ? 0 : int(it->second);
printf("\t%d\t%s\n", blockCount, toString(*c, opts).c_str());
if (FFlag::DebugLuauLogSolverIncludeDependencies)
{
for (NotNull<Constraint> dep : c->dependencies)
{
if (std::find(cs->unsolvedConstraints.begin(), cs->unsolvedConstraints.end(), dep) != cs->unsolvedConstraints.end())
printf("\t\t|\t%s\n", toString(*dep, opts).c_str());
}
}
}
}
} // namespace Luau

View file

@ -16,9 +16,6 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAGVARIABLE(LuauDfgIfBlocksShouldRespectControlFlow)
namespace Luau
{
@ -473,7 +470,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStat* s)
return visit(d);
else if (auto d = s->as<AstStatDeclareFunction>())
return visit(d);
else if (auto d = s->as<AstStatDeclareExternType>())
else if (auto d = s->as<AstStatDeclareClass>())
return visit(d);
else if (auto error = s->as<AstStatError>())
return visit(error);
@ -502,26 +499,12 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
}
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
if (FFlag::LuauDfgIfBlocksShouldRespectControlFlow)
{
// If the control flow from the `if` or `else` block is non-linear,
// then we should assume that the _other_ branch is the one taken.
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
scope->inherit(elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
scope->inherit(thenScope);
else if ((thencf | elsecf) == ControlFlow::None)
join(scope, thenScope, elseScope);
}
else
{
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
join(scope, scope, elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
join(scope, thenScope, scope);
else if ((thencf | elsecf) == ControlFlow::None)
join(scope, thenScope, elseScope);
}
if (thencf == elsecf)
return thencf;
@ -825,15 +808,12 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d)
visitGenerics(d->generics);
visitGenericPacks(d->genericPacks);
visitTypeList(d->params);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visitTypePack(d->retTypes);
else
visitTypeList(d->retTypes_DEPRECATED);
visitTypeList(d->retTypes);
return ControlFlow::None;
}
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareExternType* d)
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareClass* d)
{
// This declaration does not "introduce" any bindings in value namespace,
// so there's no symbolic value to begin with. We'll traverse the properties
@ -841,7 +821,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareExternType* d)
DfgScope* unreachable = makeChildScope();
PushScope ps{scopeStack, unreachable};
for (AstDeclaredExternTypeProperty prop : d->props)
for (AstDeclaredClassProp prop : d->props)
visitType(prop.ty);
return ControlFlow::None;
@ -1052,16 +1032,8 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
if (f->varargAnnotation)
visitTypePack(f->varargAnnotation);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (f->returnAnnotation)
visitTypePack(f->returnAnnotation);
}
else
{
if (f->returnAnnotation_DEPRECATED)
visitTypeList(*f->returnAnnotation_DEPRECATED);
}
visitTypeList(*f->returnAnnotation);
// TODO: function body can be re-entrant, as in mutations that occurs at the end of the function can also be
// visible to the beginning of the function, so statically speaking, the body of the function has an exit point
@ -1179,7 +1151,7 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef)
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
// In order to avoid alias tracking, we need to clip the reference to the parent def.
if (scope->canUpdateDefinition(l->local) && !(FFlag::LuauDoNotAddUpvalueTypesToLocalType && l->upvalue))
if (scope->canUpdateDefinition(l->local))
{
DefId updated = defArena->freshCell(l->local, l->location, containsSubscriptedDefinition(incomingDef));
scope->bindings[l->local] = updated;
@ -1303,10 +1275,7 @@ void DataFlowGraphBuilder::visitType(AstTypeFunction* f)
visitGenerics(f->generics);
visitGenericPacks(f->genericPacks);
visitTypeList(f->argTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visitTypePack(f->returnTypes);
else
visitTypeList(f->returnTypes_DEPRECATED);
visitTypeList(f->returnTypes);
}
void DataFlowGraphBuilder::visitType(AstTypeTypeof* t)

View file

@ -277,7 +277,7 @@ static DifferResult diffSingleton(DifferEnvironment& env, TypeId left, TypeId ri
static DifferResult diffFunction(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffExternType(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right);
struct FindSeteqCounterexampleResult
{
// nullopt if no counterexample found
@ -481,14 +481,14 @@ static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId rig
return differResult;
}
static DifferResult diffExternType(DifferEnvironment& env, TypeId left, TypeId right)
static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right)
{
const ExternType* leftExternType = get<ExternType>(left);
const ExternType* rightExternType = get<ExternType>(right);
LUAU_ASSERT(leftExternType);
LUAU_ASSERT(rightExternType);
const ClassType* leftClass = get<ClassType>(left);
const ClassType* rightClass = get<ClassType>(right);
LUAU_ASSERT(leftClass);
LUAU_ASSERT(rightClass);
if (leftExternType == rightExternType)
if (leftClass == rightClass)
{
return DifferResult{};
}
@ -651,9 +651,9 @@ static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId rig
{
return diffNegation(env, left, right);
}
else if (auto lc = get<ExternType>(left))
else if (auto lc = get<ClassType>(left))
{
return diffExternType(env, left, right);
return diffClass(env, left, right);
}
throw InternalCompilerError{"Unimplemented Simple TypeId variant for diffing"};
@ -960,7 +960,7 @@ bool isSimple(TypeId ty)
{
ty = follow(ty);
// TODO: think about GenericType, etc.
return get<PrimitiveType>(ty) || get<SingletonType>(ty) || get<AnyType>(ty) || get<NegationType>(ty) || get<ExternType>(ty) ||
return get<PrimitiveType>(ty) || get<SingletonType>(ty) || get<AnyType>(ty) || get<NegationType>(ty) || get<ClassType>(ty) ||
get<UnknownType>(ty) || get<NeverType>(ty);
}

View file

@ -1,9 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauDeclareExternType)
LUAU_FASTFLAG(LuauTypeFunOptional)
namespace Luau
{
@ -262,37 +259,7 @@ declare buffer: {
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionVectorSrc = (FFlag::LuauDeclareExternType)
? R"BUILTIN_SRC(
-- While vector would have been better represented as a built-in primitive type, type solver extern type handling covers most of the properties
declare extern type vector with
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"
: 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
declare class vector
@ -340,11 +307,11 @@ std::string getBuiltinDefinitionSource()
}
// TODO: split into separate tagged unions when the new solver can appropriately handle that.
static const std::string kBuiltinDefinitionTypeMethodSrc = R"BUILTIN_SRC(
static const std::string kBuiltinDefinitionTypesSrc = R"BUILTIN_SRC(
export type type = {
tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" |
"singleton" | "negation" | "union" | "intersection" | "table" | "function" | "class" | "generic",
"singleton" | "negation" | "union" | "intesection" | "table" | "function" | "class" | "generic",
is: (self: type, arg: string) -> boolean,
@ -391,10 +358,6 @@ export type type = {
ispack: (self: type) -> boolean,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC(
declare types: {
unknown: type,
never: type,
@ -414,44 +377,12 @@ declare types: {
newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type,
copy: @checked (arg: type) -> type,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionTypesLibWithOptionalSrc = R"BUILTIN_SRC(
declare types: {
unknown: type,
never: type,
any: type,
boolean: type,
number: type,
string: type,
thread: type,
buffer: type,
singleton: @checked (arg: string | boolean | nil) -> type,
optional: @checked (arg: type) -> type,
generic: @checked (name: string, ispack: boolean?) -> type,
negationof: @checked (arg: type) -> type,
unionof: @checked (...type) -> type,
intersectionof: @checked (...type) -> type,
newtable: @checked (props: {[type]: type} | {[type]: { read: type, write: type } } | nil, indexer: { index: type, readresult: type, writeresult: type }?, metatable: type?) -> type,
newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type,
copy: @checked (arg: type) -> type,
}
)BUILTIN_SRC";
std::string getTypeFunctionDefinitionSource()
{
std::string result = kBuiltinDefinitionTypeMethodSrc;
if (FFlag::LuauTypeFunOptional)
result += kBuiltinDefinitionTypesLibWithOptionalSrc;
else
result += kBuiltinDefinitionTypesLibSrc;
return result;
return kBuiltinDefinitionTypesSrc;
}
} // namespace Luau

View file

@ -330,9 +330,9 @@ Id toId(
return egraph.add(TOpaque{ty});
else if (get<FunctionType>(ty))
return egraph.add(TFunction{ty});
else if (ty == builtinTypes->externType)
else if (ty == builtinTypes->classType)
return egraph.add(TTopClass{});
else if (get<ExternType>(ty))
else if (get<ClassType>(ty))
return egraph.add(TClass{ty});
else if (get<AnyType>(ty))
return egraph.add(TAny{});
@ -752,7 +752,7 @@ TypeId fromId(
else if (node.get<TTopTable>())
return builtinTypes->tableType;
else if (node.get<TTopClass>())
return builtinTypes->externType;
return builtinTypes->classType;
else if (node.get<TBuffer>())
return builtinTypes->bufferType;
else if (auto opaque = node.get<TOpaque>())
@ -1007,7 +1007,7 @@ static std::string getNodeName(const StringCache& strings, const EType& node)
return "\xe2\x88\xa9";
else if (auto cls = node.get<TClass>())
{
const ExternType* ct = get<ExternType>(cls->value());
const ClassType* ct = get<ClassType>(cls->value());
LUAU_ASSERT(ct);
return ct->name;
}
@ -1177,12 +1177,12 @@ enum SubclassRelationship
static SubclassRelationship relateClasses(const TClass* leftClass, const TClass* rightClass)
{
const ExternType* leftExternType = Luau::get<ExternType>(leftClass->value());
const ExternType* rightExternType = Luau::get<ExternType>(rightClass->value());
const ClassType* leftClassType = Luau::get<ClassType>(leftClass->value());
const ClassType* rightClassType = Luau::get<ClassType>(rightClass->value());
if (isSubclass(leftExternType, rightExternType))
if (isSubclass(leftClassType, rightClassType))
return RightSuper;
else if (isSubclass(rightExternType, leftExternType))
else if (isSubclass(rightClassType, leftClassType))
return LeftSuper;
else
return Unrelated;

View file

@ -18,7 +18,7 @@
#include <unordered_set>
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauNonStrictFuncDefErrorFix)
static std::string wrongNumberOfArgsString(
size_t expectedCount,
@ -70,7 +70,7 @@ namespace Luau
{
// this list of binary operator type functions is used for better stringification of type functions errors
static const std::unordered_map<std::string, const char*> DEPRECATED_kBinaryOps{
static const std::unordered_map<std::string, const char*> kBinaryOps{
{"add", "+"},
{"sub", "-"},
{"mul", "*"},
@ -86,27 +86,12 @@ static const std::unordered_map<std::string, const char*> DEPRECATED_kBinaryOps{
{"eq", "== or ~="}
};
static const std::unordered_map<std::string, const char*> kBinaryOps{
{"add", "+"},
{"sub", "-"},
{"mul", "*"},
{"div", "/"},
{"idiv", "//"},
{"pow", "^"},
{"mod", "%"},
{"concat", ".."},
{"lt", "< or >="},
{"le", "<= or >"},
{"eq", "== or ~="}
};
// this list of unary operator type functions is used for better stringification of type functions errors
static const std::unordered_map<std::string, const char*> kUnaryOps{{"unm", "-"}, {"len", "#"}, {"not", "not"}};
// this list of type functions will receive a special error indicating that the user should file a bug on the GitHub repository
// putting a type function in this list indicates that it is expected to _always_ reduce
static const std::unordered_set<std::string> DEPRECATED_kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect"};
static const std::unordered_set<std::string> kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect", "and", "or"};
static const std::unordered_set<std::string> kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect"};
struct ErrorConverter
{
@ -203,7 +188,7 @@ struct ErrorConverter
TypeId t = follow(e.table);
if (get<TableType>(t))
return "Key '" + e.key + "' not found in table '" + Luau::toString(t) + "'";
else if (get<ExternType>(t))
else if (get<ClassType>(t))
return "Key '" + e.key + "' not found in class '" + Luau::toString(t) + "'";
else
return "Type '" + Luau::toString(e.table) + "' does not have key '" + e.key + "'";
@ -371,7 +356,7 @@ struct ErrorConverter
std::string s = "Key '" + e.key + "' not found in ";
TypeId t = follow(e.table);
if (get<ExternType>(t))
if (get<ClassType>(t))
s += "class";
else
s += "table";
@ -402,8 +387,8 @@ struct ErrorConverter
std::optional<TypeId> metatable;
if (const MetatableType* mtType = get<MetatableType>(type))
metatable = mtType->metatable;
else if (const ExternType* externType = get<ExternType>(type))
metatable = externType->metatable;
else if (const ClassType* classType = get<ClassType>(type))
metatable = classType->metatable;
if (!metatable)
return std::nullopt;
@ -611,7 +596,7 @@ struct ErrorConverter
return ss;
}
std::string operator()(const DynamicPropertyLookupOnExternTypesUnsafe& e) const
std::string operator()(const DynamicPropertyLookupOnClassesUnsafe& e) const
{
return "Attempting a dynamic property access on type '" + Luau::toString(e.ty) + "' is unsafe and may cause exceptions at runtime";
}
@ -658,8 +643,7 @@ struct ErrorConverter
}
// binary operators
const auto binaryOps = FFlag::DebugLuauGreedyGeneralization ? kBinaryOps : DEPRECATED_kBinaryOps;
if (auto binaryString = binaryOps.find(tfit->function->name); binaryString != binaryOps.end())
if (auto binaryString = kBinaryOps.find(tfit->function->name); binaryString != kBinaryOps.end())
{
std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types ";
@ -713,7 +697,7 @@ struct ErrorConverter
"'";
}
if ((FFlag::DebugLuauGreedyGeneralization ? kUnreachableTypeFunctions : DEPRECATED_kUnreachableTypeFunctions).count(tfit->function->name))
if (kUnreachableTypeFunctions.count(tfit->function->name))
{
return "Type function instance " + Luau::toString(e.ty) + " is uninhabited\n" +
"This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues";
@ -772,7 +756,7 @@ struct ErrorConverter
std::string operator()(const NonStrictFunctionDefinitionError& e) const
{
if (e.functionName.empty())
if (FFlag::LuauNonStrictFuncDefErrorFix && e.functionName.empty())
{
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' is used in a way that will run time error";
}
@ -819,11 +803,6 @@ struct ErrorConverter
return e.message;
}
std::string operator()(const ReservedIdentifier& e) const
{
return e.name + " cannot be used as an identifier for a type function or alias";
}
std::string operator()(const CannotAssignToNever& e) const
{
std::string result = "Cannot assign a value of type " + toString(e.rhsType) + " to a field of type never";
@ -1149,7 +1128,7 @@ bool TypePackMismatch::operator==(const TypePackMismatch& rhs) const
return *wantedTp == *rhs.wantedTp && *givenTp == *rhs.givenTp;
}
bool DynamicPropertyLookupOnExternTypesUnsafe::operator==(const DynamicPropertyLookupOnExternTypesUnsafe& rhs) const
bool DynamicPropertyLookupOnClassesUnsafe::operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const
{
return ty == rhs.ty;
}
@ -1211,11 +1190,6 @@ bool UserDefinedTypeFunctionError::operator==(const UserDefinedTypeFunctionError
return message == rhs.message;
}
bool ReservedIdentifier::operator==(const ReservedIdentifier& rhs) const
{
return name == rhs.name;
}
bool CannotAssignToNever::operator==(const CannotAssignToNever& rhs) const
{
if (cause.size() != rhs.cause.size())
@ -1391,7 +1365,7 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
e.wantedTp = clone(e.wantedTp);
e.givenTp = clone(e.givenTp);
}
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnExternTypesUnsafe>)
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>)
e.ty = clone(e.ty);
else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>)
e.ty = clone(e.ty);
@ -1435,9 +1409,6 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
for (auto& ty : e.cause)
ty = clone(ty);
}
else if constexpr (std::is_same_v<T, ReservedIdentifier>)
{
}
else
static_assert(always_false_v<T>, "Non-exhaustive type switch");
}

View file

@ -10,6 +10,10 @@
#include <string_view>
#include <utility>
LUAU_FASTFLAGVARIABLE(LuauExposeRequireByStringAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauEscapeCharactersInRequireSuggestions)
LUAU_FASTFLAGVARIABLE(LuauHideImpossibleRequireSuggestions)
namespace Luau
{
@ -18,10 +22,13 @@ static std::optional<RequireSuggestions> processRequireSuggestions(std::optional
if (!suggestions)
return suggestions;
if (FFlag::LuauEscapeCharactersInRequireSuggestions)
{
for (RequireSuggestion& suggestion : *suggestions)
{
suggestion.fullPath = escape(suggestion.fullPath);
}
}
return suggestions;
}
@ -105,11 +112,13 @@ static RequireSuggestions makeSuggestionsFromNode(std::unique_ptr<RequireNode> n
continue;
std::string pathComponent = child->getPathComponent();
if (FFlag::LuauHideImpossibleRequireSuggestions)
{
// If path component contains a slash, it cannot be required by string.
// There's no point suggesting it.
if (pathComponent.find('/') != std::string::npos)
continue;
}
RequireSuggestion suggestion;
suggestion.label = isPartialPath || path.back() == '/' ? child->getLabel() : "/" + child->getLabel();
@ -154,6 +163,9 @@ std::optional<RequireSuggestions> RequireSuggester::getRequireSuggestions(const
std::optional<RequireSuggestions> FileResolver::getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& path) const
{
if (!FFlag::LuauExposeRequireByStringAutocomplete)
return std::nullopt;
return requireSuggester ? requireSuggester->getRequireSuggestions(requirer, path) : std::nullopt;
}

View file

@ -27,20 +27,22 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit);
LUAU_FASTINT(LuauTypeInferIterationLimit);
LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf)
LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule)
LUAU_FASTFLAGVARIABLE(DebugLogFragmentsFromAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection)
LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes)
LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings)
LUAU_FASTFLAGVARIABLE(LuauCloneReturnTypePack)
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteDemandBasedCloning)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval)
LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection)
LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak)
LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace
{
@ -95,11 +97,7 @@ Location getFunctionDeclarationExtents(AstExprFunction* exprFn, AstExpr* exprNam
{
auto fnBegin = exprFn->location.begin;
auto fnEnd = exprFn->location.end;
if (auto returnAnnot = exprFn->returnAnnotation; FFlag::LuauStoreReturnTypesAsPackOnAst && returnAnnot)
{
fnEnd = returnAnnot->location.end;
}
else if (auto returnAnnot = exprFn->returnAnnotation_DEPRECATED; !FFlag::LuauStoreReturnTypesAsPackOnAst && returnAnnot)
if (auto returnAnnot = exprFn->returnAnnotation)
{
if (returnAnnot->tailType)
fnEnd = returnAnnot->tailType->location.end;
@ -544,11 +542,6 @@ struct UsageFinder : public AstVisitor
return true;
}
bool visit(AstTypePack* node) override
{
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
bool visit(AstStatTypeAlias* alias) override
{
declaredAliases.insert(std::string(alias->name.value));
@ -576,32 +569,12 @@ struct UsageFinder : public AstVisitor
return true;
}
bool visit(AstExprGlobal* global) override
{
if (FFlag::LuauGlobalVariableModuleIsolation)
globalDefsToPrePopulate.emplace_back(global->name, dfg->getDef(global));
return true;
}
bool visit(AstStatFunction* function) override
{
if (FFlag::LuauGlobalVariableModuleIsolation)
{
if (AstExprGlobal* g = function->name->as<AstExprGlobal>())
globalFunctionsReferenced.emplace_back(g->name);
}
return true;
}
NotNull<DataFlowGraph> dfg;
DenseHashSet<Name> declaredAliases{""};
std::vector<std::pair<const Def*, AstLocal*>> localBindingsReferenced;
DenseHashSet<const Def*> mentionedDefs{nullptr};
std::vector<Name> referencedBindings{""};
std::vector<std::pair<Name, Name>> referencedImportedBindings{{"", ""}};
std::vector<std::pair<AstName, const Def*>> globalDefsToPrePopulate;
std::vector<AstName> globalFunctionsReferenced;
};
// Runs the `UsageFinder` traversal on the fragment and grabs all of the types that are
@ -675,45 +648,7 @@ void cloneTypesFromFragment(
}
}
if (FFlag::LuauGlobalVariableModuleIsolation)
{
// Fourth - prepopulate the global function types
for (const auto& name : f.globalFunctionsReferenced)
{
if (auto ty = staleModule->getModuleScope()->lookup(name))
{
destScope->bindings[name] = Binding{Luau::cloneIncremental(*ty, *destArena, cloneState, destScope)};
}
else
{
TypeId bt = destArena->addType(BlockedType{});
destScope->bindings[name] = Binding{bt};
}
}
// Fifth - prepopulate the globals here
for (const auto& [name, def] : f.globalDefsToPrePopulate)
{
if (auto ty = staleModule->getModuleScope()->lookup(name))
{
destScope->lvalueTypes[def] = Luau::cloneIncremental(*ty, *destArena, cloneState, destScope);
}
else if (auto ty = destScope->lookup(name))
{
// This branch is a little strange - we are looking up a symbol in the destScope
// This scope has no parent pointer, and only cloned types are written to it, so this is a
// safe operation to do without cloning.
// The reason we do this, is the usage finder will traverse the global functions referenced first
// If there is no name associated with this function at the global scope, it must appear first in the fragment and we must
// create a blocked type for it. We write this blocked type directly into the `destScope` bindings
// Then when we go to traverse the `AstExprGlobal` associated with this function, we need to ensure that we map the def -> blockedType
// in `lvalueTypes`, which was previously written into `destScope`
destScope->lvalueTypes[def] = *ty;
}
}
}
// Finally, clone the returnType on the staleScope. This helps avoid potential leaks of free types.
// Finally - clone the returnType on the staleScope. This helps avoid potential leaks of free types.
if (staleScope->returnType)
destScope->returnType = Luau::cloneIncremental(staleScope->returnType, *destArena, cloneState, destScope);
}
@ -735,7 +670,7 @@ struct MixedModeIncrementalTCDefFinder : public AstVisitor
// requires that we find the local/global `m` and place it in the environment.
// The default behaviour here is to return false, and have individual visitors override
// the specific behaviour they need.
return true;
return FFlag::LuauMixedModeDefFinderTraversesTypeOf;
}
bool visit(AstStatTypeAlias* alias) override
@ -885,7 +820,7 @@ void cloneAndSquashScopes(
}
}
if (destScope->returnType)
if (FFlag::LuauCloneReturnTypePack && destScope->returnType)
destScope->returnType = Luau::cloneIncremental(destScope->returnType, *destArena, cloneState, destScope);
return;
@ -1225,6 +1160,31 @@ ModulePtr cloneModule(CloneState& cloneState, const ModulePtr& source, std::uniq
return incremental;
}
ModulePtr copyModule(const ModulePtr& result, std::unique_ptr<Allocator> alloc)
{
ModulePtr incrementalModule = std::make_shared<Module>();
incrementalModule->name = result->name;
incrementalModule->humanReadableName = "Incremental$" + result->humanReadableName;
incrementalModule->internalTypes.owningModule = incrementalModule.get();
incrementalModule->interfaceTypes.owningModule = incrementalModule.get();
incrementalModule->allocator = std::move(alloc);
// Don't need to keep this alive (it's already on the source module)
copyModuleVec(incrementalModule->scopes, result->scopes);
copyModuleMap(incrementalModule->astTypes, result->astTypes);
copyModuleMap(incrementalModule->astTypePacks, result->astTypePacks);
copyModuleMap(incrementalModule->astExpectedTypes, result->astExpectedTypes);
// Don't need to clone astOriginalCallTypes
copyModuleMap(incrementalModule->astOverloadResolvedTypes, result->astOverloadResolvedTypes);
// Don't need to clone astForInNextTypes
copyModuleMap(incrementalModule->astForInNextTypes, result->astForInNextTypes);
// Don't need to clone astResolvedTypes
// Don't need to clone astResolvedTypePacks
// Don't need to clone upperBoundContributors
copyModuleMap(incrementalModule->astScopes, result->astScopes);
// Don't need to clone declared Globals;
return incrementalModule;
}
void mixedModeCompatibility(
const ScopePtr& bottomScopeStale,
const ScopePtr& myFakeScope,
@ -1288,8 +1248,10 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
ModulePtr incrementalModule = nullptr;
if (FFlag::LuauAllFreeTypesHaveScopes)
incrementalModule = cloneModule(cloneState, stale, std::move(astAllocator), freshChildOfNearestScope.get());
else
else if (FFlag::LuauCloneIncrementalModule)
incrementalModule = cloneModule_DEPRECATED(cloneState, stale, std::move(astAllocator));
else
incrementalModule = copyModule(stale, std::move(astAllocator));
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneModuleEnd);
incrementalModule->checkedInNewSolver = true;
@ -1343,6 +1305,8 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
};
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart);
if (FFlag::LuauCloneIncrementalModule)
{
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
cg.rootScope = freshChildOfNearestScope.get();
@ -1363,7 +1327,22 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
for (auto p : cg.scopes)
incrementalModule->scopes.emplace_back(std::move(p));
}
}
else
{
// Any additions to the scope must occur in a fresh scope
cg.rootScope = stale->getModuleScope().get();
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
mixedModeCompatibility(closestScope, freshChildOfNearestScope, stale, NotNull{&dfg}, root);
// closest Scope -> children = { ...., freshChildOfNearestScope}
// We need to trim nearestChild from the scope hierarchy
closestScope->children.emplace_back(freshChildOfNearestScope.get());
cg.visitFragmentRoot(freshChildOfNearestScope, root);
// Trim nearestChild from the closestScope
Scope* back = closestScope->children.back().get();
LUAU_ASSERT(back == freshChildOfNearestScope.get());
closestScope->children.pop_back();
}
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverStart);
if (FFlag::LuauAllFreeTypesHaveScopes)
@ -1473,7 +1452,7 @@ FragmentTypeCheckResult typecheckFragment_(
SimplifierPtr simplifier = newSimplifier(NotNull{&incrementalModule->internalTypes}, frontend.builtinTypes);
FrontendModuleResolver& resolver = getModuleResolver(frontend, opts);
std::shared_ptr<Scope> freshChildOfNearestScope = std::make_shared<Scope>(nullptr);
/// Contraint Generator
ConstraintGenerator cg{
incrementalModule,
@ -1483,7 +1462,7 @@ FragmentTypeCheckResult typecheckFragment_(
NotNull{&resolver},
frontend.builtinTypes,
iceHandler,
FFlag::LuauGlobalVariableModuleIsolation ? freshChildOfNearestScope : stale->getModuleScope(),
stale->getModuleScope(),
frontend.globals.globalTypeFunctionScope,
nullptr,
nullptr,
@ -1492,6 +1471,7 @@ FragmentTypeCheckResult typecheckFragment_(
};
CloneState cloneState{frontend.builtinTypes};
std::shared_ptr<Scope> freshChildOfNearestScope = std::make_shared<Scope>(nullptr);
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
freshChildOfNearestScope->interiorFreeTypes.emplace();
freshChildOfNearestScope->interiorFreeTypePacks.emplace();
@ -1669,6 +1649,7 @@ FragmentAutocompleteResult fragmentAutocomplete(
IFragmentAutocompleteReporter* reporter
)
{
LUAU_ASSERT(FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
LUAU_TIMETRACE_SCOPE("Luau::fragmentAutocomplete", "FragmentAutocomplete");
LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str());

View file

@ -40,13 +40,13 @@ LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRethrowKnownExceptions, false)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
namespace Luau
@ -129,9 +129,9 @@ static void generateDocumentationSymbols(TypeId ty, const std::string& rootName)
prop.documentationSymbol = rootName + "." + name;
}
}
else if (ExternType* etv = getMutable<ExternType>(ty))
else if (ClassType* ctv = getMutable<ClassType>(ty))
{
for (auto& [name, prop] : etv->props)
for (auto& [name, prop] : ctv->props)
{
prop.documentationSymbol = rootName + "." + name;
}
@ -1003,8 +1003,11 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
freeze(module->interfaceTypes);
module->internalTypes.clear();
if (FFlag::LuauSelectivelyRetainDFGArena)
{
module->defArena.allocator.clear();
module->keyArena.allocator.clear();
}
module->astTypes.clear();
module->astTypePacks.clear();
@ -1305,7 +1308,7 @@ ModulePtr check(
struct InternalTypeFinder : TypeOnceVisitor
{
bool visit(TypeId, const ExternType&) override
bool visit(TypeId, const ClassType&) override
{
return false;
}
@ -1420,36 +1423,10 @@ ModulePtr check(
requireCycles
};
// FIXME: Delete this flag when clipping FFlag::DebugLuauGreedyGeneralization.
//
// This optional<> only exists so that we can run one constructor when the flag
// is set, and another when it is unset.
std::optional<ConstraintSolver> cs;
if (FFlag::DebugLuauGreedyGeneralization)
{
ConstraintSet constraintSet = cg.run(sourceModule.root);
result->errors = std::move(constraintSet.errors);
cs.emplace(
NotNull{&normalizer},
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
result->name,
moduleResolver,
requireCycles,
logger.get(),
NotNull{&dfg},
limits,
std::move(constraintSet)
);
}
else
{
cg.visitModuleRoot(sourceModule.root);
result->errors = std::move(cg.errors);
cs.emplace(
ConstraintSolver cs{
NotNull{&normalizer},
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
@ -1462,17 +1439,14 @@ ModulePtr check(
logger.get(),
NotNull{&dfg},
limits
);
}
LUAU_ASSERT(bool(cs));
};
if (options.randomizeConstraintResolutionSeed)
cs->randomize(*options.randomizeConstraintResolutionSeed);
cs.randomize(*options.randomizeConstraintResolutionSeed);
try
{
cs->run();
cs.run();
}
catch (const TimeLimitError&)
{
@ -1492,12 +1466,12 @@ ModulePtr check(
printf("%s\n", output.c_str());
}
for (TypeError& e : cs->errors)
for (TypeError& e : cs.errors)
result->errors.emplace_back(std::move(e));
result->scopes = std::move(cg.scopes);
result->type = sourceModule.type;
result->upperBoundContributors = std::move(cs->upperBoundContributors);
result->upperBoundContributors = std::move(cs.upperBoundContributors);
if (result->timeout || result->cancelled)
{

View file

@ -11,12 +11,11 @@
#include "Luau/Type.h"
#include "Luau/TypeArena.h"
#include "Luau/TypePack.h"
#include "Luau/Substitution.h"
#include "Luau/VisitType.h"
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization2)
LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization)
namespace Luau
{
@ -469,7 +468,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const FreeType& ft) override
{
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
{
if (!subsumes(scope, ft.scope))
return true;
@ -520,7 +519,7 @@ struct FreeTypeSearcher : TypeVisitor
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
{
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
unsealedTables.insert(ty);
else
{
@ -548,7 +547,7 @@ struct FreeTypeSearcher : TypeVisitor
traverse(*prop.readTy);
else
{
LUAU_ASSERT(prop.isShared());
LUAU_ASSERT(prop.isShared() || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
Polarity p = polarity;
polarity = Polarity::Mixed;
@ -559,7 +558,7 @@ struct FreeTypeSearcher : TypeVisitor
if (tt.indexer)
{
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
{
// {[K]: V} is equivalent to three functions: get, set, and iterate
//
@ -604,7 +603,7 @@ struct FreeTypeSearcher : TypeVisitor
return false;
}
bool visit(TypeId, const ExternType&) override
bool visit(TypeId, const ClassType&) override
{
return false;
}
@ -617,7 +616,7 @@ struct FreeTypeSearcher : TypeVisitor
if (!subsumes(scope, ftp.scope))
return true;
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
{
GeneralizationParams<TypePackId>& params = typePacks[tp];
++params.useCount;
@ -896,7 +895,7 @@ struct TypeCacher : TypeOnceVisitor
return false;
}
bool visit(TypeId ty, const ExternType&) override
bool visit(TypeId ty, const ClassType&) override
{
cache(ty);
return false;
@ -1093,95 +1092,73 @@ struct TypeCacher : TypeOnceVisitor
}
};
struct RemoveType : Substitution // NOLINT
/**
* Remove occurrences of `needle` within `haystack`. This is used to cull cyclic bounds from free types.
*
* @param haystack Either the upper or lower bound of a free type.
* @param needle The type to be removed.
*/
[[nodiscard]]
static TypeId removeType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, DenseHashSet<TypeId>& seen, TypeId haystack, TypeId needle)
{
NotNull<BuiltinTypes> builtinTypes;
TypeId needle;
haystack = follow(haystack);
RemoveType(NotNull<BuiltinTypes> builtinTypes, TypeArena* arena, TypeId needle)
: Substitution(arena)
, builtinTypes(builtinTypes)
, needle(needle)
{
}
if (seen.find(haystack))
return haystack;
seen.insert(haystack);
bool ignoreChildren(TypeId ty) override
if (const UnionType* ut = get<UnionType>(haystack))
{
if (get<UnionType>(ty) || get<IntersectionType>(ty))
return false;
OrderedSet<TypeId> newOptions;
for (TypeId option : ut)
{
if (option == needle)
continue;
if (get<NeverType>(option))
continue;
LUAU_ASSERT(!get<UnionType>(option));
if (get<IntersectionType>(option))
newOptions.insert(removeType(arena, builtinTypes, seen, option, needle));
else
return true;
newOptions.insert(option);
}
bool isDirty(TypeId ty) override
{
// A union or intersection is dirty if it contains the needle or if it has any duplicate members.
if (auto ut = get<UnionType>(ty))
{
DenseHashSet<TypeId> distinctParts{nullptr};
size_t count = 0;
for (TypeId part : ut)
{
++count;
if (part == needle)
return true;
distinctParts.insert(follow(part));
}
return distinctParts.size() != count;
}
else if (auto it = get<IntersectionType>(ty))
{
DenseHashSet<TypeId> distinctParts{nullptr};
size_t count = 0;
for (TypeId part : it)
{
++count;
if (part == needle)
return true;
distinctParts.insert(follow(part));
}
return distinctParts.size() != count;
}
return false;
}
bool isDirty(TypePackId tp) override
{
return false;
}
TypeId clean(TypeId ty) override
{
if (auto ut = get<UnionType>(ty))
{
OrderedSet<TypeId> newParts;
for (TypeId ty : ut)
{
if (ty != needle)
newParts.insert(ty);
}
if (newParts.empty())
if (newOptions.empty())
return builtinTypes->neverType;
else if (newParts.size() == 1)
else if (newOptions.size() == 1)
{
TypeId onlyType = *newParts.begin();
LUAU_ASSERT(onlyType != needle);
TypeId onlyType = *newOptions.begin();
LUAU_ASSERT(onlyType != haystack);
return onlyType;
}
else
return arena->addType(UnionType{newParts.takeVector()});
return arena->addType(UnionType{newOptions.takeVector()});
}
else if (auto it = get<IntersectionType>(ty))
if (const IntersectionType* it = get<IntersectionType>(haystack))
{
OrderedSet<TypeId> newParts;
for (TypeId ty : it)
for (TypeId part : it)
{
if (ty != needle)
newParts.insert(ty);
part = follow(part);
if (part == needle)
continue;
if (get<UnknownType>(part))
continue;
LUAU_ASSERT(!get<IntersectionType>(follow(part)));
if (get<UnionType>(part))
newParts.insert(removeType(arena, builtinTypes, seen, part, needle));
else
newParts.insert(part);
}
if (newParts.empty())
@ -1195,31 +1172,11 @@ struct RemoveType : Substitution // NOLINT
else
return arena->addType(IntersectionType{newParts.takeVector()});
}
else
return ty;
}
TypePackId clean(TypePackId tp) override
{
return tp;
}
};
/**
* Remove occurrences of `needle` within `haystack`. This is used to cull cyclic bounds from free types.
*
* @param haystack Either the upper or lower bound of a free type.
* @param needle The type to be removed.
*/
[[nodiscard]]
static std::optional<
TypeId> removeType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, TypeId haystack, TypeId needle)
{
RemoveType rt{builtinTypes, arena, needle};
return rt.substitute(haystack);
return haystack;
}
GeneralizationResult<TypeId> generalizeType(
std::optional<TypeId> generalizeType(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
@ -1232,7 +1189,7 @@ GeneralizationResult<TypeId> generalizeType(
FreeType* ft = getMutable<FreeType>(freeTy);
LUAU_ASSERT(ft);
LUAU_ASSERT(isKnown(params.polarity));
LUAU_ASSERT(isPositive(params.polarity) || isNegative(params.polarity));
const bool hasLowerBound = !get<NeverType>(follow(ft->lowerBound));
const bool hasUpperBound = !get<UnknownType>(follow(ft->upperBound));
@ -1241,12 +1198,12 @@ GeneralizationResult<TypeId> generalizeType(
if (!hasLowerBound && !hasUpperBound)
{
if (!isWithinFunction || (!FFlag::DebugLuauGreedyGeneralization && (params.polarity != Polarity::Mixed && params.useCount == 1)))
if ((params.polarity != Polarity::Mixed && params.useCount == 1) || !isWithinFunction)
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
else
{
emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity);
return {freeTy, /*wasReplacedByGeneric*/ true};
return freeTy;
}
}
// It is possible that this free type has other free types in its upper
@ -1262,24 +1219,20 @@ GeneralizationResult<TypeId> generalizeType(
lowerFree->upperBound = builtinTypes->unknownType;
else
{
std::optional<TypeId> removed = removeType(arena, builtinTypes, lb, freeTy);
if (removed)
lb = *removed;
else
return {std::nullopt, false, /*resourceLimitsExceeded*/ true};
DenseHashSet<TypeId> replaceSeen{nullptr};
lb = removeType(arena, builtinTypes, replaceSeen, lb, freeTy);
ft->lowerBound = lb;
}
if (follow(lb) != freeTy)
emplaceType<BoundType>(asMutable(freeTy), lb);
else if (!isWithinFunction || (!FFlag::DebugLuauGreedyGeneralization && params.useCount == 1))
else if (!isWithinFunction || params.useCount == 1)
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
else
{
// if the lower bound is the type in question (eg 'a <: 'a), we don't actually have a lower bound.
emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity);
return {freeTy, /*wasReplacedByGeneric*/ true};
return freeTy;
}
}
else
@ -1290,11 +1243,8 @@ GeneralizationResult<TypeId> generalizeType(
else
{
// If the free type appears within its own upper bound, cull that cycle.
std::optional<TypeId> removed = removeType(arena, builtinTypes, ub, freeTy);
if (removed)
ub = *removed;
else
return {std::nullopt, false, /*resourceLimitsExceeded*/ true};
DenseHashSet<TypeId> replaceSeen{nullptr};
ub = removeType(arena, builtinTypes, replaceSeen, ub, freeTy);
ft->upperBound = ub;
}
@ -1306,14 +1256,14 @@ GeneralizationResult<TypeId> generalizeType(
{
// if the upper bound is the type in question, we don't actually have an upper bound.
emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity);
return {freeTy, /*wasReplacedByGeneric*/ true};
return freeTy;
}
}
return {freeTy, /*wasReplacedByGeneric*/ false};
return std::nullopt;
}
GeneralizationResult<TypePackId> generalizeTypePack(
std::optional<TypePackId> generalizeTypePack(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
@ -1324,24 +1274,24 @@ GeneralizationResult<TypePackId> generalizeTypePack(
tp = follow(tp);
if (tp->owningArena != arena)
return {tp, /*wasReplacedByGeneric*/ false};
return std::nullopt;
const FreeTypePack* ftp = get<FreeTypePack>(tp);
if (!ftp)
return {tp, /*wasReplacedByGeneric*/ false};
return std::nullopt;
if (!subsumes(scope, ftp->scope))
return {tp, /*wasReplacedByGeneric*/ false};
return std::nullopt;
if (1 == params.useCount)
emplaceTypePack<BoundTypePack>(asMutable(tp), builtinTypes->unknownTypePack);
else
{
emplaceTypePack<GenericTypePack>(asMutable(tp), scope, params.polarity);
return {tp, /*wasReplacedByGeneric*/ true};
return tp;
}
return {tp, /*wasReplacedByGeneric*/ false};
return std::nullopt;
}
void sealTable(NotNull<Scope> scope, TypeId ty)
@ -1362,8 +1312,7 @@ std::optional<TypeId> generalize(
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes,
TypeId ty,
std::optional<TypeId> generalizationTarget
TypeId ty
)
{
ty = follow(ty);
@ -1374,7 +1323,7 @@ std::optional<TypeId> generalize(
FreeTypeSearcher fts{scope, cachedTypes};
fts.traverse(ty);
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
{
FunctionType* functionTy = getMutable<FunctionType>(ty);
auto pushGeneric = [&](TypeId t)
@ -1391,37 +1340,21 @@ std::optional<TypeId> generalize(
for (const auto& [freeTy, params] : fts.types)
{
if (!generalizationTarget || freeTy == *generalizationTarget)
{
GeneralizationResult<TypeId> res = generalizeType(arena, builtinTypes, scope, freeTy, params);
if (res.resourceLimitsExceeded)
return std::nullopt;
if (res && res.wasReplacedByGeneric)
pushGeneric(*res.result);
}
if (std::optional<TypeId> genericTy = generalizeType(arena, builtinTypes, scope, freeTy, params))
pushGeneric(*genericTy);
}
for (TypeId unsealedTableTy : fts.unsealedTables)
{
if (!generalizationTarget || unsealedTableTy == *generalizationTarget)
sealTable(scope, unsealedTableTy);
}
for (const auto& [freePackId, params] : fts.typePacks)
{
TypePackId freePack = follow(freePackId);
if (!generalizationTarget)
{
GeneralizationResult<TypePackId> generalizedTp = generalizeTypePack(arena, builtinTypes, scope, freePack, params);
std::optional<TypePackId> generalizedTp = generalizeTypePack(arena, builtinTypes, scope, freePack, params);
if (generalizedTp.resourceLimitsExceeded)
return std::nullopt;
if (generalizedTp && generalizedTp.wasReplacedByGeneric)
if (generalizedTp)
pushGenericPack(freePack);
}
}
TypeCacher cacher{cachedTypes};
cacher.traverse(ty);
@ -1464,197 +1397,4 @@ std::optional<TypeId> generalize(
return ty;
}
struct GenericCounter : TypeVisitor
{
struct CounterState
{
size_t count = 0;
Polarity polarity = Polarity::None;
};
NotNull<DenseHashSet<TypeId>> cachedTypes;
DenseHashMap<TypeId, CounterState> generics{nullptr};
DenseHashMap<TypePackId, CounterState> genericPacks{nullptr};
Polarity polarity = Polarity::Positive;
explicit GenericCounter(NotNull<DenseHashSet<TypeId>> cachedTypes)
: cachedTypes(cachedTypes)
{
}
bool visit(TypeId ty, const FunctionType& ft) override
{
if (ty->persistent)
return false;
polarity = invert(polarity);
traverse(ft.argTypes);
polarity = invert(polarity);
traverse(ft.retTypes);
return false;
}
bool visit(TypeId ty, const TableType& tt) override
{
if (ty->persistent)
return false;
const Polarity previous = polarity;
for (const auto& [_name, prop] : tt.props)
{
if (prop.isReadOnly())
traverse(*prop.readTy);
else
{
LUAU_ASSERT(prop.isShared());
polarity = Polarity::Mixed;
traverse(prop.type());
polarity = previous;
}
}
if (tt.indexer)
{
polarity = Polarity::Mixed;
traverse(tt.indexer->indexType);
traverse(tt.indexer->indexResultType);
polarity = previous;
}
return false;
}
bool visit(TypeId ty, const ExternType&) override
{
return false;
}
bool visit(TypeId ty, const GenericType&) override
{
auto state = generics.find(ty);
if (state)
{
++state->count;
state->polarity |= polarity;
}
return false;
}
bool visit(TypePackId tp, const GenericTypePack&) override
{
auto state = genericPacks.find(tp);
if (state)
{
++state->count;
state->polarity |= polarity;
}
return false;
}
};
void pruneUnnecessaryGenerics(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes,
TypeId ty
)
{
if (!FFlag::DebugLuauGreedyGeneralization)
return;
ty = follow(ty);
if (ty->owningArena != arena || ty->persistent)
return;
FunctionType* functionTy = getMutable<FunctionType>(ty);
if (!functionTy)
return;
// If a generic has no explicit name and is only referred to in one place in
// the function's signature, it can be replaced with unknown.
GenericCounter counter{cachedTypes};
for (TypeId generic : functionTy->generics)
{
generic = follow(generic);
auto g = get<GenericType>(generic);
if (g && !g->explicitName)
counter.generics[generic] = {};
}
// It is sometimes the case that a pack in the generic list will become a
// pack that (transitively) has a generic tail. If it does, we need to add
// that generic tail to the generic pack list.
for (size_t i = 0; i < functionTy->genericPacks.size(); ++i)
{
TypePackId genericPack = follow(functionTy->genericPacks[i]);
TypePackId tail = getTail(genericPack);
if (tail != genericPack)
functionTy->genericPacks.push_back(tail);
if (auto g = get<GenericTypePack>(tail); g && !g->explicitName)
counter.genericPacks[genericPack] = {};
}
counter.traverse(ty);
for (const auto& [generic, state] : counter.generics)
{
if (state.count == 1 && state.polarity != Polarity::Mixed)
emplaceType<BoundType>(asMutable(generic), builtinTypes->unknownType);
}
// Remove duplicates and types that aren't actually generics.
DenseHashSet<TypeId> seen{nullptr};
auto it = std::remove_if(
functionTy->generics.begin(),
functionTy->generics.end(),
[&](TypeId ty)
{
ty = follow(ty);
if (seen.contains(ty))
return true;
seen.insert(ty);
return !get<GenericType>(ty);
}
);
functionTy->generics.erase(it, functionTy->generics.end());
for (const auto& [genericPack, state] : counter.genericPacks)
{
if (state.count == 1)
emplaceTypePack<BoundTypePack>(asMutable(genericPack), builtinTypes->unknownTypePack);
}
DenseHashSet<TypePackId> seen2{nullptr};
auto it2 = std::remove_if(
functionTy->genericPacks.begin(),
functionTy->genericPacks.end(),
[&](TypePackId tp)
{
tp = follow(tp);
if (seen2.contains(tp))
return true;
seen2.insert(tp);
return !get<GenericTypePack>(tp);
}
);
functionTy->genericPacks.erase(it2, functionTy->genericPacks.end());
}
} // namespace Luau

View file

@ -5,7 +5,7 @@
#include "Luau/Scope.h"
#include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
namespace Luau
{
@ -95,16 +95,16 @@ struct InferPolarity : TypeVisitor
// types.
for (TypeId generic : ft.generics)
{
generic = follow(generic);
const auto gen = get<GenericType>(generic);
if (gen && subsumes(scope, gen->scope))
LUAU_ASSERT(gen);
if (subsumes(scope, gen->scope))
types[generic] = Polarity::None;
}
for (TypePackId genericPack : ft.genericPacks)
{
genericPack = follow(genericPack);
const auto gen = get<GenericTypePack>(genericPack);
if (gen && subsumes(scope, gen->scope))
LUAU_ASSERT(gen);
if (subsumes(scope, gen->scope))
packs[genericPack] = Polarity::None;
}
@ -118,7 +118,7 @@ struct InferPolarity : TypeVisitor
return false;
}
bool visit(TypeId, const ExternType&) override
bool visit(TypeId, const ClassType&) override
{
return false;
}
@ -133,7 +133,7 @@ struct InferPolarity : TypeVisitor
template<typename TID>
static void inferGenericPolarities_(NotNull<TypeArena> arena, NotNull<Scope> scope, TID ty)
{
if (!FFlag::LuauNonReentrantGeneralization2)
if (!FFlag::LuauNonReentrantGeneralization)
return;
InferPolarity infer{arena, scope};

View file

@ -11,6 +11,7 @@
#include <algorithm>
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
namespace Luau
{
@ -49,7 +50,7 @@ bool Instantiation::ignoreChildren(TypeId ty)
{
if (log->getMutable<FunctionType>(ty))
return true;
else if (get<ExternType>(ty))
else if (get<ClassType>(ty))
return true;
else
return false;
@ -119,7 +120,7 @@ bool ReplaceGenerics::ignoreChildren(TypeId ty)
// whenever we quantify, so the vectors overlap if and only if they are equal.
return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks);
}
else if (get<ExternType>(ty))
else if (get<ClassType>(ty))
return true;
else
{
@ -163,7 +164,7 @@ TypeId ReplaceGenerics::clean(TypeId ty)
}
else
{
return arena->freshType(builtinTypes, scope, level);
return FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtinTypes, scope, level) : addType(FreeType{scope, level});
}
}

View file

@ -6,7 +6,7 @@ namespace Luau
bool Instantiation2::ignoreChildren(TypeId ty)
{
if (get<ExternType>(ty))
if (get<ClassType>(ty))
return true;
if (auto ftv = get<FunctionType>(ty))

View file

@ -193,8 +193,8 @@ static void errorToString(std::ostream& stream, const T& err)
stream << "NormalizationTooComplex { }";
else if constexpr (std::is_same_v<T, TypePackMismatch>)
stream << "TypePackMismatch { wanted = '" + toString(err.wantedTp) + "', given = '" + toString(err.givenTp) + "' }";
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnExternTypesUnsafe>)
stream << "DynamicPropertyLookupOnExternTypesUnsafe { " << toString(err.ty) << " }";
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>)
stream << "DynamicPropertyLookupOnClassesUnsafe { " << toString(err.ty) << " }";
else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>)
stream << "UninhabitedTypeFunction { " << toString(err.ty) << " }";
else if constexpr (std::is_same_v<T, ExplicitFunctionAnnotationRecommended>)
@ -229,8 +229,6 @@ static void errorToString(std::ostream& stream, const T& err)
stream << "UnexpectedTypePackInSubtyping { tp = '" + toString(err.tp) + "' }";
else if constexpr (std::is_same_v<T, UserDefinedTypeFunctionError>)
stream << "UserDefinedTypeFunctionError { " << err.message << " }";
else if constexpr (std::is_same_v<T, ReservedIdentifier>)
stream << "ReservedIdentifier { " << err.name << " }";
else if constexpr (std::is_same_v<T, CannotAssignToNever>)
{
stream << "CannotAssignToNever { rvalueType = '" << toString(err.rhsType) << "', reason = '" << err.reason << "', cause = { ";

View file

@ -20,7 +20,6 @@ LUAU_FASTFLAG(LuauAttribute)
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau
{
@ -909,11 +908,6 @@ private:
return true;
}
bool visit(AstTypePack* node) override
{
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
bool visit(AstTypeReference* node) override
{
if (!node->prefix)
@ -1976,11 +1970,6 @@ private:
return true;
}
bool visit(AstTypePack* node) override
{
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
bool visit(AstTypeTable* node) override
{
if (FFlag::LuauSolverV2)
@ -2383,9 +2372,9 @@ private:
void check(AstExprIndexName* node, TypeId ty)
{
if (const ExternType* cty = get<ExternType>(ty))
if (const ClassType* cty = get<ClassType>(ty))
{
const Property* prop = lookupExternTypeProp(cty, node->index.value);
const Property* prop = lookupClassProp(cty, node->index.value);
if (prop && prop->deprecated)
report(node->location, *prop, cty->name.c_str(), node->index.value);

View file

@ -20,12 +20,10 @@
#include <iostream>
#include <iterator>
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauNonStrictFuncDefErrorFix)
namespace Luau
{
@ -217,7 +215,7 @@ struct NonStrictTypeChecker
return *fst;
else if (auto ftp = get<FreeTypePack>(pack))
{
TypeId result = arena->freshType(builtinTypes, ftp->scope);
TypeId result = FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtinTypes, ftp->scope) : arena->addType(FreeType{ftp->scope});
TypePackId freeTail = arena->addTypePack(FreeTypePack{ftp->scope});
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
@ -311,7 +309,7 @@ struct NonStrictTypeChecker
return visit(s);
else if (auto s = stat->as<AstStatDeclareGlobal>())
return visit(s);
else if (auto s = stat->as<AstStatDeclareExternType>())
else if (auto s = stat->as<AstStatDeclareClass>())
return visit(s);
else if (auto s = stat->as<AstStatError>())
return visit(s);
@ -337,12 +335,7 @@ struct NonStrictTypeChecker
// local x ; B generates the context of B without x
visit(local);
for (auto local : local->vars)
{
ctx.remove(dfg->getDef(local));
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(local->annotation);
}
}
else
ctx = NonStrictContext::disjunction(builtinTypes, arena, visit(stat), ctx);
@ -427,9 +420,6 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatFor* forStatement)
{
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(forStatement->var->annotation);
if (FFlag::LuauNonStrictVisitorImprovements)
{
// TODO: throwing out context based on same principle as existing code?
@ -449,12 +439,6 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatForIn* forInStatement)
{
if (FFlag::LuauNewNonStrictVisitTypes2)
{
for (auto var : forInStatement->vars)
visit(var->annotation);
}
if (FFlag::LuauNonStrictVisitorImprovements)
{
for (AstExpr* rhs : forInStatement->values)
@ -503,12 +487,6 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatTypeAlias* typeAlias)
{
if (FFlag::LuauNewNonStrictVisitTypes2)
{
visitGenerics(typeAlias->generics, typeAlias->genericPacks);
visit(typeAlias->type);
}
return {};
}
@ -519,38 +497,16 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatDeclareFunction* declFn)
{
if (FFlag::LuauNewNonStrictVisitTypes2)
{
visitGenerics(declFn->generics, declFn->genericPacks);
visit(declFn->params);
visit(declFn->retTypes);
}
return {};
}
NonStrictContext visit(AstStatDeclareGlobal* declGlobal)
{
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(declGlobal->type);
return {};
}
NonStrictContext visit(AstStatDeclareExternType* declClass)
NonStrictContext visit(AstStatDeclareClass* declClass)
{
if (FFlag::LuauNewNonStrictVisitTypes2)
{
if (declClass->indexer)
{
visit(declClass->indexer->indexType);
visit(declClass->indexer->resultType);
}
for (auto prop : declClass->props)
visit(prop.ty);
}
return {};
}
@ -809,32 +765,19 @@ struct NonStrictTypeChecker
for (AstLocal* local : exprFn->args)
{
if (std::optional<TypeId> ty = willRunTimeErrorFunctionDefinition(local, remainder))
{
if (FFlag::LuauNonStrictFuncDefErrorFix)
{
const char* debugname = exprFn->debugname.value;
reportError(NonStrictFunctionDefinitionError{debugname ? debugname : "", local->name.value, *ty}, local->location);
}
remainder.remove(dfg->getDef(local));
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(local->annotation);
}
if (FFlag::LuauNewNonStrictVisitTypes2)
{
visitGenerics(exprFn->generics, exprFn->genericPacks);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(exprFn->returnAnnotation);
else
{
if (exprFn->returnAnnotation_DEPRECATED)
visit(*exprFn->returnAnnotation_DEPRECATED);
reportError(NonStrictFunctionDefinitionError{exprFn->debugname.value, local->name.value, *ty}, local->location);
}
if (exprFn->varargAnnotation)
visit(exprFn->varargAnnotation);
}
remainder.remove(dfg->getDef(local));
}
return remainder;
}
@ -875,9 +818,6 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstExprTypeAssertion* typeAssertion)
{
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(typeAssertion->annotation);
if (FFlag::LuauNonStrictVisitorImprovements)
return visit(typeAssertion->expr, ValueContext::RValue);
else
@ -914,331 +854,6 @@ struct NonStrictTypeChecker
return {};
}
void visit(AstType* ty)
{
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes2);
// If this node is `nullptr`, early exit.
if (!ty)
return;
if (auto t = ty->as<AstTypeReference>())
return visit(t);
else if (auto t = ty->as<AstTypeTable>())
return visit(t);
else if (auto t = ty->as<AstTypeFunction>())
return visit(t);
else if (auto t = ty->as<AstTypeTypeof>())
return visit(t);
else if (auto t = ty->as<AstTypeUnion>())
return visit(t);
else if (auto t = ty->as<AstTypeIntersection>())
return visit(t);
else if (auto t = ty->as<AstTypeGroup>())
return visit(t->type);
}
void visit(AstTypeReference* ty)
{
// No further validation is necessary in this case. The main logic for
// _luau_print is contained in lookupAnnotation.
if (FFlag::DebugLuauMagicTypes && ty->name == "_luau_print")
return;
for (const AstTypeOrPack& param : ty->parameters)
{
if (param.type)
visit(param.type);
else
visit(param.typePack);
}
Scope* scope = findInnermostScope(ty->location);
LUAU_ASSERT(scope);
std::optional<TypeFun> alias = ty->prefix ? scope->lookupImportedType(ty->prefix->value, ty->name.value) : scope->lookupType(ty->name.value);
if (alias.has_value())
{
size_t typesRequired = alias->typeParams.size();
size_t packsRequired = alias->typePackParams.size();
bool hasDefaultTypes = std::any_of(
alias->typeParams.begin(),
alias->typeParams.end(),
[](auto&& el)
{
return el.defaultValue.has_value();
}
);
bool hasDefaultPacks = std::any_of(
alias->typePackParams.begin(),
alias->typePackParams.end(),
[](auto&& el)
{
return el.defaultValue.has_value();
}
);
if (!ty->hasParameterList)
{
if ((!alias->typeParams.empty() && !hasDefaultTypes) || (!alias->typePackParams.empty() && !hasDefaultPacks))
reportError(GenericError{"Type parameter list is required"}, ty->location);
}
size_t typesProvided = 0;
size_t extraTypes = 0;
size_t packsProvided = 0;
for (const AstTypeOrPack& p : ty->parameters)
{
if (p.type)
{
if (packsProvided != 0)
{
reportError(GenericError{"Type parameters must come before type pack parameters"}, ty->location);
continue;
}
if (typesProvided < typesRequired)
typesProvided += 1;
else
extraTypes += 1;
}
else if (p.typePack)
{
std::optional<TypePackId> tp = lookupPackAnnotation(p.typePack);
if (!tp.has_value())
continue;
if (typesProvided < typesRequired && size(*tp) == 1 && finite(*tp) && first(*tp))
typesProvided += 1;
else
packsProvided += 1;
}
}
if (extraTypes != 0 && packsProvided == 0)
{
// Extra types are only collected into a pack if a pack is expected
if (packsRequired != 0)
packsProvided += 1;
else
typesProvided += extraTypes;
}
for (size_t i = typesProvided; i < typesRequired; ++i)
{
if (alias->typeParams[i].defaultValue)
typesProvided += 1;
}
for (size_t i = packsProvided; i < packsRequired; ++i)
{
if (alias->typePackParams[i].defaultValue)
packsProvided += 1;
}
if (extraTypes == 0 && packsProvided + 1 == packsRequired)
packsProvided += 1;
if (typesProvided != typesRequired || packsProvided != packsRequired)
{
reportError(
IncorrectGenericParameterCount{
/* name */ ty->name.value,
/* typeFun */ *alias,
/* actualParameters */ typesProvided,
/* actualPackParameters */ packsProvided,
},
ty->location
);
}
}
else
{
if (scope->lookupPack(ty->name.value))
{
reportError(
SwappedGenericTypeParameter{
ty->name.value,
SwappedGenericTypeParameter::Kind::Type,
},
ty->location
);
}
else
{
std::string symbol = "";
if (ty->prefix)
{
symbol += (*(ty->prefix)).value;
symbol += ".";
}
symbol += ty->name.value;
reportError(UnknownSymbol{symbol, UnknownSymbol::Context::Type}, ty->location);
}
}
}
void visit(AstTypeTable* table)
{
if (table->indexer)
{
visit(table->indexer->indexType);
visit(table->indexer->resultType);
}
for (auto prop : table->props)
visit(prop.type);
}
void visit(AstTypeFunction* function)
{
visit(function->argTypes);
visit(function->returnTypes);
}
void visit(AstTypeTypeof* typeOf)
{
visit(typeOf->expr, ValueContext::RValue);
}
void visit(AstTypeUnion* unionType)
{
for (auto typ : unionType->types)
visit(typ);
}
void visit(AstTypeIntersection* intersectionType)
{
for (auto typ : intersectionType->types)
visit(typ);
}
void visit(AstTypeList& list)
{
for (auto typ : list.types)
visit(typ);
if (list.tailType)
visit(list.tailType);
}
void visit(AstTypePack* pack)
{
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes2);
// If there is no pack node, early exit.
if (!pack)
return;
if (auto p = pack->as<AstTypePackExplicit>())
return visit(p);
else if (auto p = pack->as<AstTypePackVariadic>())
return visit(p);
else if (auto p = pack->as<AstTypePackGeneric>())
return visit(p);
}
void visit(AstTypePackExplicit* tp)
{
for (AstType* type : tp->typeList.types)
visit(type);
if (tp->typeList.tailType)
visit(tp->typeList.tailType);
}
void visit(AstTypePackVariadic* tp)
{
visit(tp->variadicType);
}
void visit(AstTypePackGeneric* tp)
{
Scope* scope = findInnermostScope(tp->location);
LUAU_ASSERT(scope);
std::optional<TypePackId> alias = scope->lookupPack(tp->genericName.value);
if (!alias.has_value())
{
if (scope->lookupType(tp->genericName.value))
{
reportError(
SwappedGenericTypeParameter{
tp->genericName.value,
SwappedGenericTypeParameter::Kind::Pack,
},
tp->location
);
}
}
else
{
reportError(UnknownSymbol{tp->genericName.value, UnknownSymbol::Context::Type}, tp->location);
}
}
void visitGenerics(AstArray<AstGenericType*> generics, AstArray<AstGenericTypePack*> genericPacks)
{
DenseHashSet<AstName> seen{AstName{}};
for (const auto* g : generics)
{
if (seen.contains(g->name))
reportError(DuplicateGenericParameter{g->name.value}, g->location);
else
seen.insert(g->name);
if (g->defaultValue)
visit(g->defaultValue);
}
for (const auto* g : genericPacks)
{
if (seen.contains(g->name))
reportError(DuplicateGenericParameter{g->name.value}, g->location);
else
seen.insert(g->name);
if (g->defaultValue)
visit(g->defaultValue);
}
}
Scope* findInnermostScope(Location location) const
{
Scope* bestScope = module->getModuleScope().get();
bool didNarrow;
do
{
didNarrow = false;
for (auto scope : bestScope->children)
{
if (scope->location.encloses(location))
{
bestScope = scope.get();
didNarrow = true;
break;
}
}
} while (didNarrow && bestScope->children.size() > 0);
return bestScope;
}
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation) const
{
TypePackId* tp = module->astResolvedTypePacks.find(annotation);
if (tp != nullptr)
return {follow(*tp)};
return {};
}
void reportError(TypeErrorData data, const Location& location)
{
module->errors.emplace_back(location, module->name, std::move(data));

View file

@ -17,11 +17,15 @@
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant)
LUAU_FASTFLAGVARIABLE(LuauNormalizeNegatedErrorToAnError)
LUAU_FASTFLAGVARIABLE(LuauNormalizeIntersectErrorToAnError)
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
LUAU_FASTFLAGVARIABLE(LuauNormalizedBufferIsNotUnknown)
LUAU_FASTFLAGVARIABLE(LuauNormalizeLimitFunctionSet)
LUAU_FASTFLAGVARIABLE(LuauNormalizationCatchMetatableCycles)
namespace Luau
@ -249,23 +253,23 @@ bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& s
return true;
}
void NormalizedExternType::pushPair(TypeId ty, TypeIds negations)
void NormalizedClassType::pushPair(TypeId ty, TypeIds negations)
{
auto result = externTypes.insert(std::make_pair(ty, std::move(negations)));
auto result = classes.insert(std::make_pair(ty, std::move(negations)));
if (result.second)
ordering.push_back(ty);
LUAU_ASSERT(ordering.size() == externTypes.size());
LUAU_ASSERT(ordering.size() == classes.size());
}
void NormalizedExternType::resetToNever()
void NormalizedClassType::resetToNever()
{
ordering.clear();
externTypes.clear();
classes.clear();
}
bool NormalizedExternType::isNever() const
bool NormalizedClassType::isNever() const
{
return externTypes.empty();
return classes.empty();
}
void NormalizedFunctionType::resetToTop()
@ -304,17 +308,19 @@ bool NormalizedType::isUnknown() const
// Otherwise, we can still be unknown!
bool hasAllPrimitives = isPrim(booleans, PrimitiveType::Boolean) && isPrim(nils, PrimitiveType::NilType) && isNumber(numbers) &&
strings.isString() && isThread(threads) && isBuffer(buffers);
strings.isString() &&
(FFlag::LuauNormalizedBufferIsNotUnknown ? isThread(threads) && isBuffer(buffers)
: isPrim(threads, PrimitiveType::Thread) && isThread(threads));
// Check is class
bool isTopExternType = false;
for (const auto& [t, disj] : externTypes.externTypes)
bool isTopClass = false;
for (auto [t, disj] : classes.classes)
{
if (auto ct = get<ExternType>(t))
if (auto ct = get<ClassType>(t))
{
if (ct->name == "class" && disj.empty())
{
isTopExternType = true;
isTopClass = true;
break;
}
}
@ -330,24 +336,24 @@ bool NormalizedType::isUnknown() const
}
}
// any = unknown or error ==> we need to make sure we have all the unknown components, but not errors
return get<NeverType>(errors) && hasAllPrimitives && isTopExternType && isTopTable && functions.isTop;
return get<NeverType>(errors) && hasAllPrimitives && isTopClass && isTopTable && functions.isTop;
}
bool NormalizedType::isExactlyNumber() const
{
return hasNumbers() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() &&
return hasNumbers() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
}
bool NormalizedType::isSubtypeOfString() const
{
return hasStrings() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() &&
return hasStrings() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
}
bool NormalizedType::isSubtypeOfBooleans() const
{
return hasBooleans() && !hasTops() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasStrings() && !hasThreads() &&
return hasBooleans() && !hasTops() && !hasClasses() && !hasErrors() && !hasNils() && !hasNumbers() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
}
@ -380,9 +386,9 @@ bool NormalizedType::hasBooleans() const
return !get<NeverType>(booleans);
}
bool NormalizedType::hasExternTypes() const
bool NormalizedType::hasClasses() const
{
return !externTypes.isNever();
return !classes.isNever();
}
bool NormalizedType::hasErrors() const
@ -440,7 +446,7 @@ bool NormalizedType::isFalsy() const
hasAFalse = !bs->value;
}
return (hasAFalse || hasNils()) && (!hasTops() && !hasExternTypes() && !hasErrors() && !hasNumbers() && !hasStrings() && !hasThreads() &&
return (hasAFalse || hasNils()) && (!hasTops() && !hasClasses() && !hasErrors() && !hasNumbers() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars());
}
@ -452,7 +458,7 @@ bool NormalizedType::isTruthy() const
static bool isShallowInhabited(const NormalizedType& norm)
{
// This test is just a shallow check, for example it returns `true` for `{ p : never }`
return !get<NeverType>(norm.tops) || !get<NeverType>(norm.booleans) || !norm.externTypes.isNever() || !get<NeverType>(norm.errors) ||
return !get<NeverType>(norm.tops) || !get<NeverType>(norm.booleans) || !norm.classes.isNever() || !get<NeverType>(norm.errors) ||
!get<NeverType>(norm.nils) || !get<NeverType>(norm.numbers) || !norm.strings.isNever() || !get<NeverType>(norm.threads) ||
!get<NeverType>(norm.buffers) || !norm.functions.isNever() || !norm.tables.empty() || !norm.tyvars.empty();
}
@ -471,7 +477,7 @@ NormalizationResult Normalizer::isInhabited(const NormalizedType* norm, Set<Type
return NormalizationResult::HitLimits;
if (!get<NeverType>(norm->tops) || !get<NeverType>(norm->booleans) || !get<NeverType>(norm->errors) || !get<NeverType>(norm->nils) ||
!get<NeverType>(norm->numbers) || !get<NeverType>(norm->threads) || !get<NeverType>(norm->buffers) || !norm->externTypes.isNever() ||
!get<NeverType>(norm->numbers) || !get<NeverType>(norm->threads) || !get<NeverType>(norm->buffers) || !norm->classes.isNever() ||
!norm->strings.isNever() || !norm->functions.isNever())
return NormalizationResult::True;
@ -619,13 +625,13 @@ static int tyvarIndex(TypeId ty)
return 0;
}
static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedExternType& externTypes)
static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedClassType& classes)
{
if (externTypes.externTypes.size() != 1)
if (classes.classes.size() != 1)
return false;
auto first = externTypes.externTypes.begin();
if (first->first != builtinTypes->externType)
auto first = classes.classes.begin();
if (first->first != builtinTypes->classType)
return false;
if (!first->second.empty())
@ -634,11 +640,11 @@ static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedExternType
return true;
}
static void resetToTop(NotNull<BuiltinTypes> builtinTypes, NormalizedExternType& externTypes)
static void resetToTop(NotNull<BuiltinTypes> builtinTypes, NormalizedClassType& classes)
{
externTypes.ordering.clear();
externTypes.externTypes.clear();
externTypes.pushPair(builtinTypes->externType, TypeIds{});
classes.ordering.clear();
classes.classes.clear();
classes.pushPair(builtinTypes->classType, TypeIds{});
}
#ifdef LUAU_ASSERTENABLED
@ -762,50 +768,50 @@ static bool areNormalizedTables(const TypeIds& tys)
return true;
}
static bool areNormalizedExternTypes(const NormalizedExternType& tys)
static bool areNormalizedClasses(const NormalizedClassType& tys)
{
for (const auto& [ty, negations] : tys.externTypes)
for (const auto& [ty, negations] : tys.classes)
{
const ExternType* etv = get<ExternType>(ty);
if (!etv)
const ClassType* ctv = get<ClassType>(ty);
if (!ctv)
{
return false;
}
for (TypeId negation : negations)
{
const ExternType* nctv = get<ExternType>(negation);
const ClassType* nctv = get<ClassType>(negation);
if (!nctv)
{
return false;
}
if (!isSubclass(nctv, etv))
if (!isSubclass(nctv, ctv))
{
return false;
}
}
for (const auto& [otherTy, otherNegations] : tys.externTypes)
for (const auto& [otherTy, otherNegations] : tys.classes)
{
if (otherTy == ty)
continue;
const ExternType* octv = get<ExternType>(otherTy);
const ClassType* octv = get<ClassType>(otherTy);
if (!octv)
{
return false;
}
if (isSubclass(etv, octv))
if (isSubclass(ctv, octv))
{
auto iss = [etv](TypeId t)
auto iss = [ctv](TypeId t)
{
const ExternType* c = get<ExternType>(t);
const ClassType* c = get<ClassType>(t);
if (!c)
return false;
return isSubclass(etv, c);
return isSubclass(ctv, c);
};
if (!std::any_of(otherNegations.begin(), otherNegations.end(), iss))
@ -847,7 +853,7 @@ static void assertInvariant(const NormalizedType& norm)
LUAU_ASSERT(isNormalizedTop(norm.tops));
LUAU_ASSERT(isNormalizedBoolean(norm.booleans));
LUAU_ASSERT(areNormalizedExternTypes(norm.externTypes));
LUAU_ASSERT(areNormalizedClasses(norm.classes));
LUAU_ASSERT(isNormalizedError(norm.errors));
LUAU_ASSERT(isNormalizedNil(norm.nils));
LUAU_ASSERT(isNormalizedNumber(norm.numbers));
@ -988,7 +994,7 @@ void Normalizer::clearNormal(NormalizedType& norm)
{
norm.tops = builtinTypes->neverType;
norm.booleans = builtinTypes->neverType;
norm.externTypes.resetToNever();
norm.classes.resetToNever();
norm.errors = builtinTypes->neverType;
norm.nils = builtinTypes->neverType;
norm.numbers = builtinTypes->neverType;
@ -1138,17 +1144,17 @@ TypeId Normalizer::unionOfBools(TypeId here, TypeId there)
return builtinTypes->booleanType;
}
void Normalizer::unionExternTypesWithExternType(TypeIds& heres, TypeId there)
void Normalizer::unionClassesWithClass(TypeIds& heres, TypeId there)
{
if (heres.count(there))
return;
const ExternType* tctv = get<ExternType>(there);
const ClassType* tctv = get<ClassType>(there);
for (auto it = heres.begin(); it != heres.end();)
{
TypeId here = *it;
const ExternType* hctv = get<ExternType>(here);
const ClassType* hctv = get<ClassType>(here);
if (isSubclass(tctv, hctv))
return;
else if (isSubclass(hctv, tctv))
@ -1160,16 +1166,16 @@ void Normalizer::unionExternTypesWithExternType(TypeIds& heres, TypeId there)
heres.insert(there);
}
void Normalizer::unionExternTypes(TypeIds& heres, const TypeIds& theres)
void Normalizer::unionClasses(TypeIds& heres, const TypeIds& theres)
{
for (TypeId there : theres)
unionExternTypesWithExternType(heres, there);
unionClassesWithClass(heres, there);
}
static bool isSubclass(TypeId test, TypeId parent)
{
const ExternType* testCtv = get<ExternType>(test);
const ExternType* parentCtv = get<ExternType>(parent);
const ClassType* testCtv = get<ClassType>(test);
const ClassType* parentCtv = get<ClassType>(parent);
LUAU_ASSERT(testCtv);
LUAU_ASSERT(parentCtv);
@ -1177,12 +1183,12 @@ static bool isSubclass(TypeId test, TypeId parent)
return isSubclass(testCtv, parentCtv);
}
void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, TypeId there)
void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
{
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{
TypeId hereTy = *it;
TypeIds& hereNegations = heres.externTypes.at(hereTy);
TypeIds& hereNegations = heres.classes.at(hereTy);
// If the incoming class is a subclass of another class in the map, we
// must ensure that it is negated by one of the negations in the same
@ -1204,7 +1210,7 @@ void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, Typ
}
// If the incoming class is a superclass of one of the
// negations, then the negation no longer applies and must be
// removed. This is also true if they are equal. Since extern types
// removed. This is also true if they are equal. Since classes
// are, at this time, entirely persistent (we do not clone
// them), a pointer identity check is sufficient.
else if (isSubclass(hereNegation, there))
@ -1231,7 +1237,7 @@ void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, Typ
{
TypeIds negations = std::move(hereNegations);
it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy);
heres.classes.erase(hereTy);
heres.pushPair(there, std::move(negations));
return;
@ -1248,10 +1254,10 @@ void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, Typ
heres.pushPair(there, TypeIds{});
}
void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres)
void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassType& theres)
{
// This method bears much similarity with unionExternTypesWithExternType, but is
// solving a more general problem. In unionExternTypesWithExternType, we are dealing
// This method bears much similarity with unionClassesWithClass, but is
// solving a more general problem. In unionClassesWithClass, we are dealing
// with a singular positive type. Since it's one type, we can use early
// returns as control flow. Since it's guaranteed to be positive, we do not
// have negations to worry about combining. The two aspects combine to make
@ -1260,9 +1266,9 @@ void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedE
for (const TypeId thereTy : theres.ordering)
{
const TypeIds& thereNegations = theres.externTypes.at(thereTy);
const TypeIds& thereNegations = theres.classes.at(thereTy);
// If it happens that there are _no_ extern types in the current map, or the
// If it happens that there are _no_ classes in the current map, or the
// incoming class is completely unrelated to any class in the current
// map, we must insert the incoming pair as-is.
bool insert = true;
@ -1270,7 +1276,7 @@ void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedE
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{
TypeId hereTy = *it;
TypeIds& hereNegations = heres.externTypes.at(hereTy);
TypeIds& hereNegations = heres.classes.at(hereTy);
if (isSubclass(thereTy, hereTy))
{
@ -1294,7 +1300,7 @@ void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedE
// If the incoming class is a superclass of one of the
// negations, then the negation no longer applies and must
// be removed. This is also true if they are equal. Since
// extern types are, at this time, entirely persistent (we do not
// classes are, at this time, entirely persistent (we do not
// clone them), a pointer identity check is sufficient.
else if (isSubclass(hereNegateTy, thereTy))
{
@ -1319,17 +1325,17 @@ void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedE
else if (isSubclass(hereTy, thereTy))
{
TypeIds negations = std::move(hereNegations);
unionExternTypes(negations, thereNegations);
unionClasses(negations, thereNegations);
it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy);
heres.classes.erase(hereTy);
heres.pushPair(thereTy, std::move(negations));
insert = false;
break;
}
else if (hereTy == thereTy)
{
unionExternTypes(hereNegations, thereNegations);
unionClasses(hereNegations, thereNegations);
insert = false;
break;
}
@ -1685,12 +1691,15 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
return res;
}
if (FFlag::LuauNormalizeLimitFunctionSet)
{
// Limit based on worst-case expansion of the function unions
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeUnionLimit))
return NormalizationResult::HitLimits;
}
here.booleans = unionOfBools(here.booleans, there.booleans);
unionExternTypes(here.externTypes, there.externTypes);
unionClasses(here.classes, there.classes);
here.errors = (get<NeverType>(there.errors) ? here.errors : there.errors);
here.nils = (get<NeverType>(there.nils) ? here.nils : there.nils);
@ -1830,8 +1839,8 @@ NormalizationResult Normalizer::unionNormalWithTy(
unionFunctionsWithFunction(here.functions, there);
else if (get<TableType>(there) || get<MetatableType>(there))
unionTablesWithTable(here.tables, there);
else if (get<ExternType>(there))
unionExternTypesWithExternType(here.externTypes, there);
else if (get<ClassType>(there))
unionClassesWithClass(here.classes, there);
else if (get<ErrorType>(there))
here.errors = there;
else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
@ -1944,29 +1953,29 @@ std::optional<NormalizedType> Normalizer::negateNormal(const NormalizedType& her
result.booleans = builtinTypes->trueType;
}
if (here.externTypes.isNever())
if (here.classes.isNever())
{
resetToTop(builtinTypes, result.externTypes);
resetToTop(builtinTypes, result.classes);
}
else if (isTop(builtinTypes, result.externTypes))
else if (isTop(builtinTypes, result.classes))
{
result.externTypes.resetToNever();
result.classes.resetToNever();
}
else
{
TypeIds rootNegations{};
for (const auto& [hereParent, hereNegations] : here.externTypes.externTypes)
for (const auto& [hereParent, hereNegations] : here.classes.classes)
{
if (hereParent != builtinTypes->externType)
if (hereParent != builtinTypes->classType)
rootNegations.insert(hereParent);
for (TypeId hereNegation : hereNegations)
unionExternTypesWithExternType(result.externTypes, hereNegation);
unionClassesWithClass(result.classes, hereNegation);
}
if (!rootNegations.empty())
result.externTypes.pushPair(builtinTypes->externType, rootNegations);
result.classes.pushPair(builtinTypes->classType, rootNegations);
}
result.nils = get<NeverType>(here.nils) ? builtinTypes->nilType : builtinTypes->neverType;
@ -2144,7 +2153,7 @@ TypeId Normalizer::intersectionOfBools(TypeId here, TypeId there)
return there;
}
void Normalizer::intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres)
void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedClassType& theres)
{
if (theres.isNever())
{
@ -2178,12 +2187,12 @@ void Normalizer::intersectExternTypes(NormalizedExternType& heres, const Normali
// declare the result of the intersection operation to be never.
for (const TypeId thereTy : theres.ordering)
{
const TypeIds& thereNegations = theres.externTypes.at(thereTy);
const TypeIds& thereNegations = theres.classes.at(thereTy);
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{
TypeId hereTy = *it;
TypeIds& hereNegations = heres.externTypes.at(hereTy);
TypeIds& hereNegations = heres.classes.at(hereTy);
if (isSubclass(thereTy, hereTy))
{
@ -2206,10 +2215,10 @@ void Normalizer::intersectExternTypes(NormalizedExternType& heres, const Normali
}
}
unionExternTypes(negations, thereNegations);
unionClasses(negations, thereNegations);
it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy);
heres.classes.erase(hereTy);
heres.pushPair(thereTy, std::move(negations));
break;
}
@ -2234,15 +2243,15 @@ void Normalizer::intersectExternTypes(NormalizedExternType& heres, const Normali
{
if (isSubclass(hereTy, *nIt))
{
// eg SomeExternType & (class & ~SomeExternType)
// or SomeExternType & (class & ~ParentExternType)
heres.externTypes.erase(hereTy);
// eg SomeClass & (class & ~SomeClass)
// or SomeClass & (class & ~ParentClass)
heres.classes.erase(hereTy);
it = heres.ordering.erase(it);
erasedHere = true;
break;
}
// eg SomeExternType & (class & ~Unrelated)
// eg SomeClass & (class & ~Unrelated)
if (!isSubclass(*nIt, hereTy))
nIt = negations.erase(nIt);
else
@ -2251,30 +2260,30 @@ void Normalizer::intersectExternTypes(NormalizedExternType& heres, const Normali
if (!erasedHere)
{
unionExternTypes(hereNegations, negations);
unionClasses(hereNegations, negations);
++it;
}
}
else if (hereTy == thereTy)
{
unionExternTypes(hereNegations, thereNegations);
unionClasses(hereNegations, thereNegations);
break;
}
else
{
it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy);
heres.classes.erase(hereTy);
}
}
}
}
void Normalizer::intersectExternTypesWithExternType(NormalizedExternType& heres, TypeId there)
void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId there)
{
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{
TypeId hereTy = *it;
const TypeIds& hereNegations = heres.externTypes.at(hereTy);
const TypeIds& hereNegations = heres.classes.at(hereTy);
// If the incoming class _is_ the current class, we skip it. Maybe
// another entry will have a different story. We check for this first
@ -2319,7 +2328,7 @@ void Normalizer::intersectExternTypesWithExternType(NormalizedExternType& heres,
}
it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy);
heres.classes.erase(hereTy);
if (!emptyIntersectWithNegation)
heres.pushPair(there, std::move(negations));
break;
@ -2335,7 +2344,7 @@ void Normalizer::intersectExternTypesWithExternType(NormalizedExternType& heres,
else
{
it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy);
heres.classes.erase(hereTy);
}
}
}
@ -3078,12 +3087,15 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor
if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
return NormalizationResult::HitLimits;
if (FFlag::LuauNormalizeLimitFunctionSet)
{
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
return NormalizationResult::HitLimits;
}
here.booleans = intersectionOfBools(here.booleans, there.booleans);
intersectExternTypes(here.externTypes, there.externTypes);
intersectClasses(here.classes, there.classes);
here.errors = (get<NeverType>(there.errors) ? there.errors : here.errors);
here.nils = (get<NeverType>(there.nils) ? there.nils : here.nils);
here.numbers = (get<NeverType>(there.numbers) ? there.numbers : here.numbers);
@ -3205,18 +3217,18 @@ NormalizationResult Normalizer::intersectNormalWithTy(
intersectTablesWithTable(tables, there, seenTablePropPairs, seenSetTypes);
here.tables = std::move(tables);
}
else if (get<ExternType>(there))
else if (get<ClassType>(there))
{
NormalizedExternType nct = std::move(here.externTypes);
NormalizedClassType nct = std::move(here.classes);
clearNormal(here);
intersectExternTypesWithExternType(nct, there);
here.externTypes = std::move(nct);
intersectClassesWithClass(nct, there);
here.classes = std::move(nct);
}
else if (get<ErrorType>(there))
{
TypeId errors = here.errors;
clearNormal(here);
here.errors = get<ErrorType>(errors) ? errors : there;
here.errors = FFlag::LuauNormalizeIntersectErrorToAnError && get<ErrorType>(errors) ? errors : there;
}
else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
{
@ -3274,7 +3286,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
subtractPrimitive(here, ntv->ty);
else if (const SingletonType* stv = get<SingletonType>(t))
subtractSingleton(here, follow(ntv->ty));
else if (get<ExternType>(t))
else if (get<ClassType>(t))
{
NormalizationResult res = intersectNormalWithNegationTy(t, here);
if (shouldEarlyExit(res))
@ -3313,12 +3325,12 @@ NormalizationResult Normalizer::intersectNormalWithTy(
clearNormal(here);
return NormalizationResult::True;
}
else if (get<ErrorType>(t))
else if (FFlag::LuauNormalizeNegatedErrorToAnError && get<ErrorType>(t))
{
// ~error is still an error, so intersecting with the negation is the same as intersecting with a type
TypeId errors = here.errors;
clearNormal(here);
here.errors = get<ErrorType>(errors) ? errors : t;
here.errors = FFlag::LuauNormalizeIntersectErrorToAnError && get<ErrorType>(errors) ? errors : t;
}
else if (auto nt = get<NegationType>(t))
{
@ -3334,7 +3346,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
}
else if (get<NeverType>(there))
{
here.externTypes.resetToNever();
here.classes.resetToNever();
}
else if (get<NoRefineType>(there))
{
@ -3403,18 +3415,18 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
if (!get<NeverType>(norm.booleans))
result.push_back(norm.booleans);
if (isTop(builtinTypes, norm.externTypes))
if (isTop(builtinTypes, norm.classes))
{
result.push_back(builtinTypes->externType);
result.push_back(builtinTypes->classType);
}
else if (!norm.externTypes.isNever())
else if (!norm.classes.isNever())
{
std::vector<TypeId> parts;
parts.reserve(norm.externTypes.externTypes.size());
parts.reserve(norm.classes.classes.size());
for (const TypeId normTy : norm.externTypes.ordering)
for (const TypeId normTy : norm.classes.ordering)
{
const TypeIds& normNegations = norm.externTypes.externTypes.at(normTy);
const TypeIds& normNegations = norm.classes.classes.at(normTy);
if (normNegations.empty())
{

View file

@ -10,8 +10,6 @@
#include "Luau/TypeUtils.h"
#include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauArityMismatchOnUndersaturatedUnknownArguments)
namespace Luau
{
@ -256,24 +254,8 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
}
// If any of the unsatisfied arguments are not supertypes of
// nil or are `unknown`, then this overload does not match.
// nil, then this overload does not match.
for (size_t i = firstUnsatisfiedArgument; i < requiredHead.size(); ++i)
{
if (FFlag::LuauArityMismatchOnUndersaturatedUnknownArguments)
{
if (get<UnknownType>(follow(requiredHead[i])) || !subtyping.isSubtype(builtinTypes->nilType, requiredHead[i], scope).isSubtype)
{
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
for (auto arg : fn->argTypes)
if (get<UnknownType>(follow(arg)))
minParams += 1;
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, isVariadic}};
return {Analysis::ArityMismatch, {error}};
}
}
else
{
if (!subtyping.isSubtype(builtinTypes->nilType, requiredHead[i], scope).isSubtype)
{
@ -283,7 +265,6 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
return {Analysis::ArityMismatch, {error}};
}
}
}
return {Analysis::Ok, {}};
}

View file

@ -4,8 +4,6 @@
#include "Luau/Ast.h"
#include "Luau/Module.h"
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau
{
@ -67,12 +65,6 @@ struct RequireTracer : AstVisitor
return true;
}
bool visit(AstTypePack* node) override
{
// allow resolving require inside `typeof` annotations
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
AstExpr* getDependent_DEPRECATED(AstExpr* node)
{
if (AstExprLocal* expr = node->as<AstExprLocal>())

View file

@ -6,7 +6,6 @@
#include "Luau/DenseHash.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Set.h"
#include "Luau/Type.h"
#include "Luau/TypeArena.h"
#include "Luau/TypePairHash.h"
#include "Luau/TypeUtils.h"
@ -15,10 +14,8 @@
LUAU_FASTINT(LuauTypeReductionRecursionLimit)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8)
LUAU_FASTFLAGVARIABLE(LuauSimplificationRecheckAssumption)
LUAU_FASTFLAGVARIABLE(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType)
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8);
LUAU_FASTFLAGVARIABLE(LuauFlagBasicIntersectFollows);
namespace Luau
{
@ -50,8 +47,6 @@ struct TypeSimplifier
// Attempt to intersect the two types. Does not recurse. Does not handle
// unions, intersections, or negations.
std::optional<TypeId> basicIntersect(TypeId left, TypeId right);
std::optional<TypeId> basicIntersectWithTruthy(TypeId target) const;
std::optional<TypeId> basicIntersectWithFalsy(TypeId target) const;
TypeId intersect(TypeId left, TypeId right);
TypeId union_(TypeId left, TypeId right);
@ -318,13 +313,11 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (get<AnyType>(right))
return Relation::Subset;
if (get<UnknownType>(right))
else if (get<UnknownType>(right))
return Relation::Coincident;
if (get<ErrorType>(right))
else if (get<ErrorType>(right))
return Relation::Disjoint;
else
return Relation::Superset;
}
@ -335,7 +328,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (get<AnyType>(right))
return Relation::Coincident;
else
return Relation::Superset;
}
@ -360,7 +353,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
// * FunctionType
// * TableType
// * MetatableType
// * ExternType
// * ClassType
// * UnionType
// * IntersectionType
// * NegationType
@ -368,33 +361,26 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
if (isTypeVariable(left) || isTypeVariable(right))
return Relation::Intersects;
if (FFlag::LuauSimplificationTableExternType)
{
// if either type is a type function, we cannot know if they'll be related.
if (get<TypeFunctionInstanceType>(left) || get<TypeFunctionInstanceType>(right))
return Relation::Intersects;
}
if (get<ErrorType>(left))
{
if (get<ErrorType>(right))
return Relation::Coincident;
else if (get<AnyType>(right))
return Relation::Subset;
else
return Relation::Disjoint;
}
else if (get<ErrorType>(right))
if (get<ErrorType>(right))
return flip(relate(right, left, seen));
if (get<NeverType>(left))
{
if (get<NeverType>(right))
return Relation::Coincident;
else
return Relation::Subset;
}
else if (get<NeverType>(right))
if (get<NeverType>(right))
return flip(relate(right, left, seen));
if (auto ut = get<IntersectionType>(left))
@ -458,7 +444,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (lp->type == rp->type)
return Relation::Coincident;
else
return Relation::Disjoint;
}
@ -466,10 +452,9 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (lp->type == PrimitiveType::String && rs->variant.get_if<StringSingleton>())
return Relation::Superset;
if (lp->type == PrimitiveType::Boolean && rs->variant.get_if<BooleanSingleton>())
else if (lp->type == PrimitiveType::Boolean && rs->variant.get_if<BooleanSingleton>())
return Relation::Superset;
else
return Relation::Disjoint;
}
@ -477,34 +462,33 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (get<FunctionType>(right))
return Relation::Superset;
else
return Relation::Disjoint;
}
if (lp->type == PrimitiveType::Table)
{
if (get<TableType>(right))
return Relation::Superset;
else
return Relation::Disjoint;
}
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ExternType>(right))
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ClassType>(right))
return Relation::Disjoint;
}
if (auto ls = get<SingletonType>(left))
{
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ExternType>(right))
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ClassType>(right))
return Relation::Disjoint;
if (get<PrimitiveType>(right))
return flip(relate(right, left, seen));
if (auto rs = get<SingletonType>(right))
{
if (ls->variant == rs->variant)
return Relation::Coincident;
else
return Relation::Disjoint;
}
}
@ -515,10 +499,10 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (rp->type == PrimitiveType::Function)
return Relation::Subset;
else
return Relation::Disjoint;
}
else
return Relation::Intersects;
}
@ -528,11 +512,10 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (rp->type == PrimitiveType::Table)
return Relation::Subset;
else
return Relation::Disjoint;
}
if (auto rt = get<TableType>(right))
else if (auto rt = get<TableType>(right))
{
// TODO PROBABLY indexers and metatables.
if (1 == rt->props.size())
@ -552,57 +535,28 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
*/
if (lt->props.size() > 1 && r == Relation::Superset)
return Relation::Intersects;
else
return r;
}
if (1 == lt->props.size())
else if (1 == lt->props.size())
return flip(relate(right, left, seen));
else
return Relation::Intersects;
}
if (FFlag::LuauSimplificationTableExternType)
{
if (auto re = get<ExternType>(right))
{
Relation overall = Relation::Coincident;
for (auto& [name, prop] : lt->props)
{
if (auto propInExternType = re->props.find(name); propInExternType != re->props.end())
{
Relation propRel = relate(prop.type(), propInExternType->second.type());
if (propRel == Relation::Disjoint)
return Relation::Disjoint;
if (propRel == Relation::Coincident)
continue;
overall = Relation::Intersects;
}
}
return overall;
}
}
// TODO metatables
return Relation::Disjoint;
}
if (auto ct = get<ExternType>(left))
if (auto ct = get<ClassType>(left))
{
if (auto rct = get<ExternType>(right))
if (auto rct = get<ClassType>(right))
{
if (isSubclass(ct, rct))
return Relation::Subset;
if (isSubclass(rct, ct))
else if (isSubclass(rct, ct))
return Relation::Superset;
else
return Relation::Disjoint;
}
@ -753,9 +707,7 @@ TypeId TypeSimplifier::intersectUnionWithType(TypeId left, TypeId right)
bool changed = false;
std::set<TypeId> newParts;
size_t maxSize = DFInt::LuauSimplificationComplexityLimit;
if (leftUnion->options.size() > maxSize)
if (leftUnion->options.size() > (size_t)DFInt::LuauSimplificationComplexityLimit)
return arena->addType(IntersectionType{{left, right}});
for (TypeId part : leftUnion)
@ -770,13 +722,6 @@ TypeId TypeSimplifier::intersectUnionWithType(TypeId left, TypeId right)
}
newParts.insert(simplified);
if (FFlag::LuauSimplificationRecheckAssumption)
{
// Initial combination size check could not predict nested union iteration
if (newParts.size() > maxSize)
return arena->addType(IntersectionType{{left, right}});
}
}
if (!changed)
@ -817,13 +762,6 @@ TypeId TypeSimplifier::intersectUnions(TypeId left, TypeId right)
continue;
newParts.insert(simplified);
if (FFlag::LuauSimplificationRecheckAssumption)
{
// Initial combination size check could not predict nested union iteration
if (newParts.size() > maxSize)
return arena->addType(IntersectionType{{left, right}});
}
}
}
@ -902,78 +840,6 @@ TypeId TypeSimplifier::intersectNegatedUnion(TypeId left, TypeId right)
return intersectFromParts(std::move(newParts));
}
std::optional<TypeId> TypeSimplifier::basicIntersectWithTruthy(TypeId target) const
{
target = follow(target);
if (is<UnknownType>(target))
return builtinTypes->truthyType;
if (is<AnyType>(target))
// any = *error-type* | unknown, so truthy & any = *error-type* | truthy
return arena->addType(UnionType{{builtinTypes->truthyType, builtinTypes->errorType}});
if (is<NeverType, ErrorType>(target))
return target;
if (is<FunctionType, TableType, MetatableType, ExternType>(target))
return target;
if (auto pt = get<PrimitiveType>(target))
{
switch (pt->type)
{
case PrimitiveType::NilType:
return builtinTypes->neverType;
case PrimitiveType::Boolean:
return builtinTypes->trueType;
default:
return target;
}
}
if (auto st = get<SingletonType>(target))
return st->variant == BooleanSingleton{false} ? builtinTypes->neverType : target;
return std::nullopt;
}
std::optional<TypeId> TypeSimplifier::basicIntersectWithFalsy(TypeId target) const
{
target = follow(target);
if (is<NeverType, ErrorType>(target))
return target;
if (is<AnyType>(target))
// any = *error-type* | unknown, so falsy & any = *error-type* | falsy
return arena->addType(UnionType{{builtinTypes->falsyType, builtinTypes->errorType}});
if (is<UnknownType>(target))
return builtinTypes->falsyType;
if (is<FunctionType, TableType, MetatableType, ExternType>(target))
return builtinTypes->neverType;
if (auto pt = get<PrimitiveType>(target))
{
switch (pt->type)
{
case PrimitiveType::NilType:
return builtinTypes->nilType;
case PrimitiveType::Boolean:
return builtinTypes->falseType;
default:
return builtinTypes->neverType;
}
}
if (auto st = get<SingletonType>(target))
return st->variant == BooleanSingleton{false} ? builtinTypes->falseType : builtinTypes->neverType;
return std::nullopt;
}
TypeId TypeSimplifier::intersectTypeWithNegation(TypeId left, TypeId right)
{
const NegationType* leftNegation = get<NegationType>(left);
@ -1200,8 +1066,11 @@ TypeId TypeSimplifier::intersectIntersectionWithType(TypeId left, TypeId right)
std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
{
if (FFlag::LuauFlagBasicIntersectFollows)
{
left = follow(left);
right = follow(right);
}
if (get<AnyType>(left) && get<ErrorType>(right))
return right;
@ -1310,25 +1179,6 @@ std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
return std::nullopt;
}
if (FFlag::LuauOptimizeFalsyAndTruthyIntersect)
{
if (isTruthyType(left))
if (auto res = basicIntersectWithTruthy(right))
return res;
if (isTruthyType(right))
if (auto res = basicIntersectWithTruthy(left))
return res;
if (isFalsyType(left))
if (auto res = basicIntersectWithFalsy(right))
return res;
if (isFalsyType(right))
if (auto res = basicIntersectWithFalsy(left))
return res;
}
Relation relation = relate(left, right);
if (left == right || Relation::Coincident == relation)
return left;

View file

@ -2,10 +2,12 @@
#include "Luau/Substitution.h"
#include "Luau/Common.h"
#include "Luau/Clone.h"
#include "Luau/TxnLog.h"
#include "Luau/Type.h"
#include <algorithm>
#include <stdexcept>
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAG(LuauSolverV2)
@ -16,9 +18,9 @@ LUAU_FASTFLAG(LuauDeprecatedAttribute)
namespace Luau
{
static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysClone)
{
auto go = [ty, &dest](auto&& a)
auto go = [ty, &dest, alwaysClone](auto&& a)
{
using T = std::decay_t<decltype(a)>;
@ -136,11 +138,16 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
clone.parts = a.parts;
return dest.addType(std::move(clone));
}
else if constexpr (std::is_same_v<T, ExternType>)
else if constexpr (std::is_same_v<T, ClassType>)
{
ExternType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.definitionLocation, a.indexer};
if (alwaysClone)
{
ClassType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.definitionLocation, a.indexer};
return dest.addType(std::move(clone));
}
else
return ty;
}
else if constexpr (std::is_same_v<T, NegationType>)
return dest.addType(NegationType{a.ty});
else if constexpr (std::is_same_v<T, TypeFunctionInstanceType>)
@ -252,21 +259,21 @@ void Tarjan::visitChildren(TypeId ty, int index)
for (TypePackId a : tfit->packArguments)
visitChild(a);
}
else if (const ExternType* etv = get<ExternType>(ty))
else if (const ClassType* ctv = get<ClassType>(ty))
{
for (const auto& [name, prop] : etv->props)
for (const auto& [name, prop] : ctv->props)
visitChild(prop.type());
if (etv->parent)
visitChild(*etv->parent);
if (ctv->parent)
visitChild(*ctv->parent);
if (etv->metatable)
visitChild(*etv->metatable);
if (ctv->metatable)
visitChild(*ctv->metatable);
if (etv->indexer)
if (ctv->indexer)
{
visitChild(etv->indexer->indexType);
visitChild(etv->indexer->indexResultType);
visitChild(ctv->indexer->indexType);
visitChild(ctv->indexer->indexResultType);
}
}
else if (const NegationType* ntv = get<NegationType>(ty))
@ -540,27 +547,6 @@ void Tarjan::visitSCC(int index)
}
}
bool Tarjan::ignoreChildren(TypeId ty)
{
return false;
}
bool Tarjan::ignoreChildren(TypePackId ty)
{
return false;
}
// Some subclasses might ignore children visit, but not other actions like replacing the children
bool Tarjan::ignoreChildrenVisit(TypeId ty)
{
return ignoreChildren(ty);
}
bool Tarjan::ignoreChildrenVisit(TypePackId ty)
{
return ignoreChildren(ty);
}
TarjanResult Tarjan::findDirty(TypeId ty)
{
return visitRoot(ty);
@ -571,11 +557,6 @@ TarjanResult Tarjan::findDirty(TypePackId tp)
return visitRoot(tp);
}
Substitution::Substitution(TypeArena* arena)
: Substitution(TxnLog::empty(), arena)
{
}
Substitution::Substitution(const TxnLog* log_, TypeArena* arena)
: arena(arena)
{
@ -676,7 +657,7 @@ void Substitution::resetState(const TxnLog* log, TypeArena* arena)
TypeId Substitution::clone(TypeId ty)
{
return shallowClone(ty, *arena, log);
return shallowClone(ty, *arena, log, /* alwaysClone */ true);
}
TypePackId Substitution::clone(TypePackId tp)
@ -838,21 +819,21 @@ void Substitution::replaceChildren(TypeId ty)
for (TypePackId& a : tfit->packArguments)
a = replace(a);
}
else if (ExternType* etv = getMutable<ExternType>(ty))
else if (ClassType* ctv = getMutable<ClassType>(ty))
{
for (auto& [name, prop] : etv->props)
for (auto& [name, prop] : ctv->props)
prop.setType(replace(prop.type()));
if (etv->parent)
etv->parent = replace(*etv->parent);
if (ctv->parent)
ctv->parent = replace(*ctv->parent);
if (etv->metatable)
etv->metatable = replace(*etv->metatable);
if (ctv->metatable)
ctv->metatable = replace(*ctv->metatable);
if (etv->indexer)
if (ctv->indexer)
{
etv->indexer->indexType = replace(etv->indexer->indexType);
etv->indexer->indexResultType = replace(etv->indexer->indexResultType);
ctv->indexer->indexType = replace(ctv->indexer->indexType);
ctv->indexer->indexResultType = replace(ctv->indexer->indexResultType);
}
}
else if (NegationType* ntv = getMutable<NegationType>(ty))
@ -892,13 +873,4 @@ void Substitution::replaceChildren(TypePackId tp)
}
}
template<typename Ty>
std::optional<Ty> Substitution::replace(std::optional<Ty> ty)
{
if (ty)
return replace(*ty);
else
return std::nullopt;
}
} // namespace Luau

View file

@ -7,11 +7,13 @@
#include "Luau/Normalize.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Scope.h"
#include "Luau/StringUtils.h"
#include "Luau/Substitution.h"
#include "Luau/ToString.h"
#include "Luau/TxnLog.h"
#include "Luau/Type.h"
#include "Luau/TypeArena.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/TypeFunction.h"
#include "Luau/TypePack.h"
#include "Luau/TypePath.h"
@ -20,6 +22,7 @@
#include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_FASTFLAGVARIABLE(LuauSubtypingStopAtNormFail)
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit)
@ -31,7 +34,7 @@ struct VarianceFlipper
Subtyping::Variance* variance;
Subtyping::Variance oldValue;
explicit VarianceFlipper(Subtyping::Variance* v)
VarianceFlipper(Subtyping::Variance* v)
: variance(v)
, oldValue(*v)
{
@ -313,7 +316,7 @@ struct ApplyMappedGenerics : Substitution
bool ignoreChildren(TypeId ty) override
{
if (get<ExternType>(ty))
if (get<ClassType>(ty))
return true;
return ty->persistent;
@ -421,7 +424,7 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope);
if (result.normalizationTooComplex)
if (FFlag::LuauSubtypingStopAtNormFail && result.normalizationTooComplex)
{
if (result.isCacheable)
resultCache[{subTy, superTy}] = result;
@ -607,7 +610,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
{
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
if (semantic.normalizationTooComplex)
if (FFlag::LuauSubtypingStopAtNormFail && semantic.normalizationTooComplex)
{
result = semantic;
}
@ -627,7 +630,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
{
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
if (semantic.normalizationTooComplex)
if (FFlag::LuauSubtypingStopAtNormFail && semantic.normalizationTooComplex)
{
result = semantic;
}
@ -742,9 +745,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result = isCovariantWith(env, p, scope);
else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
else if (auto p = get2<ExternType, ExternType>(subTy, superTy))
else if (auto p = get2<ClassType, ClassType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
else if (auto p = get2<ExternType, TableType>(subTy, superTy))
else if (auto p = get2<ClassType, TableType>(subTy, superTy))
result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope);
else if (auto p = get2<TableType, PrimitiveType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
@ -1107,7 +1110,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
{
SubtypingResult next = isCovariantWith(env, subTy, ty, scope);
if (next.normalizationTooComplex)
if (FFlag::LuauSubtypingStopAtNormFail && next.normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
if (next.isSubtype)
@ -1131,7 +1134,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Unio
{
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Union}));
if (subtypings.back().normalizationTooComplex)
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
@ -1147,7 +1150,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
{
subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
if (subtypings.back().normalizationTooComplex)
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
@ -1163,7 +1166,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Inte
{
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
if (subtypings.back().normalizationTooComplex)
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
@ -1334,7 +1337,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
}
// the top class type is not actually a primitive type, so the negation of
// any one of them includes the top class type.
else if (auto p = get2<ExternType, PrimitiveType>(subTy, negatedTy))
else if (auto p = get2<ClassType, PrimitiveType>(subTy, negatedTy))
result = {true};
else if (auto p = get<PrimitiveType>(negatedTy); p && is<TableType, MetatableType>(subTy))
result = {p->type != PrimitiveType::Table};
@ -1342,9 +1345,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
result = {p.second->type != PrimitiveType::Function};
else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy))
result = {*p.first != *p.second};
else if (auto p = get2<ExternType, ExternType>(subTy, negatedTy))
else if (auto p = get2<ClassType, ClassType>(subTy, negatedTy))
result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second, scope));
else if (get2<FunctionType, ExternType>(subTy, negatedTy))
else if (get2<FunctionType, ClassType>(subTy, negatedTy))
result = {true};
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
iceReporter->ice("attempting to negate a non-testable type");
@ -1469,15 +1472,15 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta
}
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ExternType* subExternType, const ExternType* superExternType, NotNull<Scope> scope)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull<Scope> scope)
{
return {isSubclass(subExternType, superExternType)};
return {isSubclass(subClass, superClass)};
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
TypeId subTy,
const ExternType* subExternType,
const ClassType* subClass,
TypeId superTy,
const TableType* superTable,
NotNull<Scope> scope
@ -1489,7 +1492,7 @@ SubtypingResult Subtyping::isCovariantWith(
for (const auto& [name, prop] : superTable->props)
{
if (auto classProp = lookupExternTypeProp(subExternType, name))
if (auto classProp = lookupClassProp(subClass, name))
{
result.andAlso(isCovariantWith(env, *classProp, prop, name, scope));
}
@ -1659,7 +1662,7 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope);
result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope));
result.andAlso(
isCovariantWith(env, subNorm->externTypes, superNorm->externTypes, scope).orElse(isCovariantWith(env, subNorm->externTypes, superNorm->tables, scope))
isCovariantWith(env, subNorm->classes, superNorm->classes, scope).orElse(isCovariantWith(env, subNorm->classes, superNorm->tables, scope))
);
result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope));
result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope));
@ -1676,24 +1679,24 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const NormalizedExternType& subExternType,
const NormalizedExternType& superExternType,
const NormalizedClassType& subClass,
const NormalizedClassType& superClass,
NotNull<Scope> scope
)
{
for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
for (const auto& [subClassTy, _] : subClass.classes)
{
SubtypingResult result;
for (const auto& [superExternTypeTy, superNegations] : superExternType.externTypes)
for (const auto& [superClassTy, superNegations] : superClass.classes)
{
result.orElse(isCovariantWith(env, subExternTypeTy, superExternTypeTy, scope));
result.orElse(isCovariantWith(env, subClassTy, superClassTy, scope));
if (!result.isSubtype)
continue;
for (TypeId negation : superNegations)
{
result.andAlso(SubtypingResult::negate(isCovariantWith(env, subExternTypeTy, negation, scope)));
result.andAlso(SubtypingResult::negate(isCovariantWith(env, subClassTy, negation, scope)));
if (result.isSubtype)
break;
}
@ -1708,17 +1711,17 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const NormalizedExternType& subExternType,
const NormalizedClassType& subClass,
const TypeIds& superTables,
NotNull<Scope> scope
)
{
for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
for (const auto& [subClassTy, _] : subClass.classes)
{
SubtypingResult result;
for (TypeId superTableTy : superTables)
result.orElse(isCovariantWith(env, subExternTypeTy, superTableTy, scope));
result.orElse(isCovariantWith(env, subClassTy, superTableTy, scope));
if (!result.isSubtype)
return result;
@ -1809,7 +1812,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
{
results.back().orElse(isCovariantWith(env, subTy, superTy, scope));
if (results.back().normalizationTooComplex)
if (FFlag::LuauSubtypingStopAtNormFail && results.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
}

View file

@ -13,6 +13,7 @@
#include "Luau/TypeUtils.h"
#include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceUpcast)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalFailsafe)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert)
@ -140,10 +141,15 @@ TypeId matchLiteralType(
*/
if (!isLiteral(expr))
{
if (FFlag::LuauBidirectionalInferenceUpcast)
{
auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope);
return result.isSubtype ? expectedType : exprType;
}
else
return exprType;
}
expectedType = follow(expectedType);
exprType = follow(exprType);
@ -233,7 +239,7 @@ TypeId matchLiteralType(
}
if (expr->is<AstExprFunction>())
if (FFlag::LuauBidirectionalInferenceUpcast && expr->is<AstExprFunction>())
{
// TODO: Push argument / return types into the lambda. For now, just do
// the non-literal thing: check for a subtype and upcast if valid.

View file

@ -299,9 +299,9 @@ void StateDot::visitChildren(TypeId ty, int index)
finishNodeLabel(ty);
finishNode();
}
else if constexpr (std::is_same_v<T, ExternType>)
else if constexpr (std::is_same_v<T, ClassType>)
{
formatAppend(result, "ExternType %s", t.name.c_str());
formatAppend(result, "ClassType %s", t.name.c_str());
finishNodeLabel(ty);
finishNode();

View file

@ -19,11 +19,8 @@
#include <stdexcept>
#include <string>
LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauSyntheticErrors)
LUAU_FASTFLAGVARIABLE(LuauStringPartLengthLimit)
/*
* Enables increasing levels of verbosity for Luau type names when stringifying.
@ -123,7 +120,7 @@ struct FindCyclicTypes final : TypeVisitor
return true;
}
bool visit(TypeId ty, const ExternType&) override
bool visit(TypeId ty, const ClassType&) override
{
return false;
}
@ -722,13 +719,7 @@ struct TypeStringifier
if (ttv.boundTo)
return stringify(*ttv.boundTo);
bool showName = !state.exhaustive;
if (FFlag::LuauEnableDenseTableAlias)
{
// if hide table alias expansions are enabled and there is a name found for the table, use it
showName = !state.exhaustive || state.opts.hideTableAliasExpansions;
}
if (showName)
if (!state.exhaustive)
{
if (ttv.name)
{
@ -751,10 +742,6 @@ struct TypeStringifier
stringify(ttv.instantiatedTypeParams, ttv.instantiatedTypePackParams);
return;
}
}
if (!state.exhaustive)
{
if (ttv.syntheticName)
{
state.result.invalid = true;
@ -893,9 +880,9 @@ struct TypeStringifier
state.emit(" }");
}
void operator()(TypeId, const ExternType& etv)
void operator()(TypeId, const ClassType& ctv)
{
state.emit(etv.name);
state.emit(ctv.name);
}
void operator()(TypeId, const AnyType&)
@ -923,9 +910,6 @@ struct TypeStringifier
bool hasNonNilDisjunct = false;
std::vector<std::string> results = {};
size_t resultsLength = 0;
bool lengthLimitHit = false;
for (auto el : &uv)
{
el = follow(el);
@ -952,34 +936,14 @@ struct TypeStringifier
if (needParens)
state.emit(")");
if (FFlag::LuauStringPartLengthLimit)
resultsLength += state.result.name.length();
results.push_back(std::move(state.result.name));
state.result.name = std::move(saved);
if (FFlag::LuauStringPartLengthLimit)
{
lengthLimitHit = state.opts.maxTypeLength > 0 && resultsLength > state.opts.maxTypeLength;
if (lengthLimitHit)
break;
}
}
state.unsee(&uv);
if (FFlag::LuauStringPartLengthLimit)
{
if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end());
}
else
{
if (!FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end());
}
if (optional && results.size() > 1)
state.emit("(");
@ -1023,9 +987,6 @@ struct TypeStringifier
}
std::vector<std::string> results = {};
size_t resultsLength = 0;
bool lengthLimitHit = false;
for (auto el : uv.parts)
{
el = follow(el);
@ -1042,34 +1003,14 @@ struct TypeStringifier
if (needParens)
state.emit(")");
if (FFlag::LuauStringPartLengthLimit)
resultsLength += state.result.name.length();
results.push_back(std::move(state.result.name));
state.result.name = std::move(saved);
if (FFlag::LuauStringPartLengthLimit)
{
lengthLimitHit = state.opts.maxTypeLength > 0 && resultsLength > state.opts.maxTypeLength;
if (lengthLimitHit)
break;
}
}
state.unsee(&uv);
if (FFlag::LuauStringPartLengthLimit)
{
if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end());
}
else
{
if (!FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end());
}
bool first = true;
bool shouldPlaceOnNewlines = results.size() > state.opts.compositeTypesSingleLineLimit || isOverloadedFunction(ty);

View file

@ -41,8 +41,6 @@
#include <stdexcept>
#include <optional>
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau
{
@ -299,11 +297,6 @@ struct ArcCollector : public AstVisitor
add(*name);
return true;
}
bool visit(AstTypePack* node) override
{
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
};
struct ContainsFunctionCall : public AstVisitor

View file

@ -12,9 +12,9 @@
LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace
{
@ -331,7 +331,7 @@ struct Printer_DEPRECATED
}
}
void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg, bool unconditionallyParenthesize = true)
void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg)
{
advance(annotation.location.begin);
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
@ -349,7 +349,7 @@ struct Printer_DEPRECATED
else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
{
LUAU_ASSERT(!forVarArg);
visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize);
visualizeTypeList(explicitTp->typeList, true);
}
else
{
@ -705,6 +705,8 @@ struct Printer_DEPRECATED
writer.keyword("do");
for (const auto& s : block->body)
visualize(*s);
if (!FFlag::LuauFixDoBlockEndLocation)
writer.advance(block->location.end);
writeEnd(program.location);
}
else if (const auto& a = program.as<AstStatIf>())
@ -1063,15 +1065,12 @@ struct Printer_DEPRECATED
writer.symbol(")");
if (writeTypes && (FFlag::LuauStoreReturnTypesAsPackOnAst ? func.returnAnnotation != nullptr : func.returnAnnotation_DEPRECATED.has_value()))
if (writeTypes && func.returnAnnotation)
{
writer.symbol(":");
writer.space();
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visualizeTypePackAnnotation(*func.returnAnnotation, false, false);
else
visualizeTypeList(*func.returnAnnotation_DEPRECATED, false);
visualizeTypeList(*func.returnAnnotation, false);
}
visualizeBlock(*func.body);
@ -1175,10 +1174,7 @@ struct Printer_DEPRECATED
}
writer.symbol("->");
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visualizeTypePackAnnotation(*a->returnTypes, false);
else
visualizeTypeList(a->returnTypes_DEPRECATED, true);
visualizeTypeList(a->returnTypes, true);
}
else if (const auto& a = typeAnnotation.as<AstTypeTable>())
{
@ -1372,7 +1368,7 @@ struct Printer
}
}
void visualizeTypePackAnnotation(AstTypePack& annotation, bool forVarArg, bool unconditionallyParenthesize = true)
void visualizeTypePackAnnotation(AstTypePack& annotation, bool forVarArg)
{
advance(annotation.location.begin);
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
@ -1394,10 +1390,10 @@ struct Printer
LUAU_ASSERT(!forVarArg);
if (const auto cstNode = lookupCstNode<CstTypePackExplicit>(explicitTp))
visualizeTypeList(
explicitTp->typeList, FFlag::LuauStoreReturnTypesAsPackOnAst ? cstNode->hasParentheses : true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions
explicitTp->typeList, true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions
);
else
visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize);
visualizeTypeList(explicitTp->typeList, true);
}
else
{
@ -2320,6 +2316,8 @@ struct Printer
{
const auto cstNode = lookupCstNode<CstExprFunction>(&func);
// TODO(CLI-139347): need to handle return type (incl. parentheses of return type)
if (func.generics.size > 0 || func.genericPacks.size > 0)
{
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
@ -2385,23 +2383,14 @@ struct Printer
advanceBefore(func.argLocation->end, 1);
writer.symbol(")");
if (writeTypes && FFlag::LuauStoreReturnTypesAsPackOnAst ? func.returnAnnotation != nullptr : func.returnAnnotation_DEPRECATED.has_value())
if (writeTypes && func.returnAnnotation)
{
if (cstNode)
advance(cstNode->returnSpecifierPosition);
writer.symbol(":");
writer.space();
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (!cstNode)
writer.space();
visualizeTypePackAnnotation(*func.returnAnnotation, false, false);
}
else
{
writer.space();
visualizeTypeList(*func.returnAnnotation_DEPRECATED, false);
}
visualizeTypeList(*func.returnAnnotation, false);
}
visualizeBlock(*func.body);
@ -2584,10 +2573,7 @@ struct Printer
if (cstNode)
advance(cstNode->returnArrowPosition);
writer.symbol("->");
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visualizeTypePackAnnotation(*a->returnTypes, false);
else
visualizeTypeList(a->returnTypes_DEPRECATED, true);
visualizeTypeList(a->returnTypes, true);
}
else if (const auto& a = typeAnnotation.as<AstTypeTable>())
{
@ -2660,7 +2646,6 @@ struct Printer
{
advance(item.indexerOpenPosition);
writer.symbol("[");
advance(item.stringPosition);
writer.sourceString(
std::string_view(item.stringInfo->sourceString.data, item.stringInfo->sourceString.size),
item.stringInfo->quoteStyle,

View file

@ -282,8 +282,8 @@ std::optional<TypeId> getMetatable(TypeId type, NotNull<BuiltinTypes> builtinTyp
if (const MetatableType* mtType = get<MetatableType>(type))
return mtType->metatable;
else if (const ExternType* externType = get<ExternType>(type))
return externType->metatable;
else if (const ClassType* classType = get<ClassType>(type))
return classType->metatable;
else if (isString(type))
{
auto ptv = get<PrimitiveType>(builtinTypes->stringType);
@ -346,10 +346,10 @@ std::optional<ModuleName> getDefinitionModuleName(TypeId type)
if (ftv->definition)
return ftv->definition->definitionModuleName;
}
else if (auto etv = get<ExternType>(type))
else if (auto ctv = get<ClassType>(type))
{
if (!etv->definitionModuleName.empty())
return etv->definitionModuleName;
if (!ctv->definitionModuleName.empty())
return ctv->definitionModuleName;
}
return std::nullopt;
@ -506,6 +506,31 @@ FreeType::FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId uppe
{
}
// Old constructors
FreeType::FreeType(TypeLevel level)
: index(Unifiable::freshIndex())
, level(level)
, scope(nullptr)
{
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
}
FreeType::FreeType(Scope* scope)
: index(Unifiable::freshIndex())
, level{}
, scope(scope)
{
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
}
FreeType::FreeType(Scope* scope, TypeLevel level)
: index(Unifiable::freshIndex())
, level(level)
, scope(scope)
{
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
}
GenericType::GenericType()
: index(Unifiable::freshIndex())
, name("g" + std::to_string(index))
@ -989,7 +1014,7 @@ BuiltinTypes::BuiltinTypes()
, threadType(arena->addType(Type{PrimitiveType{PrimitiveType::Thread}, /*persistent*/ true}))
, bufferType(arena->addType(Type{PrimitiveType{PrimitiveType::Buffer}, /*persistent*/ true}))
, functionType(arena->addType(Type{PrimitiveType{PrimitiveType::Function}, /*persistent*/ true}))
, externType(arena->addType(Type{ExternType{"class", {}, std::nullopt, std::nullopt, {}, {}, {}, {}}, /*persistent*/ true}))
, classType(arena->addType(Type{ClassType{"class", {}, std::nullopt, std::nullopt, {}, {}, {}, {}}, /*persistent*/ true}))
, tableType(arena->addType(Type{PrimitiveType{PrimitiveType::Table}, /*persistent*/ true}))
, emptyTableType(arena->addType(Type{TableType{TableState::Sealed, TypeLevel{}, nullptr}, /*persistent*/ true}))
, trueType(arena->addType(Type{SingletonType{BooleanSingleton{true}}, /*persistent*/ true}))
@ -1001,7 +1026,6 @@ BuiltinTypes::BuiltinTypes()
, noRefineType(arena->addType(Type{NoRefineType{}, /*persistent*/ true}))
, falsyType(arena->addType(Type{UnionType{{falseType, nilType}}, /*persistent*/ true}))
, truthyType(arena->addType(Type{NegationType{falsyType}, /*persistent*/ true}))
, notNilType(arena->addType(Type{NegationType{nilType}, /*persistent*/ true}))
, optionalNumberType(arena->addType(Type{UnionType{{numberType, nilType}}, /*persistent*/ true}))
, optionalStringType(arena->addType(Type{UnionType{{stringType, nilType}}, /*persistent*/ true}))
, emptyTypePack(arena->addTypePack(TypePackVar{TypePack{{}}, /*persistent*/ true}))
@ -1080,9 +1104,9 @@ void persist(TypeId ty)
queue.push_back(ttv->indexer->indexResultType);
}
}
else if (auto etv= get<ExternType>(t))
else if (auto ctv = get<ClassType>(t))
{
for (const auto& [_name, prop] : etv->props)
for (const auto& [_name, prop] : ctv->props)
queue.push_back(prop.type());
}
else if (auto utv = get<UnionType>(t))
@ -1182,7 +1206,7 @@ std::optional<TypeLevel> getLevel(TypePackId tp)
return std::nullopt;
}
const Property* lookupExternTypeProp(const ExternType* cls, const Name& name)
const Property* lookupClassProp(const ClassType* cls, const Name& name)
{
while (cls)
{
@ -1191,7 +1215,7 @@ const Property* lookupExternTypeProp(const ExternType* cls, const Name& name)
return &it->second;
if (cls->parent)
cls = get<ExternType>(*cls->parent);
cls = get<ClassType>(*cls->parent);
else
return nullptr;
@ -1201,7 +1225,7 @@ const Property* lookupExternTypeProp(const ExternType* cls, const Name& name)
return nullptr;
}
bool isSubclass(const ExternType* cls, const ExternType* parent)
bool isSubclass(const ClassType* cls, const ClassType* parent)
{
while (cls)
{
@ -1210,7 +1234,7 @@ bool isSubclass(const ExternType* cls, const ExternType* parent)
else if (!cls->parent)
return false;
cls = get<ExternType>(*cls->parent);
cls = get<ClassType>(*cls->parent);
LUAU_ASSERT(cls);
}
@ -1279,8 +1303,8 @@ static Tags* getTags(TypeId ty)
return &ftv->tags;
else if (auto ttv = getMutable<TableType>(ty))
return &ttv->tags;
else if (auto etv = getMutable<ExternType>(ty))
return &etv->tags;
else if (auto ctv = getMutable<ClassType>(ty))
return &ctv->tags;
return nullptr;
}
@ -1310,19 +1334,19 @@ bool hasTag(TypeId ty, const std::string& tagName)
{
ty = follow(ty);
// We special case extern types because getTags only returns a pointer to one vector of tags.
// But extern types has multiple vector of tags, represented throughout the hierarchy.
if (auto etv = get<ExternType>(ty))
// We special case classes because getTags only returns a pointer to one vector of tags.
// But classes has multiple vector of tags, represented throughout the hierarchy.
if (auto ctv = get<ClassType>(ty))
{
while (etv)
while (ctv)
{
if (hasTag(etv->tags, tagName))
if (hasTag(ctv->tags, tagName))
return true;
else if (!etv->parent)
else if (!ctv->parent)
return false;
etv = get<ExternType>(*etv->parent);
LUAU_ASSERT(etv);
ctv = get<ClassType>(*ctv->parent);
LUAU_ASSERT(ctv);
}
}
else if (auto tags = getTags(ty))

View file

@ -50,6 +50,33 @@ TypeId TypeArena::freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLe
return allocated;
}
TypeId TypeArena::freshType_DEPRECATED(TypeLevel level)
{
TypeId allocated = types.allocate(FreeType{level});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypeId TypeArena::freshType_DEPRECATED(Scope* scope)
{
TypeId allocated = types.allocate(FreeType{scope});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypeId TypeArena::freshType_DEPRECATED(Scope* scope, TypeLevel level)
{
TypeId allocated = types.allocate(FreeType{scope, level});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::freshTypePack(Scope* scope, Polarity polarity)
{
TypePackId allocated = typePacks.allocate(FreeTypePack{scope, polarity});

View file

@ -14,7 +14,6 @@
#include <string>
LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
{
@ -220,21 +219,21 @@ public:
return Luau::visit(*this, mtv.table->ty);
}
AstType* operator()(const ExternType& etv)
AstType* operator()(const ClassType& ctv)
{
RecursionCounter counter(&count);
char* name = allocateString(*allocator, etv.name);
char* name = allocateString(*allocator, ctv.name);
if (!options.expandExternTypeProps || hasSeen(&etv) || count > 1)
if (!options.expandClassProps || hasSeen(&ctv) || count > 1)
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName{name}, std::nullopt, Location());
AstArray<AstTableProp> props;
props.size = etv.props.size();
props.size = ctv.props.size();
props.data = static_cast<AstTableProp*>(allocator->allocate(sizeof(AstTableProp) * props.size));
int idx = 0;
for (const auto& [propName, prop] : etv.props)
for (const auto& [propName, prop] : ctv.props)
{
char* name = allocateString(*allocator, propName);
@ -245,13 +244,13 @@ public:
}
AstTableIndexer* indexer = nullptr;
if (etv.indexer)
if (ctv.indexer)
{
RecursionCounter counter(&count);
indexer = allocator->alloc<AstTableIndexer>();
indexer->indexType = Luau::visit(*this, etv.indexer->indexType->ty);
indexer->resultType = Luau::visit(*this, etv.indexer->indexResultType->ty);
indexer->indexType = Luau::visit(*this, ctv.indexer->indexType->ty);
indexer->resultType = Luau::visit(*this, ctv.indexer->indexResultType->ty);
}
return allocator->alloc<AstTypeTable>(Location(), props, indexer);
@ -329,20 +328,10 @@ public:
if (retTail)
retTailAnnotation = rehydrate(*retTail);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
auto returnAnnotation = allocator->alloc<AstTypePackExplicit>(Location(), AstTypeList{returnTypes, retTailAnnotation});
return allocator->alloc<AstTypeFunction>(
Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, returnAnnotation
);
}
else
{
return allocator->alloc<AstTypeFunction>(
Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, AstTypeList{returnTypes, retTailAnnotation}
);
}
}
AstType* operator()(const ErrorType&)
{
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("Unifiable<Error>"), std::nullopt, Location());
@ -596,8 +585,6 @@ public:
visitLocal(arg);
}
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (!fn->returnAnnotation)
{
if (auto result = getScope(fn->body->location))
@ -610,26 +597,7 @@ public:
if (tail)
variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*tail);
fn->returnAnnotation = allocator->alloc<AstTypePackExplicit>(Location(), AstTypeList{typeAstPack(ret), variadicAnnotation});
}
}
}
else
{
if (!fn->returnAnnotation_DEPRECATED)
{
if (auto result = getScope(fn->body->location))
{
TypePackId ret = result->returnType;
AstTypePack* variadicAnnotation = nullptr;
const auto& [v, tail] = flatten(ret);
if (tail)
variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*tail);
fn->returnAnnotation_DEPRECATED = AstTypeList{typeAstPack(ret), variadicAnnotation};
}
fn->returnAnnotation = AstTypeList{typeAstPack(ret), variadicAnnotation};
}
}

View file

@ -30,11 +30,10 @@
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau
{
@ -662,7 +661,7 @@ void TypeChecker2::visit(AstStat* stat)
return visit(s);
else if (auto s = stat->as<AstStatDeclareGlobal>())
return visit(s);
else if (auto s = stat->as<AstStatDeclareExternType>())
else if (auto s = stat->as<AstStatDeclareClass>())
return visit(s);
else if (auto s = stat->as<AstStatError>())
return visit(s);
@ -1222,10 +1221,7 @@ void TypeChecker2::visit(AstStatDeclareFunction* stat)
{
visitGenerics(stat->generics, stat->genericPacks);
visit(stat->params);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(stat->retTypes);
else
visit(stat->retTypes_DEPRECATED);
}
void TypeChecker2::visit(AstStatDeclareGlobal* stat)
@ -1233,9 +1229,9 @@ void TypeChecker2::visit(AstStatDeclareGlobal* stat)
visit(stat->type);
}
void TypeChecker2::visit(AstStatDeclareExternType* stat)
void TypeChecker2::visit(AstStatDeclareClass* stat)
{
for (const AstDeclaredExternTypeProperty& prop : stat->props)
for (const AstDeclaredClassProp& prop : stat->props)
visit(prop.ty);
}
@ -1679,12 +1675,12 @@ void TypeChecker2::visit(AstExprIndexExpr* indexExpr, ValueContext context)
{
return indexExprMetatableHelper(indexExpr, mt, exprType, indexType);
}
else if (auto cls = get<ExternType>(exprType))
else if (auto cls = get<ClassType>(exprType))
{
if (cls->indexer)
testIsSubtype(indexType, cls->indexer->indexType, indexExpr->index->location);
else
reportError(DynamicPropertyLookupOnExternTypesUnsafe{exprType}, indexExpr->location);
reportError(DynamicPropertyLookupOnClassesUnsafe{exprType}, indexExpr->location);
}
else if (get<UnionType>(exprType) && isOptional(exprType))
{
@ -1825,16 +1821,8 @@ void TypeChecker2::visit(AstExprFunction* fn)
visit(fn->body);
// we need to typecheck the return annotation itself, if it exists.
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (fn->returnAnnotation)
visit(fn->returnAnnotation);
}
else
{
if (fn->returnAnnotation_DEPRECATED)
visit(*fn->returnAnnotation_DEPRECATED);
}
visit(*fn->returnAnnotation);
// If the function type has a function annotation, we need to see if we can suggest an annotation
@ -2048,7 +2036,7 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
// If we're working with things that are not tables, the metatable comparisons above are a little excessive
// It's ok for one type to have a meta table and the other to not. In that case, we should fall back on
// checking if the intersection of the types is inhabited. If `typesHaveIntersection` failed due to limits,
// TODO: Maybe add more checks here (e.g. for functions, extern types, etc)
// TODO: Maybe add more checks here (e.g. for functions, classes, etc)
if (!(get<TableType>(leftType) || get<TableType>(rightType)))
if (!leftMt.has_value() || !rightMt.has_value())
matches = matches || typesHaveIntersection != NormalizationResult::False;
@ -2113,7 +2101,10 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
}
else
{
expectedRets = module->internalTypes.addTypePack({module->internalTypes.freshType(builtinTypes, scope, TypeLevel{})});
expectedRets = module->internalTypes.addTypePack(
{FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, scope, TypeLevel{})
: module->internalTypes.freshType_DEPRECATED(scope, TypeLevel{})}
);
}
TypeId expectedTy = module->internalTypes.addType(FunctionType(expectedArgs, expectedRets));
@ -2376,7 +2367,8 @@ TypeId TypeChecker2::flattenPack(TypePackId pack)
return *fst;
else if (auto ftp = get<FreeTypePack>(pack))
{
TypeId result = module->internalTypes.freshType(builtinTypes, ftp->scope);
TypeId result = FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, ftp->scope)
: module->internalTypes.addType(FreeType{ftp->scope});
TypePackId freeTail = module->internalTypes.addTypePack(FreeTypePack{ftp->scope});
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
@ -2621,10 +2613,7 @@ void TypeChecker2::visit(AstTypeFunction* ty)
{
visitGenerics(ty->generics, ty->genericPacks);
visit(ty->argTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(ty->returnTypes);
else
visit(ty->returnTypes_DEPRECATED);
}
void TypeChecker2::visit(AstTypeTypeof* ty)
@ -2948,7 +2937,7 @@ PropertyTypes TypeChecker2::lookupProp(
if (normValid)
{
for (const auto& [ty, _negations] : norm->externTypes.externTypes)
for (const auto& [ty, _negations] : norm->classes.classes)
{
fetch(ty);
@ -3043,10 +3032,10 @@ void TypeChecker2::checkIndexTypeFromType(
if (propTypes.foundOneProp())
reportError(MissingUnionProperty{tableTy, propTypes.missingProp, prop}, location);
// For class LValues, we don't want to report an extension error,
// because extern types come into being with full knowledge of their
// because classes come into being with full knowledge of their
// shape. We instead want to report the unknown property error of
// the `else` branch.
else if (context == ValueContext::LValue && !get<ExternType>(tableTy))
else if (context == ValueContext::LValue && !get<ClassType>(tableTy))
{
const auto lvPropTypes = lookupProp(norm.get(), prop, ValueContext::RValue, location, astIndexExprType, dummy);
if (lvPropTypes.foundOneProp() && lvPropTypes.noneMissingProp())
@ -3056,7 +3045,7 @@ void TypeChecker2::checkIndexTypeFromType(
else
reportError(CannotExtendTable{tableTy, CannotExtendTable::Property, prop}, location);
}
else if (context == ValueContext::RValue && !get<ExternType>(tableTy))
else if (context == ValueContext::RValue && !get<ClassType>(tableTy))
{
const auto rvPropTypes = lookupProp(norm.get(), prop, ValueContext::LValue, location, astIndexExprType, dummy);
if (rvPropTypes.foundOneProp() && rvPropTypes.noneMissingProp())
@ -3109,25 +3098,19 @@ PropertyType TypeChecker2::hasIndexTypeFromType(
return {NormalizationResult::True, {tt->indexer->indexResultType}};
}
if (FFlag::LuauTypeCheckerStricterIndexCheck)
{
return {NormalizationResult::False, {builtinTypes->unknownType}};
}
else
{
// if we are in a conditional context, we treat the property as present and `unknown` because
// we may be _refining_ `tableTy` to include that property. we will want to revisit this a bit
// in the future once luau has support for exact tables since this only applies when inexact.
return {inConditional(typeContext) ? NormalizationResult::True : NormalizationResult::False, {builtinTypes->unknownType}};
}
}
else if (const ExternType* cls = get<ExternType>(ty))
else if (const ClassType* cls = get<ClassType>(ty))
{
// If the property doesn't exist on the class, we consult the indexer
// We need to check if the type of the index expression foo (x[foo])
// is compatible with the indexer's indexType
// Construct the intersection and test inhabitedness!
if (auto property = lookupExternTypeProp(cls, prop))
if (auto property = lookupClassProp(cls, prop))
return {NormalizationResult::True, context == ValueContext::LValue ? property->writeTy : property->readTy};
if (cls->indexer)
{
@ -3200,17 +3183,17 @@ void TypeChecker2::diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData&
if (auto ttv = getTableType(utk->table))
accumulate(ttv->props);
else if (auto etv = get<ExternType>(follow(utk->table)))
else if (auto ctv = get<ClassType>(follow(utk->table)))
{
while (etv)
while (ctv)
{
accumulate(etv->props);
accumulate(ctv->props);
if (!etv->parent)
if (!ctv->parent)
break;
etv = get<ExternType>(*etv->parent);
LUAU_ASSERT(etv);
ctv = get<ClassType>(*ctv->parent);
LUAU_ASSERT(ctv);
}
}

View file

@ -48,8 +48,7 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions)
@ -58,15 +57,14 @@ LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionFunctionMetamethods)
LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil)
LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement)
LUAU_FASTFLAGVARIABLE(LuauMetatablesHaveLength)
LUAU_FASTFLAGVARIABLE(LuauDontForgetToReduceUnionFunc)
LUAU_FASTFLAGVARIABLE(LuauSearchForRefineableType)
LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny)
LUAU_FASTFLAGVARIABLE(LuauFixCyclicIndexInIndexer)
LUAU_FASTFLAGVARIABLE(LuauSimplyRefineNotNil)
LUAU_FASTFLAGVARIABLE(LuauIndexDeferPendingIndexee)
LUAU_FASTFLAGVARIABLE(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAGVARIABLE(LuauReduceUnionFollowUnionType)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers)
LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget)
namespace Luau
{
@ -105,7 +103,7 @@ struct InstanceCollector_DEPRECATED : TypeOnceVisitor
cyclicInstance.push_back(t);
}
bool visit(TypeId ty, const ExternType&) override
bool visit(TypeId ty, const ClassType&) override
{
return false;
}
@ -181,7 +179,7 @@ struct InstanceCollector : TypeOnceVisitor
}
}
bool visit(TypeId ty, const ExternType&) override
bool visit(TypeId ty, const ClassType&) override
{
return false;
}
@ -269,7 +267,7 @@ struct UnscopedGenericFinder : TypeOnceVisitor
return false;
}
bool visit(TypeId ty, const ExternType&) override
bool visit(TypeId ty, const ClassType&) override
{
return false;
}
@ -311,22 +309,9 @@ struct TypeFunctionReducer
enum class SkipTestResult
{
/// If a type function is cyclic, it cannot be reduced, but maybe we can
/// make a guess and offer a suggested annotation to the user.
CyclicTypeFunction,
/// Indicase that we will not be able to reduce this type function this
/// time. Constraint resolution may cause this type function to become
/// reducible later.
Irreducible,
/// Some type functions can operate on generic parameters
Generic,
/// We might be able to reduce this type function, but not yet.
Defer,
/// We can attempt to reduce this type function right now.
Okay,
};
@ -349,9 +334,6 @@ struct TypeFunctionReducer
}
else if (is<GenericType>(ty))
{
if (FFlag::DebugLuauGreedyGeneralization)
return SkipTestResult::Generic;
else
return SkipTestResult::Irreducible;
}
@ -371,9 +353,6 @@ struct TypeFunctionReducer
}
else if (is<GenericTypePack>(ty))
{
if (FFlag::DebugLuauGreedyGeneralization)
return SkipTestResult::Generic;
else
return SkipTestResult::Irreducible;
}
@ -456,7 +435,7 @@ struct TypeFunctionReducer
{
SkipTestResult skip = testForSkippability(p);
if (skip == SkipTestResult::Irreducible || (skip == SkipTestResult::Generic && !tfit->function->canReduceGenerics))
if (skip == SkipTestResult::Irreducible)
{
if (FFlag::DebugLuauLogTypeFamilies)
printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
@ -482,7 +461,7 @@ struct TypeFunctionReducer
{
SkipTestResult skip = testForSkippability(p);
if (skip == SkipTestResult::Irreducible || (skip == SkipTestResult::Generic && !tfit->function->canReduceGenerics))
if (skip == SkipTestResult::Irreducible)
{
if (FFlag::DebugLuauLogTypeFamilies)
printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
@ -843,7 +822,7 @@ static std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunct
{
arguments[unionIndex] = option;
TypeFunctionReductionResult<TypeId> result = f(instance, arguments, packParams, ctx, args...); // NOLINT
TypeFunctionReductionResult<TypeId> result = f(instance, arguments, packParams, ctx, args...);
blockedTypes.insert(blockedTypes.end(), result.blockedTypes.begin(), result.blockedTypes.end());
if (result.reductionStatus != Reduction::MaybeOk)
reductionStatus = result.reductionStatus;
@ -868,7 +847,7 @@ static std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunct
{},
});
if (ctx->solver)
if (FFlag::LuauDontForgetToReduceUnionFunc && ctx->solver)
ctx->pushConstraint(ReduceConstraint{resultTy});
return {{resultTy, Reduction::MaybeOk, {}, {}}};
@ -907,7 +886,7 @@ struct FindUserTypeFunctionBlockers : TypeOnceVisitor
return true;
}
bool visit(TypeId ty, const ExternType&) override
bool visit(TypeId ty, const ClassType&) override
{
return false;
}
@ -1242,7 +1221,7 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
if (isPending(operandTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
operandTy = follow(operandTy);
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
@ -1839,20 +1818,10 @@ TypeFunctionReductionResult<TypeId> orTypeFunction(
return {rhsTy, Reduction::MaybeOk, {}, {}};
// check to see if both operand types are resolved enough, and wait to reduce if not
if (FFlag::DebugLuauGreedyGeneralization)
{
if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(rhsTy))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
}
else
{
if (isPending(lhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
}
// Or evalutes to the LHS type if the LHS is truthy, and the RHS type if LHS is falsy.
SimplifyResult filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->truthyType);
@ -1886,20 +1855,10 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
if (lhsTy == instance || rhsTy == instance)
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
if (FFlag::DebugLuauGreedyGeneralization)
{
if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(rhsTy))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
}
else
{
if (isPending(lhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
}
// Algebra Reduction Rules for comparison type functions
// Note that comparing to never tells you nothing about the other operand
@ -2149,7 +2108,7 @@ struct FindRefinementBlockers : TypeOnceVisitor
return false;
}
bool visit(TypeId ty, const ExternType&) override
bool visit(TypeId ty, const ClassType&) override
{
return false;
}
@ -2204,44 +2163,6 @@ struct ContainsRefinableType : TypeOnceVisitor
}
};
namespace
{
bool isApproximateFalsy(TypeId ty)
{
ty = follow(ty);
bool seenNil = false;
bool seenFalse = false;
if (auto ut = get<UnionType>(ty))
{
for (auto option : ut)
{
if (auto pt = get<PrimitiveType>(option); pt && pt->type == PrimitiveType::NilType)
seenNil = true;
else if (auto st = get<SingletonType>(option); st && st->variant == BooleanSingleton{false})
seenFalse = true;
else
return false;
}
}
return seenFalse && seenNil;
}
bool isApproximateTruthy(TypeId ty)
{
ty = follow(ty);
if (auto nt = get<NegationType>(ty))
return isApproximateFalsy(nt->ty);
return false;
}
bool isSimpleDiscriminant(TypeId ty)
{
ty = follow(ty);
return isApproximateTruthy(ty) || isApproximateFalsy(ty);
}
}
TypeFunctionReductionResult<TypeId> refineTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
@ -2260,12 +2181,8 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
for (size_t i = 1; i < typeParams.size(); i++)
discriminantTypes.push_back(follow(typeParams.at(i)));
const bool targetIsPending = FFlag::DebugLuauGreedyGeneralization
? is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(targetTy)
: isPending(targetTy, ctx->solver);
// check to see if both operand types are resolved enough, and wait to reduce if not
if (targetIsPending)
if (isPending(targetTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {targetTy}, {}};
else
{
@ -2275,18 +2192,6 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
return {std::nullopt, Reduction::MaybeOk, {t}, {}};
}
}
if (FFlag::LuauRefineWaitForBlockedTypesInTarget)
{
// If we have a blocked type in the target, we *could* potentially
// refine it, but more likely we end up with some type explosion in
// normalization.
FindRefinementBlockers frb;
frb.traverse(targetTy);
if (!frb.found.empty())
return {std::nullopt, Reduction::MaybeOk, {frb.found.begin(), frb.found.end()}, {}};
}
// Refine a target type and a discriminant one at a time.
// Returns result : TypeId, toBlockOn : vector<TypeId>
auto stepRefine = [&ctx](TypeId target, TypeId discriminant) -> std::pair<TypeId, std::vector<TypeId>>
@ -2316,6 +2221,8 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
return {nullptr, {}};
}
else
{
if (FFlag::LuauSearchForRefineableType)
{
// If the discriminant type is only:
// - The `*no-refine*` type or,
@ -2325,6 +2232,18 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
crt.traverse(discriminant);
if (!crt.found)
return {target, {}};
}
else
{
if (FFlag::LuauSkipNoRefineDuringRefinement)
if (get<NoRefineType>(discriminant))
return {target, {}};
if (auto nt = get<NegationType>(discriminant))
{
if (get<NoRefineType>(follow(nt->ty)))
return {target, {}};
}
}
if (FFlag::LuauSimplyRefineNotNil)
{
@ -2338,49 +2257,6 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
}
}
if (FFlag::LuauOptimizeFalsyAndTruthyIntersect)
{
// If the target type is a table, then simplification already implements the logic to deal with refinements properly since the
// type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type.
// We also fire for simple discriminants such as false? and ~(false?): the falsy and truthy types respectively
// NOTE: It would be nice to be able to do a simple intersection for something like:
//
// { a: A, b: B, ... } & { x: X }
//
if (is<TableType>(target) || isSimpleDiscriminant(discriminant))
{
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
if (FFlag::DebugLuauGreedyGeneralization)
{
// Simplification considers free and generic types to be
// 'blocking', but that's not suitable for refine<>.
//
// If we are only blocked on those types, we consider
// the simplification a success and reduce.
if (std::all_of(
begin(result.blockedTypes),
end(result.blockedTypes),
[](auto&& v)
{
return is<FreeType, GenericType>(follow(v));
}
))
{
return {result.result, {}};
}
else
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
}
else
{
if (!result.blockedTypes.empty())
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
}
return {result.result, {}};
}
}
else
{
// If the target type is a table, then simplification already implements the logic to deal with refinements properly since the
// type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type.
if (get<TableType>(target))
@ -2391,8 +2267,6 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
return {result.result, {}};
}
}
// In the general case, we'll still use normalization though.
TypeId intersection = ctx->arena->addType(IntersectionType{{target, discriminant}});
@ -2611,8 +2485,6 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
// fold over the types with `simplifyIntersection`
TypeId resultTy = ctx->builtins->unknownType;
// collect types which caused intersection to return never
DenseHashSet<TypeId> unintersectableTypes{nullptr};
for (auto ty : types)
{
// skip any `*no-refine*` types.
@ -2621,17 +2493,6 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty);
if (FFlag::LuauNarrowIntersectionNevers)
{
// If simplifying the intersection returned never, note the type we tried to intersect it with, and continue trying to intersect with the
// rest
if (get<NeverType>(result.result))
{
unintersectableTypes.insert(follow(ty));
continue;
}
}
if (FFlag::LuauIntersectNotNil)
{
for (TypeId blockedType : result.blockedTypes)
@ -2649,24 +2510,6 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
resultTy = result.result;
}
if (FFlag::LuauNarrowIntersectionNevers)
{
if (!unintersectableTypes.empty())
{
unintersectableTypes.insert(resultTy);
if (unintersectableTypes.size() > 1)
{
TypeId intersection =
ctx->arena->addType(IntersectionType{std::vector<TypeId>(unintersectableTypes.begin(), unintersectableTypes.end())});
return {intersection, Reduction::MaybeOk, {}, {}};
}
else
{
return {*unintersectableTypes.begin(), Reduction::MaybeOk, {}, {}};
}
}
}
// if the intersection simplifies to `never`, this gives us bad autocomplete.
// we'll just produce the intersection plainly instead, but this might be revisitable
// if we ever give `never` some kind of "explanation" trail.
@ -2729,7 +2572,7 @@ bool computeKeysOf(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& se
return res;
}
if (auto classTy = get<ExternType>(ty))
if (auto classTy = get<ClassType>(ty))
{
for (auto [key, _] : classTy->props)
result.insert(key);
@ -2752,7 +2595,7 @@ bool computeKeysOf(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& se
return res;
}
// this should not be reachable since the type should be a valid tables or extern types part from normalization.
// this should not be reachable since the type should be a valid tables or classes part from normalization.
LUAU_ASSERT(false);
return false;
}
@ -2778,9 +2621,9 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
if (!normTy)
return {std::nullopt, Reduction::MaybeOk, {}, {}};
// if we don't have either just tables or just extern types, we've got nothing to get keys of (at least until a future version perhaps adds extern types
// if we don't have either just tables or just classes, we've got nothing to get keys of (at least until a future version perhaps adds classes
// as well)
if (normTy->hasTables() == normTy->hasExternTypes())
if (normTy->hasTables() == normTy->hasClasses())
return {std::nullopt, Reduction::Erroneous, {}, {}};
// this is sort of atrocious, but we're trying to reject any type that has not normalized to a table or a union of tables.
@ -2791,31 +2634,31 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
// we're going to collect the keys in here
Set<std::string> keys{{}};
// computing the keys for extern types
if (normTy->hasExternTypes())
// computing the keys for classes
if (normTy->hasClasses())
{
LUAU_ASSERT(!normTy->hasTables());
// seen set for key computation for extern types
// seen set for key computation for classes
DenseHashSet<TypeId> seen{{}};
auto externTypeIter = normTy->externTypes.ordering.begin();
auto externTypeIterEnd = normTy->externTypes.ordering.end();
LUAU_ASSERT(externTypeIter != externTypeIterEnd); // should be guaranteed by the `hasExternTypes` check earlier
auto classesIter = normTy->classes.ordering.begin();
auto classesIterEnd = normTy->classes.ordering.end();
LUAU_ASSERT(classesIter != classesIterEnd); // should be guaranteed by the `hasClasses` check earlier
// collect all the properties from the first class type
if (!computeKeysOf(*externTypeIter, keys, seen, isRaw, ctx))
if (!computeKeysOf(*classesIter, keys, seen, isRaw, ctx))
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have a top type!
// we need to look at each class to remove any keys that are not common amongst them all
while (++externTypeIter != externTypeIterEnd)
while (++classesIter != classesIterEnd)
{
seen.clear(); // we'll reuse the same seen set
Set<std::string> localKeys{{}};
// we can skip to the next class if this one is a top type
if (!computeKeysOf(*externTypeIter, localKeys, seen, isRaw, ctx))
if (!computeKeysOf(*classesIter, localKeys, seen, isRaw, ctx))
continue;
for (auto& key : keys)
@ -2830,7 +2673,7 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
// computing the keys for tables
if (normTy->hasTables())
{
LUAU_ASSERT(!normTy->hasExternTypes());
LUAU_ASSERT(!normTy->hasClasses());
// seen set for key computation for tables
DenseHashSet<TypeId> seen{{}};
@ -2992,7 +2835,7 @@ bool searchPropsAndIndexer(
return false;
}
/* Handles recursion / metamethods of tables and extern types
/* Handles recursion / metamethods of tables/classes
`isRaw` parameter indicates whether or not we should follow __index metamethods
returns false if property of `ty` could not be found */
bool tblIndexInto_DEPRECATED(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result, NotNull<TypeFunctionContext> ctx, bool isRaw)
@ -3167,11 +3010,11 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}};
}
// if we don't have either just tables or just extern types, we've got nothing to index into
if (indexeeNormTy->hasTables() == indexeeNormTy->hasExternTypes())
// if we don't have either just tables or just classes, we've got nothing to index into
if (indexeeNormTy->hasTables() == indexeeNormTy->hasClasses())
return {std::nullopt, Reduction::Erroneous, {}, {}};
// we're trying to reject any type that has not normalized to a table or extern type or a union of tables or extern types.
// we're trying to reject any type that has not normalized to a table/class or a union of tables/classes.
if (indexeeNormTy->hasTops() || indexeeNormTy->hasBooleans() || indexeeNormTy->hasErrors() || indexeeNormTy->hasNils() ||
indexeeNormTy->hasNumbers() || indexeeNormTy->hasStrings() || indexeeNormTy->hasThreads() || indexeeNormTy->hasBuffers() ||
indexeeNormTy->hasFunctions() || indexeeNormTy->hasTyvars())
@ -3202,18 +3045,18 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
DenseHashSet<TypeId> properties{{}}; // vector of types that will be returned
if (indexeeNormTy->hasExternTypes())
if (indexeeNormTy->hasClasses())
{
LUAU_ASSERT(!indexeeNormTy->hasTables());
if (isRaw) // rawget should never reduce for extern types (to match the behavior of the rawget global function)
if (isRaw) // rawget should never reduce for classes (to match the behavior of the rawget global function)
return {std::nullopt, Reduction::Erroneous, {}, {}};
// at least one class is guaranteed to be in the iterator by .hasExternTypes()
for (auto externTypeIter = indexeeNormTy->externTypes.ordering.begin(); externTypeIter != indexeeNormTy->externTypes.ordering.end(); ++externTypeIter)
// at least one class is guaranteed to be in the iterator by .hasClasses()
for (auto classesIter = indexeeNormTy->classes.ordering.begin(); classesIter != indexeeNormTy->classes.ordering.end(); ++classesIter)
{
auto externTy = get<ExternType>(*externTypeIter);
if (!externTy)
auto classTy = get<ClassType>(*classesIter);
if (!classTy)
{
LUAU_ASSERT(false); // this should not be possible according to normalization's spec
return {std::nullopt, Reduction::Erroneous, {}, {}};
@ -3222,16 +3065,16 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
for (TypeId ty : *typesToFind)
{
// Search for all instances of indexer in class->props and class->indexer
if (searchPropsAndIndexer(ty, externTy->props, externTy->indexer, properties, ctx))
if (searchPropsAndIndexer(ty, classTy->props, classTy->indexer, properties, ctx))
continue; // Indexer was found in this class, so we can move on to the next
auto parent = externTy->parent;
auto parent = classTy->parent;
bool foundInParent = false;
while (parent && !foundInParent)
{
auto parentExternType = get<ExternType>(follow(*parent));
foundInParent = searchPropsAndIndexer(ty, parentExternType->props, parentExternType->indexer, properties, ctx);
parent = parentExternType->parent;
auto parentClass = get<ClassType>(follow(*parent));
foundInParent = searchPropsAndIndexer(ty, parentClass->props, parentClass->indexer, properties, ctx);
parent = parentClass->parent;
}
// we move on to the next type if any of the parents we went through had the property.
@ -3243,7 +3086,7 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
// findMetatableEntry demands the ability to emit errors, so we must give it
// the necessary state to do that, even if we intend to just eat the errors.
ErrorVec dummy;
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, *externTypeIter, "__index", Location{});
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, *classesIter, "__index", Location{});
if (!mmType) // if a metatable does not exist, there is no where else to look
return {std::nullopt, Reduction::Erroneous, {}, {}};
@ -3255,7 +3098,7 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
if (indexeeNormTy->hasTables())
{
LUAU_ASSERT(!indexeeNormTy->hasExternTypes());
LUAU_ASSERT(!indexeeNormTy->hasClasses());
// at least one table is guaranteed to be in the iterator by .hasTables()
for (auto tablesIter = indexeeNormTy->tables.begin(); tablesIter != indexeeNormTy->tables.end(); ++tablesIter)
@ -3350,7 +3193,7 @@ TypeFunctionReductionResult<TypeId> setmetatableTypeFunction(
// we're trying to reject any type that has not normalized to a table or a union/intersection of tables.
if (targetNorm->hasTops() || targetNorm->hasBooleans() || targetNorm->hasErrors() || targetNorm->hasNils() || targetNorm->hasNumbers() ||
targetNorm->hasStrings() || targetNorm->hasThreads() || targetNorm->hasBuffers() || targetNorm->hasFunctions() || targetNorm->hasTyvars() ||
targetNorm->hasExternTypes())
targetNorm->hasClasses())
return {std::nullopt, Reduction::Erroneous, {}, {}};
// if the supposed metatable is not a table, we will fail to reduce.
@ -3424,7 +3267,7 @@ static TypeFunctionReductionResult<TypeId> getmetatableHelper(TypeId targetTy, c
erroneous = false;
}
if (auto clazz = get<ExternType>(targetTy))
if (auto clazz = get<ClassType>(targetTy))
{
metatable = clazz->metatable;
erroneous = false;
@ -3570,12 +3413,12 @@ BuiltinTypeFunctions::BuiltinTypeFunctions()
, powFunc{"pow", powTypeFunction}
, modFunc{"mod", modTypeFunction}
, concatFunc{"concat", concatTypeFunction}
, andFunc{"and", andTypeFunction, /*canReduceGenerics*/ true}
, orFunc{"or", orTypeFunction, /*canReduceGenerics*/ true}
, andFunc{"and", andTypeFunction}
, orFunc{"or", orTypeFunction}
, ltFunc{"lt", ltTypeFunction}
, leFunc{"le", leTypeFunction}
, eqFunc{"eq", eqTypeFunction}
, refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ FFlag::DebugLuauGreedyGeneralization}
, refineFunc{"refine", refineTypeFunction}
, singletonFunc{"singleton", singletonTypeFunction}
, unionFunc{"union", unionTypeFunction}
, intersectFunc{"intersect", intersectTypeFunction}

View file

@ -46,7 +46,7 @@ struct InstanceCollector2 : TypeOnceVisitor
cyclicInstance.insert(t);
}
bool visit(TypeId ty, const ExternType&) override
bool visit(TypeId ty, const ClassType&) override
{
return false;
}

View file

@ -15,7 +15,6 @@
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents)
LUAU_FASTFLAGVARIABLE(LuauTypeFunOptional)
namespace Luau
{
@ -155,7 +154,7 @@ static std::string getTag(lua_State* L, TypeFunctionTypeId ty)
return "table";
else if (get<TypeFunctionFunctionType>(ty))
return "function";
else if (get<TypeFunctionExternType>(ty))
else if (get<TypeFunctionClassType>(ty))
return "class";
else if (get<TypeFunctionGenericType>(ty))
return "generic";
@ -316,38 +315,6 @@ static int getSingletonValue(lua_State* L)
luaL_error(L, "type.value: can't call `value` method on `%s` type", getTag(L, self).c_str());
}
// Luau: `types.optional(ty: type) -> type`
// Returns the type instance representing an optional version of `ty`.
// If `ty` is a union, this adds `nil` to the components of the union.
// Otherwise, makes a union of the two things.
static int createOptional(lua_State* L)
{
LUAU_ASSERT(FFlag::LuauTypeFunOptional);
int argumentCount = lua_gettop(L);
if (argumentCount != 1)
luaL_error(L, "types.optional: expected 1 argument, but got %d", argumentCount);
TypeFunctionTypeId argument = getTypeUserData(L, 1);
std::vector<TypeFunctionTypeId> components;
if (auto unionTy = get<TypeFunctionUnionType>(argument))
{
components.reserve(unionTy->components.size() + 1);
components.insert(components.begin(), unionTy->components.begin(), unionTy->components.end());
}
else
components.emplace_back(argument);
components.emplace_back(allocateTypeFunctionType(L, TypeFunctionPrimitiveType(TypeFunctionPrimitiveType::NilType)));
allocTypeUserData(L, TypeFunctionUnionType{components});
return 1;
}
// Luau: `types.unionof(...: type) -> type`
// Returns the type instance representing union
static int createUnion(lua_State* L)
@ -1147,7 +1114,7 @@ static int getClassParent_DEPRECATED(lua_State* L)
luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount);
TypeFunctionTypeId self = getTypeUserData(L, 1);
auto tfct = get<TypeFunctionExternType>(self);
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());
@ -1169,7 +1136,7 @@ static int getReadParent(lua_State* L)
luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount);
TypeFunctionTypeId self = getTypeUserData(L, 1);
auto tfct = get<TypeFunctionExternType>(self);
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());
@ -1191,7 +1158,7 @@ static int getWriteParent(lua_State* L)
luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount);
TypeFunctionTypeId self = getTypeUserData(L, 1);
auto tfct = get<TypeFunctionExternType>(self);
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());
@ -1275,7 +1242,7 @@ static int getProps(lua_State* L)
return 1;
}
if (auto tfct = get<TypeFunctionExternType>(self))
if (auto tfct = get<TypeFunctionClassType>(self))
{
lua_createtable(L, int(tfct->props.size()), 0);
for (auto& [name, prop] : tfct->props)
@ -1338,7 +1305,7 @@ static int getIndexer(lua_State* L)
return 1;
}
if (auto tfct = get<TypeFunctionExternType>(self))
if (auto tfct = get<TypeFunctionClassType>(self))
{
// if the indexer does not exist, we should return nil
if (!tfct->indexer.has_value())
@ -1386,7 +1353,7 @@ static int getReadIndexer(lua_State* L)
return 1;
}
if (auto tfct = get<TypeFunctionExternType>(self))
if (auto tfct = get<TypeFunctionClassType>(self))
{
// if the indexer does not exist, we should return nil
if (!tfct->indexer.has_value())
@ -1432,7 +1399,7 @@ static int getWriteIndexer(lua_State* L)
return 1;
}
if (auto tfct = get<TypeFunctionExternType>(self))
if (auto tfct = get<TypeFunctionClassType>(self))
{
// if the indexer does not exist, we should return nil
if (!tfct->indexer.has_value())
@ -1472,7 +1439,7 @@ static int getMetatable(lua_State* L)
return 1;
}
if (auto tfct = get<TypeFunctionExternType>(self))
if (auto tfct = get<TypeFunctionClassType>(self))
{
// if the metatable does not exist, we should return nil
if (!tfct->metatable.has_value())
@ -1557,7 +1524,6 @@ void registerTypesLibrary(lua_State* L)
{"copy", deepCopy},
{"generic", createGeneric},
{(FFlag::LuauTypeFunOptional) ? "optional" : nullptr, (FFlag::LuauTypeFunOptional) ? createOptional : nullptr},
{nullptr, nullptr}
};
@ -1627,7 +1593,7 @@ void registerTypeUserData(lua_State* L)
// Union and Intersection type methods
{"components", getComponents},
// Extern type methods
// Class type methods
{FFlag::LuauTypeFunReadWriteParents ? "readparent" : "parent", FFlag::LuauTypeFunReadWriteParents ? getReadParent : getClassParent_DEPRECATED},
// Function type methods (cont.)
@ -1638,7 +1604,7 @@ void registerTypeUserData(lua_State* L)
{"name", getGenericName},
{"ispack", getGenericIsPack},
// move this under extern type methods when removing FFlagLuauTypeFunReadWriteParents
// move this under Class type methods when removing FFlagLuauTypeFunReadWriteParents
{FFlag::LuauTypeFunReadWriteParents ? "writeparent" : nullptr, FFlag::LuauTypeFunReadWriteParents ? getWriteParent : nullptr},
{nullptr, nullptr}
@ -1937,12 +1903,12 @@ bool areEqual(SeenSet& seen, const TypeFunctionFunctionType& lhs, const TypeFunc
return true;
}
bool areEqual(SeenSet& seen, const TypeFunctionExternType& lhs, const TypeFunctionExternType& rhs)
bool areEqual(SeenSet& seen, const TypeFunctionClassType& lhs, const TypeFunctionClassType& rhs)
{
if (seenSetContains(seen, &lhs, &rhs))
return true;
return lhs.externTy == rhs.externTy;
return lhs.classTy == rhs.classTy;
}
bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType& rhs)
@ -2010,8 +1976,8 @@ bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType
}
{
const TypeFunctionExternType* lf = get<TypeFunctionExternType>(&lhs);
const TypeFunctionExternType* rf = get<TypeFunctionExternType>(&rhs);
const TypeFunctionClassType* lf = get<TypeFunctionClassType>(&lhs);
const TypeFunctionClassType* rf = get<TypeFunctionClassType>(&rhs);
if (lf && rf)
return areEqual(seen, *lf, *rf);
}
@ -2300,7 +2266,7 @@ private:
TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{});
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack});
}
else if (auto c = get<TypeFunctionExternType>(ty))
else if (auto c = get<TypeFunctionClassType>(ty))
target = ty; // Don't copy a class since they are immutable
else if (auto g = get<TypeFunctionGenericType>(ty))
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionGenericType{g->isNamed, g->isPack, g->name});
@ -2355,7 +2321,7 @@ private:
cloneChildren(t1, t2);
else if (auto [f1, f2] = std::tuple{getMutable<TypeFunctionFunctionType>(ty), getMutable<TypeFunctionFunctionType>(tfti)}; f1 && f2)
cloneChildren(f1, f2);
else if (auto [c1, c2] = std::tuple{getMutable<TypeFunctionExternType>(ty), getMutable<TypeFunctionExternType>(tfti)}; c1 && c2)
else if (auto [c1, c2] = std::tuple{getMutable<TypeFunctionClassType>(ty), getMutable<TypeFunctionClassType>(tfti)}; c1 && c2)
cloneChildren(c1, c2);
else if (auto [g1, g2] = std::tuple{getMutable<TypeFunctionGenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)}; g1 && g2)
cloneChildren(g1, g2);
@ -2465,7 +2431,7 @@ private:
f2->retTypes = shallowClone(f1->retTypes);
}
void cloneChildren(TypeFunctionExternType* c1, TypeFunctionExternType* c2)
void cloneChildren(TypeFunctionClassType* c1, TypeFunctionClassType* c2)
{
// noop.
}

View file

@ -206,12 +206,12 @@ private:
TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{});
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack});
}
else if (auto c = get<ExternType>(ty))
else if (auto c = get<ClassType>(ty))
{
// Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the original
// class
target = typeFunctionRuntime->typeArena.allocate(
TypeFunctionExternType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty}
TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty}
);
}
else if (auto g = get<GenericType>(ty))
@ -291,7 +291,7 @@ private:
serializeChildren(m1, m2);
else if (auto [f1, f2] = std::tuple{get<FunctionType>(ty), getMutable<TypeFunctionFunctionType>(tfti)}; f1 && f2)
serializeChildren(f1, f2);
else if (auto [c1, c2] = std::tuple{get<ExternType>(ty), getMutable<TypeFunctionExternType>(tfti)}; c1 && c2)
else if (auto [c1, c2] = std::tuple{get<ClassType>(ty), getMutable<TypeFunctionClassType>(tfti)}; c1 && c2)
serializeChildren(c1, c2);
else if (auto [g1, g2] = std::tuple{get<GenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)}; g1 && g2)
serializeChildren(g1, g2);
@ -411,7 +411,7 @@ private:
f2->retTypes = shallowSerialize(f1->retTypes);
}
void serializeChildren(const ExternType* c1, TypeFunctionExternType* c2)
void serializeChildren(const ClassType* c1, TypeFunctionClassType* c2)
{
for (const auto& [k, p] : c1->props)
{
@ -702,9 +702,9 @@ private:
TypePackId emptyTypePack = state->ctx->arena->addTypePack(TypePack{});
target = state->ctx->arena->addType(FunctionType{emptyTypePack, emptyTypePack, {}, false});
}
else if (auto c = get<TypeFunctionExternType>(ty))
else if (auto c = get<TypeFunctionClassType>(ty))
{
target = c->externTy;
target = c->classTy;
}
else if (auto g = get<TypeFunctionGenericType>(ty))
{
@ -811,7 +811,7 @@ private:
deserializeChildren(m2, m1);
else if (auto [f1, f2] = std::tuple{getMutable<FunctionType>(ty), getMutable<TypeFunctionFunctionType>(tfti)}; f1 && f2)
deserializeChildren(f2, f1);
else if (auto [c1, c2] = std::tuple{getMutable<ExternType>(ty), getMutable<TypeFunctionExternType>(tfti)}; c1 && c2)
else if (auto [c1, c2] = std::tuple{getMutable<ClassType>(ty), getMutable<TypeFunctionClassType>(tfti)}; c1 && c2)
deserializeChildren(c2, c1);
else if (auto [g1, g2] = std::tuple{getMutable<GenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)}; g1 && g2)
deserializeChildren(g2, g1);
@ -972,7 +972,7 @@ private:
f1->retTypes = shallowDeserialize(f2->retTypes);
}
void deserializeChildren(TypeFunctionExternType* c2, ExternType* c1)
void deserializeChildren(TypeFunctionClassType* c2, ClassType* c1)
{
// noop.
}

View file

@ -32,19 +32,17 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAGVARIABLE(LuauStatForInFix)
LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure)
LUAU_FASTFLAGVARIABLE(LuauLimitIterationWhenCheckingArgumentCounts)
namespace Luau
{
static bool typeCouldHaveMetatable(TypeId ty)
{
return get<TableType>(follow(ty)) || get<ExternType>(follow(ty)) || get<MetatableType>(follow(ty));
return get<TableType>(follow(ty)) || get<ClassType>(follow(ty)) || get<MetatableType>(follow(ty));
}
static void defaultLuauPrintLine(const std::string& s)
@ -318,7 +316,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
unifierState.skipCacheForType.clear();
duplicateTypeAliases.clear();
incorrectExternTypeDefinitions.clear();
incorrectClassDefinitions.clear();
return std::move(currentModule);
}
@ -383,7 +381,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStat& program)
}
else if (auto global = program.as<AstStatDeclareFunction>())
return check(scope, *global);
else if (auto global = program.as<AstStatDeclareExternType>())
else if (auto global = program.as<AstStatDeclareClass>())
return check(scope, *global);
else if (auto errorStatement = program.as<AstStatError>())
{
@ -498,9 +496,9 @@ ControlFlow TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope,
prototype(scope, *typealias, subLevel);
++subLevel;
}
else if (const auto& declaredExternType = stat->as<AstStatDeclareExternType>())
else if (const auto& declaredClass = stat->as<AstStatDeclareClass>())
{
prototype(scope, *declaredExternType);
prototype(scope, *declaredClass);
}
}
@ -788,7 +786,7 @@ struct Demoter : Substitution
bool ignoreChildren(TypeId ty) override
{
if (get<ExternType>(ty))
if (get<ClassType>(ty))
return true;
return false;
@ -798,7 +796,8 @@ struct Demoter : Substitution
{
auto ftv = get<FreeType>(ty);
LUAU_ASSERT(ftv);
return arena->freshType(builtins, demotedLevel(ftv->level));
return FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtins, demotedLevel(ftv->level))
: addType(FreeType{demotedLevel(ftv->level)});
}
TypePackId clean(TypePackId tp) override
@ -1685,82 +1684,82 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
}
}
void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType)
void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
{
std::optional<TypeId> superTy = std::make_optional(builtinTypes->externType);
if (declaredExternType.superName)
std::optional<TypeId> superTy = std::make_optional(builtinTypes->classType);
if (declaredClass.superName)
{
Name superName = Name(declaredExternType.superName->value);
Name superName = Name(declaredClass.superName->value);
std::optional<TypeFun> lookupType = scope->lookupType(superName);
if (!lookupType)
{
reportError(declaredExternType.location, UnknownSymbol{superName, UnknownSymbol::Type});
incorrectExternTypeDefinitions.insert(&declaredExternType);
reportError(declaredClass.location, UnknownSymbol{superName, UnknownSymbol::Type});
incorrectClassDefinitions.insert(&declaredClass);
return;
}
// We don't have generic extern types, 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);
superTy = lookupType->type;
if (!get<ExternType>(follow(*superTy)))
if (!get<ClassType>(follow(*superTy)))
{
reportError(
declaredExternType.location,
GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredExternType.name.value)}
declaredClass.location,
GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredClass.name.value)}
);
incorrectExternTypeDefinitions.insert(&declaredExternType);
incorrectClassDefinitions.insert(&declaredClass);
return;
}
}
Name className(declaredExternType.name.value);
Name className(declaredClass.name.value);
TypeId classTy = addType(ExternType(className, {}, superTy, std::nullopt, {}, {}, currentModule->name, declaredExternType.location));
ExternType* etv = getMutable<ExternType>(classTy);
TypeId classTy = addType(ClassType(className, {}, superTy, std::nullopt, {}, {}, currentModule->name, declaredClass.location));
ClassType* ctv = getMutable<ClassType>(classTy);
TypeId metaTy = addType(TableType{TableState::Sealed, scope->level});
etv->metatable = metaTy;
ctv->metatable = metaTy;
if (FFlag::LuauRetainDefinitionAliasLocations)
scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredExternType.location};
scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredClass.location};
else
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
}
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType)
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
{
Name className(declaredExternType.name.value);
Name className(declaredClass.name.value);
// Don't bother checking if the class definition was incorrect
if (incorrectExternTypeDefinitions.find(&declaredExternType))
if (incorrectClassDefinitions.find(&declaredClass))
return ControlFlow::None;
std::optional<TypeFun> binding;
if (auto it = scope->exportedTypeBindings.find(className); it != scope->exportedTypeBindings.end())
binding = it->second;
// This extern type definition must have been `prototype()`d first.
// This class definition must have been `prototype()`d first.
if (!binding)
ice("Extern type not predeclared");
ice("Class not predeclared");
TypeId externTy = binding->type;
ExternType* etv = getMutable<ExternType>(externTy);
TypeId classTy = binding->type;
ClassType* ctv = getMutable<ClassType>(classTy);
if (!etv->metatable)
ice("No metatable for declared extern type");
if (!ctv->metatable)
ice("No metatable for declared class");
if (const auto& indexer = declaredExternType.indexer)
etv->indexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType));
if (const auto& indexer = declaredClass.indexer)
ctv->indexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType));
TableType* metatable = getMutable<TableType>(*etv->metatable);
for (const AstDeclaredExternTypeProperty& prop : declaredExternType.props)
TableType* metatable = getMutable<TableType>(*ctv->metatable);
for (const AstDeclaredClassProp& prop : declaredClass.props)
{
Name propName(prop.name.value);
TypeId propTy = resolveType(scope, *prop.ty);
bool assignToMetatable = isMetamethod(propName);
Luau::ExternType::Props& assignTo = assignToMetatable ? metatable->props : etv->props;
Luau::ClassType::Props& assignTo = assignToMetatable ? metatable->props : ctv->props;
// Function types always take 'self', but this isn't reflected in the
// parsed annotation. Add it here.
@ -1769,7 +1768,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareExtern
if (FunctionType* ftv = getMutable<FunctionType>(propTy))
{
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
ftv->argTypes = addTypePack(TypePack{{externTy}, ftv->argTypes});
ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes});
ftv->hasSelf = true;
FunctionDefinition defn;
@ -1812,7 +1811,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareExtern
}
else
{
reportError(declaredExternType.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
}
}
}
@ -1851,8 +1850,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareFuncti
);
TypePackId argPack = resolveTypePack(funScope, global.params);
TypePackId retPack =
FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funScope, *global.retTypes) : resolveTypePack(funScope, global.retTypes_DEPRECATED);
TypePackId retPack = resolveTypePack(funScope, global.retTypes);
FunctionDefinition defn;
@ -1926,7 +1924,7 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
else if (auto a = expr.as<AstExprUnary>())
result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprBinary>())
result = FFlag::LuauReduceCheckBinaryExprStackPressure ? checkExpr(scope, *a, expectedType) : checkExpr_DEPRECATED(scope, *a, expectedType);
result = checkExpr(scope, *a, expectedType);
else if (auto a = expr.as<AstExprTypeAssertion>())
result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprError>())
@ -2137,9 +2135,9 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
if (auto found = findTablePropertyRespectingMeta(type, name, location, addErrors))
return *found;
}
else if (const ExternType* cls = get<ExternType>(type))
else if (const ClassType* cls = get<ClassType>(type))
{
const Property* prop = lookupExternTypeProp(cls, name);
const Property* prop = lookupClassProp(cls, name);
if (prop)
return prop->type();
@ -3188,9 +3186,6 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
}
else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe)
{
// Defer the stack allocation of lhs, predicate etc until this lambda is called.
auto checkExprOr = [&]() -> WithPredicate<TypeId>
{
// For these, passing expectedType is worse than simply forcing them, because their implementation
// may inadvertently check if expectedTypes exist first and use it, instead of forceSingleton first.
@ -3202,68 +3197,9 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
PredicateVec predicates;
if (auto lvalue = tryGetLValue(*expr.left))
predicates.emplace_back(EqPredicate{std::move(*lvalue), rhs.type, expr.location});
if (auto lvalue = tryGetLValue(*expr.right))
predicates.emplace_back(EqPredicate{std::move(*lvalue), lhs.type, expr.location});
if (!predicates.empty() && expr.op == AstExprBinary::CompareNe)
predicates = {NotPredicate{std::move(predicates)}};
return {checkBinaryOperation(scope, expr, lhs.type, rhs.type), std::move(predicates)};
};
return checkExprOr();
}
else
{
// Expected types are not useful for other binary operators.
WithPredicate<TypeId> lhs = checkExpr(scope, *expr.left);
WithPredicate<TypeId> rhs = checkExpr(scope, *expr.right);
// Intentionally discarding predicates with other operators.
return WithPredicate{checkBinaryOperation(scope, expr, lhs.type, rhs.type, lhs.predicates)};
}
}
WithPredicate<TypeId> TypeChecker::checkExpr_DEPRECATED(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType)
{
if (expr.op == AstExprBinary::And)
{
auto [lhsTy, lhsPredicates] = checkExpr(scope, *expr.left, expectedType);
ScopePtr innerScope = childScope(scope, expr.location);
resolve(lhsPredicates, innerScope, true);
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right, expectedType);
return {checkBinaryOperation(scope, expr, lhsTy, rhsTy), {AndPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
}
else if (expr.op == AstExprBinary::Or)
{
auto [lhsTy, lhsPredicates] = checkExpr(scope, *expr.left, expectedType);
ScopePtr innerScope = childScope(scope, expr.location);
resolve(lhsPredicates, innerScope, false);
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right, expectedType);
// Because of C++, I'm not sure if lhsPredicates was not moved out by the time we call checkBinaryOperation.
TypeId result = checkBinaryOperation(scope, expr, lhsTy, rhsTy, lhsPredicates);
return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
}
else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe)
{
// For these, passing expectedType is worse than simply forcing them, because their implementation
// may inadvertently check if expectedTypes exist first and use it, instead of forceSingleton first.
WithPredicate<TypeId> lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/true);
WithPredicate<TypeId> rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/true);
if (auto predicate = tryGetTypeGuardPredicate(expr))
return {booleanType, {std::move(*predicate)}};
PredicateVec predicates;
if (auto lvalue = tryGetLValue(*expr.left))
predicates.push_back(EqPredicate{std::move(*lvalue), rhs.type, expr.location});
if (auto lvalue = tryGetLValue(*expr.right))
predicates.push_back(EqPredicate{std::move(*lvalue), lhs.type, expr.location});
@ -3462,14 +3398,14 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
return errorRecoveryType(scope);
}
}
else if (const ExternType* lhsExternType = get<ExternType>(lhs))
else if (const ClassType* lhsClass = get<ClassType>(lhs))
{
if (const Property* prop = lookupExternTypeProp(lhsExternType, name))
if (const Property* prop = lookupClassProp(lhsClass, name))
{
return prop->type();
}
if (auto indexer = lhsExternType->indexer)
if (auto indexer = lhsClass->indexer)
{
Unifier state = mkUnifier(scope, expr.location);
state.tryUnify(stringType, indexer->indexType);
@ -3521,14 +3457,14 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
if (value)
{
if (const ExternType* exprExternType = get<ExternType>(exprType))
if (const ClassType* exprClass = get<ClassType>(exprType))
{
if (const Property* prop = lookupExternTypeProp(exprExternType, value->value.data))
if (const Property* prop = lookupClassProp(exprClass, value->value.data))
{
return prop->type();
}
if (auto indexer = exprExternType->indexer)
if (auto indexer = exprClass->indexer)
{
unify(stringType, indexer->indexType, scope, expr.index->location);
return indexer->indexResultType;
@ -3554,20 +3490,20 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
}
else
{
if (const ExternType* exprExternType = get<ExternType>(exprType))
if (const ClassType* exprClass = get<ClassType>(exprType))
{
if (auto indexer = exprExternType->indexer)
if (auto indexer = exprClass->indexer)
{
unify(indexType, indexer->indexType, scope, expr.index->location);
return indexer->indexResultType;
}
}
if (const ExternType* exprExternType = get<ExternType>(exprType))
if (const ClassType* exprClass = get<ClassType>(exprType))
{
if (isNonstrictMode())
return unknownType;
reportError(TypeError{expr.location, DynamicPropertyLookupOnExternTypesUnsafe{exprType}});
reportError(TypeError{expr.location, DynamicPropertyLookupOnClassesUnsafe{exprType}});
return errorRecoveryType(scope);
}
}
@ -3848,10 +3784,8 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(
auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, expr, expr.generics, expr.genericPacks);
TypePackId retPack;
if (FFlag::LuauStoreReturnTypesAsPackOnAst && expr.returnAnnotation)
if (expr.returnAnnotation)
retPack = resolveTypePack(funScope, *expr.returnAnnotation);
else if (!FFlag::LuauStoreReturnTypesAsPackOnAst && expr.returnAnnotation_DEPRECATED)
retPack = resolveTypePack(funScope, *expr.returnAnnotation_DEPRECATED);
else if (isNonstrictMode())
retPack = anyTypePack;
else if (expectedFunctionType && expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty())
@ -4058,8 +3992,7 @@ void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstE
// If we're in nonstrict mode we want to only report this missing return
// statement if there are type annotations on the function. In strict mode
// we report it regardless.
if (!isNonstrictMode() ||
(FFlag::LuauStoreReturnTypesAsPackOnAst ? function.returnAnnotation != nullptr : function.returnAnnotation_DEPRECATED.has_value()))
if (!isNonstrictMode() || function.returnAnnotation)
{
reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retTypes});
}
@ -4117,23 +4050,6 @@ void TypeChecker::checkArgumentList(
size_t paramIndex = 0;
int loopCount = 0;
auto exceedsLoopCount = [&]()
{
if (FFlag::LuauLimitIterationWhenCheckingArgumentCounts)
{
++loopCount;
if (loopCount > FInt::LuauTypeInferTypePackLoopLimit)
{
state.reportError(TypeError{state.location, CodeTooComplex{}});
reportErrorCodeTooComplex(state.location);
return true;
}
}
return false;
};
auto reportCountMismatchError = [&state, &argLocations, paramPack, argPack, &funName]()
{
// For this case, we want the error span to cover every errant extra parameter
@ -4208,17 +4124,12 @@ void TypeChecker::checkArgumentList(
}
else if (auto vtp = state.log.getMutable<VariadicTypePack>(tail))
{
loopCount = 0;
// Function is variadic and requires that all subsequent parameters
// be compatible with a type.
while (paramIter != endIter)
{
state.tryUnify(vtp->ty, *paramIter);
++paramIter;
if (exceedsLoopCount())
return;
}
return;
@ -4227,16 +4138,10 @@ void TypeChecker::checkArgumentList(
{
std::vector<TypeId> rest;
rest.reserve(std::distance(paramIter, endIter));
loopCount = 0;
while (paramIter != endIter)
{
rest.push_back(*paramIter);
++paramIter;
if (exceedsLoopCount())
return;
}
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, paramIter.tail()}});
@ -4280,17 +4185,12 @@ void TypeChecker::checkArgumentList(
// too many parameters passed
if (!paramIter.tail())
{
loopCount = 0;
while (argIter != endIter)
{
// The use of unify here is deliberate. We don't want this unification
// to be undoable.
unify(errorRecoveryType(scope), *argIter, scope, state.location);
++argIter;
if (exceedsLoopCount())
return;
}
reportCountMismatchError();
return;
@ -4304,8 +4204,6 @@ void TypeChecker::checkArgumentList(
}
else if (auto vtp = state.log.getMutable<VariadicTypePack>(tail))
{
loopCount = 0;
// Function is variadic and requires that all subsequent parameters
// be compatible with a type.
size_t argIndex = paramIndex;
@ -4321,17 +4219,12 @@ void TypeChecker::checkArgumentList(
++argIter;
++argIndex;
if (exceedsLoopCount())
return;
}
return;
}
else if (state.log.getMutable<FreeTypePack>(tail))
{
loopCount = 0;
// Create a type pack out of the remaining argument types
// and unify it with the tail.
std::vector<TypeId> rest;
@ -4340,9 +4233,6 @@ void TypeChecker::checkArgumentList(
{
rest.push_back(*argIter);
++argIter;
if (exceedsLoopCount())
return;
}
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, argIter.tail()}});
@ -4614,9 +4504,9 @@ std::unique_ptr<WithPredicate<TypePackId>> TypeChecker::checkCallOverload(
{
callTy = getIndexTypeFromType(scope, mttv->metatable, "__call", expr.func->location, /* addErrors= */ false);
}
else if (const ExternType* etv = get<ExternType>(fn); etv && etv->metatable)
else if (const ClassType* ctv = get<ClassType>(fn); ctv && ctv->metatable)
{
callTy = getIndexTypeFromType(scope, *etv->metatable, "__call", expr.func->location, /* addErrors= */ false);
callTy = getIndexTypeFromType(scope, *ctv->metatable, "__call", expr.func->location, /* addErrors= */ false);
}
if (callTy)
@ -5319,17 +5209,17 @@ void TypeChecker::diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& d
if (auto ttv = getTableType(utk->table))
accumulate(ttv->props);
else if (auto etv = get<ExternType>(follow(utk->table)))
else if (auto ctv = get<ClassType>(follow(utk->table)))
{
while (etv)
while (ctv)
{
accumulate(etv->props);
accumulate(ctv->props);
if (!etv->parent)
if (!ctv->parent)
break;
etv = get<ExternType>(*etv->parent);
LUAU_ASSERT(etv);
ctv = get<ClassType>(*ctv->parent);
LUAU_ASSERT(ctv);
}
}
@ -5408,7 +5298,8 @@ TypeId TypeChecker::freshType(const ScopePtr& scope)
TypeId TypeChecker::freshType(TypeLevel level)
{
return currentModule->internalTypes.freshType(builtinTypes, level);
return FFlag::LuauFreeTypesMustHaveBounds ? currentModule->internalTypes.freshType(builtinTypes, level)
: currentModule->internalTypes.addType(Type(FreeType(level)));
}
TypeId TypeChecker::singletonType(bool value)
@ -5805,8 +5696,7 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
auto [generics, genericPacks] = createGenericTypes(funcScope, std::nullopt, annotation, func->generics, func->genericPacks);
TypePackId argTypes = resolveTypePack(funcScope, func->argTypes);
TypePackId retTypes = FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funcScope, *func->returnTypes)
: resolveTypePack(funcScope, func->returnTypes_DEPRECATED);
TypePackId retTypes = resolveTypePack(funcScope, func->returnTypes);
std::vector<TypeId> genericTys;
genericTys.reserve(generics.size());
@ -5857,9 +5747,13 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
return builtinTypes->nilType;
}
else if (const auto& un = annotation.as<AstTypeUnion>())
{
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{
if (un->types.size == 1)
return resolveType(scope, *un->types.data[0]);
}
std::vector<TypeId> types;
for (AstType* ann : un->types)
types.push_back(resolveType(scope, *ann));
@ -5867,9 +5761,13 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
return addType(UnionType{types});
}
else if (const auto& un = annotation.as<AstTypeIntersection>())
{
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{
if (un->types.size == 1)
return resolveType(scope, *un->types.data[0]);
}
std::vector<TypeId> types;
for (AstType* ann : un->types)
types.push_back(resolveType(scope, *ann));
@ -6476,7 +6374,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
return refine(
[](TypeId ty) -> bool
{
return get<ExternType>(ty);
return get<ClassType>(ty);
}
);
}
@ -6491,13 +6389,13 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
TypeId type = follow(typeFun->type);
// You cannot refine to the top class type.
if (type == builtinTypes->externType)
if (type == builtinTypes->classType)
{
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
}
// We're only interested in the root type of any extern type.
if (auto etv = get<ExternType>(type); !etv || (etv->parent != builtinTypes->externType && !hasTag(type, kTypeofRootTag)))
// We're only interested in the root class of any classes.
if (auto ctv = get<ClassType>(type); !ctv || (ctv->parent != builtinTypes->classType && !hasTag(type, kTypeofRootTag)))
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
// This probably hints at breaking out type filtering functions from the predicate solver so that typeof is not tightly coupled with IsA.

View file

@ -208,26 +208,6 @@ TypePackIterator end(TypePackId tp)
return TypePackIterator{};
}
TypePackId getTail(TypePackId tp)
{
DenseHashSet<TypePackId> seen{nullptr};
while (tp)
{
tp = follow(tp);
if (seen.contains(tp))
break;
seen.insert(tp);
if (auto pack = get<TypePack>(tp); pack && pack->tail)
tp = *pack->tail;
else
break;
}
return follow(tp);
}
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs)
{
TypePackId lhsId = const_cast<TypePackId>(&lhs);

View file

@ -307,9 +307,9 @@ struct TraversalState
prop = &it->second;
}
}
else if (auto c = get<ExternType>(*currentType))
else if (auto c = get<ClassType>(*currentType))
{
prop = lookupExternTypeProp(c, property.name);
prop = lookupClassProp(c, property.name);
}
// For a metatable type, the table takes priority; check that before
// falling through to the metatable entry below.
@ -461,7 +461,7 @@ struct TraversalState
indexer = &(*mtMt->indexer);
}
// Note: we don't appear to walk the class hierarchy for indexers
else if (auto ct = get<ExternType>(current); ct && ct->indexer)
else if (auto ct = get<ClassType>(current); ct && ct->indexer)
indexer = &(*ct->indexer);
if (indexer)

View file

@ -11,7 +11,10 @@
#include <algorithm>
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete);
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope);
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
namespace Luau
@ -305,7 +308,7 @@ TypePack extendTypePack(
TypePack newPack;
newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity);
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
trackInteriorFreeTypePack(ftp->scope, *newPack.tail);
if (FFlag::LuauSolverV2)
@ -324,10 +327,11 @@ TypePack extendTypePack(
{
FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType, ftp->polarity};
t = arena.addType(ft);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(ftp->scope, t);
}
else
t = arena.freshType(builtinTypes, ftp->scope);
t = FFlag::LuauFreeTypesMustHaveBounds ? arena.freshType(builtinTypes, ftp->scope) : arena.freshType_DEPRECATED(ftp->scope);
}
newPack.head.push_back(t);
@ -434,6 +438,7 @@ TypeId stripNil(NotNull<BuiltinTypes> builtinTypes, TypeArena& arena, TypeId ty)
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypeId ty)
{
LUAU_ASSERT(FFlag::LuauSolverV2 || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
std::shared_ptr<const NormalizedType> normType = normalizer->normalize(ty);
if (!normType)
@ -551,8 +556,10 @@ std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMa
void trackInteriorFreeType(Scope* scope, TypeId ty)
{
if (!FFlag::LuauDisableNewSolverAssertsInMixedMode)
LUAU_ASSERT(FFlag::LuauSolverV2);
if (FFlag::LuauDisableNewSolverAssertsInMixedMode)
LUAU_ASSERT(FFlag::LuauTrackInteriorFreeTypesOnScope);
else
LUAU_ASSERT(FFlag::LuauSolverV2 && FFlag::LuauTrackInteriorFreeTypesOnScope);
for (; scope; scope = scope->parent.get())
{
if (scope->interiorFreeTypes)
@ -570,7 +577,7 @@ void trackInteriorFreeType(Scope* scope, TypeId ty)
void trackInteriorFreeTypePack(Scope* scope, TypePackId tp)
{
LUAU_ASSERT(tp);
if (!FFlag::LuauNonReentrantGeneralization2)
if (!FFlag::LuauNonReentrantGeneralization)
return;
for (; scope; scope = scope->parent.get())

View file

@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering)
LUAU_FASTFLAGVARIABLE(LuauUnifierRecursionOnRestart)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
namespace Luau
{
@ -291,7 +292,7 @@ TypePackId Widen::clean(TypePackId)
bool Widen::ignoreChildren(TypeId ty)
{
if (get<ExternType>(ty))
if (get<ClassType>(ty))
return true;
return !log->is<UnionType>(ty);
@ -692,13 +693,13 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
else if (log.getMutable<MetatableType>(subTy))
tryUnifyWithMetatable(superTy, subTy, /*reversed*/ true);
else if (log.getMutable<ExternType>(superTy))
tryUnifyWithExternType(subTy, superTy, /*reversed*/ false);
else if (log.getMutable<ClassType>(superTy))
tryUnifyWithClass(subTy, superTy, /*reversed*/ false);
// Unification of Luau types with extern types is almost, but not quite symmetrical.
// The order in which we perform this test is significant in the case that both types are extern types.
else if (log.getMutable<ExternType>(subTy))
tryUnifyWithExternType(subTy, superTy, /*reversed*/ true);
// Unification of nonclasses with classes is almost, but not quite symmetrical.
// The order in which we perform this test is significant in the case that both types are classes.
else if (log.getMutable<ClassType>(subTy))
tryUnifyWithClass(subTy, superTy, /*reversed*/ true);
else if (log.get<NegationType>(superTy) || log.get<NegationType>(subTy))
tryUnifyNegations(subTy, superTy);
@ -1106,15 +1107,15 @@ void Unifier::tryUnifyNormalizedTypes(
if (!get<PrimitiveType>(superNorm.errors))
return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()});
for (const auto& [subExternType, _] : subNorm.externTypes.externTypes)
for (const auto& [subClass, _] : subNorm.classes.classes)
{
bool found = false;
const ExternType* subCtv = get<ExternType>(subExternType);
const ClassType* subCtv = get<ClassType>(subClass);
LUAU_ASSERT(subCtv);
for (const auto& [superExternType, superNegations] : superNorm.externTypes.externTypes)
for (const auto& [superClass, superNegations] : superNorm.classes.classes)
{
const ExternType* superCtv = get<ExternType>(superExternType);
const ClassType* superCtv = get<ClassType>(superClass);
LUAU_ASSERT(superCtv);
if (isSubclass(subCtv, superCtv))
@ -1123,7 +1124,7 @@ void Unifier::tryUnifyNormalizedTypes(
for (TypeId negation : superNegations)
{
const ExternType* negationCtv = get<ExternType>(negation);
const ClassType* negationCtv = get<ClassType>(negation);
LUAU_ASSERT(negationCtv);
if (isSubclass(subCtv, negationCtv))
@ -1558,7 +1559,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
if (FFlag::LuauSolverV2)
return freshType(NotNull{types}, builtinTypes, scope);
else
return types->freshType(builtinTypes, scope, level);
return FFlag::LuauFreeTypesMustHaveBounds ? types->freshType(builtinTypes, scope, level) : types->freshType_DEPRECATED(scope, level);
};
const TypePackId emptyTp = types->addTypePack(TypePack{{}, std::nullopt});
@ -2381,8 +2382,8 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed)
}
}
// Extern type unification is almost, but not quite symmetrical. We use the 'reversed' boolean to indicate which scenario we are evaluating.
void Unifier::tryUnifyWithExternType(TypeId subTy, TypeId superTy, bool reversed)
// Class unification is almost, but not quite symmetrical. We use the 'reversed' boolean to indicate which scenario we are evaluating.
void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed)
{
if (reversed)
std::swap(superTy, subTy);
@ -2395,20 +2396,20 @@ void Unifier::tryUnifyWithExternType(TypeId subTy, TypeId superTy, bool reversed
reportError(location, TypeMismatch{subTy, superTy, mismatchContext()});
};
const ExternType* superExternType = get<ExternType>(superTy);
if (!superExternType)
ice("tryUnifyExternType invoked with non-class Type");
const ClassType* superClass = get<ClassType>(superTy);
if (!superClass)
ice("tryUnifyClass invoked with non-class Type");
if (const ExternType* subExternType = get<ExternType>(subTy))
if (const ClassType* subClass = get<ClassType>(subTy))
{
switch (variance)
{
case Covariant:
if (!isSubclass(subExternType, superExternType))
if (!isSubclass(subClass, superClass))
return fail();
return;
case Invariant:
if (subExternType != superExternType)
if (subClass != superClass)
return fail();
return;
}
@ -2433,7 +2434,7 @@ void Unifier::tryUnifyWithExternType(TypeId subTy, TypeId superTy, bool reversed
for (const auto& [propName, prop] : subTable->props)
{
const Property* classProp = lookupExternTypeProp(superExternType, propName);
const Property* classProp = lookupClassProp(superClass, propName);
if (!classProp)
{
ok = false;
@ -2461,7 +2462,7 @@ void Unifier::tryUnifyWithExternType(TypeId subTy, TypeId superTy, bool reversed
if (subTable->indexer)
{
ok = false;
std::string msg = "Extern type " + superExternType->name + " does not have an indexer";
std::string msg = "Class " + superClass->name + " does not have an indexer";
reportError(location, GenericError{msg});
}
@ -2634,9 +2635,9 @@ static void tryUnifyWithAny(
queue.push_back(mt->table);
queue.push_back(mt->metatable);
}
else if (state.log.getMutable<ExternType>(ty))
else if (state.log.getMutable<ClassType>(ty))
{
// ExternTypes never contain free types.
// ClassTypes never contain free types.
}
else if (auto union_ = state.log.getMutable<UnionType>(ty))
queue.insert(queue.end(), union_->options.begin(), union_->options.end());
@ -2653,7 +2654,7 @@ void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy)
LUAU_ASSERT(get<AnyType>(anyTy) || get<ErrorType>(anyTy) || get<UnknownType>(anyTy) || get<NeverType>(anyTy));
// These types are not visited in general loop below
if (log.get<PrimitiveType>(subTy) || log.get<AnyType>(subTy) || log.get<ExternType>(subTy))
if (log.get<PrimitiveType>(subTy) || log.get<AnyType>(subTy) || log.get<ClassType>(subTy))
return;
TypePackId anyTp = types->addTypePack(TypePackVar{VariadicTypePack{anyTy}});

View file

@ -18,7 +18,9 @@
#include <optional>
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAGVARIABLE(LuauUnifyMetatableWithAny)
LUAU_FASTFLAG(LuauExtraFollows)
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
namespace Luau
{
@ -236,9 +238,9 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
auto superMetatable = get<MetatableType>(superTy);
if (subMetatable && superMetatable)
return unify(subMetatable, superMetatable);
else if (subMetatable && superAny)
else if (FFlag::LuauUnifyMetatableWithAny && subMetatable && superAny)
return unify(subMetatable, superAny);
else if (subAny && superMetatable)
else if (FFlag::LuauUnifyMetatableWithAny && subAny && superMetatable)
return unify(subAny, superMetatable);
else if (subMetatable) // if we only have one metatable, unify with the inner table
return unify(subMetatable->table, superTy);
@ -282,7 +284,7 @@ bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy)
if (superArgTail)
return doDefault();
const IntersectionType* upperBoundIntersection = get<IntersectionType>(upperBound);
const IntersectionType* upperBoundIntersection = get<IntersectionType>(FFlag::LuauExtraFollows ? upperBound : subFree->upperBound);
if (!upperBoundIntersection)
return doDefault();
@ -319,19 +321,19 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
if (shouldInstantiate)
{
for (TypeId generic : subFn->generics)
for (auto generic : subFn->generics)
{
const GenericType* gen = get<GenericType>(follow(generic));
if (gen)
const GenericType* gen = get<GenericType>(generic);
LUAU_ASSERT(gen);
genericSubstitutions[generic] = freshType(scope, gen->polarity);
}
for (TypePackId genericPack : subFn->genericPacks)
for (auto genericPack : subFn->genericPacks)
{
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization)
{
const GenericTypePack* gen = get<GenericTypePack>(follow(genericPack));
if (gen)
const GenericTypePack* gen = get<GenericTypePack>(genericPack);
LUAU_ASSERT(gen);
genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity);
}
else
@ -649,6 +651,211 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
return true;
}
struct FreeTypeSearcher : TypeVisitor
{
NotNull<Scope> scope;
explicit FreeTypeSearcher(NotNull<Scope> scope)
: TypeVisitor(/*skipBoundTypes*/ true)
, scope(scope)
{
}
Polarity polarity = Polarity::Positive;
void flip()
{
switch (polarity)
{
case Polarity::Positive:
polarity = Polarity::Negative;
break;
case Polarity::Negative:
polarity = Polarity::Positive;
break;
case Polarity::Mixed:
break;
default:
LUAU_ASSERT(!"Unreachable");
}
}
DenseHashSet<const void*> seenPositive{nullptr};
DenseHashSet<const void*> seenNegative{nullptr};
bool seenWithCurrentPolarity(const void* ty)
{
switch (polarity)
{
case Polarity::Positive:
{
if (seenPositive.contains(ty))
return true;
seenPositive.insert(ty);
return false;
}
case Polarity::Negative:
{
if (seenNegative.contains(ty))
return true;
seenNegative.insert(ty);
return false;
}
case Polarity::Mixed:
{
if (seenPositive.contains(ty) && seenNegative.contains(ty))
return true;
seenPositive.insert(ty);
seenNegative.insert(ty);
return false;
}
default:
LUAU_ASSERT(!"Unreachable");
}
return false;
}
// The keys in these maps are either TypeIds or TypePackIds. It's safe to
// mix them because we only use these pointers as unique keys. We never
// indirect them.
DenseHashMap<const void*, size_t> negativeTypes{0};
DenseHashMap<const void*, size_t> positiveTypes{0};
bool visit(TypeId ty) override
{
if (seenWithCurrentPolarity(ty))
return false;
LUAU_ASSERT(ty);
return true;
}
bool visit(TypeId ty, const FreeType& ft) override
{
if (seenWithCurrentPolarity(ty))
return false;
if (!subsumes(scope, ft.scope))
return true;
switch (polarity)
{
case Polarity::Positive:
positiveTypes[ty]++;
break;
case Polarity::Negative:
negativeTypes[ty]++;
break;
case Polarity::Mixed:
positiveTypes[ty]++;
negativeTypes[ty]++;
break;
default:
LUAU_ASSERT(!"Unreachable");
}
return true;
}
bool visit(TypeId ty, const TableType& tt) override
{
if (seenWithCurrentPolarity(ty))
return false;
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
{
switch (polarity)
{
case Polarity::Positive:
positiveTypes[ty]++;
break;
case Polarity::Negative:
negativeTypes[ty]++;
break;
case Polarity::Mixed:
positiveTypes[ty]++;
negativeTypes[ty]++;
break;
default:
LUAU_ASSERT(!"Unreachable");
}
}
for (const auto& [_name, prop] : tt.props)
{
if (prop.isReadOnly())
traverse(*prop.readTy);
else
{
LUAU_ASSERT(prop.isShared());
Polarity p = polarity;
polarity = Polarity::Mixed;
traverse(prop.type());
polarity = p;
}
}
if (tt.indexer)
{
traverse(tt.indexer->indexType);
traverse(tt.indexer->indexResultType);
}
return false;
}
bool visit(TypeId ty, const FunctionType& ft) override
{
if (seenWithCurrentPolarity(ty))
return false;
flip();
traverse(ft.argTypes);
flip();
traverse(ft.retTypes);
return false;
}
bool visit(TypeId, const ClassType&) override
{
return false;
}
bool visit(TypePackId tp, const FreeTypePack& ftp) override
{
if (seenWithCurrentPolarity(tp))
return false;
if (!subsumes(scope, ftp.scope))
return true;
switch (polarity)
{
case Polarity::Positive:
positiveTypes[tp]++;
break;
case Polarity::Negative:
negativeTypes[tp]++;
break;
case Polarity::Mixed:
positiveTypes[tp]++;
negativeTypes[tp]++;
break;
default:
LUAU_ASSERT(!"Unreachable");
}
return true;
}
};
TypeId Unifier2::mkUnion(TypeId left, TypeId right)
{
left = follow(left);

View file

@ -87,8 +87,8 @@ struct AstLocal
template<typename T>
struct AstArray
{
T* data = nullptr;
size_t size = 0;
T* data;
size_t size;
const T* begin() const
{
@ -446,25 +446,7 @@ public:
AstStatBlock* body,
size_t functionDepth,
const AstName& debugname,
AstTypePack* returnAnnotation,
AstTypePack* varargAnnotation = nullptr,
const std::optional<Location>& argLocation = std::nullopt
);
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstExprFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
AstLocal* self,
const AstArray<AstLocal*>& args,
bool vararg,
const Location& varargLocation,
AstStatBlock* body,
size_t functionDepth,
const AstName& debugname,
const std::optional<AstTypeList>& returnAnnotation,
const std::optional<AstTypeList>& returnAnnotation = {},
AstTypePack* varargAnnotation = nullptr,
const std::optional<Location>& argLocation = std::nullopt
);
@ -479,9 +461,7 @@ public:
AstArray<AstGenericTypePack*> genericPacks;
AstLocal* self;
AstArray<AstLocal*> args;
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
std::optional<AstTypeList> returnAnnotation_DEPRECATED;
AstTypePack* returnAnnotation = nullptr;
std::optional<AstTypeList> returnAnnotation;
bool vararg = false;
Location varargLocation;
AstTypePack* varargAnnotation;
@ -949,36 +929,6 @@ class AstStatDeclareFunction : public AstStat
public:
LUAU_RTTI(AstStatDeclareFunction)
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstStatDeclareFunction(
const Location& location,
const AstName& name,
const Location& nameLocation,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& params,
const AstArray<AstArgumentName>& paramNames,
bool vararg,
const Location& varargLocation,
AstTypePack* retTypes
);
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstStatDeclareFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstName& name,
const Location& nameLocation,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& params,
const AstArray<AstArgumentName>& paramNames,
bool vararg,
const Location& varargLocation,
AstTypePack* retTypes
);
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstStatDeclareFunction(
const Location& location,
const AstName& name,
@ -992,7 +942,6 @@ public:
const AstTypeList& retTypes
);
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstStatDeclareFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
@ -1022,12 +971,10 @@ public:
AstArray<AstArgumentName> paramNames;
bool vararg = false;
Location varargLocation;
AstTypePack* retTypes;
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeList retTypes_DEPRECATED;
AstTypeList retTypes;
};
struct AstDeclaredExternTypeProperty
struct AstDeclaredClassProp
{
AstName name;
Location nameLocation;
@ -1053,16 +1000,16 @@ struct AstTableIndexer
std::optional<Location> accessLocation;
};
class AstStatDeclareExternType : public AstStat
class AstStatDeclareClass : public AstStat
{
public:
LUAU_RTTI(AstStatDeclareExternType)
LUAU_RTTI(AstStatDeclareClass)
AstStatDeclareExternType(
AstStatDeclareClass(
const Location& location,
const AstName& name,
std::optional<AstName> superName,
const AstArray<AstDeclaredExternTypeProperty>& props,
const AstArray<AstDeclaredClassProp>& props,
AstTableIndexer* indexer = nullptr
);
@ -1071,7 +1018,7 @@ public:
AstName name;
std::optional<AstName> superName;
AstArray<AstDeclaredExternTypeProperty> props;
AstArray<AstDeclaredClassProp> props;
AstTableIndexer* indexer;
};
@ -1148,26 +1095,6 @@ class AstTypeFunction : public AstType
public:
LUAU_RTTI(AstTypeFunction)
AstTypeFunction(
const Location& location,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& argTypes,
const AstArray<std::optional<AstArgumentName>>& argNames,
AstTypePack* returnTypes
);
AstTypeFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& argTypes,
const AstArray<std::optional<AstArgumentName>>& argNames,
AstTypePack* returnTypes
);
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeFunction(
const Location& location,
const AstArray<AstGenericType*>& generics,
@ -1177,7 +1104,6 @@ public:
const AstTypeList& returnTypes
);
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
@ -1198,9 +1124,7 @@ public:
AstArray<AstGenericTypePack*> genericPacks;
AstTypeList argTypes;
AstArray<std::optional<AstArgumentName>> argNames;
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeList returnTypes_DEPRECATED;
AstTypePack* returnTypes;
AstTypeList returnTypes;
};
class AstTypeTypeof : public AstType
@ -1559,7 +1483,7 @@ public:
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatDeclareExternType* node)
virtual bool visit(class AstStatDeclareClass* node)
{
return visit(static_cast<AstStat*>(node));
}

View file

@ -388,7 +388,6 @@ public:
std::optional<Position> separatorPosition;
CstExprConstantString* stringInfo = nullptr; // only if Kind == StringProperty
Position stringPosition{0, 0}; // only if Kind == StringProperty
};
CstTypeTable(AstArray<Item> items, bool isArray);
@ -473,10 +472,8 @@ class CstTypePackExplicit : public CstNode
public:
LUAU_CST_RTTI(CstTypePackExplicit)
explicit CstTypePackExplicit();
explicit CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray<Position> commaPositions);
CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray<Position> commaPositions);
bool hasParentheses;
Position openParenthesesPosition;
Position closeParenthesesPosition;
AstArray<Position> commaPositions;

View file

@ -157,8 +157,8 @@ private:
// type function Name ... end
AstStat* parseTypeFunction(const Location& start, bool exported, Position typeKeywordPosition);
AstDeclaredExternTypeProperty parseDeclaredExternTypeMethod(const AstArray<AstAttr*>& attributes);
AstDeclaredExternTypeProperty parseDeclaredExternTypeMethod_DEPRECATED();
AstDeclaredClassProp parseDeclaredClassMethod(const AstArray<AstAttr*>& attributes);
AstDeclaredClassProp parseDeclaredClassMethod_DEPRECATED();
// `declare global' Name: Type |
@ -182,14 +182,6 @@ private:
const Name* localName,
const AstArray<AstAttr*>& attributes
);
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
std::pair<AstExprFunction*, AstLocal*> parseFunctionBody_DEPRECATED(
bool hasself,
const Lexeme& matchFunction,
const AstName& debugname,
const Name* localName,
const AstArray<AstAttr*>& attributes
);
// explist ::= {exp `,'} exp
void parseExprList(TempVector<AstExpr*>& result, TempVector<Position>* commaPositions = nullptr);
@ -227,12 +219,8 @@ private:
TempVector<std::optional<Position>>* nameColonPositions = nullptr
);
AstTypePack* parseOptionalReturnType(Position* returnSpecifierPosition = nullptr);
AstTypePack* parseReturnType();
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
std::optional<AstTypeList> parseOptionalReturnType_DEPRECATED(Position* returnSpecifierPosition = nullptr);
std::pair<Location, AstTypeList> parseReturnType_DEPRECATED();
std::optional<AstTypeList> parseOptionalReturnType(Position* returnSpecifierPosition = nullptr);
std::pair<Location, AstTypeList> parseReturnType();
struct TableIndexerResult
{
@ -503,7 +491,7 @@ private:
std::vector<CstTypeTable::Item> scratchCstTableTypeProps;
std::vector<AstType*> scratchType;
std::vector<AstTypeOrPack> scratchTypeOrPack;
std::vector<AstDeclaredExternTypeProperty> scratchDeclaredClassProps;
std::vector<AstDeclaredClassProp> scratchDeclaredClassProps;
std::vector<AstExprTable::Item> scratchItem;
std::vector<CstExprTable::Item> scratchCstItem;
std::vector<AstArgumentName> scratchArgName;

View file

@ -4,7 +4,6 @@
#include "Luau/Common.h"
LUAU_FASTFLAG(LuauDeprecatedAttribute);
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau
{
@ -242,7 +241,7 @@ AstExprFunction::AstExprFunction(
AstStatBlock* body,
size_t functionDepth,
const AstName& debugname,
AstTypePack* returnAnnotation,
const std::optional<AstTypeList>& returnAnnotation,
AstTypePack* varargAnnotation,
const std::optional<Location>& argLocation
)
@ -261,41 +260,6 @@ AstExprFunction::AstExprFunction(
, debugname(debugname)
, argLocation(argLocation)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
}
AstExprFunction::AstExprFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
AstLocal* self,
const AstArray<AstLocal*>& args,
bool vararg,
const Location& varargLocation,
AstStatBlock* body,
size_t functionDepth,
const AstName& debugname,
const std::optional<AstTypeList>& returnAnnotation,
AstTypePack* varargAnnotation,
const std::optional<Location>& argLocation
)
: AstExpr(ClassIndex(), location)
, attributes(attributes)
, generics(generics)
, genericPacks(genericPacks)
, self(self)
, args(args)
, returnAnnotation_DEPRECATED(returnAnnotation)
, vararg(vararg)
, varargLocation(varargLocation)
, varargAnnotation(varargAnnotation)
, body(body)
, functionDepth(functionDepth)
, debugname(debugname)
, argLocation(argLocation)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
}
void AstExprFunction::visit(AstVisitor* visitor)
@ -311,16 +275,8 @@ void AstExprFunction::visit(AstVisitor* visitor)
if (varargAnnotation)
varargAnnotation->visit(visitor);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (returnAnnotation)
returnAnnotation->visit(visitor);
}
else
{
if (returnAnnotation_DEPRECATED)
visitTypeList(visitor, *returnAnnotation_DEPRECATED);
}
visitTypeList(visitor, *returnAnnotation);
body->visit(visitor);
}
@ -889,62 +845,6 @@ void AstStatDeclareGlobal::visit(AstVisitor* visitor)
type->visit(visitor);
}
AstStatDeclareFunction::AstStatDeclareFunction(
const Location& location,
const AstName& name,
const Location& nameLocation,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& params,
const AstArray<AstArgumentName>& paramNames,
bool vararg,
const Location& varargLocation,
AstTypePack* retTypes
)
: AstStat(ClassIndex(), location)
, attributes()
, name(name)
, nameLocation(nameLocation)
, generics(generics)
, genericPacks(genericPacks)
, params(params)
, paramNames(paramNames)
, vararg(vararg)
, varargLocation(varargLocation)
, retTypes(retTypes)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
}
AstStatDeclareFunction::AstStatDeclareFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstName& name,
const Location& nameLocation,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& params,
const AstArray<AstArgumentName>& paramNames,
bool vararg,
const Location& varargLocation,
AstTypePack* retTypes
)
: AstStat(ClassIndex(), location)
, attributes(attributes)
, name(name)
, nameLocation(nameLocation)
, generics(generics)
, genericPacks(genericPacks)
, params(params)
, paramNames(paramNames)
, vararg(vararg)
, varargLocation(varargLocation)
, retTypes(retTypes)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
}
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstStatDeclareFunction::AstStatDeclareFunction(
const Location& location,
const AstName& name,
@ -967,12 +867,10 @@ AstStatDeclareFunction::AstStatDeclareFunction(
, paramNames(paramNames)
, vararg(vararg)
, varargLocation(varargLocation)
, retTypes_DEPRECATED(retTypes)
, retTypes(retTypes)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
}
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstStatDeclareFunction::AstStatDeclareFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
@ -996,9 +894,8 @@ AstStatDeclareFunction::AstStatDeclareFunction(
, paramNames(paramNames)
, vararg(vararg)
, varargLocation(varargLocation)
, retTypes_DEPRECATED(retTypes)
, retTypes(retTypes)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
}
void AstStatDeclareFunction::visit(AstVisitor* visitor)
@ -1006,10 +903,7 @@ void AstStatDeclareFunction::visit(AstVisitor* visitor)
if (visitor->visit(this))
{
visitTypeList(visitor, params);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
retTypes->visit(visitor);
else
visitTypeList(visitor, retTypes_DEPRECATED);
visitTypeList(visitor, retTypes);
}
}
@ -1031,11 +925,11 @@ bool AstStatDeclareFunction::hasAttribute(AstAttr::Type attributeType) const
return hasAttributeInArray(attributes, attributeType);
}
AstStatDeclareExternType::AstStatDeclareExternType(
AstStatDeclareClass::AstStatDeclareClass(
const Location& location,
const AstName& name,
std::optional<AstName> superName,
const AstArray<AstDeclaredExternTypeProperty>& props,
const AstArray<AstDeclaredClassProp>& props,
AstTableIndexer* indexer
)
: AstStat(ClassIndex(), location)
@ -1046,11 +940,11 @@ AstStatDeclareExternType::AstStatDeclareExternType(
{
}
void AstStatDeclareExternType::visit(AstVisitor* visitor)
void AstStatDeclareClass::visit(AstVisitor* visitor)
{
if (visitor->visit(this))
{
for (const AstDeclaredExternTypeProperty& prop : props)
for (const AstDeclaredClassProp& prop : props)
prop.ty->visit(visitor);
}
}
@ -1135,48 +1029,6 @@ void AstTypeTable::visit(AstVisitor* visitor)
}
}
AstTypeFunction::AstTypeFunction(
const Location& location,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& argTypes,
const AstArray<std::optional<AstArgumentName>>& argNames,
AstTypePack* returnTypes
)
: AstType(ClassIndex(), location)
, attributes()
, generics(generics)
, genericPacks(genericPacks)
, argTypes(argTypes)
, argNames(argNames)
, returnTypes(returnTypes)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size);
}
AstTypeFunction::AstTypeFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& argTypes,
const AstArray<std::optional<AstArgumentName>>& argNames,
AstTypePack* returnTypes
)
: AstType(ClassIndex(), location)
, attributes(attributes)
, generics(generics)
, genericPacks(genericPacks)
, argTypes(argTypes)
, argNames(argNames)
, returnTypes(returnTypes)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size);
}
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeFunction::AstTypeFunction(
const Location& location,
const AstArray<AstGenericType*>& generics,
@ -1191,13 +1043,11 @@ AstTypeFunction::AstTypeFunction(
, genericPacks(genericPacks)
, argTypes(argTypes)
, argNames(argNames)
, returnTypes_DEPRECATED(returnTypes)
, returnTypes(returnTypes)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size);
}
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeFunction::AstTypeFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
@ -1213,9 +1063,8 @@ AstTypeFunction::AstTypeFunction(
, genericPacks(genericPacks)
, argTypes(argTypes)
, argNames(argNames)
, returnTypes_DEPRECATED(returnTypes)
, returnTypes(returnTypes)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size);
}
@ -1224,10 +1073,7 @@ void AstTypeFunction::visit(AstVisitor* visitor)
if (visitor->visit(this))
{
visitTypeList(visitor, argTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
returnTypes->visit(visitor);
else
visitTypeList(visitor, returnTypes_DEPRECATED);
visitTypeList(visitor, returnTypes);
}
}

View file

@ -252,18 +252,8 @@ CstTypeSingletonString::CstTypeSingletonString(AstArray<char> sourceString, CstE
LUAU_ASSERT(quoteStyle != CstExprConstantString::QuotedInterp);
}
CstTypePackExplicit::CstTypePackExplicit()
: CstNode(CstClassIndex())
, hasParentheses(false)
, openParenthesesPosition(Position{0, 0})
, closeParenthesesPosition(Position{0, 0})
, commaPositions({})
{
}
CstTypePackExplicit::CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray<Position> commaPositions)
: CstNode(CstClassIndex())
, hasParentheses(true)
, openParenthesesPosition(openParenthesesPosition)
, closeParenthesesPosition(closeParenthesesPosition)
, commaPositions(commaPositions)

View file

@ -18,14 +18,17 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
// flag so that we don't break production games by reverting syntax changes.
// See docs/SyntaxChanges.md for an explanation.
LUAU_FASTFLAGVARIABLE(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauAllowComplexTypesInGenericParams)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForTableTypes)
LUAU_FASTFLAGVARIABLE(LuauStoreCSTData2)
LUAU_FASTFLAGVARIABLE(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3)
LUAU_FASTFLAGVARIABLE(ParserNoErrorLimit)
LUAU_FASTFLAGVARIABLE(LuauFixDoBlockEndLocation)
LUAU_FASTFLAGVARIABLE(LuauParseOptionalAsNode2)
LUAU_FASTFLAGVARIABLE(LuauDeclareExternType)
LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer)
LUAU_FASTFLAGVARIABLE(LuauTypeFunResultInAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauDeprecatedAttribute)
LUAU_FASTFLAGVARIABLE(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauFixFunctionWithAttributesStartLocation)
LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false)
@ -554,7 +557,7 @@ AstStat* Parser::parseDo()
Location endLocation = lexer.current().location;
body->hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo);
if (body->hasEnd)
if (FFlag::LuauFixDoBlockEndLocation && body->hasEnd)
body->location.end = endLocation.end;
if (FFlag::LuauStoreCSTData2 && options.storeCstData)
@ -787,9 +790,7 @@ AstStat* Parser::parseFunctionStat(const AstArray<AstAttr*>& attributes)
matchRecoveryStopOnToken[Lexeme::ReservedEnd]++;
AstExprFunction* body = FFlag::LuauStoreReturnTypesAsPackOnAst
? parseFunctionBody(hasself, matchFunction, debugname, nullptr, attributes).first
: parseFunctionBody_DEPRECATED(hasself, matchFunction, debugname, nullptr, attributes).first;
AstExprFunction* body = parseFunctionBody(hasself, matchFunction, debugname, nullptr, attributes).first;
matchRecoveryStopOnToken[Lexeme::ReservedEnd]--;
@ -942,8 +943,7 @@ AstStat* Parser::parseLocal(const AstArray<AstAttr*>& attributes)
matchRecoveryStopOnToken[Lexeme::ReservedEnd]++;
auto [body, var] = FFlag::LuauStoreReturnTypesAsPackOnAst ? parseFunctionBody(false, matchFunction, name.name, &name, attributes)
: parseFunctionBody_DEPRECATED(false, matchFunction, name.name, &name, attributes);
auto [body, var] = parseFunctionBody(false, matchFunction, name.name, &name, attributes);
matchRecoveryStopOnToken[Lexeme::ReservedEnd]--;
@ -1113,10 +1113,7 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported, Positio
size_t oldTypeFunctionDepth = typeFunctionDepth;
typeFunctionDepth = functionStack.size();
AstExprFunction* body =
FFlag::LuauStoreReturnTypesAsPackOnAst
? parseFunctionBody(/* hasself */ false, matchFn, fnName->name, nullptr, AstArray<AstAttr*>({nullptr, 0})).first
: parseFunctionBody_DEPRECATED(/* hasself */ false, matchFn, fnName->name, nullptr, AstArray<AstAttr*>({nullptr, 0})).first;
AstExprFunction* body = parseFunctionBody(/* hasself */ false, matchFn, fnName->name, nullptr, AstArray<AstAttr*>({nullptr, 0})).first;
typeFunctionDepth = oldTypeFunctionDepth;
@ -1138,7 +1135,7 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported, Positio
}
}
AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod(const AstArray<AstAttr*>& attributes)
AstDeclaredClassProp Parser::parseDeclaredClassMethod(const AstArray<AstAttr*>& attributes)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
@ -1169,18 +1166,7 @@ AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod(const AstArr
expectMatchAndConsume(')', matchParen);
AstTypePack* retTypes;
AstTypeList retTypes_DEPRECATED;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
retTypes = parseOptionalReturnType();
if (!retTypes)
retTypes = allocator.alloc<AstTypePackExplicit>(lexer.current().location, AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
}
else
{
retTypes_DEPRECATED = parseOptionalReturnType_DEPRECATED().value_or(AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
}
AstTypeList retTypes = parseOptionalReturnType().value_or(AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
Location end = lexer.previousLocation();
TempVector<AstType*> vars(scratchType);
@ -1188,7 +1174,7 @@ AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod(const AstArr
if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr)
{
return AstDeclaredExternTypeProperty{
return AstDeclaredClassProp{
fnName.name, fnName.location, reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true
};
}
@ -1207,25 +1193,14 @@ AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod(const AstArr
if (vararg && !varargAnnotation)
report(start, "All declaration parameters aside from 'self' must be annotated");
AstType* fnType =
FFlag::LuauStoreReturnTypesAsPackOnAst
? allocator.alloc<AstTypeFunction>(
AstType* fnType = allocator.alloc<AstTypeFunction>(
Location(start, end), attributes, generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes
)
: allocator.alloc<AstTypeFunction>(
Location(start, end),
attributes,
generics,
genericPacks,
AstTypeList{copy(vars), varargAnnotation},
copy(varNames),
retTypes_DEPRECATED
);
return AstDeclaredExternTypeProperty{fnName.name, fnName.location, fnType, true, Location(start, end)};
return AstDeclaredClassProp{fnName.name, fnName.location, fnType, true, Location(start, end)};
}
AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod_DEPRECATED()
AstDeclaredClassProp Parser::parseDeclaredClassMethod_DEPRECATED()
{
Location start = lexer.current().location;
@ -1254,18 +1229,7 @@ AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod_DEPRECATED()
expectMatchAndConsume(')', matchParen);
AstTypePack* retTypes;
AstTypeList retTypes_DEPRECATED;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
retTypes = parseOptionalReturnType();
if (!retTypes)
retTypes = allocator.alloc<AstTypePackExplicit>(lexer.current().location, AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
}
else
{
retTypes_DEPRECATED = parseOptionalReturnType_DEPRECATED().value_or(AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
}
AstTypeList retTypes = parseOptionalReturnType().value_or(AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
Location end = lexer.previousLocation();
TempVector<AstType*> vars(scratchType);
@ -1273,7 +1237,7 @@ AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod_DEPRECATED()
if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr)
{
return AstDeclaredExternTypeProperty{
return AstDeclaredClassProp{
fnName.name, fnName.location, reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true
};
}
@ -1292,16 +1256,11 @@ AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod_DEPRECATED()
if (vararg && !varargAnnotation)
report(start, "All declaration parameters aside from 'self' must be annotated");
AstType* fnType =
FFlag::LuauStoreReturnTypesAsPackOnAst
? allocator.alloc<AstTypeFunction>(
AstType* fnType = allocator.alloc<AstTypeFunction>(
Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes
)
: allocator.alloc<AstTypeFunction>(
Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes_DEPRECATED
);
return AstDeclaredExternTypeProperty{fnName.name, fnName.location, fnType, true, Location(start, end)};
return AstDeclaredClassProp{fnName.name, fnName.location, fnType, true, Location(start, end)};
}
AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*>& attributes)
@ -1339,18 +1298,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectMatchAndConsume(')', matchParen);
AstTypePack* retTypes;
AstTypeList retTypes_DEPRECATED;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
retTypes = parseOptionalReturnType();
if (!retTypes)
retTypes = allocator.alloc<AstTypePackExplicit>(lexer.current().location, AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
}
else
{
retTypes_DEPRECATED = parseOptionalReturnType_DEPRECATED().value_or(AstTypeList{copy<AstType*>(nullptr, 0)});
}
AstTypeList retTypes = parseOptionalReturnType().value_or(AstTypeList{copy<AstType*>(nullptr, 0)});
Location end = lexer.current().location;
TempVector<AstType*> vars(scratchType);
@ -1368,8 +1316,6 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (vararg && !varargAnnotation)
return reportStatError(Location(start, end), {}, {}, "All declaration parameters must be annotated");
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
return allocator.alloc<AstStatDeclareFunction>(
Location(start, end),
attributes,
@ -1384,61 +1330,20 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
retTypes
);
}
else
else if (AstName(lexer.current().name) == "class")
{
return allocator.alloc<AstStatDeclareFunction>(
Location(start, end),
attributes,
globalName.name,
globalName.location,
generics,
genericPacks,
AstTypeList{copy(vars), varargAnnotation},
copy(varNames),
vararg,
varargLocation,
retTypes_DEPRECATED
);
}
}
else if (AstName(lexer.current().name) == "class" || (FFlag::LuauDeclareExternType && AstName(lexer.current().name) == "extern"))
{
bool foundExtern = false;
if (FFlag::LuauDeclareExternType)
{
if (AstName(lexer.current().name) == "extern")
{
foundExtern = true;
nextLexeme();
if (AstName(lexer.current().name) != "type")
return reportStatError(lexer.current().location, {}, {}, "Expected `type` keyword after `extern`, but got %s instead", lexer.current().name);
}
}
nextLexeme();
Location classStart = lexer.current().location;
Name className = parseName(FFlag::LuauDeclareExternType ? "type name" : "class name");
Name className = parseName("class name");
std::optional<AstName> superName = std::nullopt;
if (AstName(lexer.current().name) == "extends")
{
nextLexeme();
superName = parseName(FFlag::LuauDeclareExternType ? "supertype name" : "superclass name").name;
superName = parseName("superclass name").name;
}
if (FFlag::LuauDeclareExternType)
{
if (foundExtern)
{
if (AstName(lexer.current().name) != "with")
report(lexer.current().location, "Expected `with` keyword before listing properties of the external type, but got %s instead", lexer.current().name);
else
nextLexeme();
}
}
TempVector<AstDeclaredExternTypeProperty> props(scratchDeclaredClassProps);
TempVector<AstDeclaredClassProp> props(scratchDeclaredClassProps);
AstTableIndexer* indexer = nullptr;
while (lexer.current().type != Lexeme::ReservedEnd)
@ -1465,9 +1370,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (lexer.current().type == Lexeme::ReservedFunction)
{
if (FFlag::LuauDeprecatedAttribute)
props.push_back(parseDeclaredExternTypeMethod(attributes));
props.push_back(parseDeclaredClassMethod(attributes));
else
props.push_back(parseDeclaredExternTypeMethod_DEPRECATED());
props.push_back(parseDeclaredClassMethod_DEPRECATED());
}
else if (lexer.current().type == '[')
{
@ -1490,7 +1395,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (chars && !containsNull)
{
props.push_back(AstDeclaredExternTypeProperty{
props.push_back(AstDeclaredClassProp{
AstName(chars->data), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation())
});
}
@ -1510,9 +1415,6 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
badIndexer = parseTableIndexer_DEPRECATED(AstTableAccess::ReadWrite, std::nullopt, begin);
// we lose all additional indexer expressions from the AST after error recovery here
if (FFlag::LuauDeclareExternType)
report(badIndexer->location, "Cannot have more than one indexer on an extern type");
else
report(badIndexer->location, "Cannot have more than one class indexer");
}
else
@ -1534,7 +1436,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectAndConsume(':', "property type annotation");
AstType* propType = parseType();
props.push_back(
AstDeclaredExternTypeProperty{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())}
AstDeclaredClassProp{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())}
);
}
}
@ -1544,9 +1446,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (lexer.current().type == Lexeme::ReservedFunction)
{
if (FFlag::LuauDeprecatedAttribute)
props.push_back(parseDeclaredExternTypeMethod(attributes));
props.push_back(parseDeclaredClassMethod(attributes));
else
props.push_back(parseDeclaredExternTypeMethod_DEPRECATED());
props.push_back(parseDeclaredClassMethod_DEPRECATED());
}
else if (lexer.current().type == '[' &&
(lexer.lookahead().type == Lexeme::RawString || lexer.lookahead().type == Lexeme::QuotedString))
@ -1568,7 +1470,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (chars && !containsNull)
{
props.push_back(AstDeclaredExternTypeProperty{
props.push_back(AstDeclaredClassProp{
AstName(chars->data), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation())
});
}
@ -1592,9 +1494,6 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
badIndexer = parseTableIndexer_DEPRECATED(AstTableAccess::ReadWrite, std::nullopt, lexer.current());
// we lose all additional indexer expressions from the AST after error recovery here
if (FFlag::LuauDeclareExternType)
report(badIndexer->location, "Cannot have more than one indexer on an extern type");
else
report(badIndexer->location, "Cannot have more than one class indexer");
}
else
@ -1618,7 +1517,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectAndConsume(':', "property type annotation");
AstType* propType = parseType();
props.push_back(
AstDeclaredExternTypeProperty{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())}
AstDeclaredClassProp{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())}
);
}
}
@ -1627,7 +1526,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
Location classEnd = lexer.current().location;
nextLexeme(); // skip past `end`
return allocator.alloc<AstStatDeclareExternType>(Location(classStart, classEnd), className.name, superName, copy(props), indexer);
return allocator.alloc<AstStatDeclareClass>(Location(classStart, classEnd), className.name, superName, copy(props), indexer);
}
else if (std::optional<Name> globalName = parseNameOpt("global variable name"))
{
@ -1636,10 +1535,6 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
AstType* type = parseType(/* in declaration context */ true);
return allocator.alloc<AstStatDeclareGlobal>(Location(start, type->location), globalName->name, globalName->location, type);
}
else if (FFlag::LuauDeclareExternType)
{
return reportStatError(start, {}, {}, "declare must be followed by an identifier, 'function', or 'extern type'");
}
else
{
return reportStatError(start, {}, {}, "declare must be followed by an identifier, 'function', or 'class'");
@ -1745,8 +1640,6 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
const AstArray<AstAttr*>& attributes
)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
Location start = matchFunction.location;
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
@ -1777,6 +1670,7 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
//
// function (t: { a: number }) end
//
if (FFlag::LuauErrorRecoveryForTableTypes)
matchRecoveryStopOnToken[')']++;
TempVector<Binding> args(scratchBinding);
@ -1796,147 +1690,10 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
expectMatchAndConsume(')', matchParen, true);
if (FFlag::LuauErrorRecoveryForTableTypes)
matchRecoveryStopOnToken[')']--;
AstTypePack* typelist = parseOptionalReturnType(cstNode ? &cstNode->returnSpecifierPosition : nullptr);
AstLocal* funLocal = nullptr;
if (localName)
funLocal = pushLocal(Binding(*localName, nullptr));
unsigned int localsBegin = saveLocals();
Function fun;
fun.vararg = vararg;
functionStack.emplace_back(fun);
auto [self, vars] = prepareFunctionArguments(start, hasself, args);
AstStatBlock* body = parseBlock();
functionStack.pop_back();
restoreLocals(localsBegin);
Location end = lexer.current().location;
bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchFunction);
body->hasEnd = hasEnd;
if (FFlag::LuauStoreCSTData2)
{
AstExprFunction* node = allocator.alloc<AstExprFunction>(
Location(start, end),
attributes,
generics,
genericPacks,
self,
vars,
vararg,
varargLocation,
body,
functionStack.size(),
debugname,
typelist,
varargAnnotation,
argLocation
);
if (options.storeCstData)
{
cstNode->functionKeywordPosition = matchFunction.location.begin;
cstNodeMap[node] = cstNode;
}
return {node, funLocal};
}
else
{
return {
allocator.alloc<AstExprFunction>(
Location(start, end),
attributes,
generics,
genericPacks,
self,
vars,
vararg,
varargLocation,
body,
functionStack.size(),
debugname,
typelist,
varargAnnotation,
argLocation
),
funLocal
};
}
}
std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody_DEPRECATED(
bool hasself,
const Lexeme& matchFunction,
const AstName& debugname,
const Name* localName,
const AstArray<AstAttr*>& attributes
)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
Location start = matchFunction.location;
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
{
if (attributes.size > 0)
start = attributes.data[0]->location;
}
auto* cstNode = FFlag::LuauStoreCSTData2 && options.storeCstData ? allocator.alloc<CstExprFunction>() : nullptr;
auto [generics, genericPacks] =
FFlag::LuauStoreCSTData2 && cstNode
? parseGenericTypeList(
/* withDefaultValues= */ false, &cstNode->openGenericsPosition, &cstNode->genericsCommaPositions, &cstNode->closeGenericsPosition
)
: parseGenericTypeList(/* withDefaultValues= */ false);
MatchLexeme matchParen = lexer.current();
expectAndConsume('(', "function");
// NOTE: This was added in conjunction with passing `searchForMissing` to
// `expectMatchAndConsume` inside `parseTableType` so that the behavior of
// parsing code like below (note the missing `}`):
//
// function (t: { a: number ) end
//
// ... will still parse as (roughly):
//
// function (t: { a: number }) end
//
matchRecoveryStopOnToken[')']++;
TempVector<Binding> args(scratchBinding);
bool vararg = false;
Location varargLocation;
AstTypePack* varargAnnotation = nullptr;
if (lexer.current().type != ')')
std::tie(vararg, varargLocation, varargAnnotation) =
parseBindingList(args, /* allowDot3= */ true, cstNode ? &cstNode->argsCommaPositions : nullptr);
std::optional<Location> argLocation;
if (matchParen.type == Lexeme::Type('(') && lexer.current().type == Lexeme::Type(')'))
argLocation = Location(matchParen.position, lexer.current().location.end);
expectMatchAndConsume(')', matchParen, true);
matchRecoveryStopOnToken[')']--;
std::optional<AstTypeList> typelist = parseOptionalReturnType_DEPRECATED(cstNode ? &cstNode->returnSpecifierPosition : nullptr);
std::optional<AstTypeList> typelist = parseOptionalReturnType(cstNode ? &cstNode->returnSpecifierPosition : nullptr);
AstLocal* funLocal = nullptr;
@ -2163,9 +1920,8 @@ AstTypePack* Parser::parseTypeList(
return nullptr;
}
AstTypePack* Parser::parseOptionalReturnType(Position* returnSpecifierPosition)
std::optional<AstTypeList> Parser::parseOptionalReturnType(Position* returnSpecifierPosition)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
if (lexer.current().type == ':' || lexer.current().type == Lexeme::SkinnyArrow)
{
if (lexer.current().type == Lexeme::SkinnyArrow)
@ -2177,41 +1933,7 @@ AstTypePack* Parser::parseOptionalReturnType(Position* returnSpecifierPosition)
unsigned int oldRecursionCount = recursionCounter;
auto result = parseReturnType();
LUAU_ASSERT(result);
// At this point, if we find a , character, it indicates that there are multiple return types
// in this type annotation, but the list wasn't wrapped in parentheses.
if (lexer.current().type == ',')
{
report(lexer.current().location, "Expected a statement, got ','; did you forget to wrap the list of return types in parentheses?");
nextLexeme();
}
recursionCounter = oldRecursionCount;
return result;
}
return nullptr;
}
std::optional<AstTypeList> Parser::parseOptionalReturnType_DEPRECATED(Luau::Position* returnSpecifierPosition)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
if (lexer.current().type == ':' || lexer.current().type == Lexeme::SkinnyArrow)
{
if (lexer.current().type == Lexeme::SkinnyArrow)
report(lexer.current().location, "Function return type annotations are written after ':' instead of '->'");
if (FFlag::LuauStoreCSTData2 && returnSpecifierPosition)
*returnSpecifierPosition = lexer.current().location.begin;
nextLexeme();
unsigned int oldRecursionCount = recursionCounter;
auto [_location, result] = parseReturnType_DEPRECATED();
auto [_location, result] = parseReturnType();
// At this point, if we find a , character, it indicates that there are multiple return types
// in this type annotation, but the list wasn't wrapped in parentheses.
@ -2231,119 +1953,8 @@ std::optional<AstTypeList> Parser::parseOptionalReturnType_DEPRECATED(Luau::Posi
}
// ReturnType ::= Type | `(' TypeList `)'
AstTypePack* Parser::parseReturnType()
std::pair<Location, AstTypeList> Parser::parseReturnType()
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
incrementRecursionCounter("type annotation");
Lexeme begin = lexer.current();
if (lexer.current().type != '(')
{
if (shouldParseTypePack(lexer))
{
return parseTypePack();
}
else
{
AstType* type = parseType();
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(type->location, AstTypeList{copy(&type, 1), nullptr});
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>();
return node;
}
}
nextLexeme();
Location innerBegin = lexer.current().location;
matchRecoveryStopOnToken[Lexeme::SkinnyArrow]++;
TempVector<AstType*> result(scratchType);
TempVector<std::optional<AstArgumentName>> resultNames(scratchOptArgName);
TempVector<Position> commaPositions(scratchPosition);
AstTypePack* varargAnnotation = nullptr;
// possibly () -> ReturnType
if (lexer.current().type != ')')
{
if (options.storeCstData)
varargAnnotation = parseTypeList(result, resultNames, &commaPositions);
else
varargAnnotation = parseTypeList(result, resultNames);
}
const Location location{begin.location, lexer.current().location};
Position closeParenthesesPosition = lexer.current().location.begin;
expectMatchAndConsume(')', begin, true);
matchRecoveryStopOnToken[Lexeme::SkinnyArrow]--;
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 (FFlag::LuauAstTypeGroup3)
{
if (result.size() == 1)
{
// TODO(CLI-140667): stop parsing type suffix when varargAnnotation != nullptr - this should be a parse error
AstType* inner = varargAnnotation == nullptr ? allocator.alloc<AstTypeGroup>(location, result[0]) : result[0];
AstType* returnType = parseTypeSuffix(inner, begin.location);
if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr &&
(returnType->is<AstTypeUnion>() || returnType->is<AstTypeIntersection>()))
luau_telemetry_parsed_return_type_variadic_with_type_suffix = true;
// If parseType parses nothing, then returnType->location.end only points at the last non-type-pack
// type to successfully parse. We need the span of the whole annotation.
Position endPos = result.size() == 1 ? location.end : returnType->location.end;
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation});
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>();
return node;
}
}
else
{
if (result.size() == 1)
{
AstType* returnType = parseTypeSuffix(result[0], innerBegin);
if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr &&
(returnType->is<AstTypeUnion>() || returnType->is<AstTypeIntersection>()))
luau_telemetry_parsed_return_type_variadic_with_type_suffix = true;
// If parseType parses nothing, then returnType->location.end only points at the last non-type-pack
// type to successfully parse. We need the span of the whole annotation.
Position endPos = result.size() == 1 ? location.end : returnType->location.end;
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation});
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>();
return node;
}
}
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(location, AstTypeList{copy(result), varargAnnotation});
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>(location.begin, closeParenthesesPosition, copy(commaPositions));
return node;
}
AstType* tail = parseFunctionTypeTail(begin, {nullptr, 0}, {}, {}, copy(result), copy(resultNames), varargAnnotation);
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(Location{location, tail->location}, AstTypeList{copy(&tail, 1), nullptr});
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>();
return node;
}
// ReturnType ::= Type | `(' TypeList `)'
std::pair<Location, AstTypeList> Parser::parseReturnType_DEPRECATED()
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
incrementRecursionCounter("type annotation");
Lexeme begin = lexer.current();
@ -2562,7 +2173,6 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
if (FFlag::LuauStoreCSTData2 && options.storeCstData)
std::tie(style, blockDepth) = extractStringDetails();
Position stringPosition = lexer.current().location.begin;
AstArray<char> sourceString;
std::optional<AstArray<char>> chars = parseCharArray(options.storeCstData ? &sourceString : nullptr);
@ -2587,8 +2197,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
colonPosition,
tableSeparator(),
lexer.current().location.begin,
allocator.alloc<CstExprConstantString>(sourceString, style, blockDepth),
stringPosition
allocator.alloc<CstExprConstantString>(sourceString, style, blockDepth)
});
}
else
@ -2679,7 +2288,6 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
if (FFlag::LuauStoreCSTData2 && options.storeCstData)
std::tie(style, blockDepth) = extractStringDetails();
Position stringPosition = lexer.current().location.begin;
AstArray<char> sourceString;
std::optional<AstArray<char>> chars = parseCharArray(options.storeCstData ? &sourceString : nullptr);
@ -2704,8 +2312,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
colonPosition,
tableSeparator(),
lexer.current().location.begin,
allocator.alloc<CstExprConstantString>(sourceString, style, blockDepth),
stringPosition
allocator.alloc<CstExprConstantString>(sourceString, style, blockDepth)
});
}
else
@ -2801,7 +2408,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
Location end = lexer.current().location;
if (!expectMatchAndConsume('}', matchBrace, /* searchForMissing = */ true))
if (!expectMatchAndConsume('}', matchBrace, /* searchForMissing = */ FFlag::LuauErrorRecoveryForTableTypes))
end = lexer.previousLocation();
if (FFlag::LuauStoreCSTData2)
@ -2967,25 +2574,12 @@ AstType* Parser::parseFunctionTypeTail(
expectAndConsume(Lexeme::SkinnyArrow, "function type");
}
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
auto returnType = parseReturnType();
LUAU_ASSERT(returnType);
AstTypeList paramTypes = AstTypeList{params, varargAnnotation};
return allocator.alloc<AstTypeFunction>(
Location(begin.location, returnType->location), attributes, generics, genericPacks, paramTypes, paramNames, returnType
);
}
else
{
auto [endLocation, returnTypeList] = parseReturnType_DEPRECATED();
auto [endLocation, returnTypeList] = parseReturnType();
AstTypeList paramTypes = AstTypeList{params, varargAnnotation};
return allocator.alloc<AstTypeFunction>(
Location(begin.location, endLocation), attributes, generics, genericPacks, paramTypes, paramNames, returnTypeList
);
}
}
static bool isTypeFollow(Lexeme::Type c)
@ -3099,8 +2693,17 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
}
}
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{
if (parts.size() == 1 && !isUnion && !isIntersection)
return parts[0];
}
else
{
if (parts.size() == 1)
return parts[0];
}
if (isUnion && isIntersection)
{
return reportTypeError(
@ -3933,10 +3536,7 @@ AstExpr* Parser::parseSimpleExpr()
Lexeme matchFunction = lexer.current();
nextLexeme();
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
return parseFunctionBody(false, matchFunction, AstName(), nullptr, attributes).first;
else
return parseFunctionBody_DEPRECATED(false, matchFunction, AstName(), nullptr, attributes).first;
}
else if (lexer.current().type == Lexeme::Number)
{
@ -4487,6 +4087,8 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams(Position* openingPosition, TempV
parameters.push_back({{}, typePack});
}
else if (lexer.current().type == '(')
{
if (FFlag::LuauAllowComplexTypesInGenericParams)
{
Location begin = lexer.current().location;
AstType* type = nullptr;
@ -4550,6 +4152,16 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams(Position* openingPosition, TempV
parameters.push_back({parseTypeSuffix(type, begin), {}});
}
}
else
{
auto [type, typePack] = parseSimpleTypeOrPack();
if (typePack)
parameters.push_back({{}, typePack});
else
parameters.push_back({type, {}});
}
}
else if (lexer.current().type == '>' && parameters.empty())
{
break;
@ -5066,7 +4678,7 @@ void Parser::report(const Location& location, const char* format, va_list args)
parseErrors.emplace_back(location, message);
if (parseErrors.size() >= unsigned(FInt::LuauParseErrorLimit) && !options.noErrorLimit)
if (parseErrors.size() >= unsigned(FInt::LuauParseErrorLimit) && (!FFlag::ParserNoErrorLimit || !options.noErrorLimit))
ParseError::raise(location, "Reached error limit (%d)", int(FInt::LuauParseErrorLimit));
}

View file

@ -581,14 +581,7 @@ static bool runFile(const char* name, lua_State* GL, bool repl)
// new thread needs to have the globals sandboxed
luaL_sandboxthread(L);
// ignore file extension when storing module's chunkname
std::string chunkname = "@";
std::string_view nameView = name;
if (size_t dotPos = nameView.find_last_of('.'); dotPos != std::string_view::npos)
{
nameView.remove_suffix(nameView.size() - dotPos);
}
chunkname += nameView;
std::string chunkname = "@" + std::string(name);
std::string bytecode = Luau::compile(*source, copts());
int status = 0;

View file

@ -131,7 +131,7 @@ static luarequire_WriteResult get_config(lua_State* L, void* ctx, char* buffer,
return write(getFileContents(req->absPath, "/.luaurc"), buffer, buffer_size, size_out);
}
static int load(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* contents)
static int load(lua_State* L, void* ctx, const char* chunkname, const char* contents)
{
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);

View file

@ -5,6 +5,8 @@
#include "Luau/Common.h"
#include "Luau/IrData.h"
LUAU_FASTFLAG(LuauVectorLibNativeDot);
namespace Luau
{
namespace CodeGen
@ -13,11 +15,80 @@ namespace CodeGen
struct IrBuilder;
enum class HostMetamethod;
int getOpLength(LuauOpcode op);
bool isJumpD(LuauOpcode op);
bool isSkipC(LuauOpcode op);
bool isFastCall(LuauOpcode op);
int getJumpTarget(uint32_t insn, uint32_t pc);
inline bool isJumpD(LuauOpcode op)
{
switch (op)
{
case LOP_JUMP:
case LOP_JUMPIF:
case LOP_JUMPIFNOT:
case LOP_JUMPIFEQ:
case LOP_JUMPIFLE:
case LOP_JUMPIFLT:
case LOP_JUMPIFNOTEQ:
case LOP_JUMPIFNOTLE:
case LOP_JUMPIFNOTLT:
case LOP_FORNPREP:
case LOP_FORNLOOP:
case LOP_FORGPREP:
case LOP_FORGLOOP:
case LOP_FORGPREP_INEXT:
case LOP_FORGPREP_NEXT:
case LOP_JUMPBACK:
case LOP_JUMPXEQKNIL:
case LOP_JUMPXEQKB:
case LOP_JUMPXEQKN:
case LOP_JUMPXEQKS:
return true;
default:
return false;
}
}
inline bool isSkipC(LuauOpcode op)
{
switch (op)
{
case LOP_LOADB:
return true;
default:
return false;
}
}
inline bool isFastCall(LuauOpcode op)
{
switch (op)
{
case LOP_FASTCALL:
case LOP_FASTCALL1:
case LOP_FASTCALL2:
case LOP_FASTCALL2K:
case LOP_FASTCALL3:
return true;
default:
return false;
}
}
inline int getJumpTarget(uint32_t insn, uint32_t pc)
{
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn));
if (isJumpD(op))
return int(pc + LUAU_INSN_D(insn) + 1);
else if (isFastCall(op))
return int(pc + LUAU_INSN_C(insn) + 2);
else if (isSkipC(op) && LUAU_INSN_C(insn))
return int(pc + LUAU_INSN_C(insn) + 1);
else if (op == LOP_JUMPX)
return int(pc + LUAU_INSN_E(insn) + 1);
else
return -1;
}
inline bool isBlockTerminator(IrCmd cmd)
{
@ -109,6 +180,9 @@ inline bool hasResult(IrCmd cmd)
case IrCmd::MUL_VEC:
case IrCmd::DIV_VEC:
case IrCmd::DOT_VEC:
if (cmd == IrCmd::DOT_VEC)
LUAU_ASSERT(FFlag::LuauVectorLibNativeDot);
LUAU_FALLTHROUGH;
case IrCmd::UNM_VEC:
case IrCmd::NOT_ANY:
case IrCmd::CMP_ANY:

View file

@ -7,6 +7,8 @@
#include <stdarg.h>
#include <stdio.h>
LUAU_FASTFLAG(LuauVectorLibNativeDot);
namespace Luau
{
namespace CodeGen
@ -588,6 +590,7 @@ void AssemblyBuilderA64::fabs(RegisterA64 dst, RegisterA64 src)
void AssemblyBuilderA64::faddp(RegisterA64 dst, RegisterA64 src)
{
LUAU_ASSERT(FFlag::LuauVectorLibNativeDot);
CODEGEN_ASSERT(dst.kind == KindA64::d || dst.kind == KindA64::s);
CODEGEN_ASSERT(dst.kind == src.kind);

View file

@ -6,6 +6,8 @@
#include <stdarg.h>
#include <stdio.h>
LUAU_FASTFLAG(LuauVectorLibNativeDot);
namespace Luau
{
namespace CodeGen
@ -953,6 +955,7 @@ void AssemblyBuilderX64::vpinsrd(RegisterX64 dst, RegisterX64 src1, OperandX64 s
void AssemblyBuilderX64::vdpps(OperandX64 dst, OperandX64 src1, OperandX64 src2, uint8_t mask)
{
LUAU_ASSERT(FFlag::LuauVectorLibNativeDot);
placeAvx("vdpps", dst, src1, src2, mask, 0x40, false, AVX_0F3A, AVX_66);
}

View file

@ -1,6 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BytecodeAnalysis.h"
#include "Luau/BytecodeUtils.h"
#include "Luau/CodeGenOptions.h"
#include "Luau/IrData.h"
#include "Luau/IrUtils.h"
@ -638,7 +639,7 @@ void buildBytecodeBlocks(IrFunction& function, const std::vector<uint8_t>& jumpT
bcBlocks.push_back(BytecodeBlock{nexti, -1});
}
// Returns just terminate the block
else if (int(op) == LOP_RETURN)
else if (op == LOP_RETURN)
{
bcBlocks.back().finishpc = i;
}
@ -701,7 +702,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
BytecodeTypes& bcType = function.bcTypes[i];
switch (int(op))
switch (op)
{
case LOP_NOP:
break;

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
#include "Luau/BytecodeSummary.h"
#include "Luau/IrUtils.h"
#include "Luau/BytecodeUtils.h"
#include "CodeGenLower.h"
#include "lua.h"
@ -44,7 +42,7 @@ FunctionBytecodeSummary FunctionBytecodeSummary::fromProto(Proto* proto, unsigne
Instruction insn = proto->code[i];
uint8_t op = LUAU_INSN_OP(insn);
summary.incCount(0, op);
i += getOpLength(LuauOpcode(op));
i += Luau::getOpLength(LuauOpcode(op));
}
return summary;

View file

@ -18,7 +18,7 @@
#endif
#include <windows.h>
#elif (defined(__linux__) || defined(__APPLE__)) && (defined(CODEGEN_TARGET_X64) || defined(CODEGEN_TARGET_A64))
#elif defined(__linux__) || defined(__APPLE__)
// __register_frame and __deregister_frame are defined in libgcc or libc++
// (depending on how it's built). We want to declare them as weak symbols
@ -81,7 +81,7 @@ static int findDynamicUnwindSections(uintptr_t addr, unw_dynamic_unwind_sections
}
#endif
#if (defined(__linux__) || defined(__APPLE__)) && (defined(CODEGEN_TARGET_X64) || defined(CODEGEN_TARGET_A64))
#if defined(__linux__) || defined(__APPLE__)
static void visitFdeEntries(char* pos, void (*cb)(const void*))
{
// When using glibc++ unwinder, we need to call __register_frame/__deregister_frame on the entire .eh_frame data
@ -132,7 +132,7 @@ void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, siz
}
#endif
#elif (defined(__linux__) || defined(__APPLE__)) && (defined(CODEGEN_TARGET_X64) || defined(CODEGEN_TARGET_A64))
#elif defined(__linux__) || defined(__APPLE__)
if (!&__register_frame)
return nullptr;
@ -161,7 +161,7 @@ void destroyBlockUnwindInfo(void* context, void* unwindData)
CODEGEN_ASSERT(!"Failed to deallocate function table");
#endif
#elif (defined(__linux__) || defined(__APPLE__)) && (defined(CODEGEN_TARGET_X64) || defined(CODEGEN_TARGET_A64))
#elif defined(__linux__) || defined(__APPLE__)
if (!&__deregister_frame)
{
CODEGEN_ASSERT(!"Cannot deregister unwind information");
@ -184,7 +184,7 @@ bool isUnwindSupported()
size_t verLength = sizeof(ver);
// libunwind on macOS 12 and earlier (which maps to osrelease 21) assumes JIT frames use pointer authentication without a way to override that
return sysctlbyname("kern.osrelease", ver, &verLength, NULL, 0) == 0 && atoi(ver) >= 22;
#elif (defined(__linux__) || defined(__APPLE__)) && (defined(CODEGEN_TARGET_X64) || defined(CODEGEN_TARGET_A64))
#elif defined(__linux__) || defined(__APPLE__)
return true;
#else
return false;

View file

@ -284,6 +284,7 @@ bool initHeaderFunctions(BaseCodeGenContext& codeGenContext)
codeStart
))
{
CODEGEN_ASSERT(!"Failed to create entry function");
return false;
}

View file

@ -1,8 +1,8 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BytecodeAnalysis.h"
#include "Luau/BytecodeUtils.h"
#include "Luau/BytecodeSummary.h"
#include "Luau/IrDump.h"
#include "Luau/IrUtils.h"
#include "CodeGenLower.h"
@ -135,7 +135,7 @@ unsigned getInstructionCount(const Instruction* insns, const unsigned size)
for (unsigned i = 0; i < size;)
{
++count;
i += getOpLength(LuauOpcode(LUAU_INSN_OP(insns[i])));
i += Luau::getOpLength(LuauOpcode(LUAU_INSN_OP(insns[i])));
}
return count;
}

View file

@ -212,6 +212,7 @@ bool initHeaderFunctions(BaseCodeGenContext& codeGenContext)
codeStart
))
{
CODEGEN_ASSERT(!"Failed to create entry function");
return false;
}

View file

@ -3,6 +3,7 @@
#include "Luau/Bytecode.h"
#include "Luau/BytecodeAnalysis.h"
#include "Luau/BytecodeUtils.h"
#include "Luau/IrData.h"
#include "Luau/IrUtils.h"
@ -176,7 +177,7 @@ void IrBuilder::buildFunctionIr(Proto* proto)
// Numeric for loops require additional processing to maintain loop stack
// Notably, this must be performed even when the block is dead so that we maintain the pairing FORNPREP-FORNLOOP
if (int(op) == LOP_FORNPREP)
if (op == LOP_FORNPREP)
beforeInstForNPrep(*this, pc, i);
// We skip dead bytecode instructions when they appear after block was already terminated
@ -198,7 +199,7 @@ void IrBuilder::buildFunctionIr(Proto* proto)
}
// See above for FORNPREP..FORNLOOP processing
if (int(op) == LOP_FORNLOOP)
if (op == LOP_FORNLOOP)
afterInstForNLoop(*this, pc);
i = nexti;
@ -254,7 +255,7 @@ void IrBuilder::rebuildBytecodeBasicBlocks(Proto* proto)
void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
{
switch (int(op))
switch (op)
{
case LOP_NOP:
break;
@ -477,7 +478,7 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
int ra = LUAU_INSN_A(*pc);
IrOp loopRepeat = blockAtInst(i + 1 + LUAU_INSN_D(*pc));
IrOp loopExit = blockAtInst(i + getOpLength(LuauOpcode(LOP_FORGLOOP)));
IrOp loopExit = blockAtInst(i + getOpLength(LOP_FORGLOOP));
IrOp fallback = block(IrBlockKind::Fallback);
inst(IrCmd::INTERRUPT, constUint(i));

View file

@ -9,6 +9,8 @@
#include <stdarg.h>
LUAU_FASTFLAG(LuauVectorLibNativeDot);
namespace Luau
{
namespace CodeGen
@ -180,6 +182,7 @@ const char* getCmdName(IrCmd cmd)
case IrCmd::UNM_VEC:
return "UNM_VEC";
case IrCmd::DOT_VEC:
LUAU_ASSERT(FFlag::LuauVectorLibNativeDot);
return "DOT_VEC";
case IrCmd::NOT_ANY:
return "NOT_ANY";

View file

@ -12,6 +12,8 @@
#include "lstate.h"
#include "lgc.h"
LUAU_FASTFLAG(LuauVectorLibNativeDot)
namespace Luau
{
namespace CodeGen
@ -751,6 +753,8 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
}
case IrCmd::DOT_VEC:
{
LUAU_ASSERT(FFlag::LuauVectorLibNativeDot);
inst.regA64 = regs.allocReg(KindA64::d, index);
RegisterA64 temp = regs.allocTemp(KindA64::q);

View file

@ -16,6 +16,8 @@
#include "lstate.h"
#include "lgc.h"
LUAU_FASTFLAG(LuauVectorLibNativeDot)
namespace Luau
{
namespace CodeGen
@ -704,6 +706,8 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
}
case IrCmd::DOT_VEC:
{
LUAU_ASSERT(FFlag::LuauVectorLibNativeDot);
inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.b});
ScopedRegX64 tmp1{regs};

View file

@ -13,6 +13,8 @@
static const int kMinMaxUnrolledParams = 5;
static const int kBit32BinaryOpUnrolledParams = 5;
LUAU_FASTFLAGVARIABLE(LuauVectorLibNativeDot);
namespace Luau
{
namespace CodeGen
@ -937,9 +939,26 @@ static BuiltinImplResult translateBuiltinVectorMagnitude(
build.loadAndCheckTag(arg1, LUA_TVECTOR, build.vmExit(pcpos));
IrOp sum;
if (FFlag::LuauVectorLibNativeDot)
{
IrOp a = build.inst(IrCmd::LOAD_TVALUE, arg1, build.constInt(0));
IrOp sum = build.inst(IrCmd::DOT_VEC, a, a);
sum = build.inst(IrCmd::DOT_VEC, a, a);
}
else
{
IrOp x = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(0));
IrOp y = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(4));
IrOp z = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(8));
IrOp x2 = build.inst(IrCmd::MUL_NUM, x, x);
IrOp y2 = build.inst(IrCmd::MUL_NUM, y, y);
IrOp z2 = build.inst(IrCmd::MUL_NUM, z, z);
sum = build.inst(IrCmd::ADD_NUM, build.inst(IrCmd::ADD_NUM, x2, y2), z2);
}
IrOp mag = build.inst(IrCmd::SQRT_NUM, sum);
@ -967,6 +986,8 @@ static BuiltinImplResult translateBuiltinVectorNormalize(
build.loadAndCheckTag(arg1, LUA_TVECTOR, build.vmExit(pcpos));
if (FFlag::LuauVectorLibNativeDot)
{
IrOp a = build.inst(IrCmd::LOAD_TVALUE, arg1, build.constInt(0));
IrOp sum = build.inst(IrCmd::DOT_VEC, a, a);
@ -979,6 +1000,29 @@ static BuiltinImplResult translateBuiltinVectorNormalize(
result = build.inst(IrCmd::TAG_VECTOR, result);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), result);
}
else
{
IrOp x = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(0));
IrOp y = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(4));
IrOp z = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(8));
IrOp x2 = build.inst(IrCmd::MUL_NUM, x, x);
IrOp y2 = build.inst(IrCmd::MUL_NUM, y, y);
IrOp z2 = build.inst(IrCmd::MUL_NUM, z, z);
IrOp sum = build.inst(IrCmd::ADD_NUM, build.inst(IrCmd::ADD_NUM, x2, y2), z2);
IrOp mag = build.inst(IrCmd::SQRT_NUM, sum);
IrOp inv = build.inst(IrCmd::DIV_NUM, build.constDouble(1.0), mag);
IrOp xr = build.inst(IrCmd::MUL_NUM, x, inv);
IrOp yr = build.inst(IrCmd::MUL_NUM, y, inv);
IrOp zr = build.inst(IrCmd::MUL_NUM, z, inv);
build.inst(IrCmd::STORE_VECTOR, build.vmReg(ra), xr, yr, zr);
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TVECTOR));
}
return {BuiltinImplType::Full, 1};
}
@ -1030,10 +1074,31 @@ static BuiltinImplResult translateBuiltinVectorDot(IrBuilder& build, int nparams
build.loadAndCheckTag(arg1, LUA_TVECTOR, build.vmExit(pcpos));
build.loadAndCheckTag(args, LUA_TVECTOR, build.vmExit(pcpos));
IrOp sum;
if (FFlag::LuauVectorLibNativeDot)
{
IrOp a = build.inst(IrCmd::LOAD_TVALUE, arg1, build.constInt(0));
IrOp b = build.inst(IrCmd::LOAD_TVALUE, args, build.constInt(0));
IrOp sum = build.inst(IrCmd::DOT_VEC, a, b);
sum = build.inst(IrCmd::DOT_VEC, a, b);
}
else
{
IrOp x1 = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(0));
IrOp x2 = build.inst(IrCmd::LOAD_FLOAT, args, build.constInt(0));
IrOp xx = build.inst(IrCmd::MUL_NUM, x1, x2);
IrOp y1 = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(4));
IrOp y2 = build.inst(IrCmd::LOAD_FLOAT, args, build.constInt(4));
IrOp yy = build.inst(IrCmd::MUL_NUM, y1, y2);
IrOp z1 = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(8));
IrOp z2 = build.inst(IrCmd::LOAD_FLOAT, args, build.constInt(8));
IrOp zz = build.inst(IrCmd::MUL_NUM, z1, z2);
sum = build.inst(IrCmd::ADD_NUM, build.inst(IrCmd::ADD_NUM, xx, yy), zz);
}
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), sum);
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));

View file

@ -2,6 +2,7 @@
#include "IrTranslation.h"
#include "Luau/Bytecode.h"
#include "Luau/BytecodeUtils.h"
#include "Luau/CodeGenOptions.h"
#include "Luau/IrBuilder.h"
#include "Luau/IrUtils.h"
@ -1501,7 +1502,7 @@ bool translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos)
return false;
}
IrOp next = build.blockAtInst(pcpos + getOpLength(LuauOpcode(LOP_NAMECALL)));
IrOp next = build.blockAtInst(pcpos + getOpLength(LOP_NAMECALL));
IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp firstFastPathSuccess = build.block(IrBlockKind::Internal);
IrOp secondFastPath = build.block(IrBlockKind::Internal);

View file

@ -16,120 +16,13 @@
#include <limits.h>
#include <math.h>
LUAU_FASTFLAG(LuauVectorLibNativeDot);
namespace Luau
{
namespace CodeGen
{
int getOpLength(LuauOpcode op)
{
switch (int(op))
{
case LOP_GETGLOBAL:
case LOP_SETGLOBAL:
case LOP_GETIMPORT:
case LOP_GETTABLEKS:
case LOP_SETTABLEKS:
case LOP_NAMECALL:
case LOP_JUMPIFEQ:
case LOP_JUMPIFLE:
case LOP_JUMPIFLT:
case LOP_JUMPIFNOTEQ:
case LOP_JUMPIFNOTLE:
case LOP_JUMPIFNOTLT:
case LOP_NEWTABLE:
case LOP_SETLIST:
case LOP_FORGLOOP:
case LOP_LOADKX:
case LOP_FASTCALL2:
case LOP_FASTCALL2K:
case LOP_FASTCALL3:
case LOP_JUMPXEQKNIL:
case LOP_JUMPXEQKB:
case LOP_JUMPXEQKN:
case LOP_JUMPXEQKS:
return 2;
default:
return 1;
}
}
bool isJumpD(LuauOpcode op)
{
switch (int(op))
{
case LOP_JUMP:
case LOP_JUMPIF:
case LOP_JUMPIFNOT:
case LOP_JUMPIFEQ:
case LOP_JUMPIFLE:
case LOP_JUMPIFLT:
case LOP_JUMPIFNOTEQ:
case LOP_JUMPIFNOTLE:
case LOP_JUMPIFNOTLT:
case LOP_FORNPREP:
case LOP_FORNLOOP:
case LOP_FORGPREP:
case LOP_FORGLOOP:
case LOP_FORGPREP_INEXT:
case LOP_FORGPREP_NEXT:
case LOP_JUMPBACK:
case LOP_JUMPXEQKNIL:
case LOP_JUMPXEQKB:
case LOP_JUMPXEQKN:
case LOP_JUMPXEQKS:
return true;
default:
return false;
}
}
bool isSkipC(LuauOpcode op)
{
switch (int(op))
{
case LOP_LOADB:
return true;
default:
return false;
}
}
bool isFastCall(LuauOpcode op)
{
switch (int(op))
{
case LOP_FASTCALL:
case LOP_FASTCALL1:
case LOP_FASTCALL2:
case LOP_FASTCALL2K:
case LOP_FASTCALL3:
return true;
default:
return false;
}
}
int getJumpTarget(uint32_t insn, uint32_t pc)
{
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn));
if (isJumpD(op))
return int(pc + LUAU_INSN_D(insn) + 1);
else if (isFastCall(op))
return int(pc + LUAU_INSN_C(insn) + 2);
else if (isSkipC(op) && LUAU_INSN_C(insn))
return int(pc + LUAU_INSN_C(insn) + 1);
else if (int(op) == LOP_JUMPX)
return int(pc + LUAU_INSN_E(insn) + 1);
else
return -1;
}
IrValueKind getCmdValueKind(IrCmd cmd)
{
switch (cmd)
@ -190,6 +83,7 @@ IrValueKind getCmdValueKind(IrCmd cmd)
case IrCmd::UNM_VEC:
return IrValueKind::Tvalue;
case IrCmd::DOT_VEC:
LUAU_ASSERT(FFlag::LuauVectorLibNativeDot);
return IrValueKind::Double;
case IrCmd::NOT_ANY:
case IrCmd::CMP_ANY:

Some files were not shown because too many files have changed in this diff Show more