From b6b74b44259a41b88751cf223fde90e3d80de53c Mon Sep 17 00:00:00 2001 From: Junseo Yoo <59751754+joonyoo181@users.noreply.github.com> Date: Fri, 12 Jul 2024 10:03:36 -0700 Subject: [PATCH 1/5] Sync to upstream/release/634 (#1325) # What's Changed? - Performance improvement in the old solver - Bugfixes in the new solver ## Old Solver - Mark types that do not need instantiation when being exported to prevent unnecessary work from being done ## New Solver - Refactored instances of "type family" with "type function" - Index-out-of-bounds bug fix in the resolution resolver - Subtyping reasonings are merged only if all failed --- ### Internal Contributors Co-authored-by: Aaron Weiss Co-authored-by: Vighnesh Vijay Co-authored-by: Vyacheslav Egorov --------- Co-authored-by: Aaron Weiss Co-authored-by: Alexander McCord Co-authored-by: Andy Friesen Co-authored-by: Vighnesh Co-authored-by: Aviral Goel Co-authored-by: David Cope Co-authored-by: Lily Brown Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/Constraint.h | 2 +- Analysis/include/Luau/ConstraintGenerator.h | 8 +- Analysis/include/Luau/Error.h | 10 +- Analysis/include/Luau/Instantiation.h | 54 +++ Analysis/include/Luau/Subtyping.h | 10 +- Analysis/include/Luau/Type.h | 20 +- Analysis/include/Luau/TypeArena.h | 8 +- .../include/Luau/TypeFamilyReductionGuesser.h | 85 ---- .../Luau/{TypeFamily.h => TypeFunction.h} | 93 +++-- .../Luau/TypeFunctionReductionGuesser.h | 85 ++++ Analysis/include/Luau/TypeFwd.h | 4 +- Analysis/include/Luau/TypePack.h | 12 +- Analysis/include/Luau/VisitType.h | 17 +- Analysis/src/BuiltinDefinitions.cpp | 6 +- Analysis/src/Clone.cpp | 4 +- Analysis/src/ConstraintGenerator.cpp | 56 +-- Analysis/src/ConstraintSolver.cpp | 48 ++- Analysis/src/Error.cpp | 44 +- Analysis/src/Frontend.cpp | 7 +- Analysis/src/Generalization.cpp | 4 +- Analysis/src/IostreamHelpers.cpp | 8 +- Analysis/src/Module.cpp | 14 + Analysis/src/NonStrictTypeChecker.cpp | 10 +- Analysis/src/Normalize.cpp | 12 +- Analysis/src/OverloadResolution.cpp | 18 +- Analysis/src/Substitution.cpp | 14 +- Analysis/src/Subtyping.cpp | 40 +- Analysis/src/ToDot.cpp | 6 +- Analysis/src/ToString.cpp | 6 +- Analysis/src/Type.cpp | 8 +- Analysis/src/TypeArena.cpp | 16 +- Analysis/src/TypeAttach.cpp | 6 +- Analysis/src/TypeChecker2.cpp | 36 +- .../src/{TypeFamily.cpp => TypeFunction.cpp} | 376 +++++++++--------- ...r.cpp => TypeFunctionReductionGuesser.cpp} | 108 ++--- Analysis/src/Unifier.cpp | 8 +- Analysis/src/Unifier2.cpp | 6 +- CodeGen/src/IrAnalysis.cpp | 25 +- CodeGen/src/IrBuilder.cpp | 36 +- CodeGen/src/IrDump.cpp | 12 +- CodeGen/src/IrRegAllocA64.cpp | 5 +- CodeGen/src/IrRegAllocX64.cpp | 6 +- CodeGen/src/IrUtils.cpp | 30 +- Sources.cmake | 10 +- tests/IrBuilder.test.cpp | 4 - tests/Subtyping.test.cpp | 64 +-- ...eFamily.test.cpp => TypeFunction.test.cpp} | 54 +-- tests/TypeInfer.anyerror.test.cpp | 14 + tests/TypeInfer.functions.test.cpp | 18 +- tests/TypeInfer.operators.test.cpp | 10 +- tools/faillist.txt | 26 +- 51 files changed, 812 insertions(+), 771 deletions(-) delete mode 100644 Analysis/include/Luau/TypeFamilyReductionGuesser.h rename Analysis/include/Luau/{TypeFamily.h => TypeFunction.h} (63%) create mode 100644 Analysis/include/Luau/TypeFunctionReductionGuesser.h rename Analysis/src/{TypeFamily.cpp => TypeFunction.cpp} (83%) rename Analysis/src/{TypeFamilyReductionGuesser.cpp => TypeFunctionReductionGuesser.cpp} (72%) rename tests/{TypeFamily.test.cpp => TypeFunction.test.cpp} (94%) diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index 6a86072d..79129f72 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -242,7 +242,7 @@ struct UnpackConstraint // ty ~ reduce ty // -// Try to reduce ty, if it is a TypeFamilyInstanceType. Otherwise, do nothing. +// Try to reduce ty, if it is a TypeFunctionInstanceType. Otherwise, do nothing. struct ReduceConstraint { TypeId ty; diff --git a/Analysis/include/Luau/ConstraintGenerator.h b/Analysis/include/Luau/ConstraintGenerator.h index 28cfb5aa..cb49eef3 100644 --- a/Analysis/include/Luau/ConstraintGenerator.h +++ b/Analysis/include/Luau/ConstraintGenerator.h @@ -343,9 +343,9 @@ private: void reportError(Location location, TypeErrorData err); void reportCodeTooComplex(Location location); - // make a union type family of these two types + // make a union type function of these two types TypeId makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs); - // make an intersect type family of these two types + // make an intersect type function of these two types TypeId makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs); /** Scan the program for global definitions. @@ -370,8 +370,8 @@ private: */ std::vector> getExpectedCallTypesForFunctionOverloads(const TypeId fnType); - TypeId createTypeFamilyInstance( - const TypeFamily& family, std::vector typeArguments, std::vector packArguments, const ScopePtr& scope, Location location); + TypeId createTypeFunctionInstance( + const TypeFunction& family, std::vector typeArguments, std::vector packArguments, const ScopePtr& scope, Location location); }; /** Borrow a vector of pointers from a vector of owning pointers to constraints. diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h index 2ba284a1..06d08652 100644 --- a/Analysis/include/Luau/Error.h +++ b/Analysis/include/Luau/Error.h @@ -334,11 +334,11 @@ struct DynamicPropertyLookupOnClassesUnsafe bool operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const; }; -struct UninhabitedTypeFamily +struct UninhabitedTypeFunction { TypeId ty; - bool operator==(const UninhabitedTypeFamily& rhs) const; + bool operator==(const UninhabitedTypeFunction& rhs) const; }; struct ExplicitFunctionAnnotationRecommended @@ -348,11 +348,11 @@ struct ExplicitFunctionAnnotationRecommended bool operator==(const ExplicitFunctionAnnotationRecommended& rhs) const; }; -struct UninhabitedTypePackFamily +struct UninhabitedTypePackFunction { TypePackId tp; - bool operator==(const UninhabitedTypePackFamily& rhs) const; + bool operator==(const UninhabitedTypePackFunction& rhs) const; }; struct WhereClauseNeeded @@ -449,7 +449,7 @@ using TypeErrorData = CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, InternalError, CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning, DuplicateGenericParameter, CannotAssignToNever, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty, TypesAreUnrelated, - NormalizationTooComplex, TypePackMismatch, DynamicPropertyLookupOnClassesUnsafe, UninhabitedTypeFamily, UninhabitedTypePackFamily, + NormalizationTooComplex, TypePackMismatch, DynamicPropertyLookupOnClassesUnsafe, UninhabitedTypeFunction, UninhabitedTypePackFunction, WhereClauseNeeded, PackWhereClauseNeeded, CheckedFunctionCallError, NonStrictFunctionDefinitionError, PropertyAccessViolation, CheckedFunctionIncorrectArgs, UnexpectedTypeInSubtyping, UnexpectedTypePackInSubtyping, ExplicitFunctionAnnotationRecommended>; diff --git a/Analysis/include/Luau/Instantiation.h b/Analysis/include/Luau/Instantiation.h index 58ba88ab..8b65dfe3 100644 --- a/Analysis/include/Luau/Instantiation.h +++ b/Analysis/include/Luau/Instantiation.h @@ -5,6 +5,7 @@ #include "Luau/Substitution.h" #include "Luau/TypeFwd.h" #include "Luau/Unifiable.h" +#include "Luau/VisitType.h" namespace Luau { @@ -72,6 +73,59 @@ struct Instantiation : Substitution TypePackId clean(TypePackId tp) override; }; +// Used to find if a FunctionType requires generic type cleanup during instantiation +struct GenericTypeFinder : TypeOnceVisitor +{ + bool found = false; + + bool visit(TypeId ty) override + { + return !found; + } + + bool visit(TypePackId ty) override + { + return !found; + } + + bool visit(TypeId ty, const Luau::FunctionType& ftv) override + { + if (ftv.hasNoFreeOrGenericTypes) + return false; + + if (!ftv.generics.empty() || !ftv.genericPacks.empty()) + found = true; + + return !found; + } + + bool visit(TypeId ty, const Luau::TableType& ttv) override + { + if (ttv.state == Luau::TableState::Generic) + found = true; + + return !found; + } + + bool visit(TypeId ty, const Luau::GenericType&) override + { + found = true; + return false; + } + + bool visit(TypePackId ty, const Luau::GenericTypePack&) override + { + found = true; + return false; + } + + bool visit(TypeId ty, const Luau::ClassType&) override + { + // During function instantiation, classes are not traversed even if they have generics + return false; + } +}; + /** Attempt to instantiate a type. Only used under local type inference. * * When given a generic function type, instantiate() will return a copy with the diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index 11e20976..4c1d23bd 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -5,7 +5,7 @@ #include "Luau/TypeFwd.h" #include "Luau/TypePairHash.h" #include "Luau/TypePath.h" -#include "Luau/TypeFamily.h" +#include "Luau/TypeFunction.h" #include "Luau/TypeCheckLimits.h" #include "Luau/DenseHash.h" @@ -114,7 +114,7 @@ struct SubtypingEnvironment DenseHashMap, SubtypingResult, TypePairHash> ephemeralCache{{}}; /// Applies `mappedGenerics` to the given type. - /// This is used specifically to substitute for generics in type family instances. + /// This is used specifically to substitute for generics in type function instances. std::optional applyMappedGenerics(NotNull builtinTypes, NotNull arena, TypeId ty); }; @@ -219,8 +219,8 @@ private: SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeFamilyInstanceType* subFamilyInstance, const TypeId superTy); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFamilyInstanceType* superFamilyInstance); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeFunctionInstanceType* subFamilyInstance, const TypeId superTy); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFunctionInstanceType* superFamilyInstance); bool bindGeneric(SubtypingEnvironment& env, TypeId subTp, TypeId superTp); bool bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp); @@ -228,7 +228,7 @@ private: template TypeId makeAggregateType(const Container& container, TypeId orElse); - std::pair handleTypeFamilyReductionResult(const TypeFamilyInstanceType* familyInstance); + std::pair handleTypeFunctionReductionResult(const TypeFunctionInstanceType* familyInstance); [[noreturn]] void unexpected(TypeId ty); [[noreturn]] void unexpected(TypePackId tp); diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index 881dc646..401784ae 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -32,7 +32,7 @@ struct TypeArena; struct Scope; using ScopePtr = std::shared_ptr; -struct TypeFamily; +struct TypeFunction; struct Constraint; /** @@ -528,34 +528,34 @@ struct ClassType }; /** - * An instance of a type family that has not yet been reduced to a more concrete + * An instance of a type function that has not yet been reduced to a more concrete * type. The constraint solver receives a constraint to reduce each - * TypeFamilyInstanceType to a concrete type. A design detail is important to - * note here: the parameters for this instantiation of the type family are + * TypeFunctionInstanceType to a concrete type. A design detail is important to + * note here: the parameters for this instantiation of the type function are * contained within this type, so that they can be substituted. */ -struct TypeFamilyInstanceType +struct TypeFunctionInstanceType { - NotNull family; + NotNull family; std::vector typeArguments; std::vector packArguments; - TypeFamilyInstanceType(NotNull family, std::vector typeArguments, std::vector packArguments) + TypeFunctionInstanceType(NotNull family, std::vector typeArguments, std::vector packArguments) : family(family) , typeArguments(typeArguments) , packArguments(packArguments) { } - TypeFamilyInstanceType(const TypeFamily& family, std::vector typeArguments) + TypeFunctionInstanceType(const TypeFunction& family, std::vector typeArguments) : family{&family} , typeArguments(typeArguments) , packArguments{} { } - TypeFamilyInstanceType(const TypeFamily& family, std::vector typeArguments, std::vector packArguments) + TypeFunctionInstanceType(const TypeFunction& family, std::vector typeArguments, std::vector packArguments) : family{&family} , typeArguments(typeArguments) , packArguments(packArguments) @@ -659,7 +659,7 @@ using ErrorType = Unifiable::Error; using TypeVariant = Unifiable::Variant; + MetatableType, ClassType, AnyType, UnionType, IntersectionType, LazyType, UnknownType, NeverType, NegationType, TypeFunctionInstanceType>; struct Type final { diff --git a/Analysis/include/Luau/TypeArena.h b/Analysis/include/Luau/TypeArena.h index 162fde96..5a28aa25 100644 --- a/Analysis/include/Luau/TypeArena.h +++ b/Analysis/include/Luau/TypeArena.h @@ -49,10 +49,10 @@ struct TypeArena return addTypePack(TypePackVar(std::move(tp))); } - TypeId addTypeFamily(const TypeFamily& family, std::initializer_list types); - TypeId addTypeFamily(const TypeFamily& family, std::vector typeArguments, std::vector packArguments = {}); - TypePackId addTypePackFamily(const TypePackFamily& family, std::initializer_list types); - TypePackId addTypePackFamily(const TypePackFamily& family, std::vector typeArguments, std::vector packArguments = {}); + TypeId addTypeFunction(const TypeFunction& family, std::initializer_list types); + TypeId addTypeFunction(const TypeFunction& family, std::vector typeArguments, std::vector packArguments = {}); + TypePackId addTypePackFunction(const TypePackFunction& family, std::initializer_list types); + TypePackId addTypePackFunction(const TypePackFunction& family, std::vector typeArguments, std::vector packArguments = {}); }; void freeze(TypeArena& arena); diff --git a/Analysis/include/Luau/TypeFamilyReductionGuesser.h b/Analysis/include/Luau/TypeFamilyReductionGuesser.h deleted file mode 100644 index 29114f72..00000000 --- a/Analysis/include/Luau/TypeFamilyReductionGuesser.h +++ /dev/null @@ -1,85 +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/Ast.h" -#include "Luau/VecDeque.h" -#include "Luau/DenseHash.h" -#include "Luau/TypeFamily.h" -#include "Luau/Type.h" -#include "Luau/TypePack.h" -#include "Luau/TypeUtils.h" -#include "Luau/Normalize.h" -#include "Luau/TypeFwd.h" -#include "Luau/VisitType.h" -#include "Luau/NotNull.h" -#include "TypeArena.h" - -namespace Luau -{ - -struct TypeFamilyReductionGuessResult -{ - std::vector> guessedFunctionAnnotations; - TypeId guessedReturnType; - bool shouldRecommendAnnotation = true; -}; - -// An Inference result for a type family is a list of types corresponding to the guessed argument types, followed by a type for the result -struct TypeFamilyInferenceResult -{ - std::vector operandInference; - TypeId familyResultInference; -}; - -struct TypeFamilyReductionGuesser -{ - // Tracks our hypothesis about what a type family reduces to - DenseHashMap familyReducesTo{nullptr}; - // Tracks our constraints on type family operands - DenseHashMap substitutable{nullptr}; - // List of instances to try progress - VecDeque toInfer; - DenseHashSet cyclicInstances{nullptr}; - - // Utilities - NotNull arena; - NotNull builtins; - NotNull normalizer; - - TypeFamilyReductionGuesser(NotNull arena, NotNull builtins, NotNull normalizer); - - std::optional guess(TypeId typ); - std::optional guess(TypePackId typ); - TypeFamilyReductionGuessResult guessTypeFamilyReductionForFunction(const AstExprFunction& expr, const FunctionType* ftv, TypeId retTy); - -private: - std::optional guessType(TypeId arg); - void dumpGuesses(); - - bool isNumericBinopFamily(const TypeFamilyInstanceType& instance); - bool isComparisonFamily(const TypeFamilyInstanceType& instance); - bool isOrAndFamily(const TypeFamilyInstanceType& instance); - bool isNotFamily(const TypeFamilyInstanceType& instance); - bool isLenFamily(const TypeFamilyInstanceType& instance); - bool isUnaryMinus(const TypeFamilyInstanceType& instance); - - // Operand is assignable if it looks like a cyclic family instance, or a generic type - bool operandIsAssignable(TypeId ty); - std::optional tryAssignOperandType(TypeId ty); - - std::shared_ptr normalize(TypeId ty); - void step(); - void infer(); - bool done(); - - bool isFunctionGenericsSaturated(const FunctionType& ftv, DenseHashSet& instanceArgs); - void inferTypeFamilySubstitutions(TypeId ty, const TypeFamilyInstanceType* instance); - TypeFamilyInferenceResult inferNumericBinopFamily(const TypeFamilyInstanceType* instance); - TypeFamilyInferenceResult inferComparisonFamily(const TypeFamilyInstanceType* instance); - TypeFamilyInferenceResult inferOrAndFamily(const TypeFamilyInstanceType* instance); - TypeFamilyInferenceResult inferNotFamily(const TypeFamilyInstanceType* instance); - TypeFamilyInferenceResult inferLenFamily(const TypeFamilyInstanceType* instance); - TypeFamilyInferenceResult inferUnaryMinusFamily(const TypeFamilyInstanceType* instance); -}; -} // namespace Luau diff --git a/Analysis/include/Luau/TypeFamily.h b/Analysis/include/Luau/TypeFunction.h similarity index 63% rename from Analysis/include/Luau/TypeFamily.h rename to Analysis/include/Luau/TypeFunction.h index 9d2182df..f5ac22ad 100644 --- a/Analysis/include/Luau/TypeFamily.h +++ b/Analysis/include/Luau/TypeFunction.h @@ -18,7 +18,7 @@ struct TypeArena; struct TxnLog; class Normalizer; -struct TypeFamilyContext +struct TypeFunctionContext { NotNull arena; NotNull builtins; @@ -27,12 +27,12 @@ struct TypeFamilyContext NotNull ice; NotNull limits; - // nullptr if the type family is being reduced outside of the constraint solver. + // nullptr if the type function is being reduced outside of the constraint solver. ConstraintSolver* solver; // The constraint being reduced in this run of the reduction const Constraint* constraint; - TypeFamilyContext(NotNull cs, NotNull scope, NotNull constraint) + TypeFunctionContext(NotNull cs, NotNull scope, NotNull constraint) : arena(cs->arena) , builtins(cs->builtinTypes) , scope(scope) @@ -44,7 +44,7 @@ struct TypeFamilyContext { } - TypeFamilyContext(NotNull arena, NotNull builtins, NotNull scope, NotNull normalizer, + TypeFunctionContext(NotNull arena, NotNull builtins, NotNull scope, NotNull normalizer, NotNull ice, NotNull limits) : arena(arena) , builtins(builtins) @@ -64,13 +64,13 @@ struct TypeFamilyContext /// may have concretely failed to reduce the type, or may simply be stuck /// without more information. template -struct TypeFamilyReductionResult +struct TypeFunctionReductionResult { - /// The result of the reduction, if any. If this is nullopt, the family + /// The result of the reduction, if any. If this is nullopt, the type function /// could not be reduced. std::optional result; /// Whether the result is uninhabited: whether we know, unambiguously and - /// permanently, whether this type family reduction results in an + /// permanently, whether this type function reduction results in an /// uninhabitable type. This will trigger an error to be reported. bool uninhabited; /// Any types that need to be progressed or mutated before the reduction may @@ -83,33 +83,33 @@ struct TypeFamilyReductionResult template using ReducerFunction = - std::function(T, const std::vector&, const std::vector&, NotNull)>; + std::function(T, const std::vector&, const std::vector&, NotNull)>; /// Represents a type function that may be applied to map a series of types and /// type packs to a single output type. -struct TypeFamily +struct TypeFunction { - /// The human-readable name of the type family. Used to stringify instance + /// The human-readable name of the type function. Used to stringify instance /// types. std::string name; - /// The reducer function for the type family. + /// The reducer function for the type function. ReducerFunction reducer; }; /// Represents a type function that may be applied to map a series of types and /// type packs to a single output type pack. -struct TypePackFamily +struct TypePackFunction { - /// The human-readable name of the type pack family. Used to stringify + /// The human-readable name of the type pack function. Used to stringify /// instance packs. std::string name; - /// The reducer function for the type pack family. + /// The reducer function for the type pack function. ReducerFunction reducer; }; -struct FamilyGraphReductionResult +struct FunctionGraphReductionResult { ErrorVec errors; DenseHashSet blockedTypes{nullptr}; @@ -119,7 +119,7 @@ struct FamilyGraphReductionResult }; /** - * Attempt to reduce all instances of any type or type pack family in the type + * Attempt to reduce all instances of any type or type pack functions in the type * graph provided. * * @param entrypoint the entry point to the type graph. @@ -130,10 +130,10 @@ struct FamilyGraphReductionResult * @param normalizer the normalizer to use when normalizing types * @param ice the internal error reporter to use for ICEs */ -FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, TypeFamilyContext, bool force = false); +FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext, bool force = false); /** - * Attempt to reduce all instances of any type or type pack family in the type + * Attempt to reduce all instances of any type or type pack functions in the type * graph provided. * * @param entrypoint the entry point to the type graph. @@ -144,47 +144,46 @@ FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, * @param normalizer the normalizer to use when normalizing types * @param ice the internal error reporter to use for ICEs */ -FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, TypeFamilyContext, bool force = false); +FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location location, TypeFunctionContext, bool force = false); -struct BuiltinTypeFamilies +struct BuiltinTypeFunctions { - BuiltinTypeFamilies(); + BuiltinTypeFunctions(); - TypeFamily notFamily; - TypeFamily lenFamily; - TypeFamily unmFamily; + TypeFunction notFunc; + TypeFunction lenFunc; + TypeFunction unmFunc; - TypeFamily addFamily; - TypeFamily subFamily; - TypeFamily mulFamily; - TypeFamily divFamily; - TypeFamily idivFamily; - TypeFamily powFamily; - TypeFamily modFamily; + TypeFunction addFunc; + TypeFunction subFunc; + TypeFunction mulFunc; + TypeFunction divFunc; + TypeFunction idivFunc; + TypeFunction powFunc; + TypeFunction modFunc; - TypeFamily concatFamily; + TypeFunction concatFunc; - TypeFamily andFamily; - TypeFamily orFamily; + TypeFunction andFunc; + TypeFunction orFunc; - TypeFamily ltFamily; - TypeFamily leFamily; - TypeFamily eqFamily; + TypeFunction ltFunc; + TypeFunction leFunc; + TypeFunction eqFunc; - TypeFamily refineFamily; - TypeFamily singletonFamily; - TypeFamily unionFamily; - TypeFamily intersectFamily; + TypeFunction refineFunc; + TypeFunction singletonFunc; + TypeFunction unionFunc; + TypeFunction intersectFunc; - TypeFamily keyofFamily; - TypeFamily rawkeyofFamily; - - TypeFamily indexFamily; - TypeFamily rawgetFamily; + TypeFunction keyofFunc; + TypeFunction rawkeyofFunc; + TypeFunction indexFunc; + TypeFunction rawgetFunc; void addToScope(NotNull arena, NotNull scope) const; }; -const BuiltinTypeFamilies& builtinTypeFunctions(); +const BuiltinTypeFunctions& builtinTypeFunctions(); } // namespace Luau diff --git a/Analysis/include/Luau/TypeFunctionReductionGuesser.h b/Analysis/include/Luau/TypeFunctionReductionGuesser.h new file mode 100644 index 00000000..62206efc --- /dev/null +++ b/Analysis/include/Luau/TypeFunctionReductionGuesser.h @@ -0,0 +1,85 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details + +#pragma once + +#include "Luau/Ast.h" +#include "Luau/VecDeque.h" +#include "Luau/DenseHash.h" +#include "Luau/TypeFunction.h" +#include "Luau/Type.h" +#include "Luau/TypePack.h" +#include "Luau/TypeUtils.h" +#include "Luau/Normalize.h" +#include "Luau/TypeFwd.h" +#include "Luau/VisitType.h" +#include "Luau/NotNull.h" +#include "TypeArena.h" + +namespace Luau +{ + +struct TypeFunctionReductionGuessResult +{ + std::vector> guessedFunctionAnnotations; + TypeId guessedReturnType; + bool shouldRecommendAnnotation = true; +}; + +// An Inference result for a type function is a list of types corresponding to the guessed argument types, followed by a type for the result +struct TypeFunctionInferenceResult +{ + std::vector operandInference; + TypeId familyResultInference; +}; + +struct TypeFunctionReductionGuesser +{ + // Tracks our hypothesis about what a type function reduces to + DenseHashMap familyReducesTo{nullptr}; + // Tracks our constraints on type function operands + DenseHashMap substitutable{nullptr}; + // List of instances to try progress + VecDeque toInfer; + DenseHashSet cyclicInstances{nullptr}; + + // Utilities + NotNull arena; + NotNull builtins; + NotNull normalizer; + + TypeFunctionReductionGuesser(NotNull arena, NotNull builtins, NotNull normalizer); + + std::optional guess(TypeId typ); + std::optional guess(TypePackId typ); + TypeFunctionReductionGuessResult guessTypeFunctionReductionForFunctionExpr(const AstExprFunction& expr, const FunctionType* ftv, TypeId retTy); + +private: + std::optional guessType(TypeId arg); + void dumpGuesses(); + + bool isNumericBinopFamily(const TypeFunctionInstanceType& instance); + bool isComparisonFamily(const TypeFunctionInstanceType& instance); + bool isOrAndFamily(const TypeFunctionInstanceType& instance); + bool isNotFamily(const TypeFunctionInstanceType& instance); + bool isLenFamily(const TypeFunctionInstanceType& instance); + bool isUnaryMinus(const TypeFunctionInstanceType& instance); + + // Operand is assignable if it looks like a cyclic family instance, or a generic type + bool operandIsAssignable(TypeId ty); + std::optional tryAssignOperandType(TypeId ty); + + std::shared_ptr normalize(TypeId ty); + void step(); + void infer(); + bool done(); + + bool isFunctionGenericsSaturated(const FunctionType& ftv, DenseHashSet& instanceArgs); + void inferTypeFunctionSubstitutions(TypeId ty, const TypeFunctionInstanceType* instance); + TypeFunctionInferenceResult inferNumericBinopFamily(const TypeFunctionInstanceType* instance); + TypeFunctionInferenceResult inferComparisonFamily(const TypeFunctionInstanceType* instance); + TypeFunctionInferenceResult inferOrAndFamily(const TypeFunctionInstanceType* instance); + TypeFunctionInferenceResult inferNotFamily(const TypeFunctionInstanceType* instance); + TypeFunctionInferenceResult inferLenFamily(const TypeFunctionInstanceType* instance); + TypeFunctionInferenceResult inferUnaryMinusFamily(const TypeFunctionInstanceType* instance); +}; +} // namespace Luau diff --git a/Analysis/include/Luau/TypeFwd.h b/Analysis/include/Luau/TypeFwd.h index e4318156..42d582fe 100644 --- a/Analysis/include/Luau/TypeFwd.h +++ b/Analysis/include/Luau/TypeFwd.h @@ -37,7 +37,7 @@ struct LazyType; struct UnknownType; struct NeverType; struct NegationType; -struct TypeFamilyInstanceType; +struct TypeFunctionInstanceType; struct TypePackVar; using TypePackId = const TypePackVar*; @@ -47,7 +47,7 @@ struct GenericTypePack; struct TypePack; struct VariadicTypePack; struct BlockedTypePack; -struct TypeFamilyInstanceTypePack; +struct TypeFunctionInstanceTypePack; using Name = std::string; using ModuleName = std::string; diff --git a/Analysis/include/Luau/TypePack.h b/Analysis/include/Luau/TypePack.h index 691a8ed9..5d10454d 100644 --- a/Analysis/include/Luau/TypePack.h +++ b/Analysis/include/Luau/TypePack.h @@ -15,13 +15,13 @@ namespace Luau { struct TypeArena; -struct TypePackFamily; +struct TypePackFunction; struct TxnLog; struct TypePack; struct VariadicTypePack; struct BlockedTypePack; -struct TypeFamilyInstanceTypePack; +struct TypeFunctionInstanceTypePack; struct FreeTypePack { @@ -55,7 +55,7 @@ using BoundTypePack = Unifiable::Bound; using ErrorTypePack = Unifiable::Error; using TypePackVariant = - Unifiable::Variant; + Unifiable::Variant; /* A TypePack is a rope-like string of TypeIds. We use this structure to encode * notions like packs of unknown length and packs of any length, as well as more @@ -88,11 +88,11 @@ struct BlockedTypePack }; /** - * Analogous to a TypeFamilyInstanceType. + * Analogous to a TypeFunctionInstanceType. */ -struct TypeFamilyInstanceTypePack +struct TypeFunctionInstanceTypePack { - NotNull family; + NotNull family; std::vector typeArguments; std::vector packArguments; diff --git a/Analysis/include/Luau/VisitType.h b/Analysis/include/Luau/VisitType.h index 8c0f5ed9..e588d06b 100644 --- a/Analysis/include/Luau/VisitType.h +++ b/Analysis/include/Luau/VisitType.h @@ -7,6 +7,7 @@ #include "Luau/RecursionCounter.h" #include "Luau/TypePack.h" #include "Luau/Type.h" +#include "Type.h" LUAU_FASTINT(LuauVisitRecursionLimit) LUAU_FASTFLAG(LuauBoundLazyTypes2) @@ -65,7 +66,7 @@ inline void unsee(DenseHashSet& seen, const void* tv) } // namespace visit_detail // recursion counter is equivalent here, but we'd like a better name to express the intent. -using TypeFamilyDepthCounter = RecursionCounter; +using TypeFunctionDepthCounter = RecursionCounter; template struct GenericTypeVisitor @@ -75,7 +76,7 @@ struct GenericTypeVisitor Set seen; bool skipBoundTypes = false; int recursionCounter = 0; - int typeFamilyDepth = 0; + int typeFunctionDepth = 0; GenericTypeVisitor() = default; @@ -164,7 +165,7 @@ struct GenericTypeVisitor { return visit(ty); } - virtual bool visit(TypeId ty, const TypeFamilyInstanceType& tfit) + virtual bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) { return visit(ty); } @@ -201,7 +202,7 @@ struct GenericTypeVisitor { return visit(tp); } - virtual bool visit(TypePackId tp, const TypeFamilyInstanceTypePack& tfitp) + virtual bool visit(TypePackId tp, const TypeFunctionInstanceTypePack& tfitp) { return visit(tp); } @@ -415,9 +416,9 @@ struct GenericTypeVisitor if (visit(ty, *ntv)) traverse(ntv->ty); } - else if (auto tfit = get(ty)) + else if (auto tfit = get(ty)) { - TypeFamilyDepthCounter tfdc{&typeFamilyDepth}; + TypeFunctionDepthCounter tfdc{&typeFunctionDepth}; if (visit(ty, *tfit)) { @@ -477,9 +478,9 @@ struct GenericTypeVisitor } else if (auto btp = get(tp)) visit(tp, *btp); - else if (auto tfitp = get(tp)) + else if (auto tfitp = get(tp)) { - TypeFamilyDepthCounter tfdc{&typeFamilyDepth}; + TypeFunctionDepthCounter tfdc{&typeFunctionDepth}; if (visit(tp, *tfitp)) { diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 582d5a7d..e7782150 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -10,7 +10,7 @@ #include "Luau/ConstraintGenerator.h" #include "Luau/NotNull.h" #include "Luau/TypeInfer.h" -#include "Luau/TypeFamily.h" +#include "Luau/TypeFunction.h" #include "Luau/TypePack.h" #include "Luau/Type.h" #include "Luau/TypeUtils.h" @@ -312,8 +312,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC { // declare function assert(value: T, errorMessage: string?): intersect TypeId genericT = arena.addType(GenericType{"T"}); - TypeId refinedTy = arena.addType(TypeFamilyInstanceType{ - NotNull{&builtinTypeFunctions().intersectFamily}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {}}); + TypeId refinedTy = arena.addType(TypeFunctionInstanceType{ + NotNull{&builtinTypeFunctions().intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {}}); TypeId assertTy = arena.addType(FunctionType{ {genericT}, {}, arena.addTypePack(TypePack{{genericT, builtinTypes->optionalStringType}}), arena.addTypePack(TypePack{{refinedTy}})}); diff --git a/Analysis/src/Clone.cpp b/Analysis/src/Clone.cpp index 371ace2e..76bf9233 100644 --- a/Analysis/src/Clone.cpp +++ b/Analysis/src/Clone.cpp @@ -388,7 +388,7 @@ private: t->ty = shallowClone(t->ty); } - void cloneChildren(TypeFamilyInstanceType* t) + void cloneChildren(TypeFunctionInstanceType* t) { for (TypeId& ty : t->typeArguments) ty = shallowClone(ty); @@ -432,7 +432,7 @@ private: t->tail = shallowClone(*t->tail); } - void cloneChildren(TypeFamilyInstanceTypePack* t) + void cloneChildren(TypeFunctionInstanceTypePack* t) { for (TypeId& ty : t->typeArguments) ty = shallowClone(ty); diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index a449949a..aa5e4ca8 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -16,7 +16,7 @@ #include "Luau/StringUtils.h" #include "Luau/TableLiteralInference.h" #include "Luau/Type.h" -#include "Luau/TypeFamily.h" +#include "Luau/TypeFunction.h" #include "Luau/TypePack.h" #include "Luau/TypeUtils.h" #include "Luau/Unifier2.h" @@ -432,7 +432,7 @@ void ConstraintGenerator::computeRefinement(const ScopePtr& scope, Location loca discriminantTy = arena->addType(NegationType{discriminantTy}); if (eq) - discriminantTy = createTypeFamilyInstance(builtinTypeFunctions().singletonFamily, {discriminantTy}, {}, scope, location); + discriminantTy = createTypeFunctionInstance(builtinTypeFunctions().singletonFunc, {discriminantTy}, {}, scope, location); for (const RefinementKey* key = proposition->key; key; key = key->parent) { @@ -544,7 +544,7 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat { if (mustDeferIntersection(ty) || mustDeferIntersection(dt)) { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().refineFamily, {ty, dt}, {}, scope, location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().refineFunc, {ty, dt}, {}, scope, location); ty = resultType; } @@ -2090,17 +2090,17 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprUnary* unary) { case AstExprUnary::Op::Not: { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().notFamily, {operandType}, {}, scope, unary->location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().notFunc, {operandType}, {}, scope, unary->location); return Inference{resultType, refinementArena.negation(refinement)}; } case AstExprUnary::Op::Len: { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().lenFamily, {operandType}, {}, scope, unary->location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().lenFunc, {operandType}, {}, scope, unary->location); return Inference{resultType, refinementArena.negation(refinement)}; } case AstExprUnary::Op::Minus: { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().unmFamily, {operandType}, {}, scope, unary->location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().unmFunc, {operandType}, {}, scope, unary->location); return Inference{resultType, refinementArena.negation(refinement)}; } default: // msvc can't prove that this is exhaustive. @@ -2116,75 +2116,75 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprBinary* binar { case AstExprBinary::Op::Add: { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().addFamily, {leftType, rightType}, {}, scope, binary->location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().addFunc, {leftType, rightType}, {}, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Sub: { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().subFamily, {leftType, rightType}, {}, scope, binary->location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().subFunc, {leftType, rightType}, {}, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Mul: { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().mulFamily, {leftType, rightType}, {}, scope, binary->location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().mulFunc, {leftType, rightType}, {}, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Div: { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().divFamily, {leftType, rightType}, {}, scope, binary->location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().divFunc, {leftType, rightType}, {}, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::FloorDiv: { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().idivFamily, {leftType, rightType}, {}, scope, binary->location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().idivFunc, {leftType, rightType}, {}, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Pow: { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().powFamily, {leftType, rightType}, {}, scope, binary->location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().powFunc, {leftType, rightType}, {}, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Mod: { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().modFamily, {leftType, rightType}, {}, scope, binary->location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().modFunc, {leftType, rightType}, {}, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Concat: { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().concatFamily, {leftType, rightType}, {}, scope, binary->location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().concatFunc, {leftType, rightType}, {}, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::And: { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().andFamily, {leftType, rightType}, {}, scope, binary->location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().andFunc, {leftType, rightType}, {}, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Or: { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().orFamily, {leftType, rightType}, {}, scope, binary->location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().orFunc, {leftType, rightType}, {}, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareLt: { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().ltFamily, {leftType, rightType}, {}, scope, binary->location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().ltFunc, {leftType, rightType}, {}, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareGe: { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().ltFamily, + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().ltFunc, {rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)` {}, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareLe: { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().leFamily, {leftType, rightType}, {}, scope, binary->location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().leFunc, {leftType, rightType}, {}, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareGt: { - TypeId resultType = createTypeFamilyInstance( -builtinTypeFunctions().leFamily, + TypeId resultType = createTypeFunctionInstance( +builtinTypeFunctions().leFunc, {rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)` {}, scope, binary->location); return Inference{resultType, std::move(refinement)}; @@ -2206,7 +2206,7 @@ builtinTypeFunctions().leFamily, else if (rightSubscripted) rightType = makeUnion(scope, binary->location, rightType, builtinTypes->nilType); - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().eqFamily, {leftType, rightType}, {}, scope, binary->location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().eqFunc, {leftType, rightType}, {}, scope, binary->location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Op__Count: @@ -3160,14 +3160,14 @@ TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location, if (get(follow(rhs))) return lhs; - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().unionFamily, {lhs, rhs}, {}, scope, location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().unionFunc, {lhs, rhs}, {}, scope, location); return resultType; } TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs) { - TypeId resultType = createTypeFamilyInstance(builtinTypeFunctions().intersectFamily, {lhs, rhs}, {}, scope, location); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().intersectFunc, {lhs, rhs}, {}, scope, location); return resultType; } @@ -3285,7 +3285,7 @@ void ConstraintGenerator::fillInInferredBindings(const ScopePtr& globalScope, As scope->bindings[symbol] = Binding{tys.front(), location}; else { - TypeId ty = createTypeFamilyInstance(builtinTypeFunctions().unionFamily, std::move(tys), {}, globalScope, location); + TypeId ty = createTypeFunctionInstance(builtinTypeFunctions().unionFunc, std::move(tys), {}, globalScope, location); scope->bindings[symbol] = Binding{ty, location}; } @@ -3355,10 +3355,10 @@ std::vector> ConstraintGenerator::getExpectedCallTypesForF return expectedTypes; } -TypeId ConstraintGenerator::createTypeFamilyInstance( - const TypeFamily& family, std::vector typeArguments, std::vector packArguments, const ScopePtr& scope, Location location) +TypeId ConstraintGenerator::createTypeFunctionInstance( + const TypeFunction& family, std::vector typeArguments, std::vector packArguments, const ScopePtr& scope, Location location) { - TypeId result = arena->addTypeFamily(family, typeArguments, packArguments); + TypeId result = arena->addTypeFunction(family, typeArguments, packArguments); addConstraint(scope, location, ReduceConstraint{result}); return result; } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index c77581a7..15f150e8 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -18,7 +18,7 @@ #include "Luau/TimeTrace.h" #include "Luau/ToString.h" #include "Luau/Type.h" -#include "Luau/TypeFamily.h" +#include "Luau/TypeFunction.h" #include "Luau/TypeFwd.h" #include "Luau/TypeUtils.h" #include "Luau/Unifier2.h" @@ -298,7 +298,7 @@ struct InstantiationQueuer : TypeOnceVisitor return false; } - bool visit(TypeId ty, const TypeFamilyInstanceType&) override + bool visit(TypeId ty, const TypeFunctionInstanceType&) override { solver->pushConstraint(scope, location, ReduceConstraint{ty}); return true; @@ -510,10 +510,10 @@ void ConstraintSolver::finalizeTypeFamilies() for (auto [t, constraint] : typeFamiliesToFinalize) { TypeId ty = follow(t); - if (get(ty)) + if (get(ty)) { - FamilyGraphReductionResult result = - reduceFamilies(t, constraint->location, TypeFamilyContext{NotNull{this}, constraint->scope, NotNull{constraint}}, true); + FunctionGraphReductionResult result = + reduceTypeFunctions(t, constraint->location, TypeFunctionContext{NotNull{this}, constraint->scope, NotNull{constraint}}, true); for (TypeId r : result.reducedTypes) unblock(r, constraint->location); @@ -1394,7 +1394,7 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull(resultType)); LUAU_ASSERT(canMutate(resultType, constraint)); - if (isBlocked(subjectType) || get(subjectType) || get(subjectType)) + if (isBlocked(subjectType) || get(subjectType) || get(subjectType)) return block(subjectType, constraint); if (const TableType* subjectTable = getTableType(subjectType)) @@ -1433,6 +1433,12 @@ bool ConstraintSolver::tryDispatchHasIndexer( LUAU_ASSERT(get(resultType)); LUAU_ASSERT(canMutate(resultType, constraint)); + if (get(subjectType)) + { + bind(constraint, resultType, builtinTypes->anyType); + return true; + } + if (auto ft = get(subjectType)) { if (auto tbl = get(follow(ft->upperBound)); tbl && tbl->indexer) @@ -1955,8 +1961,8 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull constraint, bool force) { TypeId ty = follow(c.ty); - FamilyGraphReductionResult result = - reduceFamilies(ty, constraint->location, TypeFamilyContext{NotNull{this}, constraint->scope, constraint}, force); + FunctionGraphReductionResult result = + reduceTypeFunctions(ty, constraint->location, TypeFunctionContext{NotNull{this}, constraint->scope, constraint}, force); for (TypeId r : result.reducedTypes) unblock(r, constraint->location); @@ -1967,8 +1973,8 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull(ty)) + // If we couldn't reduce this type function, stick it in the set! + if (get(ty)) typeFamiliesToFinalize[ty] = constraint; if (force || reductionFinished) @@ -1976,9 +1982,9 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull(error)) + if (auto utf = get(error)) uninhabitedTypeFamilies.insert(utf->ty); - else if (auto utpf = get(error)) + else if (auto utpf = get(error)) uninhabitedTypeFamilies.insert(utpf->tp); } } @@ -1998,8 +2004,8 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull constraint, bool force) { TypePackId tp = follow(c.tp); - FamilyGraphReductionResult result = - reduceFamilies(tp, constraint->location, TypeFamilyContext{NotNull{this}, constraint->scope, constraint}, force); + FunctionGraphReductionResult result = + reduceTypeFunctions(tp, constraint->location, TypeFunctionContext{NotNull{this}, constraint->scope, constraint}, force); for (TypeId r : result.reducedTypes) unblock(r, constraint->location); @@ -2014,9 +2020,9 @@ bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull(error)) + if (auto utf = get(error)) uninhabitedTypeFamilies.insert(utf->ty); - else if (auto utpf = get(error)) + else if (auto utpf = get(error)) uninhabitedTypeFamilies.insert(utpf->tp); } } @@ -2103,7 +2109,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl * it's possible that there are other constraints on the table that will * clarify what we should do. * - * We should eventually introduce a type family to talk about iteration. + * We should eventually introduce a type function to talk about iteration. */ if (iteratorTable->state == TableState::Free && !force) return block(iteratorTy, constraint); @@ -2713,13 +2719,13 @@ void ConstraintSolver::reproduceConstraints(NotNull scope, const Location { for (auto [_, newTy] : subst.newTypes) { - if (get(newTy)) + if (get(newTy)) pushConstraint(scope, location, ReduceConstraint{newTy}); } for (auto [_, newPack] : subst.newPacks) { - if (get(newPack)) + if (get(newPack)) pushConstraint(scope, location, ReducePackConstraint{newPack}); } } @@ -2728,7 +2734,7 @@ bool ConstraintSolver::isBlocked(TypeId ty) { ty = follow(ty); - if (auto tfit = get(ty)) + if (auto tfit = get(ty)) return uninhabitedTypeFamilies.contains(ty) == false; return nullptr != get(ty) || nullptr != get(ty); @@ -2738,7 +2744,7 @@ bool ConstraintSolver::isBlocked(TypePackId tp) { tp = follow(tp); - if (auto tfitp = get(tp)) + if (auto tfitp = get(tp)) return uninhabitedTypeFamilies.contains(tp) == false; return nullptr != get(tp); diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index 5a9e42a7..6e778e57 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -8,7 +8,7 @@ #include "Luau/StringUtils.h" #include "Luau/ToString.h" #include "Luau/Type.h" -#include "Luau/TypeFamily.h" +#include "Luau/TypeFunction.h" #include #include @@ -64,16 +64,16 @@ static std::string wrongNumberOfArgsString( namespace Luau { -// this list of binary operator type families is used for better stringification of type families errors +// this list of binary operator type functions is used for better stringification of type functions errors static const std::unordered_map kBinaryOps{{"add", "+"}, {"sub", "-"}, {"mul", "*"}, {"div", "/"}, {"idiv", "//"}, {"pow", "^"}, {"mod", "%"}, {"concat", ".."}, {"and", "and"}, {"or", "or"}, {"lt", "< or >="}, {"le", "<= or >"}, {"eq", "== or ~="}}; -// this list of unary operator type families is used for better stringification of type families errors +// this list of unary operator type functions is used for better stringification of type functions errors static const std::unordered_map kUnaryOps{{"unm", "-"}, {"len", "#"}, {"not", "not"}}; -// this list of type families will receive a special error indicating that the user should file a bug on the GitHub repository -// putting a type family in this list indicates that it is expected to _always_ reduce -static const std::unordered_set kUnreachableTypeFamilies{"refine", "singleton", "union", "intersect"}; +// 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 kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect"}; struct ErrorConverter { @@ -577,12 +577,12 @@ struct ErrorConverter return "Attempting a dynamic property access on type '" + Luau::toString(e.ty) + "' is unsafe and may cause exceptions at runtime"; } - std::string operator()(const UninhabitedTypeFamily& e) const + std::string operator()(const UninhabitedTypeFunction& e) const { - auto tfit = get(e.ty); - LUAU_ASSERT(tfit); // Luau analysis has actually done something wrong if this type is not a type family. + auto tfit = get(e.ty); + LUAU_ASSERT(tfit); // Luau analysis has actually done something wrong if this type is not a type function. if (!tfit) - return "Unexpected type " + Luau::toString(e.ty) + " flagged as an uninhabited type family."; + return "Unexpected type " + Luau::toString(e.ty) + " flagged as an uninhabited type function."; // unary operators if (auto unaryString = kUnaryOps.find(tfit->family->name); unaryString != kUnaryOps.end()) @@ -658,13 +658,13 @@ struct ErrorConverter if (tfit->typeArguments.size() == 1 && tfit->packArguments.empty()) return "Type '" + toString(tfit->typeArguments[0]) + "' does not have keys, so '" + Luau::toString(e.ty) + "' is invalid"; else - return "Type family instance " + Luau::toString(e.ty) + " is ill-formed, and thus invalid"; + return "Type function instance " + Luau::toString(e.ty) + " is ill-formed, and thus invalid"; } if ("index" == tfit->family->name || "rawget" == tfit->family->name) { if (tfit->typeArguments.size() != 2) - return "Type family instance " + Luau::toString(e.ty) + " is ill-formed, and thus invalid"; + return "Type function instance " + Luau::toString(e.ty) + " is ill-formed, and thus invalid"; if (auto errType = get(tfit->typeArguments[1])) // Second argument to (index | rawget)<_,_> is not a type return "Second argument to " + tfit->family->name + "<" + Luau::toString(tfit->typeArguments[0]) + ", _> is not a valid index type"; @@ -673,15 +673,15 @@ struct ErrorConverter "'"; } - if (kUnreachableTypeFamilies.count(tfit->family->name)) + if (kUnreachableTypeFunctions.count(tfit->family->name)) { - return "Type family instance " + Luau::toString(e.ty) + " is uninhabited\n" + + 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"; } - // Everything should be specialized above to report a more descriptive error that hopefully does not mention "type families" explicitly. + // Everything should be specialized above to report a more descriptive error that hopefully does not mention "type functions" explicitly. // If we produce this message, it's an indication that we've missed a specialization and it should be fixed! - return "Type family instance " + Luau::toString(e.ty) + " is uninhabited"; + return "Type function instance " + Luau::toString(e.ty) + " is uninhabited"; } std::string operator()(const ExplicitFunctionAnnotationRecommended& r) const @@ -704,14 +704,14 @@ struct ErrorConverter return "Consider placing the following annotations on the arguments: " + argAnnotations + " or instead annotating the return as " + toReturn; } - std::string operator()(const UninhabitedTypePackFamily& e) const + std::string operator()(const UninhabitedTypePackFunction& e) const { return "Type pack family instance " + Luau::toString(e.tp) + " is uninhabited"; } std::string operator()(const WhereClauseNeeded& e) const { - return "Type family instance " + Luau::toString(e.ty) + + return "Type function instance " + Luau::toString(e.ty) + " depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this " "time"; } @@ -1092,7 +1092,7 @@ bool DynamicPropertyLookupOnClassesUnsafe::operator==(const DynamicPropertyLooku return ty == rhs.ty; } -bool UninhabitedTypeFamily::operator==(const UninhabitedTypeFamily& rhs) const +bool UninhabitedTypeFunction::operator==(const UninhabitedTypeFunction& rhs) const { return ty == rhs.ty; } @@ -1103,7 +1103,7 @@ bool ExplicitFunctionAnnotationRecommended::operator==(const ExplicitFunctionAnn return recommendedReturn == rhs.recommendedReturn && recommendedArgs == rhs.recommendedArgs; } -bool UninhabitedTypePackFamily::operator==(const UninhabitedTypePackFamily& rhs) const +bool UninhabitedTypePackFunction::operator==(const UninhabitedTypePackFunction& rhs) const { return tp == rhs.tp; } @@ -1316,7 +1316,7 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState) } else if constexpr (std::is_same_v) e.ty = clone(e.ty); - else if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) e.ty = clone(e.ty); else if constexpr (std::is_same_v) { @@ -1324,7 +1324,7 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState) for (auto& [_, t] : e.recommendedArgs) t = clone(t); } - else if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) e.tp = clone(e.tp); else if constexpr (std::is_same_v) e.ty = clone(e.ty); diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index fb7b8909..391ece8c 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -131,9 +131,10 @@ static ParseResult parseSourceForModule(std::string_view source, Luau::SourceMod if (FFlag::LuauStoreCommentsForDefinitionFiles && options.captureComments) { - sourceModule.hotcomments = std::move(parseResult.hotcomments); - sourceModule.commentLocations = std::move(parseResult.commentLocations); + sourceModule.hotcomments = parseResult.hotcomments; + sourceModule.commentLocations = parseResult.commentLocations; } + return parseResult; } @@ -1236,7 +1237,7 @@ struct InternalTypeFinder : TypeOnceVisitor return false; } - bool visit(TypePackId, const TypeFamilyInstanceTypePack&) override + bool visit(TypePackId, const TypeFunctionInstanceTypePack&) override { LUAU_ASSERT(false); return false; diff --git a/Analysis/src/Generalization.cpp b/Analysis/src/Generalization.cpp index 5020ea58..e6a8bda3 100644 --- a/Analysis/src/Generalization.cpp +++ b/Analysis/src/Generalization.cpp @@ -804,7 +804,7 @@ struct TypeCacher : TypeOnceVisitor return false; } - bool visit(TypeId ty, const TypeFamilyInstanceType& tfit) override + bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override { if (isCached(ty) || isUncacheable(ty)) return false; @@ -860,7 +860,7 @@ struct TypeCacher : TypeOnceVisitor return false; } - bool visit(TypePackId tp, const TypeFamilyInstanceTypePack&) override + bool visit(TypePackId tp, const TypeFunctionInstanceTypePack&) override { markUncacheable(tp); return false; diff --git a/Analysis/src/IostreamHelpers.cpp b/Analysis/src/IostreamHelpers.cpp index 59aa577e..b6d50173 100644 --- a/Analysis/src/IostreamHelpers.cpp +++ b/Analysis/src/IostreamHelpers.cpp @@ -193,8 +193,8 @@ static void errorToString(std::ostream& stream, const T& err) stream << "TypePackMismatch { wanted = '" + toString(err.wantedTp) + "', given = '" + toString(err.givenTp) + "' }"; else if constexpr (std::is_same_v) stream << "DynamicPropertyLookupOnClassesUnsafe { " << toString(err.ty) << " }"; - else if constexpr (std::is_same_v) - stream << "UninhabitedTypeFamily { " << toString(err.ty) << " }"; + else if constexpr (std::is_same_v) + stream << "UninhabitedTypeFunction { " << toString(err.ty) << " }"; else if constexpr (std::is_same_v) { std::string recArgs = "["; @@ -204,8 +204,8 @@ static void errorToString(std::ostream& stream, const T& err) stream << "ExplicitFunctionAnnotationRecommended { recommmendedReturn = '" + toString(err.recommendedReturn) + "', recommmendedArgs = " + recArgs + "}"; } - else if constexpr (std::is_same_v) - stream << "UninhabitedTypePackFamily { " << toString(err.tp) << " }"; + else if constexpr (std::is_same_v) + stream << "UninhabitedTypePackFunction { " << toString(err.tp) << " }"; else if constexpr (std::is_same_v) stream << "WhereClauseNeeded { " << toString(err.ty) << " }"; else if constexpr (std::is_same_v) diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index 440b510b..9dab5123 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -15,6 +15,7 @@ #include LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); +LUAU_FASTFLAGVARIABLE(LuauSkipEmptyInstantiations, false); namespace Luau { @@ -115,9 +116,22 @@ struct ClonePublicInterface : Substitution TypeId result = clone(ty); if (FunctionType* ftv = getMutable(result)) + { + if (FFlag::LuauSkipEmptyInstantiations && ftv->generics.empty() && ftv->genericPacks.empty()) + { + GenericTypeFinder marker; + marker.traverse(result); + + if (!marker.found) + ftv->hasNoFreeOrGenericTypes = true; + } + ftv->level = TypeLevel{0, 0}; + } else if (TableType* ttv = getMutable(result)) + { ttv->level = TypeLevel{0, 0}; + } return result; } diff --git a/Analysis/src/NonStrictTypeChecker.cpp b/Analysis/src/NonStrictTypeChecker.cpp index 2a1b3cc4..ea617279 100644 --- a/Analysis/src/NonStrictTypeChecker.cpp +++ b/Analysis/src/NonStrictTypeChecker.cpp @@ -10,7 +10,7 @@ #include "Luau/Normalize.h" #include "Luau/Error.h" #include "Luau/TypeArena.h" -#include "Luau/TypeFamily.h" +#include "Luau/TypeFunction.h" #include "Luau/Def.h" #include "Luau/ToString.h" #include "Luau/TypeFwd.h" @@ -153,7 +153,7 @@ struct NonStrictTypeChecker Normalizer normalizer; Subtyping subtyping; NotNull dfg; - DenseHashSet noTypeFamilyErrors{nullptr}; + DenseHashSet noTypeFunctionErrors{nullptr}; std::vector> stack; DenseHashMap cachedNegations{nullptr}; @@ -208,14 +208,14 @@ struct NonStrictTypeChecker TypeId checkForFamilyInhabitance(TypeId instance, Location location) { - if (noTypeFamilyErrors.find(instance)) + if (noTypeFunctionErrors.find(instance)) return instance; ErrorVec errors = - reduceFamilies(instance, location, TypeFamilyContext{arena, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true).errors; + reduceTypeFunctions(instance, location, TypeFunctionContext{arena, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true).errors; if (errors.empty()) - noTypeFamilyErrors.insert(instance); + noTypeFunctionErrors.insert(instance); // TODO?? // if (!isErrorSuppressing(location, instance)) // reportErrors(std::move(errors)); diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 17be78a5..9585cdc7 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -816,7 +816,7 @@ static bool areNormalizedClasses(const NormalizedClassType& tys) static bool isPlainTyvar(TypeId ty) { - return (get(ty) || get(ty) || get(ty) || get(ty) || get(ty)); + return (get(ty) || get(ty) || get(ty) || get(ty) || get(ty)); } static bool isNormalizedTyvar(const NormalizedTyvars& tyvars) @@ -883,7 +883,7 @@ static bool isCacheable(TypePackId tp, Set& seen) if (auto tail = it.tail()) { - if (get(*tail) || get(*tail) || get(*tail)) + if (get(*tail) || get(*tail) || get(*tail)) return false; } @@ -901,7 +901,7 @@ static bool isCacheable(TypeId ty, Set& seen) if (get(ty) || get(ty) || get(ty)) return false; - if (auto tfi = get(ty)) + if (auto tfi = get(ty)) { for (TypeId t : tfi->typeArguments) { @@ -1795,7 +1795,7 @@ NormalizationResult Normalizer::unionNormalWithTy(NormalizedType& here, TypeId t else if (get(here.tops)) return NormalizationResult::True; else if (get(there) || get(there) || get(there) || get(there) || - get(there)) + get(there)) { if (tyvarIndex(there) <= ignoreSmallerTyvars) return NormalizationResult::True; @@ -1872,7 +1872,7 @@ NormalizationResult Normalizer::unionNormalWithTy(NormalizedType& here, TypeId t if (res != NormalizationResult::True) return res; } - else if (get(there) || get(there)) + else if (get(there) || get(there)) { // nothing } @@ -3080,7 +3080,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(NormalizedType& here, Type return NormalizationResult::True; } else if (get(there) || get(there) || get(there) || get(there) || - get(there)) + get(there)) { NormalizedType thereNorm{builtinTypes}; NormalizedType topNorm{builtinTypes}; diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index 8bbdfec4..3cc0bc64 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -5,7 +5,7 @@ #include "Luau/Subtyping.h" #include "Luau/TxnLog.h" #include "Luau/Type.h" -#include "Luau/TypeFamily.h" +#include "Luau/TypeFunction.h" #include "Luau/TypePack.h" #include "Luau/TypeUtils.h" #include "Luau/Unifier2.h" @@ -175,14 +175,15 @@ bool OverloadResolver::isLiteral(AstExpr* expr) std::pair OverloadResolver::checkOverload_( TypeId fnTy, const FunctionType* fn, const TypePack* args, AstExpr* fnExpr, const std::vector* argExprs) { - FamilyGraphReductionResult result = - reduceFamilies(fnTy, callLoc, TypeFamilyContext{arena, builtinTypes, scope, normalizer, ice, limits}, /*force=*/true); + FunctionGraphReductionResult result = + reduceTypeFunctions(fnTy, callLoc, TypeFunctionContext{arena, builtinTypes, scope, normalizer, ice, limits}, /*force=*/true); if (!result.errors.empty()) return {OverloadIsNonviable, result.errors}; ErrorVec argumentErrors; + TypePackId typ = arena->addTypePack(*args); - TypeId prospectiveFunction = arena->addType(FunctionType{arena->addTypePack(*args), builtinTypes->anyTypePack}); + TypeId prospectiveFunction = arena->addType(FunctionType{typ, builtinTypes->anyTypePack}); SubtypingResult sr = subtyping.isSubtype(fnTy, prospectiveFunction); if (sr.isSubtype) @@ -253,7 +254,14 @@ std::pair OverloadResolver::checkOverload_ if (const Luau::TypePath::Index* pathIndexComponent = get_if(&reason.superPath.components.at(1))) { size_t nthArgument = pathIndexComponent->index; - argLocation = argExprs->at(nthArgument)->location; + // if the nth type argument to the function is less than the number of ast expressions we passed to the function + // we should be able to pull out the location of the argument + // If the nth type argument to the function is out of range of the ast expressions we passed to the function + // e.g. table.pack(functionThatReturnsMultipleArguments(arg1, arg2, ....)), default to the location of the last passed expression + // If we passed no expression arguments to the call, default to the location of the function expression. + argLocation = nthArgument < argExprs->size() ? argExprs->at(nthArgument)->location + : argExprs->size() != 0 ? argExprs->back()->location + : fnExpr->location; std::optional failedSubTy = traverseForType(fnTy, reason.subPath, builtinTypes); std::optional failedSuperTy = traverseForType(prospectiveFunction, reason.superPath, builtinTypes); diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index 91103ccb..fae4b60c 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -125,9 +125,9 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a } else if constexpr (std::is_same_v) return dest.addType(NegationType{a.ty}); - else if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) { - TypeFamilyInstanceType clone{a.family, a.typeArguments, a.packArguments}; + TypeFunctionInstanceType clone{a.family, a.typeArguments, a.packArguments}; return dest.addType(std::move(clone)); } else @@ -226,7 +226,7 @@ void Tarjan::visitChildren(TypeId ty, int index) for (TypePackId a : petv->packArguments) visitChild(a); } - else if (const TypeFamilyInstanceType* tfit = get(ty)) + else if (const TypeFunctionInstanceType* tfit = get(ty)) { for (TypeId a : tfit->typeArguments) visitChild(a); @@ -669,9 +669,9 @@ TypePackId Substitution::clone(TypePackId tp) clone.hidden = vtp->hidden; return addTypePack(std::move(clone)); } - else if (const TypeFamilyInstanceTypePack* tfitp = get(tp)) + else if (const TypeFunctionInstanceTypePack* tfitp = get(tp)) { - TypeFamilyInstanceTypePack clone{ + TypeFunctionInstanceTypePack clone{ tfitp->family, std::vector(tfitp->typeArguments.size()), std::vector(tfitp->packArguments.size())}; clone.typeArguments.assign(tfitp->typeArguments.begin(), tfitp->typeArguments.end()); clone.packArguments.assign(tfitp->packArguments.begin(), tfitp->packArguments.end()); @@ -798,7 +798,7 @@ void Substitution::replaceChildren(TypeId ty) for (TypePackId& a : petv->packArguments) a = replace(a); } - else if (TypeFamilyInstanceType* tfit = getMutable(ty)) + else if (TypeFunctionInstanceType* tfit = getMutable(ty)) { for (TypeId& a : tfit->typeArguments) a = replace(a); @@ -850,7 +850,7 @@ void Substitution::replaceChildren(TypePackId tp) { vtp->ty = replace(vtp->ty); } - else if (TypeFamilyInstanceTypePack* tfitp = getMutable(tp)) + else if (TypeFunctionInstanceTypePack* tfitp = getMutable(tp)) { for (TypeId& t : tfitp->typeArguments) t = replace(t); diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index 040c3fc6..d6b4e31d 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -13,7 +13,7 @@ #include "Luau/Type.h" #include "Luau/TypeArena.h" #include "Luau/TypeCheckLimits.h" -#include "Luau/TypeFamily.h" +#include "Luau/TypeFunction.h" #include "Luau/TypePack.h" #include "Luau/TypePath.h" #include "Luau/TypeUtils.h" @@ -121,9 +121,9 @@ SubtypingResult& SubtypingResult::andAlso(const SubtypingResult& other) { // If the other result is not a subtype, we want to join all of its // reasonings to this one. If this result already has reasonings of its own, - // those need to be attributed here. + // those need to be attributed here whenever this _also_ failed. if (!other.isSubtype) - reasoning = mergeReasonings(reasoning, other.reasoning); + reasoning = isSubtype ? std::move(other.reasoning) : mergeReasonings(reasoning, other.reasoning); isSubtype &= other.isSubtype; normalizationTooComplex |= other.normalizationTooComplex; @@ -579,19 +579,19 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub } } } - else if (auto subTypeFamilyInstance = get(subTy)) + else if (auto subTypeFunctionInstance = get(subTy)) { if (auto substSubTy = env.applyMappedGenerics(builtinTypes, arena, subTy)) - subTypeFamilyInstance = get(*substSubTy); + subTypeFunctionInstance = get(*substSubTy); - result = isCovariantWith(env, subTypeFamilyInstance, superTy); + result = isCovariantWith(env, subTypeFunctionInstance, superTy); } - else if (auto superTypeFamilyInstance = get(superTy)) + else if (auto superTypeFunctionInstance = get(superTy)) { if (auto substSuperTy = env.applyMappedGenerics(builtinTypes, arena, superTy)) - superTypeFamilyInstance = get(*substSuperTy); + superTypeFunctionInstance = get(*substSuperTy); - result = isCovariantWith(env, subTy, superTypeFamilyInstance); + result = isCovariantWith(env, subTy, superTypeFunctionInstance); } else if (auto subGeneric = get(subTy); subGeneric && variance == Variance::Covariant) { @@ -1584,19 +1584,19 @@ bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId supe return true; } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeFamilyInstanceType* subFamilyInstance, const TypeId superTy) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeFunctionInstanceType* subFamilyInstance, const TypeId superTy) { - // Reduce the typefamily instance - auto [ty, errors] = handleTypeFamilyReductionResult(subFamilyInstance); + // Reduce the type function instance + auto [ty, errors] = handleTypeFunctionReductionResult(subFamilyInstance); - // If we return optional, that means the type family was irreducible - we can reduce that to never + // If we return optional, that means the type function was irreducible - we can reduce that to never return isCovariantWith(env, ty, superTy).withErrors(errors).withSubComponent(TypePath::Reduction{ty}); } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFamilyInstanceType* superFamilyInstance) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFunctionInstanceType* superFamilyInstance) { - // Reduce the typefamily instance - auto [ty, errors] = handleTypeFamilyReductionResult(superFamilyInstance); + // Reduce the type function instance + auto [ty, errors] = handleTypeFunctionReductionResult(superFamilyInstance); return isCovariantWith(env, subTy, ty).withErrors(errors).withSuperComponent(TypePath::Reduction{ty}); } @@ -1632,15 +1632,15 @@ TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse) return arena->addType(T{std::vector(begin(container), end(container))}); } -std::pair Subtyping::handleTypeFamilyReductionResult(const TypeFamilyInstanceType* familyInstance) +std::pair Subtyping::handleTypeFunctionReductionResult(const TypeFunctionInstanceType* familyInstance) { - TypeFamilyContext context{arena, builtinTypes, scope, normalizer, iceReporter, NotNull{&limits}}; + TypeFunctionContext context{arena, builtinTypes, scope, normalizer, iceReporter, NotNull{&limits}}; TypeId family = arena->addType(*familyInstance); - FamilyGraphReductionResult result = reduceFamilies(family, {}, context, true); + FunctionGraphReductionResult result = reduceTypeFunctions(family, {}, context, true); ErrorVec errors; if (result.blockedTypes.size() != 0 || result.blockedPacks.size() != 0) { - errors.push_back(TypeError{{}, UninhabitedTypeFamily{family}}); + errors.push_back(TypeError{{}, UninhabitedTypeFunction{family}}); return {builtinTypes->neverType, errors}; } if (result.reducedTypes.contains(family)) diff --git a/Analysis/src/ToDot.cpp b/Analysis/src/ToDot.cpp index 17b595b1..055ed7ad 100644 --- a/Analysis/src/ToDot.cpp +++ b/Analysis/src/ToDot.cpp @@ -4,7 +4,7 @@ #include "Luau/ToString.h" #include "Luau/TypePack.h" #include "Luau/Type.h" -#include "Luau/TypeFamily.h" +#include "Luau/TypeFunction.h" #include "Luau/StringUtils.h" #include @@ -343,9 +343,9 @@ void StateDot::visitChildren(TypeId ty, int index) visitChild(t.ty, index, "[negated]"); } - else if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) { - formatAppend(result, "TypeFamilyInstanceType %s %d", t.family->name.c_str(), index); + formatAppend(result, "TypeFunctionInstanceType %s %d", t.family->name.c_str(), index); finishNodeLabel(ty); finishNode(); diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index dca041a2..3440c3d7 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -11,7 +11,7 @@ #include "Luau/TypeInfer.h" #include "Luau/TypePack.h" #include "Luau/Type.h" -#include "Luau/TypeFamily.h" +#include "Luau/TypeFunction.h" #include "Luau/VisitType.h" #include "Luau/TypeOrPack.h" @@ -1031,7 +1031,7 @@ struct TypeStringifier state.emit(")"); } - void operator()(TypeId, const TypeFamilyInstanceType& tfitv) + void operator()(TypeId, const TypeFunctionInstanceType& tfitv) { state.emit(tfitv.family->name); state.emit("<"); @@ -1232,7 +1232,7 @@ struct TypePackStringifier state.emit("*"); } - void operator()(TypePackId, const TypeFamilyInstanceTypePack& tfitp) + void operator()(TypePackId, const TypeFunctionInstanceTypePack& tfitp) { state.emit(tfitp.family->name); state.emit("<"); diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index 71cac6fd..6735e367 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -9,7 +9,7 @@ #include "Luau/RecursionCounter.h" #include "Luau/StringUtils.h" #include "Luau/ToString.h" -#include "Luau/TypeFamily.h" +#include "Luau/TypeFunction.h" #include "Luau/TypeInfer.h" #include "Luau/TypePack.h" #include "Luau/VecDeque.h" @@ -423,7 +423,7 @@ bool maybeSingleton(TypeId ty) for (TypeId part : itv) if (maybeSingleton(part)) // will i regret this? return true; - if (const TypeFamilyInstanceType* tfit = get(ty)) + if (const TypeFunctionInstanceType* tfit = get(ty)) if (tfit->family->name == "keyof" || tfit->family->name == "rawkeyof") return true; return false; @@ -1081,7 +1081,7 @@ void persist(TypeId ty) else if (get(t) || get(t) || get(t) || get(t) || get(t) || get(t)) { } - else if (auto tfit = get(t)) + else if (auto tfit = get(t)) { for (auto ty : tfit->typeArguments) queue.push_back(ty); @@ -1117,7 +1117,7 @@ void persist(TypePackId tp) else if (get(tp)) { } - else if (auto tfitp = get(tp)) + else if (auto tfitp = get(tp)) { for (auto ty : tfitp->typeArguments) persist(ty); diff --git a/Analysis/src/TypeArena.cpp b/Analysis/src/TypeArena.cpp index 03c1e99a..ef19196c 100644 --- a/Analysis/src/TypeArena.cpp +++ b/Analysis/src/TypeArena.cpp @@ -94,24 +94,24 @@ TypePackId TypeArena::addTypePack(TypePackVar tp) return allocated; } -TypeId TypeArena::addTypeFamily(const TypeFamily& family, std::initializer_list types) +TypeId TypeArena::addTypeFunction(const TypeFunction& family, std::initializer_list types) { - return addType(TypeFamilyInstanceType{family, std::move(types)}); + return addType(TypeFunctionInstanceType{family, std::move(types)}); } -TypeId TypeArena::addTypeFamily(const TypeFamily& family, std::vector typeArguments, std::vector packArguments) +TypeId TypeArena::addTypeFunction(const TypeFunction& family, std::vector typeArguments, std::vector packArguments) { - return addType(TypeFamilyInstanceType{family, std::move(typeArguments), std::move(packArguments)}); + return addType(TypeFunctionInstanceType{family, std::move(typeArguments), std::move(packArguments)}); } -TypePackId TypeArena::addTypePackFamily(const TypePackFamily& family, std::initializer_list types) +TypePackId TypeArena::addTypePackFunction(const TypePackFunction& family, std::initializer_list types) { - return addTypePack(TypeFamilyInstanceTypePack{NotNull{&family}, std::move(types)}); + return addTypePack(TypeFunctionInstanceTypePack{NotNull{&family}, std::move(types)}); } -TypePackId TypeArena::addTypePackFamily(const TypePackFamily& family, std::vector typeArguments, std::vector packArguments) +TypePackId TypeArena::addTypePackFunction(const TypePackFunction& family, std::vector typeArguments, std::vector packArguments) { - return addTypePack(TypeFamilyInstanceTypePack{NotNull{&family}, std::move(typeArguments), std::move(packArguments)}); + return addTypePack(TypeFunctionInstanceTypePack{NotNull{&family}, std::move(typeArguments), std::move(packArguments)}); } void freeze(TypeArena& arena) diff --git a/Analysis/src/TypeAttach.cpp b/Analysis/src/TypeAttach.cpp index c0294fc9..6dc5b02d 100644 --- a/Analysis/src/TypeAttach.cpp +++ b/Analysis/src/TypeAttach.cpp @@ -9,7 +9,7 @@ #include "Luau/TypeInfer.h" #include "Luau/TypePack.h" #include "Luau/Type.h" -#include "Luau/TypeFamily.h" +#include "Luau/TypeFunction.h" #include @@ -380,7 +380,7 @@ public: // FIXME: do the same thing we do with ErrorType throw InternalCompilerError("Cannot convert NegationType into AstNode"); } - AstType* operator()(const TypeFamilyInstanceType& tfit) + AstType* operator()(const TypeFunctionInstanceType& tfit) { return allocator->alloc(Location(), std::nullopt, AstName{tfit.family->name.c_str()}, std::nullopt, Location()); } @@ -454,7 +454,7 @@ public: return allocator->alloc(Location(), AstName("Unifiable")); } - AstTypePack* operator()(const TypeFamilyInstanceTypePack& tfitp) const + AstTypePack* operator()(const TypeFunctionInstanceTypePack& tfitp) const { return allocator->alloc(Location(), AstName(tfitp.family->name.c_str())); } diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index c53a5d30..97cec176 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -16,8 +16,8 @@ #include "Luau/ToString.h" #include "Luau/TxnLog.h" #include "Luau/Type.h" -#include "Luau/TypeFamily.h" -#include "Luau/TypeFamilyReductionGuesser.h" +#include "Luau/TypeFunction.h" +#include "Luau/TypeFunctionReductionGuesser.h" #include "Luau/TypeFwd.h" #include "Luau/TypePack.h" #include "Luau/TypePath.h" @@ -121,13 +121,13 @@ struct FamilyFinder : TypeOnceVisitor DenseHashSet mentionedFamilies{nullptr}; DenseHashSet mentionedFamilyPacks{nullptr}; - bool visit(TypeId ty, const TypeFamilyInstanceType&) override + bool visit(TypeId ty, const TypeFunctionInstanceType&) override { mentionedFamilies.insert(ty); return true; } - bool visit(TypePackId tp, const TypeFamilyInstanceTypePack&) override + bool visit(TypePackId tp, const TypeFunctionInstanceTypePack&) override { mentionedFamilyPacks.insert(tp); return true; @@ -151,7 +151,7 @@ struct InternalFamilyFinder : TypeOnceVisitor mentionedFamilyPacks = std::move(f.mentionedFamilyPacks); } - bool visit(TypeId ty, const TypeFamilyInstanceType& tfit) override + bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override { bool hasGeneric = false; @@ -177,7 +177,7 @@ struct InternalFamilyFinder : TypeOnceVisitor { for (TypeId mentioned : mentionedFamilies) { - const TypeFamilyInstanceType* mentionedTfit = get(mentioned); + const TypeFunctionInstanceType* mentionedTfit = get(mentioned); LUAU_ASSERT(mentionedTfit); if (areEquivalent(tfit, *mentionedTfit)) { @@ -191,7 +191,7 @@ struct InternalFamilyFinder : TypeOnceVisitor return true; } - bool visit(TypePackId tp, const TypeFamilyInstanceTypePack& tfitp) override + bool visit(TypePackId tp, const TypeFunctionInstanceTypePack& tfitp) override { bool hasGeneric = false; @@ -217,7 +217,7 @@ struct InternalFamilyFinder : TypeOnceVisitor { for (TypePackId mentioned : mentionedFamilyPacks) { - const TypeFamilyInstanceTypePack* mentionedTfitp = get(mentioned); + const TypeFunctionInstanceTypePack* mentionedTfitp = get(mentioned); LUAU_ASSERT(mentionedTfitp); if (areEquivalent(tfitp, *mentionedTfitp)) { @@ -245,7 +245,7 @@ struct TypeChecker2 std::vector> stack; std::vector functionDeclStack; - DenseHashSet seenTypeFamilyInstances{nullptr}; + DenseHashSet seenTypeFunctionInstances{nullptr}; Normalizer normalizer; Subtyping _subtyping; @@ -437,12 +437,12 @@ struct TypeChecker2 TypeId checkForFamilyInhabitance(TypeId instance, Location location) { - if (seenTypeFamilyInstances.find(instance)) + if (seenTypeFunctionInstances.find(instance)) return instance; - seenTypeFamilyInstances.insert(instance); + seenTypeFunctionInstances.insert(instance); - ErrorVec errors = reduceFamilies(instance, location, - TypeFamilyContext{NotNull{&module->internalTypes}, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true) + ErrorVec errors = reduceTypeFunctions(instance, location, + TypeFunctionContext{NotNull{&module->internalTypes}, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true) .errors; if (!isErrorSuppressing(location, instance)) reportErrors(std::move(errors)); @@ -1731,12 +1731,12 @@ struct TypeChecker2 const FunctionType* inferredFtv = get(normalizedFnTy->functions.parts.front()); LUAU_ASSERT(inferredFtv); - TypeFamilyReductionGuesser guesser{NotNull{&module->internalTypes}, builtinTypes, NotNull{&normalizer}}; + TypeFunctionReductionGuesser guesser{NotNull{&module->internalTypes}, builtinTypes, NotNull{&normalizer}}; for (TypeId retTy : inferredFtv->retTypes) { - if (get(follow(retTy))) + if (get(follow(retTy))) { - TypeFamilyReductionGuessResult result = guesser.guessTypeFamilyReductionForFunction(*fn, inferredFtv, retTy); + TypeFunctionReductionGuessResult result = guesser.guessTypeFunctionReductionForFunctionExpr(*fn, inferredFtv, retTy); if (result.shouldRecommendAnnotation) reportError(ExplicitFunctionAnnotationRecommended{std::move(result.guessedFunctionAnnotations), result.guessedReturnType}, fn->location); @@ -1853,7 +1853,7 @@ struct TypeChecker2 TypeId rightType = follow(lookupType(expr->right)); TypeId expectedResult = follow(lookupType(expr)); - if (get(expectedResult)) + if (get(expectedResult)) { checkForInternalFamily(expectedResult, expr->location); return expectedResult; @@ -1954,7 +1954,7 @@ struct TypeChecker2 if (!selectedOverloadTy) { // reportError(CodeTooComplex{}, expr->location); - // was handled by a type family + // was handled by a type function return expectedResult; } diff --git a/Analysis/src/TypeFamily.cpp b/Analysis/src/TypeFunction.cpp similarity index 83% rename from Analysis/src/TypeFamily.cpp rename to Analysis/src/TypeFunction.cpp index 816cf005..136eac69 100644 --- a/Analysis/src/TypeFamily.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -1,6 +1,6 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Luau/TypeFamily.h" +#include "Luau/TypeFunction.h" #include "Luau/Common.h" #include "Luau/ConstraintSolver.h" @@ -15,7 +15,7 @@ #include "Luau/ToString.h" #include "Luau/TxnLog.h" #include "Luau/Type.h" -#include "Luau/TypeFamilyReductionGuesser.h" +#include "Luau/TypeFunctionReductionGuesser.h" #include "Luau/TypeFwd.h" #include "Luau/TypeUtils.h" #include "Luau/Unifier2.h" @@ -24,10 +24,10 @@ #include -// used to control emitting CodeTooComplex warnings on type family reduction +// used to control emitting CodeTooComplex warnings on type function reduction LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyGraphReductionMaximumSteps, 1'000'000); -// used to control the limits of type family application over union type arguments +// used to control the limits of type function application over union type arguments // e.g. `mul` blows up into `mul | mul | mul | mul` LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyApplicationCartesianProductLimit, 5'000); @@ -49,7 +49,7 @@ struct InstanceCollector : TypeOnceVisitor TypeOrTypePackIdSet shouldGuess{nullptr}; std::vector cyclicInstance; - bool visit(TypeId ty, const TypeFamilyInstanceType&) override + bool visit(TypeId ty, const TypeFunctionInstanceType&) override { // TypeOnceVisitor performs a depth-first traversal in the absence of // cycles. This means that by pushing to the front of the queue, we will @@ -58,7 +58,7 @@ struct InstanceCollector : TypeOnceVisitor // we want to reduce the innermost Add instantiation // first. - if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && typeFamilyDepth > DFInt::LuauTypeFamilyUseGuesserDepth) + if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && typeFunctionDepth > DFInt::LuauTypeFamilyUseGuesserDepth) shouldGuess.insert(ty); tys.push_front(ty); @@ -70,7 +70,7 @@ struct InstanceCollector : TypeOnceVisitor { /// Detected cyclic type pack TypeId t = follow(ty); - if (get(t)) + if (get(t)) cyclicInstance.push_back(t); } @@ -79,7 +79,7 @@ struct InstanceCollector : TypeOnceVisitor return false; } - bool visit(TypePackId tp, const TypeFamilyInstanceTypePack&) override + bool visit(TypePackId tp, const TypeFunctionInstanceTypePack&) override { // TypeOnceVisitor performs a depth-first traversal in the absence of // cycles. This means that by pushing to the front of the queue, we will @@ -88,7 +88,7 @@ struct InstanceCollector : TypeOnceVisitor // we want to reduce the innermost Add instantiation // first. - if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && typeFamilyDepth > DFInt::LuauTypeFamilyUseGuesserDepth) + if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && typeFunctionDepth > DFInt::LuauTypeFamilyUseGuesserDepth) shouldGuess.insert(tp); tps.push_front(tp); @@ -97,23 +97,23 @@ struct InstanceCollector : TypeOnceVisitor } }; -struct FamilyReducer +struct TypeFunctionReducer { - TypeFamilyContext ctx; + TypeFunctionContext ctx; VecDeque queuedTys; VecDeque queuedTps; TypeOrTypePackIdSet shouldGuess; std::vector cyclicTypeFamilies; TypeOrTypePackIdSet irreducible{nullptr}; - FamilyGraphReductionResult result; + FunctionGraphReductionResult result; bool force = false; // Local to the constraint being reduced. Location location; - FamilyReducer(VecDeque queuedTys, VecDeque queuedTps, TypeOrTypePackIdSet shouldGuess, std::vector cyclicTypes, - Location location, TypeFamilyContext ctx, bool force = false) + TypeFunctionReducer(VecDeque queuedTys, VecDeque queuedTps, TypeOrTypePackIdSet shouldGuess, std::vector cyclicTypes, + Location location, TypeFunctionContext ctx, bool force = false) : ctx(ctx) , queuedTys(std::move(queuedTys)) , queuedTps(std::move(queuedTps)) @@ -126,7 +126,7 @@ struct FamilyReducer enum class SkipTestResult { - CyclicTypeFamily, + CyclicTypeFunction, Irreducible, Defer, Okay, @@ -136,12 +136,12 @@ struct FamilyReducer { ty = follow(ty); - if (is(ty)) + if (is(ty)) { for (auto t : cyclicTypeFamilies) { if (ty == t) - return SkipTestResult::CyclicTypeFamily; + return SkipTestResult::CyclicTypeFunction; } if (!irreducible.contains(ty)) @@ -161,7 +161,7 @@ struct FamilyReducer { ty = follow(ty); - if (is(ty)) + if (is(ty)) { if (!irreducible.contains(ty)) return SkipTestResult::Defer; @@ -181,7 +181,7 @@ struct FamilyReducer { if (subject->owningArena != ctx.arena.get()) { - result.errors.emplace_back(location, InternalError{"Attempting to modify a type family instance from another arena"}); + result.errors.emplace_back(location, InternalError{"Attempting to modify a type function instance from another arena"}); return; } @@ -197,7 +197,7 @@ struct FamilyReducer } template - void handleFamilyReduction(T subject, TypeFamilyReductionResult reduction) + void handleTypeFunctionReduction(T subject, TypeFunctionReductionResult reduction) { if (reduction.result) replace(subject, *reduction.result); @@ -211,9 +211,9 @@ struct FamilyReducer printf("%s is uninhabited\n", toString(subject, {true}).c_str()); if constexpr (std::is_same_v) - result.errors.push_back(TypeError{location, UninhabitedTypeFamily{subject}}); + result.errors.push_back(TypeError{location, UninhabitedTypeFunction{subject}}); else if constexpr (std::is_same_v) - result.errors.push_back(TypeError{location, UninhabitedTypePackFamily{subject}}); + result.errors.push_back(TypeError{location, UninhabitedTypePackFunction{subject}}); } else if (!reduction.uninhabited && !force) { @@ -301,7 +301,7 @@ struct FamilyReducer if (FFlag::DebugLuauLogTypeFamilies) printf("Flagged %s for reduction with guesser.\n", toString(subject, {true}).c_str()); - TypeFamilyReductionGuesser guesser{ctx.arena, ctx.builtins, ctx.normalizer}; + TypeFunctionReductionGuesser guesser{ctx.arena, ctx.builtins, ctx.normalizer}; auto guessed = guesser.guess(subject); if (guessed) @@ -332,11 +332,11 @@ struct FamilyReducer if (FFlag::DebugLuauLogTypeFamilies) printf("Trying to reduce %s\n", toString(subject, {true}).c_str()); - if (const TypeFamilyInstanceType* tfit = get(subject)) + if (const TypeFunctionInstanceType* tfit = get(subject)) { SkipTestResult testCyclic = testForSkippability(subject); - if (!testParameters(subject, tfit) && testCyclic != SkipTestResult::CyclicTypeFamily) + if (!testParameters(subject, tfit) && testCyclic != SkipTestResult::CyclicTypeFunction) { if (FFlag::DebugLuauLogTypeFamilies) printf("Irreducible due to irreducible/pending and a non-cyclic family\n"); @@ -347,8 +347,8 @@ struct FamilyReducer if (tryGuessing(subject)) return; - TypeFamilyReductionResult result = tfit->family->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); - handleFamilyReduction(subject, result); + TypeFunctionReductionResult result = tfit->family->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); + handleTypeFunctionReduction(subject, result); } } @@ -363,7 +363,7 @@ struct FamilyReducer if (FFlag::DebugLuauLogTypeFamilies) printf("Trying to reduce %s\n", toString(subject, {true}).c_str()); - if (const TypeFamilyInstanceTypePack* tfit = get(subject)) + if (const TypeFunctionInstanceTypePack* tfit = get(subject)) { if (!testParameters(subject, tfit)) return; @@ -371,8 +371,8 @@ struct FamilyReducer if (tryGuessing(subject)) return; - TypeFamilyReductionResult result = tfit->family->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); - handleFamilyReduction(subject, result); + TypeFunctionReductionResult result = tfit->family->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); + handleTypeFunctionReduction(subject, result); } } @@ -385,10 +385,10 @@ struct FamilyReducer } }; -static FamilyGraphReductionResult reduceFamiliesInternal(VecDeque queuedTys, VecDeque queuedTps, TypeOrTypePackIdSet shouldGuess, - std::vector cyclics, Location location, TypeFamilyContext ctx, bool force) +static FunctionGraphReductionResult reduceFamiliesInternal(VecDeque queuedTys, VecDeque queuedTps, TypeOrTypePackIdSet shouldGuess, + std::vector cyclics, Location location, TypeFunctionContext ctx, bool force) { - FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(shouldGuess), std::move(cyclics), location, ctx, force}; + TypeFunctionReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(shouldGuess), std::move(cyclics), location, ctx, force}; int iterationCount = 0; while (!reducer.done()) @@ -406,7 +406,7 @@ static FamilyGraphReductionResult reduceFamiliesInternal(VecDeque queued return std::move(reducer.result); } -FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, TypeFamilyContext ctx, bool force) +FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext ctx, bool force) { InstanceCollector collector; @@ -416,7 +416,7 @@ FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, } catch (RecursionLimitException&) { - return FamilyGraphReductionResult{}; + return FunctionGraphReductionResult{}; } if (collector.tys.empty() && collector.tps.empty()) @@ -426,7 +426,7 @@ FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, std::move(collector.cyclicInstance), location, ctx, force); } -FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, TypeFamilyContext ctx, bool force) +FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location location, TypeFunctionContext ctx, bool force) { InstanceCollector collector; @@ -436,7 +436,7 @@ FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location locati } catch (RecursionLimitException&) { - return FamilyGraphReductionResult{}; + return FunctionGraphReductionResult{}; } if (collector.tys.empty() && collector.tps.empty()) @@ -448,12 +448,12 @@ FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location locati bool isPending(TypeId ty, ConstraintSolver* solver) { - return is(ty) || (solver && solver->hasUnresolvedConstraints(ty)); + return is(ty) || (solver && solver->hasUnresolvedConstraints(ty)); } template -static std::optional> tryDistributeTypeFamilyApp(F f, TypeId instance, const std::vector& typeParams, - const std::vector& packParams, NotNull ctx, Args&&... args) +static std::optional> tryDistributeTypeFunctionApp(F f, TypeId instance, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx, Args&&... args) { // op (a | b) (c | d) ~ (op a (c | d)) | (op b (c | d)) ~ (op a c) | (op a d) | (op b c) | (op b d) bool uninhabited = false; @@ -483,7 +483,7 @@ static std::optional> tryDistributeTypeFamilyA cartesianProductSize *= std::distance(begin(ut), end(ut)); - // TODO: We'd like to report that the type family application is too complex here. + // TODO: We'd like to report that the type function application is too complex here. if (size_t(DFInt::LuauTypeFamilyApplicationCartesianProductLimit) <= cartesianProductSize) return {{std::nullopt, true, {}, {}}}; } @@ -498,7 +498,7 @@ static std::optional> tryDistributeTypeFamilyA { arguments[unionIndex] = option; - TypeFamilyReductionResult result = f(instance, arguments, packParams, ctx, args...); + TypeFunctionReductionResult result = f(instance, arguments, packParams, ctx, args...); blockedTypes.insert(blockedTypes.end(), result.blockedTypes.begin(), result.blockedTypes.end()); uninhabited |= result.uninhabited; @@ -516,8 +516,8 @@ static std::optional> tryDistributeTypeFamilyA if (results.size() == 1) return {{results[0], false, {}, {}}}; - TypeId resultTy = ctx->arena->addType(TypeFamilyInstanceType{ - NotNull{&builtinTypeFunctions().unionFamily}, + TypeId resultTy = ctx->arena->addType(TypeFunctionInstanceType{ + NotNull{&builtinTypeFunctions().unionFunc}, std::move(results), {}, }); @@ -528,12 +528,12 @@ static std::optional> tryDistributeTypeFamilyA return std::nullopt; } -TypeFamilyReductionResult notFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult notFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 1 || !packParams.empty()) { - ctx->ice->ice("not type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("not type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } @@ -545,19 +545,19 @@ TypeFamilyReductionResult notFamilyFn( if (isPending(ty, ctx->solver)) return {std::nullopt, false, {ty}, {}}; - if (auto result = tryDistributeTypeFamilyApp(notFamilyFn, instance, typeParams, packParams, ctx)) + if (auto result = tryDistributeTypeFunctionApp(notFamilyFn, instance, typeParams, packParams, ctx)) return *result; // `not` operates on anything and returns a `boolean` always. return {ctx->builtins->booleanType, false, {}, {}}; } -TypeFamilyReductionResult lenFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult lenFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 1 || !packParams.empty()) { - ctx->ice->ice("len type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("len type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } @@ -604,7 +604,7 @@ TypeFamilyReductionResult lenFamilyFn( if (normTy->hasTopTable() || get(normalizedOperand)) return {ctx->builtins->numberType, false, {}, {}}; - if (auto result = tryDistributeTypeFamilyApp(notFamilyFn, instance, typeParams, packParams, ctx)) + if (auto result = tryDistributeTypeFunctionApp(notFamilyFn, instance, typeParams, packParams, ctx)) return *result; // findMetatableEntry demands the ability to emit errors, so we must give it @@ -644,12 +644,12 @@ TypeFamilyReductionResult lenFamilyFn( return {ctx->builtins->numberType, false, {}, {}}; } -TypeFamilyReductionResult unmFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult unmFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 1 || !packParams.empty()) { - ctx->ice->ice("unm type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("unm type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } @@ -689,7 +689,7 @@ TypeFamilyReductionResult unmFamilyFn( if (normTy->isExactlyNumber()) return {ctx->builtins->numberType, false, {}, {}}; - if (auto result = tryDistributeTypeFamilyApp(notFamilyFn, instance, typeParams, packParams, ctx)) + if (auto result = tryDistributeTypeFunctionApp(notFamilyFn, instance, typeParams, packParams, ctx)) return *result; // findMetatableEntry demands the ability to emit errors, so we must give it @@ -731,7 +731,7 @@ TypeFamilyReductionResult unmFamilyFn( return {std::nullopt, true, {}, {}}; } -NotNull TypeFamilyContext::pushConstraint(ConstraintV&& c) +NotNull TypeFunctionContext::pushConstraint(ConstraintV&& c) { LUAU_ASSERT(solver); NotNull newConstraint = solver->pushConstraint(scope, constraint ? constraint->location : Location{}, std::move(c)); @@ -744,12 +744,12 @@ NotNull TypeFamilyContext::pushConstraint(ConstraintV&& c) return newConstraint; } -TypeFamilyReductionResult numericBinopFamilyFn(TypeId instance, const std::vector& typeParams, - const std::vector& packParams, NotNull ctx, const std::string metamethod) +TypeFunctionReductionResult numericBinopFamilyFn(TypeId instance, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx, const std::string metamethod) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("encountered a type family instance without the required argument structure"); + ctx->ice->ice("encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } @@ -803,7 +803,7 @@ TypeFamilyReductionResult numericBinopFamilyFn(TypeId instance, const st if (normLhsTy->isExactlyNumber() && normRhsTy->isExactlyNumber()) return {ctx->builtins->numberType, false, {}, {}}; - if (auto result = tryDistributeTypeFamilyApp(numericBinopFamilyFn, instance, typeParams, packParams, ctx, metamethod)) + if (auto result = tryDistributeTypeFunctionApp(numericBinopFamilyFn, instance, typeParams, packParams, ctx, metamethod)) return *result; // findMetatableEntry demands the ability to emit errors, so we must give it @@ -847,96 +847,96 @@ TypeFamilyReductionResult numericBinopFamilyFn(TypeId instance, const st return {extracted.head.front(), false, {}, {}}; } -TypeFamilyReductionResult addFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult addFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("add type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("add type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__add"); } -TypeFamilyReductionResult subFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult subFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("sub type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("sub type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__sub"); } -TypeFamilyReductionResult mulFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult mulFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("mul type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("mul type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__mul"); } -TypeFamilyReductionResult divFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult divFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("div type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("div type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__div"); } -TypeFamilyReductionResult idivFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult idivFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("integer div type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("integer div type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__idiv"); } -TypeFamilyReductionResult powFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult powFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("pow type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("pow type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__pow"); } -TypeFamilyReductionResult modFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult modFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("modulo type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("modulo type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__mod"); } -TypeFamilyReductionResult concatFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult concatFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("concat type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("concat type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } @@ -987,7 +987,7 @@ TypeFamilyReductionResult concatFamilyFn( if ((normLhsTy->isSubtypeOfString() || normLhsTy->isExactlyNumber()) && (normRhsTy->isSubtypeOfString() || normRhsTy->isExactlyNumber())) return {ctx->builtins->stringType, false, {}, {}}; - if (auto result = tryDistributeTypeFamilyApp(concatFamilyFn, instance, typeParams, packParams, ctx)) + if (auto result = tryDistributeTypeFunctionApp(concatFamilyFn, instance, typeParams, packParams, ctx)) return *result; // findMetatableEntry demands the ability to emit errors, so we must give it @@ -1039,12 +1039,12 @@ TypeFamilyReductionResult concatFamilyFn( return {ctx->builtins->stringType, false, {}, {}}; } -TypeFamilyReductionResult andFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult andFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("and type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("and type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } @@ -1090,12 +1090,12 @@ TypeFamilyReductionResult andFamilyFn( return {overallResult.result, false, std::move(blockedTypes), {}}; } -TypeFamilyReductionResult orFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult orFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("or type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("or type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } @@ -1141,13 +1141,13 @@ TypeFamilyReductionResult orFamilyFn( return {overallResult.result, false, std::move(blockedTypes), {}}; } -static TypeFamilyReductionResult comparisonFamilyFn(TypeId instance, const std::vector& typeParams, - const std::vector& packParams, NotNull ctx, const std::string metamethod) +static TypeFunctionReductionResult comparisonFamilyFn(TypeId instance, const std::vector& typeParams, + const std::vector& packParams, NotNull ctx, const std::string metamethod) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("encountered a type family instance without the required argument structure"); + ctx->ice->ice("encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } @@ -1238,7 +1238,7 @@ static TypeFamilyReductionResult comparisonFamilyFn(TypeId instance, con if (normLhsTy->isExactlyNumber() && normRhsTy->isExactlyNumber()) return {ctx->builtins->booleanType, false, {}, {}}; - if (auto result = tryDistributeTypeFamilyApp(comparisonFamilyFn, instance, typeParams, packParams, ctx, metamethod)) + if (auto result = tryDistributeTypeFunctionApp(comparisonFamilyFn, instance, typeParams, packParams, ctx, metamethod)) return *result; // findMetatableEntry demands the ability to emit errors, so we must give it @@ -1280,36 +1280,36 @@ static TypeFamilyReductionResult comparisonFamilyFn(TypeId instance, con return {ctx->builtins->booleanType, false, {}, {}}; } -TypeFamilyReductionResult ltFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult ltFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("lt type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("lt type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } return comparisonFamilyFn(instance, typeParams, packParams, ctx, "__lt"); } -TypeFamilyReductionResult leFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult leFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("le type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("le type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } return comparisonFamilyFn(instance, typeParams, packParams, ctx, "__le"); } -TypeFamilyReductionResult eqFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult eqFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("eq type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("eq type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } @@ -1435,12 +1435,12 @@ struct FindRefinementBlockers : TypeOnceVisitor }; -TypeFamilyReductionResult refineFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult refineFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("refine type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("refine type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } @@ -1520,12 +1520,12 @@ TypeFamilyReductionResult refineFamilyFn( return {resultTy, false, {}, {}}; } -TypeFamilyReductionResult singletonFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult singletonFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 1 || !packParams.empty()) { - ctx->ice->ice("singleton type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("singleton type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } @@ -1557,12 +1557,12 @@ TypeFamilyReductionResult singletonFamilyFn( return {ctx->builtins->unknownType, false, {}, {}}; } -TypeFamilyReductionResult unionFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult unionFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (!packParams.empty()) { - ctx->ice->ice("union type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("union type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } @@ -1618,12 +1618,12 @@ TypeFamilyReductionResult unionFamilyFn( } -TypeFamilyReductionResult intersectFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult intersectFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (!packParams.empty()) { - ctx->ice->ice("intersect type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("intersect type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } @@ -1673,7 +1673,7 @@ TypeFamilyReductionResult intersectFamilyFn( // computes the keys of `ty` into `result` // `isRaw` parameter indicates whether or not we should follow __index metamethods // returns `false` if `result` should be ignored because the answer is "all strings" -bool computeKeysOf(TypeId ty, Set& result, DenseHashSet& seen, bool isRaw, NotNull ctx) +bool computeKeysOf(TypeId ty, Set& result, DenseHashSet& seen, bool isRaw, NotNull ctx) { // if the type is the top table type, the answer is just "all strings" if (get(ty)) @@ -1725,12 +1725,12 @@ bool computeKeysOf(TypeId ty, Set& result, DenseHashSet& se return false; } -TypeFamilyReductionResult keyofFamilyImpl( - const std::vector& typeParams, const std::vector& packParams, NotNull ctx, bool isRaw) +TypeFunctionReductionResult keyofFamilyImpl( + const std::vector& typeParams, const std::vector& packParams, NotNull ctx, bool isRaw) { if (typeParams.size() != 1 || !packParams.empty()) { - ctx->ice->ice("keyof type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("keyof type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } @@ -1842,24 +1842,24 @@ TypeFamilyReductionResult keyofFamilyImpl( return {ctx->arena->addType(UnionType{singletons}), false, {}, {}}; } -TypeFamilyReductionResult keyofFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult keyofFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 1 || !packParams.empty()) { - ctx->ice->ice("keyof type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("keyof type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } return keyofFamilyImpl(typeParams, packParams, ctx, /* isRaw */ false); } -TypeFamilyReductionResult rawkeyofFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult rawkeyofFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 1 || !packParams.empty()) { - ctx->ice->ice("rawkeyof type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("rawkeyof type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } @@ -1870,7 +1870,7 @@ TypeFamilyReductionResult rawkeyofFamilyFn( If found, appends that property to `result` and returns true Else, returns false */ bool searchPropsAndIndexer( - TypeId ty, TableType::Props tblProps, std::optional tblIndexer, DenseHashSet& result, NotNull ctx) + TypeId ty, TableType::Props tblProps, std::optional tblIndexer, DenseHashSet& result, NotNull ctx) { ty = follow(ty); @@ -1920,7 +1920,7 @@ bool searchPropsAndIndexer( /* 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(TypeId indexer, TypeId indexee, DenseHashSet& result, NotNull ctx, bool isRaw) +bool tblIndexInto(TypeId indexer, TypeId indexee, DenseHashSet& result, NotNull ctx, bool isRaw) { indexer = follow(indexer); indexee = follow(indexee); @@ -1960,8 +1960,8 @@ bool tblIndexInto(TypeId indexer, TypeId indexee, DenseHashSet& result, /* Vocabulary note: indexee refers to the type that contains the properties, indexer refers to the type that is used to access indexee Example: index => `Person` is the indexee and `"name"` is the indexer */ -TypeFamilyReductionResult indexFamilyImpl( - const std::vector& typeParams, const std::vector& packParams, NotNull ctx, bool isRaw) +TypeFunctionReductionResult indexFamilyImpl( + const std::vector& typeParams, const std::vector& packParams, NotNull ctx, bool isRaw) { TypeId indexeeTy = follow(typeParams.at(0)); std::shared_ptr indexeeNormTy = ctx->normalizer->normalize(indexeeTy); @@ -2064,104 +2064,104 @@ TypeFamilyReductionResult indexFamilyImpl( return {ctx->arena->addType(UnionType{std::vector(properties.begin(), properties.end())}), false, {}, {}}; } -TypeFamilyReductionResult indexFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult indexFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("index type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("index type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } return indexFamilyImpl(typeParams, packParams, ctx, /* isRaw */ false); } -TypeFamilyReductionResult rawgetFamilyFn( - TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) +TypeFunctionReductionResult rawgetFamilyFn( + TypeId instance, const std::vector& typeParams, const std::vector& packParams, NotNull ctx) { if (typeParams.size() != 2 || !packParams.empty()) { - ctx->ice->ice("rawget type family: encountered a type family instance without the required argument structure"); + ctx->ice->ice("rawget type function: encountered a type function instance without the required argument structure"); LUAU_ASSERT(false); } return indexFamilyImpl(typeParams, packParams, ctx, /* isRaw */ true); } -BuiltinTypeFamilies::BuiltinTypeFamilies() - : notFamily{"not", notFamilyFn} - , lenFamily{"len", lenFamilyFn} - , unmFamily{"unm", unmFamilyFn} - , addFamily{"add", addFamilyFn} - , subFamily{"sub", subFamilyFn} - , mulFamily{"mul", mulFamilyFn} - , divFamily{"div", divFamilyFn} - , idivFamily{"idiv", idivFamilyFn} - , powFamily{"pow", powFamilyFn} - , modFamily{"mod", modFamilyFn} - , concatFamily{"concat", concatFamilyFn} - , andFamily{"and", andFamilyFn} - , orFamily{"or", orFamilyFn} - , ltFamily{"lt", ltFamilyFn} - , leFamily{"le", leFamilyFn} - , eqFamily{"eq", eqFamilyFn} - , refineFamily{"refine", refineFamilyFn} - , singletonFamily{"singleton", singletonFamilyFn} - , unionFamily{"union", unionFamilyFn} - , intersectFamily{"intersect", intersectFamilyFn} - , keyofFamily{"keyof", keyofFamilyFn} - , rawkeyofFamily{"rawkeyof", rawkeyofFamilyFn} - , indexFamily{"index", indexFamilyFn} - , rawgetFamily{"rawget", rawgetFamilyFn} +BuiltinTypeFunctions::BuiltinTypeFunctions() + : notFunc{"not", notFamilyFn} + , lenFunc{"len", lenFamilyFn} + , unmFunc{"unm", unmFamilyFn} + , addFunc{"add", addFamilyFn} + , subFunc{"sub", subFamilyFn} + , mulFunc{"mul", mulFamilyFn} + , divFunc{"div", divFamilyFn} + , idivFunc{"idiv", idivFamilyFn} + , powFunc{"pow", powFamilyFn} + , modFunc{"mod", modFamilyFn} + , concatFunc{"concat", concatFamilyFn} + , andFunc{"and", andFamilyFn} + , orFunc{"or", orFamilyFn} + , ltFunc{"lt", ltFamilyFn} + , leFunc{"le", leFamilyFn} + , eqFunc{"eq", eqFamilyFn} + , refineFunc{"refine", refineFamilyFn} + , singletonFunc{"singleton", singletonFamilyFn} + , unionFunc{"union", unionFamilyFn} + , intersectFunc{"intersect", intersectFamilyFn} + , keyofFunc{"keyof", keyofFamilyFn} + , rawkeyofFunc{"rawkeyof", rawkeyofFamilyFn} + , indexFunc{"index", indexFamilyFn} + , rawgetFunc{"rawget", rawgetFamilyFn} { } -void BuiltinTypeFamilies::addToScope(NotNull arena, NotNull scope) const +void BuiltinTypeFunctions::addToScope(NotNull arena, NotNull scope) const { - // make a type function for a one-argument type family - auto mkUnaryTypeFamily = [&](const TypeFamily* family) { + // make a type function for a one-argument type function + auto mkUnaryTypeFunction = [&](const TypeFunction* tf) { TypeId t = arena->addType(GenericType{"T"}); GenericTypeDefinition genericT{t}; - return TypeFun{{genericT}, arena->addType(TypeFamilyInstanceType{NotNull{family}, {t}, {}})}; + return TypeFun{{genericT}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t}, {}})}; }; - // make a type function for a two-argument type family - auto mkBinaryTypeFamily = [&](const TypeFamily* family) { + // make a type function for a two-argument type function + auto mkBinaryTypeFunction = [&](const TypeFunction* tf) { TypeId t = arena->addType(GenericType{"T"}); TypeId u = arena->addType(GenericType{"U"}); GenericTypeDefinition genericT{t}; GenericTypeDefinition genericU{u, {t}}; - return TypeFun{{genericT, genericU}, arena->addType(TypeFamilyInstanceType{NotNull{family}, {t, u}, {}})}; + return TypeFun{{genericT, genericU}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t, u}, {}})}; }; - scope->exportedTypeBindings[lenFamily.name] = mkUnaryTypeFamily(&lenFamily); - scope->exportedTypeBindings[unmFamily.name] = mkUnaryTypeFamily(&unmFamily); + scope->exportedTypeBindings[lenFunc.name] = mkUnaryTypeFunction(&lenFunc); + scope->exportedTypeBindings[unmFunc.name] = mkUnaryTypeFunction(&unmFunc); - scope->exportedTypeBindings[addFamily.name] = mkBinaryTypeFamily(&addFamily); - scope->exportedTypeBindings[subFamily.name] = mkBinaryTypeFamily(&subFamily); - scope->exportedTypeBindings[mulFamily.name] = mkBinaryTypeFamily(&mulFamily); - scope->exportedTypeBindings[divFamily.name] = mkBinaryTypeFamily(&divFamily); - scope->exportedTypeBindings[idivFamily.name] = mkBinaryTypeFamily(&idivFamily); - scope->exportedTypeBindings[powFamily.name] = mkBinaryTypeFamily(&powFamily); - scope->exportedTypeBindings[modFamily.name] = mkBinaryTypeFamily(&modFamily); - scope->exportedTypeBindings[concatFamily.name] = mkBinaryTypeFamily(&concatFamily); + scope->exportedTypeBindings[addFunc.name] = mkBinaryTypeFunction(&addFunc); + scope->exportedTypeBindings[subFunc.name] = mkBinaryTypeFunction(&subFunc); + scope->exportedTypeBindings[mulFunc.name] = mkBinaryTypeFunction(&mulFunc); + scope->exportedTypeBindings[divFunc.name] = mkBinaryTypeFunction(&divFunc); + scope->exportedTypeBindings[idivFunc.name] = mkBinaryTypeFunction(&idivFunc); + scope->exportedTypeBindings[powFunc.name] = mkBinaryTypeFunction(&powFunc); + scope->exportedTypeBindings[modFunc.name] = mkBinaryTypeFunction(&modFunc); + scope->exportedTypeBindings[concatFunc.name] = mkBinaryTypeFunction(&concatFunc); - scope->exportedTypeBindings[ltFamily.name] = mkBinaryTypeFamily(<Family); - scope->exportedTypeBindings[leFamily.name] = mkBinaryTypeFamily(&leFamily); - scope->exportedTypeBindings[eqFamily.name] = mkBinaryTypeFamily(&eqFamily); + scope->exportedTypeBindings[ltFunc.name] = mkBinaryTypeFunction(<Func); + scope->exportedTypeBindings[leFunc.name] = mkBinaryTypeFunction(&leFunc); + scope->exportedTypeBindings[eqFunc.name] = mkBinaryTypeFunction(&eqFunc); - scope->exportedTypeBindings[keyofFamily.name] = mkUnaryTypeFamily(&keyofFamily); - scope->exportedTypeBindings[rawkeyofFamily.name] = mkUnaryTypeFamily(&rawkeyofFamily); + scope->exportedTypeBindings[keyofFunc.name] = mkUnaryTypeFunction(&keyofFunc); + scope->exportedTypeBindings[rawkeyofFunc.name] = mkUnaryTypeFunction(&rawkeyofFunc); - scope->exportedTypeBindings[indexFamily.name] = mkBinaryTypeFamily(&indexFamily); - scope->exportedTypeBindings[rawgetFamily.name] = mkBinaryTypeFamily(&rawgetFamily); + scope->exportedTypeBindings[indexFunc.name] = mkBinaryTypeFunction(&indexFunc); + scope->exportedTypeBindings[rawgetFunc.name] = mkBinaryTypeFunction(&rawgetFunc); } -const BuiltinTypeFamilies& builtinTypeFunctions() +const BuiltinTypeFunctions& builtinTypeFunctions() { - static std::unique_ptr result = std::make_unique(); + static std::unique_ptr result = std::make_unique(); return *result; } diff --git a/Analysis/src/TypeFamilyReductionGuesser.cpp b/Analysis/src/TypeFunctionReductionGuesser.cpp similarity index 72% rename from Analysis/src/TypeFamilyReductionGuesser.cpp rename to Analysis/src/TypeFunctionReductionGuesser.cpp index 7f865998..9279c1d3 100644 --- a/Analysis/src/TypeFamilyReductionGuesser.cpp +++ b/Analysis/src/TypeFunctionReductionGuesser.cpp @@ -1,9 +1,9 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Luau/TypeFamilyReductionGuesser.h" +#include "Luau/TypeFunctionReductionGuesser.h" #include "Luau/DenseHash.h" #include "Luau/Normalize.h" -#include "Luau/TypeFamily.h" +#include "Luau/TypeFunction.h" #include "Luau/Type.h" #include "Luau/TypePack.h" #include "Luau/TypeUtils.h" @@ -23,7 +23,7 @@ struct InstanceCollector2 : TypeOnceVisitor DenseHashSet cyclicInstance{nullptr}; DenseHashSet instanceArguments{nullptr}; - bool visit(TypeId ty, const TypeFamilyInstanceType& it) override + bool visit(TypeId ty, const TypeFunctionInstanceType& it) override { // TypeOnceVisitor performs a depth-first traversal in the absence of // cycles. This means that by pushing to the front of the queue, we will @@ -41,7 +41,7 @@ struct InstanceCollector2 : TypeOnceVisitor { /// Detected cyclic type pack TypeId t = follow(ty); - if (get(t)) + if (get(t)) cyclicInstance.insert(t); } @@ -50,7 +50,7 @@ struct InstanceCollector2 : TypeOnceVisitor return false; } - bool visit(TypePackId tp, const TypeFamilyInstanceTypePack&) override + bool visit(TypePackId tp, const TypeFunctionInstanceTypePack&) override { // TypeOnceVisitor performs a depth-first traversal in the absence of // cycles. This means that by pushing to the front of the queue, we will @@ -65,14 +65,14 @@ struct InstanceCollector2 : TypeOnceVisitor -TypeFamilyReductionGuesser::TypeFamilyReductionGuesser(NotNull arena, NotNull builtins, NotNull normalizer) +TypeFunctionReductionGuesser::TypeFunctionReductionGuesser(NotNull arena, NotNull builtins, NotNull normalizer) : arena(arena) , builtins(builtins) , normalizer(normalizer) { } -bool TypeFamilyReductionGuesser::isFunctionGenericsSaturated(const FunctionType& ftv, DenseHashSet& argsUsed) +bool TypeFunctionReductionGuesser::isFunctionGenericsSaturated(const FunctionType& ftv, DenseHashSet& argsUsed) { bool sameSize = ftv.generics.size() == argsUsed.size(); bool allGenericsAppear = true; @@ -81,7 +81,7 @@ bool TypeFamilyReductionGuesser::isFunctionGenericsSaturated(const FunctionType& return sameSize && allGenericsAppear; } -void TypeFamilyReductionGuesser::dumpGuesses() +void TypeFunctionReductionGuesser::dumpGuesses() { for (auto [tf, t] : familyReducesTo) printf("Type family %s ~~> %s\n", toString(tf).c_str(), toString(t).c_str()); @@ -89,7 +89,7 @@ void TypeFamilyReductionGuesser::dumpGuesses() printf("Substitute %s for %s\n", toString(t).c_str(), toString(t_).c_str()); } -std::optional TypeFamilyReductionGuesser::guess(TypeId typ) +std::optional TypeFunctionReductionGuesser::guess(TypeId typ) { std::optional guessedType = guessType(typ); @@ -97,13 +97,13 @@ std::optional TypeFamilyReductionGuesser::guess(TypeId typ) return {}; TypeId guess = follow(*guessedType); - if (get(guess)) + if (get(guess)) return {}; return guess; } -std::optional TypeFamilyReductionGuesser::guess(TypePackId tp) +std::optional TypeFunctionReductionGuesser::guess(TypePackId tp) { auto [head, tail] = flatten(tp); @@ -118,7 +118,7 @@ std::optional TypeFamilyReductionGuesser::guess(TypePackId tp) return {}; TypeId guess = follow(*guessedType); - if (get(guess)) + if (get(guess)) return {}; guessedHead.push_back(*guessedType); @@ -127,7 +127,7 @@ std::optional TypeFamilyReductionGuesser::guess(TypePackId tp) return arena->addTypePack(TypePack{guessedHead, tail}); } -TypeFamilyReductionGuessResult TypeFamilyReductionGuesser::guessTypeFamilyReductionForFunction( +TypeFunctionReductionGuessResult TypeFunctionReductionGuesser::guessTypeFunctionReductionForFunctionExpr( const AstExprFunction& expr, const FunctionType* ftv, TypeId retTy) { InstanceCollector2 collector; @@ -136,7 +136,7 @@ TypeFamilyReductionGuessResult TypeFamilyReductionGuesser::guessTypeFamilyReduct cyclicInstances = std::move(collector.cyclicInstance); if (isFunctionGenericsSaturated(*ftv, collector.instanceArguments)) - return TypeFamilyReductionGuessResult{{}, nullptr, false}; + return TypeFunctionReductionGuessResult{{}, nullptr, false}; infer(); std::vector> results; @@ -157,7 +157,7 @@ TypeFamilyReductionGuessResult TypeFamilyReductionGuesser::guessTypeFamilyReduct if (!guessedType.has_value()) continue; TypeId guess = follow(*guessedType); - if (get(guess)) + if (get(guess)) continue; results.push_back({local->name.value, guess}); @@ -170,7 +170,7 @@ TypeFamilyReductionGuessResult TypeFamilyReductionGuesser::guessTypeFamilyReduct recommendedAnnotation = builtins->unknownType; else recommendedAnnotation = follow(*guessedReturnType); - if (auto t = get(recommendedAnnotation)) + if (auto t = get(recommendedAnnotation)) recommendedAnnotation = builtins->unknownType; toInfer.clear(); @@ -178,10 +178,10 @@ TypeFamilyReductionGuessResult TypeFamilyReductionGuesser::guessTypeFamilyReduct familyReducesTo.clear(); substitutable.clear(); - return TypeFamilyReductionGuessResult{results, recommendedAnnotation}; + return TypeFunctionReductionGuessResult{results, recommendedAnnotation}; } -std::optional TypeFamilyReductionGuesser::guessType(TypeId arg) +std::optional TypeFunctionReductionGuesser::guessType(TypeId arg) { TypeId t = follow(arg); if (substitutable.contains(t)) @@ -189,12 +189,12 @@ std::optional TypeFamilyReductionGuesser::guessType(TypeId arg) TypeId subst = follow(substitutable[t]); if (subst == t || substitutable.contains(subst)) return subst; - else if (!get(subst)) + else if (!get(subst)) return subst; else return guessType(subst); } - if (get(t)) + if (get(t)) { if (familyReducesTo.contains(t)) return familyReducesTo[t]; @@ -202,41 +202,41 @@ std::optional TypeFamilyReductionGuesser::guessType(TypeId arg) return {}; } -bool TypeFamilyReductionGuesser::isNumericBinopFamily(const TypeFamilyInstanceType& instance) +bool TypeFunctionReductionGuesser::isNumericBinopFamily(const TypeFunctionInstanceType& instance) { return instance.family->name == "add" || instance.family->name == "sub" || instance.family->name == "mul" || instance.family->name == "div" || instance.family->name == "idiv" || instance.family->name == "pow" || instance.family->name == "mod"; } -bool TypeFamilyReductionGuesser::isComparisonFamily(const TypeFamilyInstanceType& instance) +bool TypeFunctionReductionGuesser::isComparisonFamily(const TypeFunctionInstanceType& instance) { return instance.family->name == "lt" || instance.family->name == "le" || instance.family->name == "eq"; } -bool TypeFamilyReductionGuesser::isOrAndFamily(const TypeFamilyInstanceType& instance) +bool TypeFunctionReductionGuesser::isOrAndFamily(const TypeFunctionInstanceType& instance) { return instance.family->name == "or" || instance.family->name == "and"; } -bool TypeFamilyReductionGuesser::isNotFamily(const TypeFamilyInstanceType& instance) +bool TypeFunctionReductionGuesser::isNotFamily(const TypeFunctionInstanceType& instance) { return instance.family->name == "not"; } -bool TypeFamilyReductionGuesser::isLenFamily(const TypeFamilyInstanceType& instance) +bool TypeFunctionReductionGuesser::isLenFamily(const TypeFunctionInstanceType& instance) { return instance.family->name == "len"; } -bool TypeFamilyReductionGuesser::isUnaryMinus(const TypeFamilyInstanceType& instance) +bool TypeFunctionReductionGuesser::isUnaryMinus(const TypeFunctionInstanceType& instance) { return instance.family->name == "unm"; } // Operand is assignable if it looks like a cyclic family instance, or a generic type -bool TypeFamilyReductionGuesser::operandIsAssignable(TypeId ty) +bool TypeFunctionReductionGuesser::operandIsAssignable(TypeId ty) { - if (get(ty)) + if (get(ty)) return true; if (get(ty)) return true; @@ -245,17 +245,17 @@ bool TypeFamilyReductionGuesser::operandIsAssignable(TypeId ty) return false; } -std::shared_ptr TypeFamilyReductionGuesser::normalize(TypeId ty) +std::shared_ptr TypeFunctionReductionGuesser::normalize(TypeId ty) { return normalizer->normalize(ty); } -std::optional TypeFamilyReductionGuesser::tryAssignOperandType(TypeId ty) +std::optional TypeFunctionReductionGuesser::tryAssignOperandType(TypeId ty) { - // Because we collect innermost instances first, if we see a typefamily instance as an operand, + // Because we collect innermost instances first, if we see a type function instance as an operand, // We try to check if we guessed a type for it - if (auto tfit = get(ty)) + if (auto tfit = get(ty)) { if (familyReducesTo.contains(ty)) return {familyReducesTo[ty]}; @@ -272,30 +272,30 @@ std::optional TypeFamilyReductionGuesser::tryAssignOperandType(TypeId ty return {}; } -void TypeFamilyReductionGuesser::step() +void TypeFunctionReductionGuesser::step() { TypeId t = toInfer.front(); toInfer.pop_front(); t = follow(t); - if (auto tf = get(t)) - inferTypeFamilySubstitutions(t, tf); + if (auto tf = get(t)) + inferTypeFunctionSubstitutions(t, tf); } -void TypeFamilyReductionGuesser::infer() +void TypeFunctionReductionGuesser::infer() { while (!done()) step(); } -bool TypeFamilyReductionGuesser::done() +bool TypeFunctionReductionGuesser::done() { return toInfer.empty(); } -void TypeFamilyReductionGuesser::inferTypeFamilySubstitutions(TypeId ty, const TypeFamilyInstanceType* instance) +void TypeFunctionReductionGuesser::inferTypeFunctionSubstitutions(TypeId ty, const TypeFunctionInstanceType* instance) { - TypeFamilyInferenceResult result; + TypeFunctionInferenceResult result; LUAU_ASSERT(instance); // TODO: Make an inexhaustive version of this warn in the compiler? if (isNumericBinopFamily(*instance)) @@ -323,7 +323,7 @@ void TypeFamilyReductionGuesser::inferTypeFamilySubstitutions(TypeId ty, const T { TypeId arg = follow(instance->typeArguments[i]); TypeId inference = follow(result.operandInference[i]); - if (auto tfit = get(arg)) + if (auto tfit = get(arg)) { if (!familyReducesTo.contains(arg)) familyReducesTo.try_insert(arg, inference); @@ -334,14 +334,14 @@ void TypeFamilyReductionGuesser::inferTypeFamilySubstitutions(TypeId ty, const T } } -TypeFamilyInferenceResult TypeFamilyReductionGuesser::inferNumericBinopFamily(const TypeFamilyInstanceType* instance) +TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferNumericBinopFamily(const TypeFunctionInstanceType* instance) { LUAU_ASSERT(instance->typeArguments.size() == 2); - TypeFamilyInferenceResult defaultNumericBinopInference{{builtins->numberType, builtins->numberType}, builtins->numberType}; + TypeFunctionInferenceResult defaultNumericBinopInference{{builtins->numberType, builtins->numberType}, builtins->numberType}; return defaultNumericBinopInference; } -TypeFamilyInferenceResult TypeFamilyReductionGuesser::inferComparisonFamily(const TypeFamilyInstanceType* instance) +TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferComparisonFamily(const TypeFunctionInstanceType* instance) { LUAU_ASSERT(instance->typeArguments.size() == 2); // Comparison families are lt/le/eq. @@ -350,8 +350,8 @@ TypeFamilyInferenceResult TypeFamilyReductionGuesser::inferComparisonFamily(cons TypeId lhsTy = follow(instance->typeArguments[0]); TypeId rhsTy = follow(instance->typeArguments[1]); - auto comparisonInference = [&](TypeId op) -> TypeFamilyInferenceResult { - return TypeFamilyInferenceResult{{op, op}, builtins->booleanType}; + auto comparisonInference = [&](TypeId op) -> TypeFunctionInferenceResult { + return TypeFunctionInferenceResult{{op, op}, builtins->booleanType}; }; if (std::optional ty = tryAssignOperandType(lhsTy)) @@ -365,7 +365,7 @@ TypeFamilyInferenceResult TypeFamilyReductionGuesser::inferComparisonFamily(cons return comparisonInference(builtins->numberType); } -TypeFamilyInferenceResult TypeFamilyReductionGuesser::inferOrAndFamily(const TypeFamilyInstanceType* instance) +TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferOrAndFamily(const TypeFunctionInstanceType* instance) { LUAU_ASSERT(instance->typeArguments.size() == 2); @@ -377,7 +377,7 @@ TypeFamilyInferenceResult TypeFamilyReductionGuesser::inferOrAndFamily(const Typ lhsTy = follow(*ty); if (std::optional ty = tryAssignOperandType(rhsTy)) rhsTy = follow(*ty); - TypeFamilyInferenceResult defaultAndOrInference{{builtins->unknownType, builtins->unknownType}, builtins->booleanType}; + TypeFunctionInferenceResult defaultAndOrInference{{builtins->unknownType, builtins->unknownType}, builtins->booleanType}; std::shared_ptr lty = normalize(lhsTy); std::shared_ptr rty = normalize(lhsTy); @@ -389,9 +389,9 @@ TypeFamilyInferenceResult TypeFamilyReductionGuesser::inferOrAndFamily(const Typ if (operandIsAssignable(lhsTy) && operandIsAssignable(rhsTy)) return defaultAndOrInference; if (operandIsAssignable(lhsTy)) - return TypeFamilyInferenceResult{{builtins->unknownType, rhsTy}, rhsTy}; + return TypeFunctionInferenceResult{{builtins->unknownType, rhsTy}, rhsTy}; if (operandIsAssignable(rhsTy)) - return TypeFamilyInferenceResult{{lhsTy, builtins->unknownType}, lhsTy}; + return TypeFunctionInferenceResult{{lhsTy, builtins->unknownType}, lhsTy}; if (lhsTruthy) return {{lhsTy, rhsTy}, lhsTy}; if (rhsTruthy) @@ -404,9 +404,9 @@ TypeFamilyInferenceResult TypeFamilyReductionGuesser::inferOrAndFamily(const Typ if (operandIsAssignable(lhsTy) && operandIsAssignable(rhsTy)) return defaultAndOrInference; if (operandIsAssignable(lhsTy)) - return TypeFamilyInferenceResult{{}, rhsTy}; + return TypeFunctionInferenceResult{{}, rhsTy}; if (operandIsAssignable(rhsTy)) - return TypeFamilyInferenceResult{{}, lhsTy}; + return TypeFunctionInferenceResult{{}, lhsTy}; if (lhsTruthy) return {{lhsTy, rhsTy}, rhsTy}; else @@ -416,7 +416,7 @@ TypeFamilyInferenceResult TypeFamilyReductionGuesser::inferOrAndFamily(const Typ return defaultAndOrInference; } -TypeFamilyInferenceResult TypeFamilyReductionGuesser::inferNotFamily(const TypeFamilyInstanceType* instance) +TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferNotFamily(const TypeFunctionInstanceType* instance) { LUAU_ASSERT(instance->typeArguments.size() == 1); TypeId opTy = follow(instance->typeArguments[0]); @@ -425,7 +425,7 @@ TypeFamilyInferenceResult TypeFamilyReductionGuesser::inferNotFamily(const TypeF return {{opTy}, builtins->booleanType}; } -TypeFamilyInferenceResult TypeFamilyReductionGuesser::inferLenFamily(const TypeFamilyInstanceType* instance) +TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferLenFamily(const TypeFunctionInstanceType* instance) { LUAU_ASSERT(instance->typeArguments.size() == 1); TypeId opTy = follow(instance->typeArguments[0]); @@ -434,7 +434,7 @@ TypeFamilyInferenceResult TypeFamilyReductionGuesser::inferLenFamily(const TypeF return {{opTy}, builtins->numberType}; } -TypeFamilyInferenceResult TypeFamilyReductionGuesser::inferUnaryMinusFamily(const TypeFamilyInstanceType* instance) +TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferUnaryMinusFamily(const TypeFunctionInstanceType* instance) { LUAU_ASSERT(instance->typeArguments.size() == 1); TypeId opTy = follow(instance->typeArguments[0]); diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index 1802345d..90d9e92e 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -454,11 +454,11 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool else if (isBlocked(log, superTy)) blockedTypes.push_back(superTy); - if (log.get(superTy)) - ice("Unexpected TypeFamilyInstanceType superTy"); + if (log.get(superTy)) + ice("Unexpected TypeFunctionInstanceType superTy"); - if (log.get(subTy)) - ice("Unexpected TypeFamilyInstanceType subTy"); + if (log.get(subTy)) + ice("Unexpected TypeFunctionInstanceType subTy"); auto superFree = log.getMutable(superTy); auto subFree = log.getMutable(subTy); diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index 6dcd7197..c43edbd1 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -8,7 +8,7 @@ #include "Luau/Type.h" #include "Luau/TypeArena.h" #include "Luau/TypeCheckLimits.h" -#include "Luau/TypeFamily.h" +#include "Luau/TypeFunction.h" #include "Luau/TypeFwd.h" #include "Luau/TypePack.h" #include "Luau/TypeUtils.h" @@ -76,13 +76,13 @@ static bool areCompatible(TypeId left, TypeId right) // returns `true` if `ty` is irressolvable and should be added to `incompleteSubtypes`. static bool isIrresolvable(TypeId ty) { - return get(ty) || get(ty); + return get(ty) || get(ty); } // returns `true` if `tp` is irressolvable and should be added to `incompleteSubtypes`. static bool isIrresolvable(TypePackId tp) { - return get(tp) || get(tp); + return get(tp) || get(tp); } Unifier2::Unifier2(NotNull arena, NotNull builtinTypes, NotNull scope, NotNull ice) diff --git a/CodeGen/src/IrAnalysis.cpp b/CodeGen/src/IrAnalysis.cpp index f78823df..73775bf9 100644 --- a/CodeGen/src/IrAnalysis.cpp +++ b/CodeGen/src/IrAnalysis.cpp @@ -13,8 +13,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauCodegenInstG, false) - namespace Luau { namespace CodeGen @@ -54,9 +52,7 @@ void updateUseCounts(IrFunction& function) checkOp(inst.d); checkOp(inst.e); checkOp(inst.f); - - if (FFlag::LuauCodegenInstG) - checkOp(inst.g); + checkOp(inst.g); } } @@ -100,9 +96,7 @@ void updateLastUseLocations(IrFunction& function, const std::vector& s checkOp(inst.d); checkOp(inst.e); checkOp(inst.f); - - if (FFlag::LuauCodegenInstG) - checkOp(inst.g); + checkOp(inst.g); } } } @@ -137,11 +131,8 @@ uint32_t getNextInstUse(IrFunction& function, uint32_t targetInstIdx, uint32_t s if (inst.f.kind == IrOpKind::Inst && inst.f.index == targetInstIdx) return i; - if (FFlag::LuauCodegenInstG) - { - if (inst.g.kind == IrOpKind::Inst && inst.g.index == targetInstIdx) - return i; - } + if (inst.g.kind == IrOpKind::Inst && inst.g.index == targetInstIdx) + return i; } // There must be a next use since there is the last use location @@ -179,9 +170,7 @@ std::pair getLiveInOutValueCount(IrFunction& function, IrBlo checkOp(inst.d); checkOp(inst.e); checkOp(inst.f); - - if (FFlag::LuauCodegenInstG) - checkOp(inst.g); + checkOp(inst.g); } return std::make_pair(liveIns, liveOuts); @@ -505,9 +494,7 @@ static void computeCfgBlockEdges(IrFunction& function) checkOp(inst.d); checkOp(inst.e); checkOp(inst.f); - - if (FFlag::LuauCodegenInstG) - checkOp(inst.g); + checkOp(inst.g); } } diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index 061bf388..73a1bdc2 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -13,7 +13,6 @@ #include -LUAU_FASTFLAG(LuauCodegenInstG) LUAU_FASTFLAG(LuauCodegenFastcall3) namespace Luau @@ -641,9 +640,7 @@ void IrBuilder::clone(const IrBlock& source, bool removeCurrentTerminator) redirect(clone.d); redirect(clone.e); redirect(clone.f); - - if (FFlag::LuauCodegenInstG) - redirect(clone.g); + redirect(clone.g); addUse(function, clone.a); addUse(function, clone.b); @@ -651,18 +648,13 @@ void IrBuilder::clone(const IrBlock& source, bool removeCurrentTerminator) addUse(function, clone.d); addUse(function, clone.e); addUse(function, clone.f); - - if (FFlag::LuauCodegenInstG) - addUse(function, clone.g); + addUse(function, clone.g); // Instructions that referenced the original will have to be adjusted to use the clone instRedir[index] = uint32_t(function.instructions.size()); // Reconstruct the fresh clone - if (FFlag::LuauCodegenInstG) - inst(clone.cmd, clone.a, clone.b, clone.c, clone.d, clone.e, clone.f, clone.g); - else - inst(clone.cmd, clone.a, clone.b, clone.c, clone.d, clone.e, clone.f); + inst(clone.cmd, clone.a, clone.b, clone.c, clone.d, clone.e, clone.f, clone.g); } } @@ -760,31 +752,11 @@ IrOp IrBuilder::inst(IrCmd cmd, IrOp a, IrOp b, IrOp c, IrOp d, IrOp e) IrOp IrBuilder::inst(IrCmd cmd, IrOp a, IrOp b, IrOp c, IrOp d, IrOp e, IrOp f) { - if (FFlag::LuauCodegenInstG) - { - return inst(cmd, a, b, c, d, e, f, {}); - } - else - { - uint32_t index = uint32_t(function.instructions.size()); - function.instructions.push_back({cmd, a, b, c, d, e, f}); - - CODEGEN_ASSERT(!inTerminatedBlock); - - if (isBlockTerminator(cmd)) - { - function.blocks[activeBlockIdx].finish = index; - inTerminatedBlock = true; - } - - return {IrOpKind::Inst, index}; - } + return inst(cmd, a, b, c, d, e, f, {}); } IrOp IrBuilder::inst(IrCmd cmd, IrOp a, IrOp b, IrOp c, IrOp d, IrOp e, IrOp f, IrOp g) { - CODEGEN_ASSERT(FFlag::LuauCodegenInstG); - uint32_t index = uint32_t(function.instructions.size()); function.instructions.push_back({cmd, a, b, c, d, e, f, g}); diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index e54c2661..ab20c28d 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -7,8 +7,6 @@ #include -LUAU_FASTFLAG(LuauCodegenInstG) - namespace Luau { namespace CodeGen @@ -419,9 +417,7 @@ void toString(IrToStringContext& ctx, const IrInst& inst, uint32_t index) checkOp(inst.d, ", "); checkOp(inst.e, ", "); checkOp(inst.f, ", "); - - if (FFlag::LuauCodegenInstG) - checkOp(inst.g, ", "); + checkOp(inst.g, ", "); } void toString(IrToStringContext& ctx, const IrBlock& block, uint32_t index) @@ -611,7 +607,7 @@ static RegisterSet getJumpTargetExtraLiveIn(IrToStringContext& ctx, const IrBloc op = inst.e; else if (inst.f.kind == IrOpKind::Block) op = inst.f; - else if (FFlag::LuauCodegenInstG && inst.g.kind == IrOpKind::Block) + else if (inst.g.kind == IrOpKind::Block) op = inst.g; if (op.kind == IrOpKind::Block && op.index < ctx.cfg.in.size()) @@ -897,9 +893,7 @@ std::string toDot(const IrFunction& function, bool includeInst) checkOp(inst.d); checkOp(inst.e); checkOp(inst.f); - - if (FFlag::LuauCodegenInstG) - checkOp(inst.g); + checkOp(inst.g); } } diff --git a/CodeGen/src/IrRegAllocA64.cpp b/CodeGen/src/IrRegAllocA64.cpp index af63a2fc..522b0bc1 100644 --- a/CodeGen/src/IrRegAllocA64.cpp +++ b/CodeGen/src/IrRegAllocA64.cpp @@ -11,7 +11,6 @@ #include LUAU_FASTFLAGVARIABLE(DebugCodegenChaosA64, false) -LUAU_FASTFLAG(LuauCodegenInstG) namespace Luau { @@ -257,9 +256,7 @@ void IrRegAllocA64::freeLastUseRegs(const IrInst& inst, uint32_t index) checkOp(inst.d); checkOp(inst.e); checkOp(inst.f); - - if (FFlag::LuauCodegenInstG) - checkOp(inst.g); + checkOp(inst.g); } void IrRegAllocA64::freeTempRegs() diff --git a/CodeGen/src/IrRegAllocX64.cpp b/CodeGen/src/IrRegAllocX64.cpp index 60326074..12b9e379 100644 --- a/CodeGen/src/IrRegAllocX64.cpp +++ b/CodeGen/src/IrRegAllocX64.cpp @@ -6,8 +6,6 @@ #include "EmitCommonX64.h" -LUAU_FASTFLAG(LuauCodegenInstG) - namespace Luau { namespace CodeGen @@ -183,9 +181,7 @@ void IrRegAllocX64::freeLastUseRegs(const IrInst& inst, uint32_t instIdx) checkOp(inst.d); checkOp(inst.e); checkOp(inst.f); - - if (FFlag::LuauCodegenInstG) - checkOp(inst.g); + checkOp(inst.g); } bool IrRegAllocX64::isLastUseReg(const IrInst& target, uint32_t instIdx) const diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index 129945df..b5795ca1 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -12,8 +12,6 @@ #include #include -LUAU_FASTFLAG(LuauCodegenInstG) - namespace Luau { namespace CodeGen @@ -317,9 +315,7 @@ void kill(IrFunction& function, IrInst& inst) removeUse(function, inst.d); removeUse(function, inst.e); removeUse(function, inst.f); - - if (FFlag::LuauCodegenInstG) - removeUse(function, inst.g); + removeUse(function, inst.g); inst.a = {}; inst.b = {}; @@ -327,9 +323,7 @@ void kill(IrFunction& function, IrInst& inst) inst.d = {}; inst.e = {}; inst.f = {}; - - if (FFlag::LuauCodegenInstG) - inst.g = {}; + inst.g = {}; } void kill(IrFunction& function, uint32_t start, uint32_t end) @@ -378,9 +372,7 @@ void replace(IrFunction& function, IrBlock& block, uint32_t instIdx, IrInst repl addUse(function, replacement.d); addUse(function, replacement.e); addUse(function, replacement.f); - - if (FFlag::LuauCodegenInstG) - addUse(function, replacement.g); + addUse(function, replacement.g); // An extra reference is added so block will not remove itself block.useCount++; @@ -403,9 +395,7 @@ void replace(IrFunction& function, IrBlock& block, uint32_t instIdx, IrInst repl removeUse(function, inst.d); removeUse(function, inst.e); removeUse(function, inst.f); - - if (FFlag::LuauCodegenInstG) - removeUse(function, inst.g); + removeUse(function, inst.g); // Inherit existing use count (last use is skipped as it will be defined later) replacement.useCount = inst.useCount; @@ -431,9 +421,7 @@ void substitute(IrFunction& function, IrInst& inst, IrOp replacement) removeUse(function, inst.d); removeUse(function, inst.e); removeUse(function, inst.f); - - if (FFlag::LuauCodegenInstG) - removeUse(function, inst.g); + removeUse(function, inst.g); inst.a = replacement; inst.b = {}; @@ -441,9 +429,7 @@ void substitute(IrFunction& function, IrInst& inst, IrOp replacement) inst.d = {}; inst.e = {}; inst.f = {}; - - if (FFlag::LuauCodegenInstG) - inst.g = {}; + inst.g = {}; } void applySubstitutions(IrFunction& function, IrOp& op) @@ -487,9 +473,7 @@ void applySubstitutions(IrFunction& function, IrInst& inst) applySubstitutions(function, inst.d); applySubstitutions(function, inst.e); applySubstitutions(function, inst.f); - - if (FFlag::LuauCodegenInstG) - applySubstitutions(function, inst.g); + applySubstitutions(function, inst.g); } bool compare(double a, double b, IrCondition cond) diff --git a/Sources.cmake b/Sources.cmake index 4c5504b6..72038e70 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -219,8 +219,8 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/TypeChecker2.h Analysis/include/Luau/TypeCheckLimits.h Analysis/include/Luau/TypedAllocator.h - Analysis/include/Luau/TypeFamily.h - Analysis/include/Luau/TypeFamilyReductionGuesser.h + Analysis/include/Luau/TypeFunction.h + Analysis/include/Luau/TypeFunctionReductionGuesser.h Analysis/include/Luau/TypeFwd.h Analysis/include/Luau/TypeInfer.h Analysis/include/Luau/TypeOrPack.h @@ -283,8 +283,8 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/TypeAttach.cpp Analysis/src/TypeChecker2.cpp Analysis/src/TypedAllocator.cpp - Analysis/src/TypeFamily.cpp - Analysis/src/TypeFamilyReductionGuesser.cpp + Analysis/src/TypeFunction.cpp + Analysis/src/TypeFunctionReductionGuesser.cpp Analysis/src/TypeInfer.cpp Analysis/src/TypeOrPack.cpp Analysis/src/TypePack.cpp @@ -454,7 +454,7 @@ if(TARGET Luau.UnitTest) tests/ToString.test.cpp tests/Transpiler.test.cpp tests/TxnLog.test.cpp - tests/TypeFamily.test.cpp + tests/TypeFunction.test.cpp tests/TypeInfer.aliases.test.cpp tests/TypeInfer.annotations.test.cpp tests/TypeInfer.anyerror.test.cpp diff --git a/tests/IrBuilder.test.cpp b/tests/IrBuilder.test.cpp index 611eb7b5..238cf161 100644 --- a/tests/IrBuilder.test.cpp +++ b/tests/IrBuilder.test.cpp @@ -13,7 +13,6 @@ #include LUAU_FASTFLAG(DebugLuauAbortingChecks) -LUAU_FASTFLAG(LuauCodegenInstG) LUAU_FASTFLAG(LuauCodegenFastcall3) LUAU_FASTFLAG(LuauCodegenMathSign) @@ -1123,8 +1122,6 @@ bb_0: TEST_CASE_FIXTURE(IrBuilderFixture, "BuiltinFastcallsMayInvalidateMemory") { - ScopedFastFlag luauCodegenInstG{FFlag::LuauCodegenInstG, true}; - IrOp block = build.block(IrBlockKind::Internal); IrOp fallback = build.block(IrBlockKind::Fallback); @@ -2814,7 +2811,6 @@ bb_1: TEST_CASE_FIXTURE(IrBuilderFixture, "ExplicitUseOfRegisterInVarargSequence") { - ScopedFastFlag luauCodegenInstG{FFlag::LuauCodegenInstG, true}; ScopedFastFlag luauCodegenFastcall3{FFlag::LuauCodegenFastcall3, true}; IrOp entry = build.block(IrBlockKind::Internal); diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index afc22d0f..e70ec1ae 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -7,7 +7,7 @@ #include "Luau/Subtyping.h" #include "Luau/Type.h" #include "Luau/TypePack.h" -#include "Luau/TypeFamily.h" +#include "Luau/TypeFunction.h" #include "doctest.h" #include "Fixture.h" @@ -73,7 +73,7 @@ struct SubtypeFixture : Fixture ScopePtr moduleScope{new Scope(rootScope)}; Subtyping subtyping = mkSubtyping(rootScope); - BuiltinTypeFamilies builtinTypeFamilies{}; + BuiltinTypeFunctions builtinTypeFunctions{}; Subtyping mkSubtyping(const ScopePtr& scope) { @@ -398,51 +398,51 @@ TEST_SUITE_BEGIN("Subtyping"); TEST_IS_SUBTYPE(builtinTypes->numberType, builtinTypes->anyType); TEST_IS_NOT_SUBTYPE(builtinTypes->numberType, builtinTypes->stringType); -TEST_CASE_FIXTURE(SubtypeFixture, "basic_reducible_sub_typefamily") +TEST_CASE_FIXTURE(SubtypeFixture, "basic_reducible_sub_type_function") { // add <: number - TypeId typeFamilyNum = - arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.addFamily}, {builtinTypes->numberType, builtinTypes->numberType}, {}}); + TypeId typeFunctionNum = + arena.addType(TypeFunctionInstanceType{NotNull{&builtinTypeFunctions.addFunc}, {builtinTypes->numberType, builtinTypes->numberType}, {}}); TypeId superTy = builtinTypes->numberType; - SubtypingResult result = isSubtype(typeFamilyNum, superTy); + SubtypingResult result = isSubtype(typeFunctionNum, superTy); CHECK(result.isSubtype); } -TEST_CASE_FIXTURE(SubtypeFixture, "basic_reducible_super_typefamily") +TEST_CASE_FIXTURE(SubtypeFixture, "basic_reducible_super_type_function") { // number <: add ~ number - TypeId typeFamilyNum = - arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.addFamily}, {builtinTypes->numberType, builtinTypes->numberType}, {}}); + TypeId typeFunctionNum = + arena.addType(TypeFunctionInstanceType{NotNull{&builtinTypeFunctions.addFunc}, {builtinTypes->numberType, builtinTypes->numberType}, {}}); TypeId subTy = builtinTypes->numberType; - SubtypingResult result = isSubtype(subTy, typeFamilyNum); + SubtypingResult result = isSubtype(subTy, typeFunctionNum); CHECK(result.isSubtype); } -TEST_CASE_FIXTURE(SubtypeFixture, "basic_irreducible_sub_typefamily") +TEST_CASE_FIXTURE(SubtypeFixture, "basic_irreducible_sub_type_function") { // add ~ never <: number - TypeId typeFamilyNum = - arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.addFamily}, {builtinTypes->stringType, builtinTypes->booleanType}, {}}); + TypeId typeFunctionNum = + arena.addType(TypeFunctionInstanceType{NotNull{&builtinTypeFunctions.addFunc}, {builtinTypes->stringType, builtinTypes->booleanType}, {}}); TypeId superTy = builtinTypes->numberType; - SubtypingResult result = isSubtype(typeFamilyNum, superTy); + SubtypingResult result = isSubtype(typeFunctionNum, superTy); CHECK(result.isSubtype); } -TEST_CASE_FIXTURE(SubtypeFixture, "basic_irreducible_super_typefamily") +TEST_CASE_FIXTURE(SubtypeFixture, "basic_irreducible_super_type_function") { // number <\: add ~ irreducible/never - TypeId typeFamilyNum = - arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.addFamily}, {builtinTypes->stringType, builtinTypes->booleanType}, {}}); + TypeId typeFunctionNum = + arena.addType(TypeFunctionInstanceType{NotNull{&builtinTypeFunctions.addFunc}, {builtinTypes->stringType, builtinTypes->booleanType}, {}}); TypeId subTy = builtinTypes->numberType; - SubtypingResult result = isSubtype(subTy, typeFamilyNum); + SubtypingResult result = isSubtype(subTy, typeFunctionNum); CHECK(!result.isSubtype); } -TEST_CASE_FIXTURE(SubtypeFixture, "basic_typefamily_with_generics") +TEST_CASE_FIXTURE(SubtypeFixture, "basic_type_function_with_generics") { // (x: T, x: U) -> add <: (number, number) -> number - TypeId addFamily = arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.addFamily}, {genericT, genericU}, {}}); - FunctionType ft{{genericT, genericU}, {}, arena.addTypePack({genericT, genericU}), arena.addTypePack({addFamily})}; + TypeId addTypeFunction = arena.addType(TypeFunctionInstanceType{NotNull{&builtinTypeFunctions.addFunc}, {genericT, genericU}, {}}); + FunctionType ft{{genericT, genericU}, {}, arena.addTypePack({genericT, genericU}), arena.addTypePack({addTypeFunction})}; TypeId functionType = arena.addType(std::move(ft)); FunctionType superFt{arena.addTypePack({builtinTypes->numberType, builtinTypes->numberType}), arena.addTypePack({builtinTypes->numberType})}; TypeId superFunction = arena.addType(std::move(superFt)); @@ -1292,7 +1292,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type builtinTypes->numberType, builtinTypes->stringType, builtinTypes->tableType, builtinTypes->threadType}}); TypeId tblTy = tbl({{"depth", builtinTypes->unknownType}}); TypeId combined = meet(longTy, tblTy); - TypeId subTy = arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.unionFamily}, {combined, builtinTypes->neverType}, {}}); + TypeId subTy = arena.addType(TypeFunctionInstanceType{NotNull{&builtinTypeFunctions.unionFunc}, {combined, builtinTypes->neverType}, {}}); TypeId superTy = builtinTypes->neverType; SubtypingResult result = isSubtype(subTy, superTy); CHECK(!result.isSubtype); @@ -1321,9 +1321,10 @@ TEST_CASE_FIXTURE(SubtypeFixture, "table_property") SubtypingResult result = isSubtype(subTy, superTy); CHECK(!result.isSubtype); - CHECK(result.reasoning == std::vector{SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("X")), + REQUIRE(result.reasoning.size() == 1); + CHECK(*result.reasoning.begin() == SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("X")), /* superPath */ Path(TypePath::Property::read("X")), - /* variance */ SubtypingVariance::Invariant}}); + /* variance */ SubtypingVariance::Invariant}); } TEST_CASE_FIXTURE(SubtypeFixture, "table_indexers") @@ -1394,10 +1395,11 @@ TEST_CASE_FIXTURE(SubtypeFixture, "fn_rets") SubtypingResult result = isSubtype(subTy, superTy); CHECK(!result.isSubtype); - CHECK(result.reasoning == std::vector{SubtypingReasoning{ + REQUIRE(result.reasoning.size() == 1); + CHECK(*result.reasoning.begin() == SubtypingReasoning{ /* subPath */ TypePath::PathBuilder().rets().index(0).build(), /* superPath */ TypePath::PathBuilder().rets().index(0).build(), - }}); + }); } TEST_CASE_FIXTURE(SubtypeFixture, "fn_rets_tail") @@ -1407,10 +1409,11 @@ TEST_CASE_FIXTURE(SubtypeFixture, "fn_rets_tail") SubtypingResult result = isSubtype(subTy, superTy); CHECK(!result.isSubtype); - CHECK(result.reasoning == std::vector{SubtypingReasoning{ + REQUIRE(result.reasoning.size() == 1); + CHECK(*result.reasoning.begin() == SubtypingReasoning{ /* subPath */ TypePath::PathBuilder().rets().tail().variadic().build(), /* superPath */ TypePath::PathBuilder().rets().tail().variadic().build(), - }}); + }); } TEST_CASE_FIXTURE(SubtypeFixture, "nested_table_properties") @@ -1420,11 +1423,12 @@ TEST_CASE_FIXTURE(SubtypeFixture, "nested_table_properties") SubtypingResult result = isSubtype(subTy, superTy); CHECK(!result.isSubtype); - CHECK(result.reasoning == std::vector{SubtypingReasoning{ + REQUIRE(result.reasoning.size() == 1); + CHECK(*result.reasoning.begin() == SubtypingReasoning{ /* subPath */ TypePath::PathBuilder().readProp("X").readProp("Y").readProp("Z").build(), /* superPath */ TypePath::PathBuilder().readProp("X").readProp("Y").readProp("Z").build(), /* variance */ SubtypingVariance::Invariant, - }}); + }); } TEST_CASE_FIXTURE(SubtypeFixture, "string_table_mt") diff --git a/tests/TypeFamily.test.cpp b/tests/TypeFunction.test.cpp similarity index 94% rename from tests/TypeFamily.test.cpp rename to tests/TypeFunction.test.cpp index 068e8684..9638b3ff 100644 --- a/tests/TypeFamily.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -1,5 +1,5 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Luau/TypeFamily.h" +#include "Luau/TypeFunction.h" #include "Luau/ConstraintSolver.h" #include "Luau/NotNull.h" @@ -17,34 +17,34 @@ LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) struct FamilyFixture : Fixture { - TypeFamily swapFamily; + TypeFunction swapFamily; FamilyFixture() : Fixture(true, false) { - swapFamily = TypeFamily{/* name */ "Swap", + swapFamily = TypeFunction{/* name */ "Swap", /* reducer */ [](TypeId instance, const std::vector& tys, const std::vector& tps, - NotNull ctx) -> TypeFamilyReductionResult { + NotNull ctx) -> TypeFunctionReductionResult { LUAU_ASSERT(tys.size() == 1); TypeId param = follow(tys.at(0)); if (isString(param)) { - return TypeFamilyReductionResult{ctx->builtins->numberType, false, {}, {}}; + return TypeFunctionReductionResult{ctx->builtins->numberType, false, {}, {}}; } else if (isNumber(param)) { - return TypeFamilyReductionResult{ctx->builtins->stringType, false, {}, {}}; + return TypeFunctionReductionResult{ctx->builtins->stringType, false, {}, {}}; } - else if (is(param) || is(param) || is(param) || + else if (is(param) || is(param) || is(param) || (ctx->solver && ctx->solver->hasUnresolvedConstraints(param))) { - return TypeFamilyReductionResult{std::nullopt, false, {param}, {}}; + return TypeFunctionReductionResult{std::nullopt, false, {param}, {}}; } else { - return TypeFamilyReductionResult{std::nullopt, true, {}, {}}; + return TypeFunctionReductionResult{std::nullopt, true, {}, {}}; } }}; @@ -54,12 +54,12 @@ struct FamilyFixture : Fixture ScopePtr globalScope = frontend.globals.globalScope; globalScope->exportedTypeBindings["Swap"] = - TypeFun{{genericT}, frontend.globals.globalTypes.addType(TypeFamilyInstanceType{NotNull{&swapFamily}, {t}, {}})}; + TypeFun{{genericT}, frontend.globals.globalTypes.addType(TypeFunctionInstanceType{NotNull{&swapFamily}, {t}, {}})}; freeze(frontend.globals.globalTypes); } }; -TEST_SUITE_BEGIN("TypeFamilyTests"); +TEST_SUITE_BEGIN("TypeFunctionTests"); TEST_CASE_FIXTURE(FamilyFixture, "basic_type_family") { @@ -80,7 +80,7 @@ TEST_CASE_FIXTURE(FamilyFixture, "basic_type_family") CHECK("number" == toString(requireTypeAlias("B"))); CHECK("Swap" == toString(requireTypeAlias("C"))); CHECK("string" == toString(requireType("y"))); - CHECK("Type family instance Swap is uninhabited" == toString(result.errors[0])); + CHECK("Type function instance Swap is uninhabited" == toString(result.errors[0])); }; TEST_CASE_FIXTURE(FamilyFixture, "family_as_fn_ret") @@ -99,7 +99,7 @@ TEST_CASE_FIXTURE(FamilyFixture, "family_as_fn_ret") CHECK("string" == toString(requireType("a"))); CHECK("number" == toString(requireType("b"))); CHECK("Swap" == toString(requireType("c"))); - CHECK("Type family instance Swap is uninhabited" == toString(result.errors[0])); + CHECK("Type function instance Swap is uninhabited" == toString(result.errors[0])); } TEST_CASE_FIXTURE(FamilyFixture, "family_as_fn_arg") @@ -117,8 +117,8 @@ TEST_CASE_FIXTURE(FamilyFixture, "family_as_fn_arg") // FIXME: Can we constrain these to `never` or `unknown`? CHECK("a" == toString(requireType("a"))); CHECK("a" == toString(requireType("b"))); - CHECK("Type family instance Swap is uninhabited" == toString(result.errors[0])); - CHECK("Type family instance Swap is uninhabited" == toString(result.errors[1])); + CHECK("Type function instance Swap is uninhabited" == toString(result.errors[0])); + CHECK("Type function instance Swap is uninhabited" == toString(result.errors[1])); } TEST_CASE_FIXTURE(FamilyFixture, "resolve_deep_families") @@ -148,7 +148,7 @@ TEST_CASE_FIXTURE(FamilyFixture, "unsolvable_family") LUAU_REQUIRE_ERROR_COUNT(2, result); for (size_t i = 0; i < 2; ++i) { - CHECK(toString(result.errors[i]) == "Type family instance Swap is uninhabited"); + CHECK(toString(result.errors[i]) == "Type function instance Swap is uninhabited"); } } @@ -169,7 +169,7 @@ TEST_CASE_FIXTURE(FamilyFixture, "table_internal_families") CHECK(toString(requireType("b")) == "{number}"); // FIXME: table types are constructing a trivial union here. CHECK(toString(requireType("c")) == "{Swap}"); - CHECK(toString(result.errors[0]) == "Type family instance Swap is uninhabited"); + CHECK(toString(result.errors[0]) == "Type function instance Swap is uninhabited"); } TEST_CASE_FIXTURE(FamilyFixture, "function_internal_families") @@ -190,7 +190,7 @@ TEST_CASE_FIXTURE(FamilyFixture, "function_internal_families") CHECK(toString(requireType("a")) == "() -> string"); CHECK(toString(requireType("b")) == "() -> number"); CHECK(toString(requireType("c")) == "() -> Swap"); - CHECK(toString(result.errors[0]) == "Type family instance Swap is uninhabited"); + CHECK(toString(result.errors[0]) == "Type function instance Swap is uninhabited"); } TEST_CASE_FIXTURE(Fixture, "add_family_at_work") @@ -212,8 +212,8 @@ TEST_CASE_FIXTURE(Fixture, "add_family_at_work") CHECK(toString(requireType("a")) == "number"); CHECK(toString(requireType("b")) == "Add"); CHECK(toString(requireType("c")) == "Add"); - CHECK(toString(result.errors[0]) == "Type family instance Add is uninhabited"); - CHECK(toString(result.errors[1]) == "Type family instance Add is uninhabited"); + CHECK(toString(result.errors[0]) == "Type function instance Add is uninhabited"); + CHECK(toString(result.errors[1]) == "Type function instance Add is uninhabited"); } TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_add_family_at_work") @@ -284,7 +284,7 @@ TEST_CASE_FIXTURE(Fixture, "internal_families_raise_errors") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(toString(result.errors[0]) == "Type family instance Add depends on generic function parameters but does not appear in the function " + CHECK(toString(result.errors[0]) == "Type function instance Add depends on generic function parameters but does not appear in the function " "signature; this construct cannot be type-checked at this time"); } @@ -301,7 +301,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_families_can_be_shadowed") return string.format("hi %s", f) end - -- this should still work totally fine (and use the real type family) + -- this should still work totally fine (and use the real type function) function plus(a, b) return a + b end @@ -387,7 +387,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_errors_if_it_has_nontable_ local function err(idx: KeysOfMyObject): "x" | "y" | "z" return idx end )"); - // FIXME(CLI-95289): we should actually only report the type family being uninhabited error at its first use, I think? + // FIXME(CLI-95289): we should actually only report the type function being uninhabited error at its first use, I think? LUAU_REQUIRE_ERROR_COUNT(2, result); CHECK(toString(result.errors[0]) == "Type 'MyObject | boolean' does not have keys, so 'keyof' is invalid"); CHECK(toString(result.errors[1]) == "Type 'MyObject | boolean' does not have keys, so 'keyof' is invalid"); @@ -513,7 +513,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_errors_if_it_has_nontab local function err(idx: KeysOfMyObject): "x" | "y" | "z" return idx end )"); - // FIXME(CLI-95289): we should actually only report the type family being uninhabited error at its first use, I think? + // FIXME(CLI-95289): we should actually only report the type function being uninhabited error at its first use, I think? LUAU_REQUIRE_ERROR_COUNT(2, result); CHECK(toString(result.errors[0]) == "Type 'MyObject | boolean' does not have keys, so 'rawkeyof' is invalid"); CHECK(toString(result.errors[1]) == "Type 'MyObject | boolean' does not have keys, so 'rawkeyof' is invalid"); @@ -586,7 +586,7 @@ TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_errors_if_it_has_nonclass_par local function err(idx: KeysOfMyObject): "BaseMethod" | "BaseField" return idx end )"); - // FIXME(CLI-95289): we should actually only report the type family being uninhabited error at its first use, I think? + // FIXME(CLI-95289): we should actually only report the type function being uninhabited error at its first use, I think? LUAU_REQUIRE_ERROR_COUNT(2, result); CHECK(toString(result.errors[0]) == "Type 'BaseClass | boolean' does not have keys, so 'keyof' is invalid"); CHECK(toString(result.errors[1]) == "Type 'BaseClass | boolean' does not have keys, so 'keyof' is invalid"); @@ -757,7 +757,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "exceeded_distributivity_limits") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(get(result.errors[0])); + CHECK(get(result.errors[0])); } TEST_CASE_FIXTURE(BuiltinsFixture, "didnt_quite_exceed_distributivity_limits") @@ -1163,4 +1163,4 @@ TEST_CASE_FIXTURE(ClassFixture, "rawget_type_family_errors_w_classes") CHECK(toString(result.errors[0]) == "Property '\"BaseField\"' does not exist on type 'BaseClass'"); } -TEST_SUITE_END(); \ No newline at end of file +TEST_SUITE_END(); diff --git a/tests/TypeInfer.anyerror.test.cpp b/tests/TypeInfer.anyerror.test.cpp index c532c069..d1cbd048 100644 --- a/tests/TypeInfer.anyerror.test.cpp +++ b/tests/TypeInfer.anyerror.test.cpp @@ -386,6 +386,20 @@ stat = stat and tonumber(stat) or stat LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "table_of_any_calls") +{ + CheckResult result = check(R"( + local function testFunc(input: {any}) + end + + local v = {true} + + testFunc(v) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_CASE_FIXTURE(Fixture, "intersection_of_any_can_have_props") { // *blocked-130* ~ hasProp any & ~(false?), "_status" diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 410a9859..94b53cc5 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -2262,7 +2262,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "regex_benchmark_string_format_minimization") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(BuiltinsFixture, "subgeneric_typefamily_super_monomorphic") +TEST_CASE_FIXTURE(BuiltinsFixture, "subgeneric_type_function_super_monomorphic") { CheckResult result = check(R"( local a: (number, number) -> number = function(a, b) return a - b end @@ -2747,5 +2747,21 @@ _ = _,{} )"); } +TEST_CASE_FIXTURE(BuiltinsFixture, "overload_resolution_crash_when_argExprs_is_smaller_than_type_args") +{ + CheckResult result = check(R"( +--!strict +local parseError +type Set = {[T]: any} +local function captureDependencies( + saveToSet: Set, + callback: (...any) -> any, + ... +) + local data = table.pack(xpcall(callback, parseError, ...)) + end +)"); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index b8bb9795..929f8159 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -264,7 +264,7 @@ TEST_CASE_FIXTURE(Fixture, "cannot_indirectly_compare_types_that_do_not_have_a_m if (FFlag::DebugLuauDeferredConstraintResolution) { - UninhabitedTypeFamily* utf = get(result.errors[0]); + UninhabitedTypeFunction* utf = get(result.errors[0]); REQUIRE(utf); REQUIRE_EQ(toString(utf->ty), "lt"); } @@ -294,7 +294,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cannot_indirectly_compare_types_that_do_not_ if (FFlag::DebugLuauDeferredConstraintResolution) { - UninhabitedTypeFamily* utf = get(result.errors[0]); + UninhabitedTypeFunction* utf = get(result.errors[0]); REQUIRE(utf); REQUIRE_EQ(toString(utf->ty), "lt"); } @@ -557,7 +557,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus") { LUAU_REQUIRE_ERROR_COUNT(2, result); - UninhabitedTypeFamily* utf = get(result.errors[0]); + UninhabitedTypeFunction* utf = get(result.errors[0]); REQUIRE(utf); CHECK_EQ(toString(utf->ty), "unm"); @@ -786,7 +786,7 @@ TEST_CASE_FIXTURE(Fixture, "error_on_invalid_operand_types_to_relational_operato if (FFlag::DebugLuauDeferredConstraintResolution) { - UninhabitedTypeFamily* utf = get(result.errors[0]); + UninhabitedTypeFunction* utf = get(result.errors[0]); REQUIRE(utf); REQUIRE_EQ(toString(utf->ty), "lt"); } @@ -817,7 +817,7 @@ TEST_CASE_FIXTURE(Fixture, "error_on_invalid_operand_types_to_relational_operato if (FFlag::DebugLuauDeferredConstraintResolution) { - UninhabitedTypeFamily* utf = get(result.errors[0]); + UninhabitedTypeFunction* utf = get(result.errors[0]); REQUIRE(utf); REQUIRE_EQ(toString(utf->ty), "lt"); } diff --git a/tools/faillist.txt b/tools/faillist.txt index 0ada384d..51f086ff 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -246,18 +246,18 @@ TypeAliases.report_shadowed_aliases TypeAliases.type_alias_local_mutation TypeAliases.type_alias_local_rename TypeAliases.type_alias_of_an_imported_recursive_generic_type -TypeFamilyTests.add_family_at_work -TypeFamilyTests.cyclic_add_family_at_work -TypeFamilyTests.cyclic_concat_family_at_work -TypeFamilyTests.didnt_quite_exceed_distributivity_limits -TypeFamilyTests.ensure_equivalence_with_distributivity -TypeFamilyTests.family_as_fn_arg -TypeFamilyTests.index_type_family_works_w_generic_types -TypeFamilyTests.internal_families_raise_errors -TypeFamilyTests.keyof_oss_crash_gh1161 -TypeFamilyTests.mul_family_with_union_of_multiplicatives -TypeFamilyTests.mul_family_with_union_of_multiplicatives_2 -TypeFamilyTests.unsolvable_family +TypeFunctionTests.add_family_at_work +TypeFunctionTests.cyclic_add_family_at_work +TypeFunctionTests.cyclic_concat_family_at_work +TypeFunctionTests.didnt_quite_exceed_distributivity_limits +TypeFunctionTests.ensure_equivalence_with_distributivity +TypeFunctionTests.family_as_fn_arg +TypeFunctionTests.index_type_family_works_w_generic_types +TypeFunctionTests.internal_families_raise_errors +TypeFunctionTests.keyof_oss_crash_gh1161 +TypeFunctionTests.mul_family_with_union_of_multiplicatives +TypeFunctionTests.mul_family_with_union_of_multiplicatives_2 +TypeFunctionTests.unsolvable_family TypeInfer.be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload TypeInfer.check_type_infer_recursion_count TypeInfer.checking_should_not_ice @@ -277,7 +277,6 @@ TypeInfer.tc_if_else_expressions_expected_type_3 TypeInfer.type_infer_recursion_limit_no_ice TypeInfer.type_infer_recursion_limit_normalizer TypeInfer.unify_nearly_identical_recursive_types -TypeInferAnyError.can_subscript_any TypeInferAnyError.for_in_loop_iterator_is_any TypeInferAnyError.for_in_loop_iterator_is_any2 TypeInferAnyError.for_in_loop_iterator_is_any_pack @@ -290,7 +289,6 @@ TypeInferClasses.class_type_mismatch_with_name_conflict TypeInferClasses.detailed_class_unification_error TypeInferClasses.indexable_classes TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties -TypeInferClasses.table_indexers_are_invariant TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class TypeInferFunctions.another_other_higher_order_function TypeInferFunctions.bidirectional_checking_of_callback_property From e1bf6289c705e08fc859cb04aceb32c6a8322f81 Mon Sep 17 00:00:00 2001 From: Alexander McCord <11488393+alexmccord@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:35:20 -0700 Subject: [PATCH 2/5] Equality graphs (#1285) Working towards a full e-graph implementation as described by the [egg paper](https://arxiv.org/pdf/2004.03082). The type system has a couple of places where e-graphs would've been useful and solved some classes of problems trivially. For example: 1. Normalization and simplification cannot handle cyclic types due to the nature of their implementation. 2. Normalization can't tell when two tables or functions are equivalent, but simplification theoretically can albeit not implemented. 3. Normalization requires deep normalization for inhabitance check, whereas simplification would've returned the `never` type itself indicating uninhabited. 4. Simplification requires constraint ordering to have perfect timing to simplify. 5. Adding a rewrite rule requires implementing it twice, once in simplification and once again in normalization with completely different code design making it hard to verify that their behavior is materially equivalent. 6. In cases where we must cache for performance, two different types that are isomorphic have different cache entries resulting in cache misses. 7. Type family reduction can handle cyclic type families, but only if the cycle is not obscured by a different type family instance. (`t1 where t1 = union>` is irreducible) I think we're getting the point! --- Currently the implementation is missing a few features that makes e-graphs actually useful. Those will be coming in a future PR. 1. Pattern matching, 6. Applying rewrites, 7. Rewrite until saturation, and 8. Extracting the best e-node according to some cost function. --- .gitignore | 2 + CMakeLists.txt | 12 +- {Analysis => Common}/include/Luau/Variant.h | 0 EqSat/include/Luau/EGraph.h | 228 +++++++++++++++ EqSat/include/Luau/Id.h | 29 ++ EqSat/include/Luau/Language.h | 304 ++++++++++++++++++++ EqSat/include/Luau/LanguageHash.h | 56 ++++ EqSat/include/Luau/Slice.h | 78 +++++ EqSat/include/Luau/UnionFind.h | 22 ++ EqSat/src/Id.cpp | 32 +++ EqSat/src/UnionFind.cpp | 35 +++ Makefile | 28 +- Sources.cmake | 18 +- tests/EqSat.language.test.cpp | 144 ++++++++++ tests/EqSat.propositional.test.cpp | 197 +++++++++++++ tests/EqSat.slice.test.cpp | 58 ++++ 16 files changed, 1228 insertions(+), 15 deletions(-) rename {Analysis => Common}/include/Luau/Variant.h (100%) create mode 100644 EqSat/include/Luau/EGraph.h create mode 100644 EqSat/include/Luau/Id.h create mode 100644 EqSat/include/Luau/Language.h create mode 100644 EqSat/include/Luau/LanguageHash.h create mode 100644 EqSat/include/Luau/Slice.h create mode 100644 EqSat/include/Luau/UnionFind.h create mode 100644 EqSat/src/Id.cpp create mode 100644 EqSat/src/UnionFind.cpp create mode 100644 tests/EqSat.language.test.cpp create mode 100644 tests/EqSat.propositional.test.cpp create mode 100644 tests/EqSat.slice.test.cpp diff --git a/.gitignore b/.gitignore index 8de6d91d..8e5c95dd 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ /luau-analyze /luau-compile __pycache__ +.cache +.clangd diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b7e551e..34e104e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ add_library(Luau.Ast STATIC) add_library(Luau.Compiler STATIC) add_library(Luau.Config STATIC) add_library(Luau.Analysis STATIC) +add_library(Luau.EqSat STATIC) add_library(Luau.CodeGen STATIC) add_library(Luau.VM STATIC) add_library(isocline STATIC) @@ -83,7 +84,11 @@ target_link_libraries(Luau.Config PUBLIC Luau.Ast) target_compile_features(Luau.Analysis PUBLIC cxx_std_17) target_include_directories(Luau.Analysis PUBLIC Analysis/include) -target_link_libraries(Luau.Analysis PUBLIC Luau.Ast Luau.Config) +target_link_libraries(Luau.Analysis PUBLIC Luau.Ast Luau.EqSat Luau.Config) + +target_compile_features(Luau.EqSat PUBLIC cxx_std_17) +target_include_directories(Luau.EqSat PUBLIC EqSat/include) +target_link_libraries(Luau.EqSat PUBLIC Luau.Common) target_compile_features(Luau.CodeGen PRIVATE cxx_std_17) target_include_directories(Luau.CodeGen PUBLIC CodeGen/include) @@ -141,6 +146,7 @@ endif() target_compile_options(Luau.Ast PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.Analysis PRIVATE ${LUAU_OPTIONS}) +target_compile_options(Luau.EqSat PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.CLI.lib PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.CodeGen PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.VM PRIVATE ${LUAU_OPTIONS}) @@ -263,13 +269,13 @@ endif() add_subdirectory(fuzz) # validate dependencies for internal libraries -foreach(LIB Luau.Ast Luau.Compiler Luau.Config Luau.Analysis Luau.CodeGen Luau.VM) +foreach(LIB Luau.Ast Luau.Compiler Luau.Config Luau.Analysis Luau.EqSat Luau.CodeGen Luau.VM) if(TARGET ${LIB}) get_target_property(DEPENDS ${LIB} LINK_LIBRARIES) if(LIB MATCHES "CodeGen|VM" AND DEPENDS MATCHES "Ast|Analysis|Config|Compiler") message(FATAL_ERROR ${LIB} " is a runtime component but it depends on one of the offline components") endif() - if(LIB MATCHES "Ast|Analysis|Compiler" AND DEPENDS MATCHES "CodeGen|VM") + if(LIB MATCHES "Ast|Analysis|EqSat|Compiler" AND DEPENDS MATCHES "CodeGen|VM") message(FATAL_ERROR ${LIB} " is an offline component but it depends on one of the runtime components") endif() if(LIB MATCHES "Ast|Compiler" AND DEPENDS MATCHES "Analysis|Config") diff --git a/Analysis/include/Luau/Variant.h b/Common/include/Luau/Variant.h similarity index 100% rename from Analysis/include/Luau/Variant.h rename to Common/include/Luau/Variant.h diff --git a/EqSat/include/Luau/EGraph.h b/EqSat/include/Luau/EGraph.h new file mode 100644 index 00000000..abccd70c --- /dev/null +++ b/EqSat/include/Luau/EGraph.h @@ -0,0 +1,228 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/Common.h" +#include "Luau/Id.h" +#include "Luau/Language.h" +#include "Luau/UnionFind.h" +#include "Luau/VecDeque.h" + +#include +#include +#include + +namespace Luau::EqSat +{ + +template +struct EGraph; + +template +struct Analysis final +{ + N analysis; + + using D = typename N::Data; + + template + static D fnMake(const N& analysis, const EGraph& egraph, const L& enode) + { + return analysis.make(egraph, *enode.template get()); + } + + template + D make(const EGraph& egraph, const Language& enode) const + { + using FnMake = D (*)(const N&, const EGraph&, const L&); + static constexpr FnMake tableMake[sizeof...(Ts)] = {&fnMake...}; + + return tableMake[enode.index()](analysis, egraph, enode); + } + + void join(D& a, const D& b) const + { + return analysis.join(a, b); + } +}; + +/// Each e-class is a set of e-nodes representing equivalent terms from a given language, +/// and an e-node is a function symbol paired with a list of children e-classes. +template +struct EClass final +{ + Id id; + std::vector nodes; + D data; + std::vector> parents; +}; + +/// See . +template +struct EGraph final +{ + Id find(Id id) const + { + return unionfind.find(id); + } + + std::optional lookup(const L& enode) const + { + LUAU_ASSERT(isCanonical(enode)); + + if (auto it = hashcons.find(enode); it != hashcons.end()) + return it->second; + + return std::nullopt; + } + + Id add(L enode) + { + canonicalize(enode); + + if (auto id = lookup(enode)) + return *id; + + Id id = makeEClass(enode); + return id; + } + + void merge(Id id1, Id id2) + { + id1 = find(id1); + id2 = find(id2); + if (id1 == id2) + return; + + unionfind.merge(id1, id2); + + EClass& eclass1 = get(id1); + EClass eclass2 = std::move(get(id2)); + classes.erase(id2); + + worklist.reserve(worklist.size() + eclass2.parents.size()); + for (auto [enode, id] : eclass2.parents) + worklist.push_back({std::move(enode), id}); + + analysis.join(eclass1.data, eclass2.data); + } + + void rebuild() + { + while (!worklist.empty()) + { + auto [enode, id] = worklist.back(); + worklist.pop_back(); + repair(get(find(id))); + } + } + + size_t size() const + { + return classes.size(); + } + + EClass& operator[](Id id) + { + return get(find(id)); + } + + const EClass& operator[](Id id) const + { + return const_cast(this)->get(find(id)); + } + +private: + Analysis analysis; + + /// A union-find data structure 𝑈 stores an equivalence relation over e-class ids. + UnionFind unionfind; + + /// The e-class map 𝑀 maps e-class ids to e-classes. All equivalent e-class ids map to the same + /// e-class, i.e., 𝑎 ≡id 𝑏 iff 𝑀[𝑎] is the same set as 𝑀[𝑏]. An e-class id 𝑎 is said to refer to the + /// e-class 𝑀[find(𝑎)]. + std::unordered_map> classes; + + /// The hashcons 𝐻 is a map from e-nodes to e-class ids. + std::unordered_map hashcons; + + VecDeque> worklist; + +private: + void canonicalize(L& enode) + { + // An e-node 𝑛 is canonical iff 𝑛 = canonicalize(𝑛), where + // canonicalize(𝑓(𝑎1, 𝑎2, ...)) = 𝑓(find(𝑎1), find(𝑎2), ...). + for (Id& id : enode.operands()) + id = find(id); + } + + bool isCanonical(const L& enode) const + { + bool canonical = true; + for (Id id : enode.operands()) + canonical &= (id == find(id)); + return canonical; + } + + Id makeEClass(const L& enode) + { + LUAU_ASSERT(isCanonical(enode)); + + Id id = unionfind.makeSet(); + + classes.insert_or_assign(id, EClass{ + id, + {enode}, + analysis.make(*this, enode), + {}, + }); + + for (Id operand : enode.operands()) + get(operand).parents.push_back({enode, id}); + + worklist.push_back({enode, id}); + hashcons.insert_or_assign(enode, id); + + return id; + } + + // Looks up for an eclass from a given non-canonicalized `id`. + // For a canonicalized eclass, use `get(find(id))` or `egraph[id]`. + EClass& get(Id id) + { + return classes.at(id); + } + + void repair(EClass& eclass) + { + // In the egg paper, the `repair` function makes use of two loops over the `eclass.parents` + // by first erasing the old enode entry, and adding back the canonicalized enode with the canonical id. + // And then in another loop that follows, deduplicate it. + // + // Here, we unify the two loops. I think it's equivalent? + + // After canonicalizing the enodes, the eclass may contain multiple enodes that are equivalent. + std::unordered_map map; + for (auto& [enode, id] : eclass.parents) + { + // By removing the old enode from the hashcons map, we will always find our new canonicalized eclass id. + hashcons.erase(enode); + canonicalize(enode); + hashcons.insert_or_assign(enode, find(id)); + + if (auto it = map.find(enode); it != map.end()) + merge(id, it->second); + + map.insert_or_assign(enode, find(id)); + } + + eclass.parents.clear(); + for (auto it = map.begin(); it != map.end();) + { + auto node = map.extract(it++); + eclass.parents.emplace_back(std::move(node.key()), node.mapped()); + } + } +}; + +} // namespace Luau::EqSat diff --git a/EqSat/include/Luau/Id.h b/EqSat/include/Luau/Id.h new file mode 100644 index 00000000..c56a6ab6 --- /dev/null +++ b/EqSat/include/Luau/Id.h @@ -0,0 +1,29 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include +#include + +namespace Luau::EqSat +{ + +struct Id final +{ + explicit Id(size_t id); + + explicit operator size_t() const; + + bool operator==(Id rhs) const; + bool operator!=(Id rhs) const; + +private: + size_t id; +}; + +} // namespace Luau::EqSat + +template<> +struct std::hash +{ + size_t operator()(Luau::EqSat::Id id) const; +}; diff --git a/EqSat/include/Luau/Language.h b/EqSat/include/Luau/Language.h new file mode 100644 index 00000000..c17ac577 --- /dev/null +++ b/EqSat/include/Luau/Language.h @@ -0,0 +1,304 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/Id.h" +#include "Luau/LanguageHash.h" +#include "Luau/Slice.h" +#include "Luau/Variant.h" + +#include +#include +#include +#include + +#define LUAU_EQSAT_ATOM(name, t) \ + struct name : public ::Luau::EqSat::Atom \ + { \ + static constexpr const char* tag = #name; \ + using Atom::Atom; \ + } + +#define LUAU_EQSAT_NODE_ARRAY(name, ops) \ + struct name : public ::Luau::EqSat::NodeVector> \ + { \ + static constexpr const char* tag = #name; \ + using NodeVector::NodeVector; \ + } + +#define LUAU_EQSAT_NODE_VECTOR(name) \ + struct name : public ::Luau::EqSat::NodeVector> \ + { \ + static constexpr const char* tag = #name; \ + using NodeVector::NodeVector; \ + } + +#define LUAU_EQSAT_FIELD(name) \ + struct name : public ::Luau::EqSat::Field \ + { \ + } + +#define LUAU_EQSAT_NODE_FIELDS(name, ...) \ + struct name : public ::Luau::EqSat::NodeFields \ + { \ + static constexpr const char* tag = #name; \ + using NodeFields::NodeFields; \ + } + +namespace Luau::EqSat +{ + +template +struct Atom +{ + Atom(const T& value) + : _value(value) + { + } + + const T& value() const + { + return _value; + } + +public: + Slice operands() + { + return {}; + } + + Slice operands() const + { + return {}; + } + + bool operator==(const Atom& rhs) const + { + return _value == rhs._value; + } + + bool operator!=(const Atom& rhs) const + { + return !(*this == rhs); + } + + struct Hash + { + size_t operator()(const Atom& value) const + { + return languageHash(value._value); + } + }; + +private: + T _value; +}; + +template +struct NodeVector +{ + template + NodeVector(Args&&... args) + : vector{std::forward(args)...} + { + } + + Id operator[](size_t i) const + { + return vector[i]; + } + +public: + Slice operands() + { + return Slice{vector.data(), vector.size()}; + } + + Slice operands() const + { + return Slice{vector.data(), vector.size()}; + } + + bool operator==(const NodeVector& rhs) const + { + return vector == rhs.vector; + } + + bool operator!=(const NodeVector& rhs) const + { + return !(*this == rhs); + } + + struct Hash + { + size_t operator()(const NodeVector& value) const + { + return languageHash(value.vector); + } + }; + +private: + T vector; +}; + +/// Empty base class just for static_asserts. +struct FieldBase +{ + FieldBase() = delete; + + FieldBase(FieldBase&&) = delete; + FieldBase& operator=(FieldBase&&) = delete; + + FieldBase(const FieldBase&) = delete; + FieldBase& operator=(const FieldBase&) = delete; +}; + +template +struct Field : FieldBase +{ +}; + +template +struct NodeFields +{ + static_assert(std::conjunction...>::value); + + template + static constexpr int getIndex() + { + constexpr int N = sizeof...(Fields); + constexpr bool is[N] = {std::is_same_v, Fields>...}; + + for (int i = 0; i < N; ++i) + if (is[i]) + return i; + + return -1; + } + +public: + template + NodeFields(Args&&... args) + : array{std::forward(args)...} + { + } + + Slice operands() + { + return Slice{array}; + } + + Slice operands() const + { + return Slice{array.data(), array.size()}; + } + + template + Id field() const + { + static_assert(std::disjunction_v, Fields>...>); + return array[getIndex()]; + } + + bool operator==(const NodeFields& rhs) const + { + return array == rhs.array; + } + + bool operator!=(const NodeFields& rhs) const + { + return !(*this == rhs); + } + + struct Hash + { + size_t operator()(const NodeFields& value) const + { + return languageHash(value.array); + } + }; + +private: + std::array array; +}; + +template +struct Language final +{ + template + using WithinDomain = std::disjunction, Ts>...>; + + template + Language(T&& t, std::enable_if_t::value>* = 0) noexcept + : v(std::forward(t)) + { + } + + Language(const Language&) noexcept = default; + Language& operator=(const Language&) noexcept = default; + + Language(Language&&) noexcept = default; + Language& operator=(Language&&) noexcept = default; + + int index() const noexcept + { + return v.index(); + } + + /// You should never call this function with the intention of mutating the `Id`. + /// Reading is ok, but you should also never assume that these `Id`s are stable. + Slice operands() noexcept + { + return visit([](auto&& v) -> Slice { + return v.operands(); + }, v); + } + + Slice operands() const noexcept + { + return visit([](auto&& v) -> Slice { + return v.operands(); + }, v); + } + + template + T* get() noexcept + { + static_assert(WithinDomain::value); + return v.template get_if(); + } + + template + const T* get() const noexcept + { + static_assert(WithinDomain::value); + return v.template get_if(); + } + + bool operator==(const Language& rhs) const noexcept + { + return v == rhs.v; + } + + bool operator!=(const Language& rhs) const noexcept + { + return !(*this == rhs); + } + +public: + struct Hash + { + size_t operator()(const Language& language) const + { + size_t seed = std::hash{}(language.index()); + hashCombine(seed, visit([](auto&& v) { + return typename std::decay_t::Hash{}(v); + }, language.v)); + return seed; + } + }; + +private: + Variant v; +}; + +} // namespace Luau::EqSat diff --git a/EqSat/include/Luau/LanguageHash.h b/EqSat/include/Luau/LanguageHash.h new file mode 100644 index 00000000..8c5f837c --- /dev/null +++ b/EqSat/include/Luau/LanguageHash.h @@ -0,0 +1,56 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include +#include + +namespace Luau::EqSat +{ + +template +struct LanguageHash +{ + size_t operator()(const T& t, decltype(std::hash{}(std::declval()))* = 0) const + { + return std::hash{}(t); + } +}; + +template +size_t languageHash(const T& lang) +{ + return LanguageHash{}(lang); +} + +inline void hashCombine(size_t& seed, size_t hash) +{ + // Golden Ratio constant used for better hash scattering + // See https://softwareengineering.stackexchange.com/a/402543 + seed ^= hash + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +template +struct LanguageHash> +{ + size_t operator()(const std::array& array) const + { + size_t seed = 0; + for (const T& t : array) + hashCombine(seed, languageHash(t)); + return seed; + } +}; + +template +struct LanguageHash> +{ + size_t operator()(const std::vector& vector) const + { + size_t seed = 0; + for (const T& t : vector) + hashCombine(seed, languageHash(t)); + return seed; + } +}; + +} // namespace Luau::EqSat diff --git a/EqSat/include/Luau/Slice.h b/EqSat/include/Luau/Slice.h new file mode 100644 index 00000000..c1c8f098 --- /dev/null +++ b/EqSat/include/Luau/Slice.h @@ -0,0 +1,78 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/Common.h" + +#include +#include + +namespace Luau::EqSat +{ + +template +struct Slice final +{ + Slice() + : _data(nullptr) + , _size(0) + { + } + + /// Use this constructor if you have a dynamically sized vector. + /// The slice is valid for as long as the backing vector has not moved + /// elsewhere in memory. + /// + /// In general, a slice should never be used from vectors except for + /// any vectors whose size are statically unknown, but remains fixed + /// upon the construction of such a slice over a vector. + Slice(T* first, size_t last) + : _data(first) + , _size(last) + { + } + + template + explicit Slice(std::array& array) + : _data(array.data()) + , _size(array.size()) + { + } + + T* data() const + { + return _data; + } + + size_t size() const + { + return _size; + } + + bool empty() const + { + return _size == 0; + } + + T& operator[](size_t i) const + { + LUAU_ASSERT(i < _size); + return _data[i]; + } + +public: + T* _data; + size_t _size; + +public: + T* begin() const + { + return _data; + } + + T* end() const + { + return _data + _size; + } +}; + +} // namespace Luau::EqSat diff --git a/EqSat/include/Luau/UnionFind.h b/EqSat/include/Luau/UnionFind.h new file mode 100644 index 00000000..dd886a44 --- /dev/null +++ b/EqSat/include/Luau/UnionFind.h @@ -0,0 +1,22 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/Id.h" + +#include + +namespace Luau::EqSat +{ + +/// See . +struct UnionFind final +{ + Id makeSet(); + Id find(Id id) const; + void merge(Id a, Id b); + +private: + std::vector parents; +}; + +} // namespace Luau::EqSat diff --git a/EqSat/src/Id.cpp b/EqSat/src/Id.cpp new file mode 100644 index 00000000..960249ba --- /dev/null +++ b/EqSat/src/Id.cpp @@ -0,0 +1,32 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/Id.h" + +namespace Luau::EqSat +{ + +Id::Id(size_t id) + : id(id) +{ +} + +Id::operator size_t() const +{ + return id; +} + +bool Id::operator==(Id rhs) const +{ + return id == rhs.id; +} + +bool Id::operator!=(Id rhs) const +{ + return id != rhs.id; +} + +} // namespace Luau::EqSat + +size_t std::hash::operator()(Luau::EqSat::Id id) const +{ + return std::hash()(size_t(id)); +} diff --git a/EqSat/src/UnionFind.cpp b/EqSat/src/UnionFind.cpp new file mode 100644 index 00000000..04d9ba74 --- /dev/null +++ b/EqSat/src/UnionFind.cpp @@ -0,0 +1,35 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/UnionFind.h" + +#include "Luau/Common.h" + +namespace Luau::EqSat +{ + +Id UnionFind::makeSet() +{ + Id id{parents.size()}; + parents.push_back(id); + return id; +} + +Id UnionFind::find(Id id) const +{ + LUAU_ASSERT(size_t(id) < parents.size()); + + // An e-class id 𝑎 is canonical iff find(𝑎) = 𝑎. + while (id != parents[size_t(id)]) + id = parents[size_t(id)]; + + return id; +} + +void UnionFind::merge(Id a, Id b) +{ + LUAU_ASSERT(size_t(a) < parents.size()); + LUAU_ASSERT(size_t(b) < parents.size()); + + parents[size_t(b)] = a; +} + +} // namespace Luau::EqSat diff --git a/Makefile b/Makefile index 0f7c1927..7eead323 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,10 @@ ANALYSIS_SOURCES=$(wildcard Analysis/src/*.cpp) ANALYSIS_OBJECTS=$(ANALYSIS_SOURCES:%=$(BUILD)/%.o) ANALYSIS_TARGET=$(BUILD)/libluauanalysis.a +EQSAT_SOURCES=$(wildcard EqSat/src/*.cpp) +EQSAT_OBJECTS=$(EQSAT_SOURCES:%=$(BUILD)/%.o) +EQSAT_TARGET=$(BUILD)/libluaueqsat.a + CODEGEN_SOURCES=$(wildcard CodeGen/src/*.cpp) CODEGEN_OBJECTS=$(CODEGEN_SOURCES:%=$(BUILD)/%.o) CODEGEN_TARGET=$(BUILD)/libluaucodegen.a @@ -69,7 +73,7 @@ ifneq ($(opt),) TESTS_ARGS+=-O$(opt) endif -OBJECTS=$(AST_OBJECTS) $(COMPILER_OBJECTS) $(CONFIG_OBJECTS) $(ANALYSIS_OBJECTS) $(CODEGEN_OBJECTS) $(VM_OBJECTS) $(ISOCLINE_OBJECTS) $(TESTS_OBJECTS) $(REPL_CLI_OBJECTS) $(ANALYZE_CLI_OBJECTS) $(COMPILE_CLI_OBJECTS) $(BYTECODE_CLI_OBJECTS) $(FUZZ_OBJECTS) +OBJECTS=$(AST_OBJECTS) $(COMPILER_OBJECTS) $(CONFIG_OBJECTS) $(ANALYSIS_OBJECTS) $(EQSAT_OBJECTS) $(CODEGEN_OBJECTS) $(VM_OBJECTS) $(ISOCLINE_OBJECTS) $(TESTS_OBJECTS) $(REPL_CLI_OBJECTS) $(ANALYZE_CLI_OBJECTS) $(COMPILE_CLI_OBJECTS) $(BYTECODE_CLI_OBJECTS) $(FUZZ_OBJECTS) EXECUTABLE_ALIASES = luau luau-analyze luau-compile luau-bytecode luau-tests # common flags @@ -138,16 +142,17 @@ endif $(AST_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include $(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -ICommon/include -IAst/include $(CONFIG_OBJECTS): CXXFLAGS+=-std=c++17 -IConfig/include -ICommon/include -IAst/include -$(ANALYSIS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -IConfig/include +$(ANALYSIS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -IEqSat/include -IConfig/include +$(EQSAT_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IEqSat/include $(CODEGEN_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -ICodeGen/include -IVM/include -IVM/src # Code generation needs VM internals $(VM_OBJECTS): CXXFLAGS+=-std=c++11 -ICommon/include -IVM/include $(ISOCLINE_OBJECTS): CXXFLAGS+=-Wno-unused-function -Iextern/isocline/include -$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IConfig/include -IAnalysis/include -ICodeGen/include -IVM/include -ICLI -Iextern -DDOCTEST_CONFIG_DOUBLE_STRINGIFY +$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IConfig/include -IAnalysis/include -IEqSat/include -ICodeGen/include -IVM/include -ICLI -Iextern -DDOCTEST_CONFIG_DOUBLE_STRINGIFY $(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -Iextern -Iextern/isocline/include -$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -IConfig/include -Iextern +$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -IEqSat/include -IConfig/include -Iextern $(COMPILE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include $(BYTECODE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IVM/include -ICodeGen/include -IConfig/include +$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IEqSat/include -IVM/include -ICodeGen/include -IConfig/include $(TESTS_TARGET): LDFLAGS+=-lpthread $(REPL_CLI_TARGET): LDFLAGS+=-lpthread @@ -218,9 +223,9 @@ luau-tests: $(TESTS_TARGET) ln -fs $^ $@ # executable targets -$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(CONFIG_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET) +$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(EQSAT_TARGET) $(COMPILER_TARGET) $(CONFIG_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET) $(REPL_CLI_TARGET): $(REPL_CLI_OBJECTS) $(COMPILER_TARGET) $(CONFIG_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET) -$(ANALYZE_CLI_TARGET): $(ANALYZE_CLI_OBJECTS) $(ANALYSIS_TARGET) $(AST_TARGET) $(CONFIG_TARGET) +$(ANALYZE_CLI_TARGET): $(ANALYZE_CLI_OBJECTS) $(ANALYSIS_TARGET) $(EQSAT_TARGET) $(AST_TARGET) $(CONFIG_TARGET) $(COMPILE_CLI_TARGET): $(COMPILE_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(BYTECODE_CLI_TARGET): $(BYTECODE_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) @@ -228,22 +233,23 @@ $(TESTS_TARGET) $(REPL_CLI_TARGET) $(ANALYZE_CLI_TARGET) $(COMPILE_CLI_TARGET) $ $(CXX) $^ $(LDFLAGS) -o $@ # executable targets for fuzzing -fuzz-%: $(BUILD)/fuzz/%.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CONFIG_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) +fuzz-%: $(BUILD)/fuzz/%.cpp.o $(ANALYSIS_TARGET) $(EQSAT_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CONFIG_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(CXX) $^ $(LDFLAGS) -o $@ -fuzz-proto: $(BUILD)/fuzz/proto.cpp.o $(BUILD)/fuzz/protoprint.cpp.o $(BUILD)/fuzz/luau.pb.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CONFIG_TARGET) $(VM_TARGET) | build/libprotobuf-mutator -fuzz-prototest: $(BUILD)/fuzz/prototest.cpp.o $(BUILD)/fuzz/protoprint.cpp.o $(BUILD)/fuzz/luau.pb.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CONFIG_TARGET) $(VM_TARGET) | build/libprotobuf-mutator +fuzz-proto: $(BUILD)/fuzz/proto.cpp.o $(BUILD)/fuzz/protoprint.cpp.o $(BUILD)/fuzz/luau.pb.cpp.o $(ANALYSIS_TARGET) $(EQSAT_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CONFIG_TARGET) $(VM_TARGET) | build/libprotobuf-mutator +fuzz-prototest: $(BUILD)/fuzz/prototest.cpp.o $(BUILD)/fuzz/protoprint.cpp.o $(BUILD)/fuzz/luau.pb.cpp.o $(ANALYSIS_TARGET) $(EQSAT_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CONFIG_TARGET) $(VM_TARGET) | build/libprotobuf-mutator # static library targets $(AST_TARGET): $(AST_OBJECTS) $(COMPILER_TARGET): $(COMPILER_OBJECTS) $(CONFIG_TARGET): $(CONFIG_OBJECTS) $(ANALYSIS_TARGET): $(ANALYSIS_OBJECTS) +$(EQSAT_TARGET): $(EQSAT_OBJECTS) $(CODEGEN_TARGET): $(CODEGEN_OBJECTS) $(VM_TARGET): $(VM_OBJECTS) $(ISOCLINE_TARGET): $(ISOCLINE_OBJECTS) -$(AST_TARGET) $(COMPILER_TARGET) $(CONFIG_TARGET) $(ANALYSIS_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET): +$(AST_TARGET) $(COMPILER_TARGET) $(CONFIG_TARGET) $(ANALYSIS_TARGET) $(EQSAT_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET): ar rcs $@ $^ # object file targets diff --git a/Sources.cmake b/Sources.cmake index 72038e70..e0711f51 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -7,6 +7,7 @@ if(NOT ${CMAKE_VERSION} VERSION_LESS "3.19") Common/include/Luau/BytecodeUtils.h Common/include/Luau/DenseHash.h Common/include/Luau/ExperimentalFlags.h + Common/include/Luau/Variant.h Common/include/Luau/VecDeque.h ) endif() @@ -232,7 +233,6 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/Unifier.h Analysis/include/Luau/Unifier2.h Analysis/include/Luau/UnifierSharedState.h - Analysis/include/Luau/Variant.h Analysis/include/Luau/VisitType.h Analysis/src/Anyification.cpp @@ -295,6 +295,19 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/Unifier2.cpp ) +# Luau.Analysis Sources +target_sources(Luau.EqSat PRIVATE + EqSat/include/Luau/EGraph.h + EqSat/include/Luau/Id.h + EqSat/include/Luau/Language.h + EqSat/include/Luau/LanguageHash.h + EqSat/include/Luau/Slice.h + EqSat/include/Luau/UnionFind.h + + EqSat/src/Id.cpp + EqSat/src/UnionFind.cpp +) + # Luau.VM Sources target_sources(Luau.VM PRIVATE VM/include/lua.h @@ -418,6 +431,9 @@ if(TARGET Luau.UnitTest) tests/DiffAsserts.cpp tests/DiffAsserts.h tests/Differ.test.cpp + tests/EqSat.language.test.cpp + tests/EqSat.propositional.test.cpp + tests/EqSat.slice.test.cpp tests/Error.test.cpp tests/Fixture.cpp tests/Fixture.h diff --git a/tests/EqSat.language.test.cpp b/tests/EqSat.language.test.cpp new file mode 100644 index 00000000..282d4ad2 --- /dev/null +++ b/tests/EqSat.language.test.cpp @@ -0,0 +1,144 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include + +#include "Luau/Id.h" +#include "Luau/Language.h" + +#include +#include + +LUAU_EQSAT_ATOM(I32, int); +LUAU_EQSAT_ATOM(Bool, bool); +LUAU_EQSAT_ATOM(Str, std::string); + +LUAU_EQSAT_FIELD(Left); +LUAU_EQSAT_FIELD(Right); +LUAU_EQSAT_NODE_FIELDS(Add, Left, Right); + +using namespace Luau; + +using Value = EqSat::Language; + +TEST_SUITE_BEGIN("EqSatLanguage"); + +TEST_CASE("atom_equality") +{ + CHECK(I32{0} == I32{0}); + CHECK(I32{0} != I32{1}); +} + +TEST_CASE("node_equality") +{ + CHECK(Add{EqSat::Id{0}, EqSat::Id{0}} == Add{EqSat::Id{0}, EqSat::Id{0}}); + CHECK(Add{EqSat::Id{1}, EqSat::Id{0}} != Add{EqSat::Id{0}, EqSat::Id{0}}); +} + +TEST_CASE("language_get") +{ + Value v{I32{5}}; + + auto i = v.get(); + REQUIRE(i); + CHECK(i->value()); + + CHECK(!v.get()); +} + +TEST_CASE("language_copy_ctor") +{ + Value v1{I32{5}}; + Value v2 = v1; + + auto i1 = v1.get(); + auto i2 = v2.get(); + REQUIRE(i1); + REQUIRE(i2); + CHECK(i1->value() == i2->value()); +} + +TEST_CASE("language_move_ctor") +{ + Value v1{Str{"hello"}}; + { + auto s1 = v1.get(); + REQUIRE(s1); + CHECK(s1->value() == "hello"); + } + + Value v2 = std::move(v1); + + auto s1 = v1.get(); + REQUIRE(s1); + CHECK(s1->value() == ""); // this also tests the dtor. + + auto s2 = v2.get(); + REQUIRE(s2); + CHECK(s2->value() == "hello"); +} + +TEST_CASE("language_equality") +{ + Value v1{I32{0}}; + Value v2{I32{0}}; + Value v3{I32{1}}; + Value v4{Bool{true}}; + Value v5{Add{EqSat::Id{0}, EqSat::Id{1}}}; + + CHECK(v1 == v2); + CHECK(v2 != v3); + CHECK(v3 != v4); + CHECK(v4 != v5); +} + +TEST_CASE("language_is_mappable") +{ + std::unordered_map map; + + Value v1{I32{5}}; + Value v2{I32{5}}; + Value v3{Bool{true}}; + Value v4{Add{EqSat::Id{0}, EqSat::Id{1}}}; + + map[v1] = 1; + map[v2] = 2; + map[v3] = 42; + map[v4] = 37; + + CHECK(map[v1] == 2); + CHECK(map[v2] == 2); + CHECK(map[v3] == 42); + CHECK(map[v4] == 37); +} + +TEST_CASE("node_field") +{ + EqSat::Id left{0}; + EqSat::Id right{1}; + + Add add{left, right}; + + EqSat::Id left2 = add.field(); + EqSat::Id right2 = add.field(); + + CHECK(left == left2); + CHECK(left != right2); + CHECK(right == right2); + CHECK(right != left2); +} + +TEST_CASE("language_operands") +{ + Value v1{I32{0}}; + CHECK(v1.operands().empty()); + + Value v2{Add{EqSat::Id{0}, EqSat::Id{1}}}; + const Add* add = v2.get(); + REQUIRE(add); + + EqSat::Slice actual = v2.operands(); + CHECK(actual.size() == 2); + CHECK(actual[0] == add->field()); + CHECK(actual[1] == add->field()); +} + +TEST_SUITE_END(); diff --git a/tests/EqSat.propositional.test.cpp b/tests/EqSat.propositional.test.cpp new file mode 100644 index 00000000..5b2d34b4 --- /dev/null +++ b/tests/EqSat.propositional.test.cpp @@ -0,0 +1,197 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include + +#include "Luau/EGraph.h" +#include "Luau/Id.h" +#include "Luau/Language.h" + +#include + +LUAU_EQSAT_ATOM(Var, std::string); +LUAU_EQSAT_ATOM(Bool, bool); +LUAU_EQSAT_NODE_ARRAY(Not, 1); +LUAU_EQSAT_NODE_ARRAY(And, 2); +LUAU_EQSAT_NODE_ARRAY(Or, 2); +LUAU_EQSAT_NODE_ARRAY(Implies, 2); + +using namespace Luau; + +using PropositionalLogic = EqSat::Language; + +using EGraph = EqSat::EGraph; + +struct ConstantFold +{ + using Data = std::optional; + + Data make(const EGraph& egraph, const Var& var) const + { + return std::nullopt; + } + + Data make(const EGraph& egraph, const Bool& b) const + { + return b.value(); + } + + Data make(const EGraph& egraph, const Not& n) const + { + Data data = egraph[n[0]].data; + if (data) + return !*data; + + return std::nullopt; + } + + Data make(const EGraph& egraph, const And& a) const + { + Data l = egraph[a[0]].data; + Data r = egraph[a[1]].data; + if (l && r) + return *l && *r; + + return std::nullopt; + } + + Data make(const EGraph& egraph, const Or& o) const + { + Data l = egraph[o[0]].data; + Data r = egraph[o[1]].data; + if (l && r) + return *l || *r; + + return std::nullopt; + } + + Data make(const EGraph& egraph, const Implies& i) const + { + Data antecedent = egraph[i[0]].data; + Data consequent = egraph[i[1]].data; + if (antecedent && consequent) + return !*antecedent || *consequent; + + return std::nullopt; + } + + void join(Data& a, const Data& b) const + { + if (!a && b) + a = b; + } +}; + +TEST_SUITE_BEGIN("EqSatPropositionalLogic"); + +TEST_CASE("egraph_hashconsing") +{ + EGraph egraph; + + EqSat::Id id1 = egraph.add(Bool{true}); + EqSat::Id id2 = egraph.add(Bool{true}); + EqSat::Id id3 = egraph.add(Bool{false}); + + CHECK(id1 == id2); + CHECK(id2 != id3); +} + +TEST_CASE("egraph_data") +{ + EGraph egraph; + + EqSat::Id id1 = egraph.add(Bool{true}); + EqSat::Id id2 = egraph.add(Bool{false}); + + CHECK(egraph[id1].data == true); + CHECK(egraph[id2].data == false); +} + +TEST_CASE("egraph_merge") +{ + EGraph egraph; + + EqSat::Id id1 = egraph.add(Var{"a"}); + EqSat::Id id2 = egraph.add(Bool{true}); + egraph.merge(id1, id2); + + CHECK(egraph[id1].data == true); + CHECK(egraph[id2].data == true); +} + +TEST_CASE("const_fold_true_and_true") +{ + EGraph egraph; + + EqSat::Id id1 = egraph.add(Bool{true}); + EqSat::Id id2 = egraph.add(Bool{true}); + EqSat::Id id3 = egraph.add(And{id1, id2}); + + CHECK(egraph[id3].data == true); +} + +TEST_CASE("const_fold_true_and_false") +{ + EGraph egraph; + + EqSat::Id id1 = egraph.add(Bool{true}); + EqSat::Id id2 = egraph.add(Bool{false}); + EqSat::Id id3 = egraph.add(And{id1, id2}); + + CHECK(egraph[id3].data == false); +} + +TEST_CASE("const_fold_false_and_false") +{ + EGraph egraph; + + EqSat::Id id1 = egraph.add(Bool{false}); + EqSat::Id id2 = egraph.add(Bool{false}); + EqSat::Id id3 = egraph.add(And{id1, id2}); + + CHECK(egraph[id3].data == false); +} + +TEST_CASE("implications") +{ + EGraph egraph; + + EqSat::Id t = egraph.add(Bool{true}); + EqSat::Id f = egraph.add(Bool{false}); + + EqSat::Id a = egraph.add(Implies{t, t}); // true + EqSat::Id b = egraph.add(Implies{t, f}); // false + EqSat::Id c = egraph.add(Implies{f, t}); // true + EqSat::Id d = egraph.add(Implies{f, f}); // true + + CHECK(egraph[a].data == true); + CHECK(egraph[b].data == false); + CHECK(egraph[c].data == true); + CHECK(egraph[d].data == true); +} + +TEST_CASE("merge_x_and_y") +{ + EGraph egraph; + + EqSat::Id x = egraph.add(Var{"x"}); + EqSat::Id y = egraph.add(Var{"y"}); + + EqSat::Id a = egraph.add(Var{"a"}); + EqSat::Id ax = egraph.add(And{a, x}); + EqSat::Id ay = egraph.add(And{a, y}); + + egraph.merge(x, y); // [x y] [ax] [ay] [a] + CHECK_EQ(egraph.size(), 4); + CHECK_EQ(egraph.find(x), egraph.find(y)); + CHECK_NE(egraph.find(ax), egraph.find(ay)); + CHECK_NE(egraph.find(a), egraph.find(x)); + CHECK_NE(egraph.find(a), egraph.find(y)); + + egraph.rebuild(); // [x y] [ax ay] [a] + CHECK_EQ(egraph.size(), 3); + CHECK_EQ(egraph.find(x), egraph.find(y)); + CHECK_EQ(egraph.find(ax), egraph.find(ay)); + CHECK_NE(egraph.find(a), egraph.find(x)); + CHECK_NE(egraph.find(a), egraph.find(y)); +} + +TEST_SUITE_END(); diff --git a/tests/EqSat.slice.test.cpp b/tests/EqSat.slice.test.cpp new file mode 100644 index 00000000..26ca3bfd --- /dev/null +++ b/tests/EqSat.slice.test.cpp @@ -0,0 +1,58 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include + +#include "Luau/Slice.h" + +#include + +using namespace Luau; + +TEST_SUITE_BEGIN("EqSatSlice"); + +TEST_CASE("slice_is_a_view_over_array") +{ + std::array a{1, 2, 3, 4, 5, 6, 7, 8}; + + EqSat::Slice slice{a}; + + CHECK(slice.data() == a.data()); + CHECK(slice.size() == a.size()); + + for (size_t i = 0; i < a.size(); ++i) + { + CHECK(slice[i] == a[i]); + CHECK(&slice[i] == &a[i]); + } +} + +TEST_CASE("slice_is_a_view_over_vector") +{ + std::vector vector{1, 2, 3, 4, 5, 6, 7, 8}; + + EqSat::Slice slice{vector.data(), vector.size()}; + + CHECK(slice.data() == vector.data()); + CHECK(slice.size() == vector.size()); + + for (size_t i = 0; i < vector.size(); ++i) + { + CHECK(slice[i] == vector[i]); + CHECK(&slice[i] == &vector[i]); + } +} + +TEST_CASE("mutate_via_slice") +{ + std::array a{1, 2}; + CHECK(a[0] == 1); + CHECK(a[1] == 2); + + EqSat::Slice slice{a}; + slice[0] = 42; + slice[1] = 37; + + CHECK(a[0] == 42); + CHECK(a[1] == 37); +} + +TEST_SUITE_END(); From 4f917420d74aae84912acbaeb42e86691ddea309 Mon Sep 17 00:00:00 2001 From: Alexander McCord <11488393+alexmccord@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:51:51 -0700 Subject: [PATCH 3/5] Fix incorrect comment in sources of Luau.EqSat. (#1333) --- Sources.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources.cmake b/Sources.cmake index e0711f51..c6bbfdbc 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -295,7 +295,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/Unifier2.cpp ) -# Luau.Analysis Sources +# Luau.EqSat Sources target_sources(Luau.EqSat PRIVATE EqSat/include/Luau/EGraph.h EqSat/include/Luau/Id.h From 623e1e30dbf0e875349182d75937caa57d6b9c1a Mon Sep 17 00:00:00 2001 From: JohnnyMorganz Date: Wed, 17 Jul 2024 16:43:31 +0200 Subject: [PATCH 4/5] Store `definitionLocation` in ClassType (#1313) Store the location of an `AstStatDeclareClass` in the `ClassType` as `definitionLocation`. This is useful for documentation comments and Go To Definition on a class type https://github.com/luau-lang/luau/issues/1137#issuecomment-2212413633 --- Analysis/include/Luau/Type.h | 8 ++++++-- Analysis/src/ConstraintGenerator.cpp | 2 +- Analysis/src/Substitution.cpp | 2 +- Analysis/src/Type.cpp | 2 +- Analysis/src/TypeInfer.cpp | 2 +- fuzz/proto.cpp | 6 +++--- tests/ClassFixture.cpp | 30 ++++++++++++++-------------- tests/Fixture.cpp | 8 ++++---- tests/Generalization.test.cpp | 2 +- tests/Linter.test.cpp | 2 +- tests/Module.test.cpp | 4 ++-- tests/Subtyping.test.cpp | 2 +- tests/ToDot.test.cpp | 4 ++-- tests/TypeInfer.classes.test.cpp | 8 ++++---- tests/TypeInfer.refinements.test.cpp | 8 ++++---- tests/TypeVar.test.cpp | 6 +++--- 16 files changed, 50 insertions(+), 46 deletions(-) diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index 401784ae..dfcf8b6b 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -499,10 +499,11 @@ struct ClassType Tags tags; std::shared_ptr userData; ModuleName definitionModuleName; + std::optional definitionLocation; std::optional indexer; ClassType(Name name, Props props, std::optional parent, std::optional metatable, Tags tags, - std::shared_ptr userData, ModuleName definitionModuleName) + std::shared_ptr userData, ModuleName definitionModuleName, std::optional definitionLocation) : name(name) , props(props) , parent(parent) @@ -510,11 +511,13 @@ struct ClassType , tags(tags) , userData(userData) , definitionModuleName(definitionModuleName) + , definitionLocation(definitionLocation) { } ClassType(Name name, Props props, std::optional parent, std::optional metatable, Tags tags, - std::shared_ptr userData, ModuleName definitionModuleName, std::optional indexer) + std::shared_ptr userData, ModuleName definitionModuleName, std::optional definitionLocation, + std::optional indexer) : name(name) , props(props) , parent(parent) @@ -522,6 +525,7 @@ struct ClassType , tags(tags) , userData(userData) , definitionModuleName(definitionModuleName) + , definitionLocation(definitionLocation) , indexer(indexer) { } diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index aa5e4ca8..bcfc21dd 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -1349,7 +1349,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas Name className(declaredClass->name.value); - TypeId classTy = arena->addType(ClassType(className, {}, superTy, std::nullopt, {}, {}, module->name)); + TypeId classTy = arena->addType(ClassType(className, {}, superTy, std::nullopt, {}, {}, module->name, declaredClass->location)); ClassType* ctv = getMutable(classTy); TypeId metaTy = arena->addType(TableType{TableState::Sealed, scope->level, scope.get()}); diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index fae4b60c..a0128b41 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -117,7 +117,7 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a { if (alwaysClone) { - ClassType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.indexer}; + 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 diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index 6735e367..9479d0ef 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -969,7 +969,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})) - , classType(arena->addType(Type{ClassType{"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})) diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index d4c25c34..00d683dd 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -1733,7 +1733,7 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& de Name className(declaredClass.name.value); - TypeId classTy = addType(ClassType(className, {}, superTy, std::nullopt, {}, {}, currentModule->name)); + TypeId classTy = addType(ClassType(className, {}, superTy, std::nullopt, {}, {}, currentModule->name, declaredClass.location)); ClassType* ctv = getMutable(classTy); TypeId metaTy = addType(TableType{TableState::Sealed, scope->level}); diff --git a/fuzz/proto.cpp b/fuzz/proto.cpp index d06189a4..b9af5247 100644 --- a/fuzz/proto.cpp +++ b/fuzz/proto.cpp @@ -124,7 +124,7 @@ int registerTypes(Luau::Frontend& frontend, Luau::GlobalTypes& globals, bool for // Vector3 stub TypeId vector3MetaType = arena.addType(TableType{}); - TypeId vector3InstanceType = arena.addType(ClassType{"Vector3", {}, nullopt, vector3MetaType, {}, {}, "Test"}); + TypeId vector3InstanceType = arena.addType(ClassType{"Vector3", {}, nullopt, vector3MetaType, {}, {}, "Test", {}}); getMutable(vector3InstanceType)->props = { {"X", {builtinTypes.numberType}}, {"Y", {builtinTypes.numberType}}, @@ -138,7 +138,7 @@ int registerTypes(Luau::Frontend& frontend, Luau::GlobalTypes& globals, bool for globals.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vector3InstanceType}; // Instance stub - TypeId instanceType = arena.addType(ClassType{"Instance", {}, nullopt, nullopt, {}, {}, "Test"}); + TypeId instanceType = arena.addType(ClassType{"Instance", {}, nullopt, nullopt, {}, {}, "Test", {}}); getMutable(instanceType)->props = { {"Name", {builtinTypes.stringType}}, }; @@ -146,7 +146,7 @@ int registerTypes(Luau::Frontend& frontend, Luau::GlobalTypes& globals, bool for globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType}; // Part stub - TypeId partType = arena.addType(ClassType{"Part", {}, instanceType, nullopt, {}, {}, "Test"}); + TypeId partType = arena.addType(ClassType{"Part", {}, instanceType, nullopt, {}, {}, "Test", {}}); getMutable(partType)->props = { {"Position", {vector3InstanceType}}, }; diff --git a/tests/ClassFixture.cpp b/tests/ClassFixture.cpp index 7e35e40a..c369cb30 100644 --- a/tests/ClassFixture.cpp +++ b/tests/ClassFixture.cpp @@ -18,9 +18,9 @@ ClassFixture::ClassFixture() unfreeze(arena); - TypeId connectionType = arena.addType(ClassType{"Connection", {}, nullopt, nullopt, {}, {}, "Connection"}); + TypeId connectionType = arena.addType(ClassType{"Connection", {}, nullopt, nullopt, {}, {}, "Connection", {}}); - TypeId baseClassInstanceType = arena.addType(ClassType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test"}); + TypeId baseClassInstanceType = arena.addType(ClassType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); getMutable(baseClassInstanceType)->props = { {"BaseMethod", Property::readonly(makeFunction(arena, baseClassInstanceType, {numberType}, {}))}, {"BaseField", {numberType}}, @@ -31,7 +31,7 @@ ClassFixture::ClassFixture() getMutable(connectionType)->props = { {"Connect", {makeFunction(arena, connectionType, {makeFunction(arena, nullopt, {baseClassInstanceType}, {})}, {})}}}; - TypeId baseClassType = arena.addType(ClassType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test"}); + TypeId baseClassType = arena.addType(ClassType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); getMutable(baseClassType)->props = { {"StaticMethod", {makeFunction(arena, nullopt, {}, {numberType})}}, {"Clone", {makeFunction(arena, nullopt, {baseClassInstanceType}, {baseClassInstanceType})}}, @@ -40,48 +40,48 @@ ClassFixture::ClassFixture() globals.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType}; addGlobalBinding(globals, "BaseClass", baseClassType, "@test"); - TypeId childClassInstanceType = arena.addType(ClassType{"ChildClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test"}); + TypeId childClassInstanceType = arena.addType(ClassType{"ChildClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}}); getMutable(childClassInstanceType)->props = { {"Method", {makeFunction(arena, childClassInstanceType, {}, {stringType})}}, }; - TypeId childClassType = arena.addType(ClassType{"ChildClass", {}, baseClassType, nullopt, {}, {}, "Test"}); + TypeId childClassType = arena.addType(ClassType{"ChildClass", {}, baseClassType, nullopt, {}, {}, "Test", {}}); getMutable(childClassType)->props = { {"New", {makeFunction(arena, nullopt, {}, {childClassInstanceType})}}, }; globals.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType}; addGlobalBinding(globals, "ChildClass", childClassType, "@test"); - TypeId grandChildInstanceType = arena.addType(ClassType{"GrandChild", {}, childClassInstanceType, nullopt, {}, {}, "Test"}); + TypeId grandChildInstanceType = arena.addType(ClassType{"GrandChild", {}, childClassInstanceType, nullopt, {}, {}, "Test", {}}); getMutable(grandChildInstanceType)->props = { {"Method", {makeFunction(arena, grandChildInstanceType, {}, {stringType})}}, }; - TypeId grandChildType = arena.addType(ClassType{"GrandChild", {}, baseClassType, nullopt, {}, {}, "Test"}); + TypeId grandChildType = arena.addType(ClassType{"GrandChild", {}, baseClassType, nullopt, {}, {}, "Test", {}}); getMutable(grandChildType)->props = { {"New", {makeFunction(arena, nullopt, {}, {grandChildInstanceType})}}, }; globals.globalScope->exportedTypeBindings["GrandChild"] = TypeFun{{}, grandChildInstanceType}; addGlobalBinding(globals, "GrandChild", childClassType, "@test"); - TypeId anotherChildInstanceType = arena.addType(ClassType{"AnotherChild", {}, baseClassInstanceType, nullopt, {}, {}, "Test"}); + TypeId anotherChildInstanceType = arena.addType(ClassType{"AnotherChild", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}}); getMutable(anotherChildInstanceType)->props = { {"Method", {makeFunction(arena, anotherChildInstanceType, {}, {stringType})}}, }; - TypeId anotherChildType = arena.addType(ClassType{"AnotherChild", {}, baseClassType, nullopt, {}, {}, "Test"}); + TypeId anotherChildType = arena.addType(ClassType{"AnotherChild", {}, baseClassType, nullopt, {}, {}, "Test", {}}); getMutable(anotherChildType)->props = { {"New", {makeFunction(arena, nullopt, {}, {anotherChildInstanceType})}}, }; globals.globalScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildInstanceType}; addGlobalBinding(globals, "AnotherChild", childClassType, "@test"); - TypeId unrelatedClassInstanceType = arena.addType(ClassType{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test"}); + TypeId unrelatedClassInstanceType = arena.addType(ClassType{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); - TypeId unrelatedClassType = arena.addType(ClassType{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test"}); + TypeId unrelatedClassType = arena.addType(ClassType{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); getMutable(unrelatedClassType)->props = { {"New", {makeFunction(arena, nullopt, {}, {unrelatedClassInstanceType})}}, }; @@ -90,13 +90,13 @@ ClassFixture::ClassFixture() TypeId vector2MetaType = arena.addType(TableType{}); - vector2InstanceType = arena.addType(ClassType{"Vector2", {}, nullopt, vector2MetaType, {}, {}, "Test"}); + vector2InstanceType = arena.addType(ClassType{"Vector2", {}, nullopt, vector2MetaType, {}, {}, "Test", {}}); getMutable(vector2InstanceType)->props = { {"X", {numberType}}, {"Y", {numberType}}, }; - vector2Type = arena.addType(ClassType{"Vector2", {}, nullopt, nullopt, {}, {}, "Test"}); + vector2Type = arena.addType(ClassType{"Vector2", {}, nullopt, nullopt, {}, {}, "Test", {}}); getMutable(vector2Type)->props = { {"New", {makeFunction(arena, nullopt, {numberType, numberType}, {vector2InstanceType})}}, }; @@ -110,7 +110,7 @@ ClassFixture::ClassFixture() addGlobalBinding(globals, "Vector2", vector2Type, "@test"); TypeId callableClassMetaType = arena.addType(TableType{}); - TypeId callableClassType = arena.addType(ClassType{"CallableClass", {}, nullopt, callableClassMetaType, {}, {}, "Test"}); + TypeId callableClassType = arena.addType(ClassType{"CallableClass", {}, nullopt, callableClassMetaType, {}, {}, "Test", {}}); getMutable(callableClassMetaType)->props = { {"__call", {makeFunction(arena, nullopt, {callableClassType, stringType}, {numberType})}}, }; @@ -119,7 +119,7 @@ ClassFixture::ClassFixture() auto addIndexableClass = [&arena, &globals](const char* className, TypeId keyType, TypeId returnType) { TypeId indexableClassMetaType = arena.addType(TableType{}); TypeId indexableClassType = - arena.addType(ClassType{className, {}, nullopt, indexableClassMetaType, {}, {}, "Test", TableIndexer{keyType, returnType}}); + arena.addType(ClassType{className, {}, nullopt, indexableClassMetaType, {}, {}, "Test", {}, TableIndexer{keyType, returnType}}); globals.globalScope->exportedTypeBindings[className] = TypeFun{{}, indexableClassType}; }; diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index 3651dfeb..ef0731fa 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -658,7 +658,7 @@ void createSomeClasses(Frontend* frontend) ScopePtr moduleScope = globals.globalScope; - TypeId parentType = arena.addType(ClassType{"Parent", {}, frontend->builtinTypes->classType, std::nullopt, {}, nullptr, "Test"}); + TypeId parentType = arena.addType(ClassType{"Parent", {}, frontend->builtinTypes->classType, std::nullopt, {}, nullptr, "Test", {}}); ClassType* parentClass = getMutable(parentType); parentClass->props["method"] = {makeFunction(arena, parentType, {}, {})}; @@ -668,17 +668,17 @@ void createSomeClasses(Frontend* frontend) addGlobalBinding(globals, "Parent", {parentType}); moduleScope->exportedTypeBindings["Parent"] = TypeFun{{}, parentType}; - TypeId childType = arena.addType(ClassType{"Child", {}, parentType, std::nullopt, {}, nullptr, "Test"}); + TypeId childType = arena.addType(ClassType{"Child", {}, parentType, std::nullopt, {}, nullptr, "Test", {}}); addGlobalBinding(globals, "Child", {childType}); moduleScope->exportedTypeBindings["Child"] = TypeFun{{}, childType}; - TypeId anotherChildType = arena.addType(ClassType{"AnotherChild", {}, parentType, std::nullopt, {}, nullptr, "Test"}); + TypeId anotherChildType = arena.addType(ClassType{"AnotherChild", {}, parentType, std::nullopt, {}, nullptr, "Test", {}}); addGlobalBinding(globals, "AnotherChild", {anotherChildType}); moduleScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildType}; - TypeId unrelatedType = arena.addType(ClassType{"Unrelated", {}, frontend->builtinTypes->classType, std::nullopt, {}, nullptr, "Test"}); + TypeId unrelatedType = arena.addType(ClassType{"Unrelated", {}, frontend->builtinTypes->classType, std::nullopt, {}, nullptr, "Test", {}}); addGlobalBinding(globals, "Unrelated", {unrelatedType}); moduleScope->exportedTypeBindings["Unrelated"] = TypeFun{{}, unrelatedType}; diff --git a/tests/Generalization.test.cpp b/tests/Generalization.test.cpp index 901461ae..dabdf258 100644 --- a/tests/Generalization.test.cpp +++ b/tests/Generalization.test.cpp @@ -112,7 +112,7 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "dont_traverse_into_class_types_when_ge { auto [propTy, _] = freshType(); - TypeId cursedClass = arena.addType(ClassType{"Cursed", {{"oh_no", Property::readonly(propTy)}}, std::nullopt, std::nullopt, {}, {}, ""}); + TypeId cursedClass = arena.addType(ClassType{"Cursed", {{"oh_no", Property::readonly(propTy)}}, std::nullopt, std::nullopt, {}, {}, "", {}}); auto genClass = generalize(cursedClass); REQUIRE(genClass); diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index 807b5e73..b758764d 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -1485,7 +1485,7 @@ TEST_CASE_FIXTURE(Fixture, "LintHygieneUAF") TEST_CASE_FIXTURE(BuiltinsFixture, "DeprecatedApiTyped") { unfreeze(frontend.globals.globalTypes); - TypeId instanceType = frontend.globals.globalTypes.addType(ClassType{"Instance", {}, std::nullopt, std::nullopt, {}, {}, "Test"}); + TypeId instanceType = frontend.globals.globalTypes.addType(ClassType{"Instance", {}, std::nullopt, std::nullopt, {}, {}, "Test", {}}); persist(instanceType); frontend.globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType}; diff --git a/tests/Module.test.cpp b/tests/Module.test.cpp index dd7538ae..3894881c 100644 --- a/tests/Module.test.cpp +++ b/tests/Module.test.cpp @@ -244,13 +244,13 @@ TEST_CASE_FIXTURE(Fixture, "clone_class") { {"__add", {builtinTypes->anyType}}, }, - std::nullopt, std::nullopt, {}, {}, "Test"}}; + std::nullopt, std::nullopt, {}, {}, "Test", {}}}; Type exampleClass{ClassType{"ExampleClass", { {"PropOne", {builtinTypes->numberType}}, {"PropTwo", {builtinTypes->stringType}}, }, - std::nullopt, &exampleMetaClass, {}, {}, "Test"}}; + std::nullopt, &exampleMetaClass, {}, {}, "Test", {}}}; TypeArena dest; CloneState cloneState{builtinTypes}; diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index e70ec1ae..832049a4 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -146,7 +146,7 @@ struct SubtypeFixture : Fixture TypeId cls(const std::string& name, std::optional parent = std::nullopt) { - return arena.addType(ClassType{name, {}, parent.value_or(builtinTypes->classType), {}, {}, nullptr, ""}); + return arena.addType(ClassType{name, {}, parent.value_or(builtinTypes->classType), {}, {}, nullptr, "", {}}); } TypeId cls(const std::string& name, ClassType::Props&& props) diff --git a/tests/ToDot.test.cpp b/tests/ToDot.test.cpp index 1a0fe411..16f4dc48 100644 --- a/tests/ToDot.test.cpp +++ b/tests/ToDot.test.cpp @@ -21,13 +21,13 @@ struct ToDotClassFixture : Fixture TypeId baseClassMetaType = arena.addType(TableType{}); - TypeId baseClassInstanceType = arena.addType(ClassType{"BaseClass", {}, std::nullopt, baseClassMetaType, {}, {}, "Test"}); + TypeId baseClassInstanceType = arena.addType(ClassType{"BaseClass", {}, std::nullopt, baseClassMetaType, {}, {}, "Test", {}}); getMutable(baseClassInstanceType)->props = { {"BaseField", {builtinTypes->numberType}}, }; frontend.globals.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType}; - TypeId childClassInstanceType = arena.addType(ClassType{"ChildClass", {}, baseClassInstanceType, std::nullopt, {}, {}, "Test"}); + TypeId childClassInstanceType = arena.addType(ClassType{"ChildClass", {}, baseClassInstanceType, std::nullopt, {}, {}, "Test", {}}); getMutable(childClassInstanceType)->props = { {"ChildField", {builtinTypes->stringType}}, }; diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index f90324d7..90271e29 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -706,19 +706,19 @@ TEST_CASE_FIXTURE(Fixture, "read_write_class_properties") unfreeze(arena); - TypeId instanceType = arena.addType(ClassType{"Instance", {}, nullopt, nullopt, {}, {}, "Test"}); + TypeId instanceType = arena.addType(ClassType{"Instance", {}, nullopt, nullopt, {}, {}, "Test", {}}); getMutable(instanceType)->props = {{"Parent", Property::rw(instanceType)}}; // - TypeId workspaceType = arena.addType(ClassType{"Workspace", {}, nullopt, nullopt, {}, {}, "Test"}); + TypeId workspaceType = arena.addType(ClassType{"Workspace", {}, nullopt, nullopt, {}, {}, "Test", {}}); TypeId scriptType = - arena.addType(ClassType{"Script", {{"Parent", Property::rw(workspaceType, instanceType)}}, instanceType, nullopt, {}, {}, "Test"}); + arena.addType(ClassType{"Script", {{"Parent", Property::rw(workspaceType, instanceType)}}, instanceType, nullopt, {}, {}, "Test", {}}); TypeId partType = arena.addType( ClassType{"Part", {{"BrickColor", Property::rw(builtinTypes->stringType)}, {"Parent", Property::rw(workspaceType, instanceType)}}, - instanceType, nullopt, {}, {}, "Test"}); + instanceType, nullopt, {}, {}, "Test", {}}); getMutable(workspaceType)->props = {{"Script", Property::readonly(scriptType)}, {"Part", Property::readonly(partType)}}; diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 868aa9f2..b0d509ac 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -66,14 +66,14 @@ struct RefinementClassFixture : BuiltinsFixture std::optional rootSuper = std::make_optional(builtinTypes->classType); unfreeze(arena); - TypeId vec3 = arena.addType(ClassType{"Vector3", {}, rootSuper, std::nullopt, {}, nullptr, "Test"}); + TypeId vec3 = arena.addType(ClassType{"Vector3", {}, rootSuper, std::nullopt, {}, nullptr, "Test", {}}); getMutable(vec3)->props = { {"X", Property{builtinTypes->numberType}}, {"Y", Property{builtinTypes->numberType}}, {"Z", Property{builtinTypes->numberType}}, }; - TypeId inst = arena.addType(ClassType{"Instance", {}, rootSuper, std::nullopt, {}, nullptr, "Test"}); + TypeId inst = arena.addType(ClassType{"Instance", {}, rootSuper, std::nullopt, {}, nullptr, "Test", {}}); TypePackId isAParams = arena.addTypePack({inst, builtinTypes->stringType}); TypePackId isARets = arena.addTypePack({builtinTypes->booleanType}); @@ -86,8 +86,8 @@ struct RefinementClassFixture : BuiltinsFixture {"IsA", Property{isA}}, }; - TypeId folder = frontend.globals.globalTypes.addType(ClassType{"Folder", {}, inst, std::nullopt, {}, nullptr, "Test"}); - TypeId part = frontend.globals.globalTypes.addType(ClassType{"Part", {}, inst, std::nullopt, {}, nullptr, "Test"}); + TypeId folder = frontend.globals.globalTypes.addType(ClassType{"Folder", {}, inst, std::nullopt, {}, nullptr, "Test", {}}); + TypeId part = frontend.globals.globalTypes.addType(ClassType{"Part", {}, inst, std::nullopt, {}, nullptr, "Test", {}}); getMutable(part)->props = { {"Position", Property{vec3}}, }; diff --git a/tests/TypeVar.test.cpp b/tests/TypeVar.test.cpp index 674a4155..683e9027 100644 --- a/tests/TypeVar.test.cpp +++ b/tests/TypeVar.test.cpp @@ -314,7 +314,7 @@ TEST_CASE("tagging_tables") TEST_CASE("tagging_classes") { - Type base{ClassType{"Base", {}, std::nullopt, std::nullopt, {}, nullptr, "Test"}}; + Type base{ClassType{"Base", {}, std::nullopt, std::nullopt, {}, nullptr, "Test", {}}}; CHECK(!Luau::hasTag(&base, "foo")); Luau::attachTag(&base, "foo"); CHECK(Luau::hasTag(&base, "foo")); @@ -322,8 +322,8 @@ TEST_CASE("tagging_classes") TEST_CASE("tagging_subclasses") { - Type base{ClassType{"Base", {}, std::nullopt, std::nullopt, {}, nullptr, "Test"}}; - Type derived{ClassType{"Derived", {}, &base, std::nullopt, {}, nullptr, "Test"}}; + Type base{ClassType{"Base", {}, std::nullopt, std::nullopt, {}, nullptr, "Test", {}}}; + Type derived{ClassType{"Derived", {}, &base, std::nullopt, {}, nullptr, "Test", {}}}; CHECK(!Luau::hasTag(&base, "foo")); CHECK(!Luau::hasTag(&derived, "foo")); From 2874ca9e86aae9e75178602a27db77179bf37428 Mon Sep 17 00:00:00 2001 From: birds3345 <31601136+birds3345@users.noreply.github.com> Date: Wed, 17 Jul 2024 19:19:57 -0400 Subject: [PATCH 5/5] Optimizations for UnionFind (#1334) Implements ranks & path compression for union find. --------- Co-authored-by: Alexander McCord <11488393+alexmccord@users.noreply.github.com> --- EqSat/include/Luau/UnionFind.h | 5 ++++ EqSat/src/UnionFind.cpp | 49 ++++++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/EqSat/include/Luau/UnionFind.h b/EqSat/include/Luau/UnionFind.h index dd886a44..559ee119 100644 --- a/EqSat/include/Luau/UnionFind.h +++ b/EqSat/include/Luau/UnionFind.h @@ -13,10 +13,15 @@ struct UnionFind final { Id makeSet(); Id find(Id id) const; + Id find(Id id); void merge(Id a, Id b); private: std::vector parents; + std::vector ranks; + +private: + Id canonicalize(Id id) const; }; } // namespace Luau::EqSat diff --git a/EqSat/src/UnionFind.cpp b/EqSat/src/UnionFind.cpp index 04d9ba74..5c01e968 100644 --- a/EqSat/src/UnionFind.cpp +++ b/EqSat/src/UnionFind.cpp @@ -10,10 +10,51 @@ Id UnionFind::makeSet() { Id id{parents.size()}; parents.push_back(id); + ranks.push_back(0); + return id; } Id UnionFind::find(Id id) const +{ + return canonicalize(id); +} + +Id UnionFind::find(Id id) +{ + Id set = canonicalize(id); + + // An e-class id 𝑎 is canonical iff find(𝑎) = 𝑎. + while (id != parents[size_t(id)]) + { + // Note: we don't update the ranks here since a rank + // represents the upper bound on the maximum depth of a tree + Id parent = parents[size_t(id)]; + parents[size_t(id)] = set; + id = parent; + } + + return set; +} + +void UnionFind::merge(Id a, Id b) +{ + Id aSet = find(a); + Id bSet = find(b); + if (aSet == bSet) + return; + + // Ensure that the rank of set A is greater than the rank of set B + if (ranks[size_t(aSet)] < ranks[size_t(bSet)]) + std::swap(aSet, bSet); + + parents[size_t(bSet)] = aSet; + + if (ranks[size_t(aSet)] == ranks[size_t(bSet)]) + ranks[size_t(aSet)]++; +} + +Id UnionFind::canonicalize(Id id) const { LUAU_ASSERT(size_t(id) < parents.size()); @@ -24,12 +65,4 @@ Id UnionFind::find(Id id) const return id; } -void UnionFind::merge(Id a, Id b) -{ - LUAU_ASSERT(size_t(a) < parents.size()); - LUAU_ASSERT(size_t(b) < parents.size()); - - parents[size_t(b)] = a; -} - } // namespace Luau::EqSat